From 2976f55de155204a09f56651edeff5152ca5e8d8 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Tue, 2 Jun 2026 19:21:26 +0100 Subject: [PATCH 01/41] add usage_windows counter storage + cus_ent model field --- .../utils/fullSubjectScenarioBuilders.ts | 1 + .../usage-windows/buildUsageWindowKey.test.ts | 101 + .../drizzle/0002_jittery_dexter_bennett.sql | 1 + shared/drizzle/meta/0002_snapshot.json | 6944 +++++++++++++++++ shared/drizzle/meta/_journal.json | 45 +- shared/index.ts | 2 + .../fullSubject/normalizedFullSubjectModel.ts | 2 + .../cusEntModels/cusEntModels.ts | 5 + .../cusEntModels/cusEntTable.ts | 6 + .../cusEntModels/usageWindowModels.ts | 80 + .../usageWindowUtils/buildUsageWindowKey.ts | 50 + 11 files changed, 7218 insertions(+), 19 deletions(-) create mode 100644 server/tests/unit/usage-windows/buildUsageWindowKey.test.ts create mode 100644 shared/drizzle/0002_jittery_dexter_bennett.sql create mode 100644 shared/drizzle/meta/0002_snapshot.json create mode 100644 shared/models/cusProductModels/cusEntModels/usageWindowModels.ts create mode 100644 shared/utils/usageWindowUtils/buildUsageWindowKey.ts diff --git a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts index c007c2710..3b2c30deb 100644 --- a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts +++ b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts @@ -337,6 +337,7 @@ const buildCustomerEntitlement = ({ adjustment: 0, additional_balance: 0, entities: null, + usage_windows: null, expires_at: expiresAt, cache_version: 0, customer_id: customer.id ?? null, diff --git a/server/tests/unit/usage-windows/buildUsageWindowKey.test.ts b/server/tests/unit/usage-windows/buildUsageWindowKey.test.ts new file mode 100644 index 000000000..4b67ff93b --- /dev/null +++ b/server/tests/unit/usage-windows/buildUsageWindowKey.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, test } from "bun:test"; +import { buildUsageWindowKey, EntInterval } from "@autumn/shared"; + +describe("buildUsageWindowKey", () => { + test("customer-scoped balance-dimension window", () => { + const key = buildUsageWindowKey({ + scopeType: "customer", + internalEntityId: null, + dimensionType: "balance", + dimensionFeatureId: null, + interval: EntInterval.Day, + windowStartAt: 1_700_000_000_000, + }); + + expect(key).toBe("customer:_:balance:_:day:1700000000000"); + }); + + test("entity-scoped metered-feature window keys by internal entity id", () => { + const key = buildUsageWindowKey({ + scopeType: "entity", + internalEntityId: "ient_123", + dimensionType: "metered_feature", + dimensionFeatureId: "action1", + interval: EntInterval.Month, + windowStartAt: 1_700_000_000_000, + }); + + expect(key).toBe( + "entity:ient_123:metered_feature:action1:month:1700000000000", + ); + }); + + test("is deterministic for identical inputs", () => { + const params = { + scopeType: "customer" as const, + internalEntityId: null, + dimensionType: "metered_feature" as const, + dimensionFeatureId: "action1", + interval: EntInterval.Month, + windowStartAt: 1_700_000_000_000, + }; + + expect(buildUsageWindowKey(params)).toBe(buildUsageWindowKey(params)); + }); + + test("distinguishes windows that differ only by dimension feature", () => { + const base = { + scopeType: "customer" as const, + internalEntityId: null, + dimensionType: "metered_feature" as const, + interval: EntInterval.Month, + windowStartAt: 1_700_000_000_000, + }; + + expect( + buildUsageWindowKey({ ...base, dimensionFeatureId: "action1" }), + ).not.toBe(buildUsageWindowKey({ ...base, dimensionFeatureId: "action2" })); + }); + + test("distinguishes windows that differ only by window start", () => { + const base = { + scopeType: "customer" as const, + internalEntityId: null, + dimensionType: "balance" as const, + dimensionFeatureId: null, + interval: EntInterval.Day, + }; + + expect( + buildUsageWindowKey({ ...base, windowStartAt: 1_700_000_000_000 }), + ).not.toBe( + buildUsageWindowKey({ ...base, windowStartAt: 1_700_086_400_000 }), + ); + }); + + test("rejects a segment containing the ':' delimiter", () => { + expect(() => + buildUsageWindowKey({ + scopeType: "customer", + internalEntityId: null, + dimensionType: "metered_feature", + dimensionFeatureId: "a:b", + interval: EntInterval.Month, + windowStartAt: 1_700_000_000_000, + }), + ).toThrow(); + }); + + test("rejects a segment that is the literal null sentinel", () => { + expect(() => + buildUsageWindowKey({ + scopeType: "entity", + internalEntityId: "_", + dimensionType: "balance", + dimensionFeatureId: null, + interval: EntInterval.Day, + windowStartAt: 1_700_000_000_000, + }), + ).toThrow(); + }); +}); diff --git a/shared/drizzle/0002_jittery_dexter_bennett.sql b/shared/drizzle/0002_jittery_dexter_bennett.sql new file mode 100644 index 000000000..fe92af437 --- /dev/null +++ b/shared/drizzle/0002_jittery_dexter_bennett.sql @@ -0,0 +1 @@ +ALTER TABLE "customer_entitlements" ADD COLUMN "usage_windows" jsonb; \ No newline at end of file diff --git a/shared/drizzle/meta/0002_snapshot.json b/shared/drizzle/meta/0002_snapshot.json new file mode 100644 index 000000000..8c906a63c --- /dev/null +++ b/shared/drizzle/meta/0002_snapshot.json @@ -0,0 +1,6944 @@ +{ + "id": "5ce8bd9b-f568-4ec0-8de8-3c0c33cd7aa7", + "prevId": "3d398bca-e922-45ac-bdc7-52f121289654", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "nullsNotDistinct": false, + "columns": [ + "hashed_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_windows": { + "name": "usage_windows", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id_btree": { + "name": "idx_customer_entitlements_internal_customer_id_btree", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_entity_id": { + "name": "idx_customer_entitlements_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_on_next_reset_at": { + "name": "idx_customer_entitlements_on_next_reset_at", + "columns": [ + { + "expression": "next_reset_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "prices", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "access_starts_at": { + "name": "access_starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_anchor_resets_at": { + "name": "billing_cycle_anchor_resets_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_customer_product_id": { + "name": "previous_customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_trial_end": { + "name": "on_trial_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_product_id": { + "name": "idx_customer_products_on_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_subscription_ids": { + "name": "idx_customer_products_subscription_ids", + "columns": [ + { + "expression": "subscription_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_scheduled_ids": { + "name": "idx_customer_products_scheduled_ids", + "columns": [ + { + "expression": "scheduled_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_stripe_checkout_session_id": { + "name": "idx_customer_products_stripe_checkout_session_id", + "columns": [ + { + "expression": "stripe_checkout_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_revenuecat_processor": { + "name": "idx_customer_products_revenuecat_processor", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "(\"customer_products\".\"processor\" ->> 'type') = 'revenuecat'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "tableTo": "free_trials", + "columnsFrom": [ + "free_trial_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processor_id": { + "name": "idx_customers_processor_id", + "columns": [ + { + "expression": "(\"processor\" ->> 'id')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_composite": { + "name": "idx_customers_composite", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_internal_id": { + "name": "idx_customers_org_env_internal_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_email_trgm": { + "name": "idx_customers_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_name_trgm": { + "name": "idx_customers_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_id_trgm": { + "name": "idx_customers_id_trgm", + "columns": [ + { + "expression": "\"id\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"id\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_org_id_env_created_at": { + "name": "idx_customers_org_id_env_created_at", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_cursor": { + "name": "idx_customers_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_revenuecat": { + "name": "idx_customers_processors_revenuecat", + "columns": [ + { + "expression": "(\"processors\" ->> 'revenuecat')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_vercel": { + "name": "idx_customers_processors_vercel", + "columns": [ + { + "expression": "(\"processors\" ->> 'vercel')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_org_env_id": { + "name": "idx_entities_org_env_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_cursor": { + "name": "idx_entities_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "expiry_duration": { + "name": "expiry_duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_length": { + "name": "expiry_length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id": { + "name": "idx_entitlements_internal_reward_id", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_reward_feature": { + "name": "idx_entitlements_reward_feature", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id_c_partial": { + "name": "idx_entitlements_internal_reward_id_c_partial", + "columns": [ + { + "expression": "\"internal_reward_id\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"entitlements\".\"internal_reward_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "entitlements_internal_reward_id_fkey": { + "name": "entitlements_internal_reward_id_fkey", + "tableFrom": "entitlements", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deductions": { + "name": "deductions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"events\".\"set_usage\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "on_end": { + "name": "on_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor_type": { + "name": "processor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "refunded_amount": { + "name": "refunded_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "migration_jobs", + "columnsFrom": [ + "migration_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_item_runs": { + "name": "migration_item_runs", + "schema": "", + "columns": { + "migration_item_run_id": { + "name": "migration_item_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_run_id": { + "name": "migration_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "item_kind": { + "name": "item_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_item_runs_live_unique": { + "name": "migration_item_runs_live_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_dry_run_unique": { + "name": "migration_item_runs_dry_run_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_customer_recent_idx": { + "name": "migration_item_runs_customer_recent_idx", + "columns": [ + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"migration_item_runs\".\"item_kind\" = 'customer'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "from_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "to_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lazy_run": { + "name": "lazy_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_run_id": { + "name": "trigger_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "only_ids": { + "name": "only_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "target_limit": { + "name": "target_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_runs_active_per_migration_unique": { + "name": "migration_runs_active_per_migration_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_runs\".\"status\" IN ('queued', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_runs_migration_internal_id_fkey": { + "name": "migration_runs_migration_internal_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "migrations", + "columnsFrom": [ + "migration_internal_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_org_id_fkey": { + "name": "migration_runs_org_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migrations": { + "name": "migrations", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prepared_state": { + "name": "prepared_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "no_billing_changes": { + "name": "no_billing_changes", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "retry_failed": { + "name": "retry_failed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migrations_org_env_id_unique": { + "name": "migrations_org_env_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migrations_org_id_fkey": { + "name": "migrations_org_id_fkey", + "tableFrom": "migrations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redis_config": { + "name": "redis_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_organizations_name_trgm": { + "name": "idx_organizations_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_slug_trgm": { + "name": "idx_organizations_slug_trgm", + "columns": [ + { + "expression": "\"slug\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"slug\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_created_at_id": { + "name": "idx_organizations_created_at_id", + "columns": [ + { + "expression": "\"createdAt\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "test_pkey" + ] + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "live_pkey" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_credentialId_idx": { + "name": "passkey_credentialId_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "passkey_credential_id_unique": { + "name": "passkey_credential_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credential_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_products_org_env_id_version": { + "name": "idx_products_org_env_id_version", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_referral_codes_internal_customer_id": { + "name": "idx_referral_codes_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_internal_id": { + "name": "reward_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_code": { + "name": "promo_code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_reward_redemptions_referral_code_id": { + "name": "idx_reward_redemptions_referral_code_id", + "columns": [ + { + "expression": "referral_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_reward_internal_id": { + "name": "idx_reward_redemptions_reward_internal_id", + "columns": [ + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_customer_reward": { + "name": "idx_reward_redemptions_customer_reward", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "referral_codes", + "columnsFrom": [ + "referral_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.phases": { + "name": "phases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "phases_schedule_id_fkey": { + "name": "phases_schedule_id_fkey", + "tableFrom": "phases", + "tableTo": "schedules", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "phases_schedule_id_starts_at_key": { + "name": "phases_schedule_id_starts_at_key", + "nullsNotDistinct": false, + "columns": [ + "schedule_id", + "starts_at" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "schedules_customer_scope_unique": { + "name": "schedules_customer_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedules_entity_scope_unique": { + "name": "schedules_entity_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_customer_id": { + "name": "idx_schedules_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_entity_id": { + "name": "idx_schedules_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schedules_org_id_fkey": { + "name": "schedules_org_id_fkey", + "tableFrom": "schedules", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_customer_id_fkey": { + "name": "schedules_internal_customer_id_fkey", + "tableFrom": "schedules", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_entity_id_fkey": { + "name": "schedules_internal_entity_id_fkey", + "tableFrom": "schedules", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_name_trgm": { + "name": "idx_user_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_email_trgm": { + "name": "idx_user_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_created_at_id": { + "name": "idx_user_created_at_id", + "columns": [ + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "tableTo": "organizations", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "vercel_resources_installation_name_unique_idx": { + "name": "vercel_resources_installation_name_unique_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status <> 'uninstalled'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json index 49d8d0828..d0517ce33 100644 --- a/shared/drizzle/meta/_journal.json +++ b/shared/drizzle/meta/_journal.json @@ -1,20 +1,27 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1779096275848, - "tag": "0000_bumpy_tinkerer", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1779971507895, - "tag": "0001_concerned_ravenous", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1779096275848, + "tag": "0000_bumpy_tinkerer", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1779971507895, + "tag": "0001_concerned_ravenous", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1780302718193, + "tag": "0002_jittery_dexter_bennett", + "breakpoints": true + } + ] +} diff --git a/shared/index.ts b/shared/index.ts index 437e4bf13..41a9e6d06 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -82,6 +82,7 @@ export * from "./models/cusProductModels/cusEntModels/replaceableSchema"; export * from "./models/cusProductModels/cusEntModels/replaceableTable"; export * from "./models/cusProductModels/cusEntModels/resetCusEnt"; export * from "./models/cusProductModels/cusEntModels/rolloverModels/rolloverTable"; +export * from "./models/cusProductModels/cusEntModels/usageWindowModels"; export * from "./models/cusProductModels/cusPriceModels/cusPriceModels"; export * from "./models/cusProductModels/cusPriceModels/cusPriceTable"; export * from "./models/cusProductModels/cusProductEnums"; @@ -209,6 +210,7 @@ export * from "./utils/cusEntUtils/balanceUtils/cusEntToMinBalance"; export * from "./utils/cusEntUtils/balanceUtils/cusEntToUsageAllowed"; export * from "./utils/cusEntUtils/index"; // Utils +export * from "./utils/usageWindowUtils/buildUsageWindowKey"; export * from "./utils/displayUtils"; export * from "./utils/fullSubjectUtils"; export * from "./utils/index"; diff --git a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts index ed2faa099..11dd2f25b 100644 --- a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts +++ b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts @@ -7,6 +7,7 @@ import { type EntityBalance, FullCustomerEntitlementSchema, } from "../../cusProductModels/cusEntModels/cusEntModels.js"; +import type { UsageWindows } from "../../cusProductModels/cusEntModels/usageWindowModels.js"; import type { Replaceable } from "../../cusProductModels/cusEntModels/replaceableTable.js"; import type { DbRollover } from "../../cusProductModels/cusEntModels/rolloverModels/rolloverTable.js"; import type { FullCustomerPrice } from "../../cusProductModels/cusPriceModels/cusPriceModels.js"; @@ -89,6 +90,7 @@ export type SubjectBalance = { expires_at: number | null; external_id: string | null; entities: Record | null; + usage_windows?: UsageWindows | null; cache_version: number | null; created_at: number; customer_id?: string | null; diff --git a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts index 32c01941b..119b42db4 100644 --- a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts +++ b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts @@ -3,6 +3,7 @@ import { EntitlementWithFeatureSchema } from "../../productModels/entModels/entM import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; import { ReplaceableSchema } from "./replaceableSchema.js"; import { RolloverSchema } from "./rolloverModels/rolloverTable.js"; +import { UsageWindowsSchema } from "./usageWindowModels.js"; export const CustomerEntitlementFiltersSchema = z.object({ cusEntIds: z.array(z.string()).optional(), @@ -48,6 +49,10 @@ export const CustomerEntitlementSchema = z.object({ // Group by fields entities: z.record(z.string(), EntityBalanceSchema).nullish(), + // Windowed usage-limit counters scoped beneath this entitlement (second + // limit dimension on top of balance). Keyed by buildUsageWindowKey. + usage_windows: UsageWindowsSchema.nullish(), + external_id: z.string().nullable(), }); diff --git a/shared/models/cusProductModels/cusEntModels/cusEntTable.ts b/shared/models/cusProductModels/cusEntModels/cusEntTable.ts index fd0adbb6f..24f1287ae 100644 --- a/shared/models/cusProductModels/cusEntModels/cusEntTable.ts +++ b/shared/models/cusProductModels/cusEntModels/cusEntTable.ts @@ -15,6 +15,7 @@ import { features } from "../../featureModels/featureTable.js"; import { entitlements } from "../../productModels/entModels/entTable.js"; import { customerProducts } from "../cusProductTable.js"; import type { EntityBalance } from "./cusEntModels.js"; +import type { UsageWindows } from "./usageWindowModels.js"; export const customerEntitlements = pgTable( "customer_entitlements", @@ -41,6 +42,11 @@ export const customerEntitlements = pgTable( // Need to work on free balance... entities: jsonb("entities").$type>(), + // Windowed usage-limit counters (windowKey -> UsageWindow). Embedded + // here so they live in the same SubjectBalance hot object as the balance + // and are mutated atomically by the deduction script. + usage_windows: jsonb("usage_windows").$type(), + // Expiry for loose entitlements (entitlements without reset intervals) expires_at: numeric({ mode: "number" }), cache_version: integer("cache_version").default(0), diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts new file mode 100644 index 000000000..ee1bad79d --- /dev/null +++ b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts @@ -0,0 +1,80 @@ +import { z } from "zod/v4"; +import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; + +/** + * Which dimension a usage window counts against: + * - `balance`: the credit pool / balance itself (e.g. "max 3 credits/day") + * - `metered_feature`: a member feature within a credit system + * (e.g. "max 5 workflows", independent of credits remaining) + */ +export const UsageWindowDimensionSchema = z.enum([ + "balance", + "metered_feature", +]); +export type UsageWindowDimension = z.infer; + +/** Whether the window is tracked per-customer (aggregate) or per-entity. */ +export const UsageWindowScopeSchema = z.enum(["customer", "entity"]); +export type UsageWindowScope = z.infer; + +/** + * A single windowed usage counter scoped beneath a customer entitlement. + * + * This is the embedded counter state. The enforced limit is resolved at + * deduction time (mirroring spend limits), so `limit_snapshot` is audit-only, + * never the enforcement source. `key` is built by `buildUsageWindowKey`. + * + * `usage_amount` is in the dimension's native units (e.g. workflow count); + * `balance_amount` records the pool/credit units consumed, for attribution. + */ +export const UsageWindowSchema = z.object({ + key: z.string(), + dimension_type: UsageWindowDimensionSchema, + dimension_feature_id: z.string().nullable(), + scope_type: UsageWindowScopeSchema, + entity_id: z.string().nullable(), + internal_entity_id: z.string().nullable(), + interval: z.enum(EntInterval), + window_start_at: z.number(), + window_end_at: z.number(), + usage_amount: z.number(), + balance_amount: z.number(), + limit_snapshot: z.number().nullish(), + updated_at: z.number(), +}); + +export type UsageWindow = z.infer; + +/** Map of windowKey -> UsageWindow, embedded on a customer entitlement. */ +export const UsageWindowsSchema = z.record(z.string(), UsageWindowSchema); +export type UsageWindows = z.infer; + +/** + * A resolved, enforceable usage-window limit: the runtime input handed to the + * deduction script. Built each deduction from the windowed usage cap + * (`usage_limit_interval` + inherited/override limit) on a `spend_limit` billing + * control plus the current window bounds (NOT stored). + * Carries the resolved `limit` and `key`/window so Lua can find-or-create the + * matching counter. + */ +export const UsageWindowLimitSchema = z.object({ + feature_id: z.string(), + key: z.string(), + dimension_type: UsageWindowDimensionSchema, + dimension_feature_id: z.string().nullable(), + scope_type: UsageWindowScopeSchema, + entity_id: z.string().nullable(), + internal_entity_id: z.string().nullable(), + interval: z.enum(EntInterval), + window_start_at: z.number(), + window_end_at: z.number(), + limit: z.number(), + // The single entitlement that owns this counter, resolved in TS so it is + // deduction-order-independent. Null when no eligible owner exists (e.g. a + // customer-scope cap with only entity-scoped entitlements) -> enforcement + // must fail closed rather than split or silently allow. + anchor_customer_entitlement_id: z.string().nullable(), + anchor_feature_id: z.string().nullable(), +}); + +export type UsageWindowLimit = z.infer; diff --git a/shared/utils/usageWindowUtils/buildUsageWindowKey.ts b/shared/utils/usageWindowUtils/buildUsageWindowKey.ts new file mode 100644 index 000000000..57bee8dd8 --- /dev/null +++ b/shared/utils/usageWindowUtils/buildUsageWindowKey.ts @@ -0,0 +1,50 @@ +import type { + UsageWindowDimension, + UsageWindowScope, +} from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { EntInterval } from "../../models/productModels/intervals/entitlementInterval.js"; + +const NULL_SEGMENT = "_"; +const DELIMITER = ":"; + +/** + * Deterministic key for one usage-window counter. Lua looks the counter up by + * this exact string (`limit.key`), so the format must stay stable. Fail-closed if + * an externally-influenced segment is the literal NULL_SEGMENT or contains the + * DELIMITER, either of which would alias two distinct windows onto one counter. + */ +export const buildUsageWindowKey = ({ + scopeType, + internalEntityId, + dimensionType, + dimensionFeatureId, + interval, + windowStartAt, +}: { + scopeType: UsageWindowScope; + internalEntityId: string | null; + dimensionType: UsageWindowDimension; + dimensionFeatureId: string | null; + interval: EntInterval; + windowStartAt: number; +}): string => { + for (const segment of [internalEntityId, dimensionFeatureId]) { + if ( + segment !== null && + (segment === NULL_SEGMENT || segment.includes(DELIMITER)) + ) { + throw new Error( + `buildUsageWindowKey: segment "${segment}" collides with the key encoding (reserved "${NULL_SEGMENT}" / "${DELIMITER}")`, + ); + } + } + + return [ + scopeType, + internalEntityId ?? NULL_SEGMENT, + dimensionType, + dimensionFeatureId ?? NULL_SEGMENT, + interval, + windowStartAt, + ].join(DELIMITER); +}; From 8eab888024d35cb472913b8265361c15896f3cff Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Tue, 2 Jun 2026 19:21:32 +0100 Subject: [PATCH 02/41] add flat spend_limit usage cap config + cycle-aligned window resolver --- .../deductionV2/executePostgresDeductionV2.ts | 1 + .../deductionV2/executeRedisDeductionV2.ts | 29 + .../deductionV2/prepareFeatureDeductionV2.ts | 47 ++ .../balances/utils/types/deductionTypes.ts | 4 + .../buildDeductFromSubjectBalancesKeys.ts | 20 +- .../entities/handlers/handleListEntities.ts | 2 +- .../fullSubjectToUsageWindowLimits.test.ts | 566 ++++++++++++++++++ .../getUsageWindowBounds.test.ts | 131 ++++ .../pickAnchorCustomerEntitlementId.test.ts | 130 ++++ .../billingControls/entityBillingControls.ts | 3 +- shared/enums/ErrCode.ts | 1 + shared/index.ts | 2 + .../customerBillingControls.ts | 6 +- .../billingControls/entityBillingControls.ts | 3 +- .../cusModels/billingControls/spendLimit.ts | 24 +- .../cusModels/entityModels/entityTable.ts | 6 +- .../cusEntModels/usageWindowModels.ts | 2 +- .../fullSubjectToUsageWindowLimits.ts | 191 ++++++ shared/utils/fullSubjectUtils/index.ts | 1 + .../usageWindowUtils/getUsageWindowBounds.ts | 98 +++ .../pickAnchorCustomerEntitlementId.ts | 55 ++ 21 files changed, 1297 insertions(+), 25 deletions(-) create mode 100644 server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts create mode 100644 server/tests/unit/usage-windows/getUsageWindowBounds.test.ts create mode 100644 server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts create mode 100644 shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts create mode 100644 shared/utils/usageWindowUtils/getUsageWindowBounds.ts create mode 100644 shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts diff --git a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts index 9f025e7fb..48e469d4c 100644 --- a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts @@ -116,6 +116,7 @@ export const executePostgresDeductionV2 = async ({ fullSubject, deduction, options: resolvedOptions, + now: Date.now(), }); if (customerEntitlements.length === 0 || unlimitedFeatureIds.length > 0) { diff --git a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts index a9501a6e4..ac26b243b 100644 --- a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts @@ -2,6 +2,7 @@ import { type FullCusEntWithFullCusProduct, type FullSubject, fullSubjectToFullCustomer, + notNullish, } from "@autumn/shared"; import type { Redis } from "ioredis"; import { currentRegion } from "@/external/redis/initRedis.js"; @@ -104,6 +105,10 @@ export const executeRedisDeductionV2 = async ({ entityId: fullSubject.entityId, }); + // One timestamp for the whole operation: the resolver keys windows from it + // and Lua receives the same value, so they never disagree on the window. + const usageWindowNow = Date.now(); + for (const deduction of deductions) { const { feature, @@ -117,6 +122,7 @@ export const executeRedisDeductionV2 = async ({ customerEntitlementDeductions, spendLimitByFeatureId, usageBasedCusEntIdsByFeatureId, + usageWindowLimits, rollovers, customerEntitlements, unlimitedFeatureIds, @@ -127,6 +133,7 @@ export const executeRedisDeductionV2 = async ({ fullSubject, deduction, options, + now: usageWindowNow, }); if (unlimitedFeatureIds.length > 0) { @@ -171,6 +178,16 @@ export const executeRedisDeductionV2 = async ({ }).redisKey : null; + // Anchor features own usage-window counters and may not be in the + // deduction set, so their balance hash keys must be declared too. + const anchorFeatureIds = [ + ...new Set( + (usageWindowLimits ?? []) + .map((limit) => limit.anchor_feature_id) + .filter((featureId): featureId is string => featureId !== null), + ), + ]; + const { keys, balanceKeyIndexByFeatureId } = buildDeductFromSubjectBalancesKeys({ orgId: org.id, @@ -181,8 +198,17 @@ export const executeRedisDeductionV2 = async ({ idempotencyKey: idempotencyRedisKey, customerEntitlementDeductions, fallbackFeatureId: feature.id, + anchorFeatureIds, }); + // Usage windows are enforced/incremented only for real positive + // consumption, never for target_balance set-downs or granted-balance edits. + const isConsumption = + notNullish(toDeduct) && + (toDeduct as number) > 0 && + !notNullish(targetBalance) && + !options.alterGrantedBalance; + const luaParams = { org_id: org.id, env, @@ -192,6 +218,9 @@ export const executeRedisDeductionV2 = async ({ spend_limit_by_feature_id: spendLimitByFeatureId ?? null, usage_based_cus_ent_ids_by_feature_id: usageBasedCusEntIdsByFeatureId ?? null, + usage_window_limits: usageWindowLimits ?? null, + usage_window_now: usageWindowNow, + is_consumption: isConsumption, amount_to_deduct: toDeduct ?? null, target_balance: targetBalance ?? null, target_entity_id: entityId || null, diff --git a/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts index 4b550de10..5760e07d5 100644 --- a/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts @@ -1,18 +1,21 @@ import { AllowanceType, cusEntToStartingBalance, + ErrCode, type FullCusEntWithFullCusProduct, type FullSubject, fullSubjectToCustomerEntitlements, fullSubjectToOverageAllowedByFeatureId, fullSubjectToSpendLimitByFeatureId, fullSubjectToUsageBasedCusEntsByFeatureId, + fullSubjectToUsageWindowLimits, getMaxOverage, getRelevantFeatures, isAllocatedCustomerEntitlement, isFreeCustomerEntitlement, notNullish, orgToInStatuses, + RecaseError, } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { buildLockReceiptKey } from "@/internal/balances/utils/lock/buildLockReceiptKey.js"; @@ -34,11 +37,15 @@ export const prepareFeatureDeductionV2 = ({ fullSubject, deduction, options = {}, + now, }: { ctx: AutumnContext; fullSubject: FullSubject; deduction: FeatureDeduction; options?: DeductionOptions; + // Single timestamp shared with the Lua param so the resolved window key and + // the script agree on which window a boundary-crossing request lands in. + now: number; }): PreparedFeatureDeduction => { const { org, env } = ctx; const { feature, lock, targetBalance } = deduction; @@ -108,6 +115,44 @@ export const prepareFeatureDeductionV2 = ({ fullSubject, featureIds: effectiveFeatureIds, }); + // Resolve windows against the full relevant set (incl credit-system parents) + // even under set_usage, so a parent-feature cap can't be bypassed by set_usage + // on a member feature. + const windowFeatureIds = notNullish(targetBalance) + ? getRelevantFeatures({ + features: ctx.features, + featureId: feature.id, + }).map((candidate) => candidate.id) + : effectiveFeatureIds; + const usageWindowLimits = fullSubjectToUsageWindowLimits({ + fullSubject, + featureIds: windowFeatureIds, + features: ctx.features, + now, + inStatuses: orgToInStatuses({ org }), + }); + + for (const windowLimit of usageWindowLimits) { + if (windowLimit.anchor_customer_entitlement_id === null) { + ctx.logger.warn( + `usage window for feature ${windowLimit.feature_id} has no eligible anchor entitlement; failing closed (rejecting). Likely a misconfigured cap with no in-status, non-entity-scoped owning entitlement.`, + ); + } + } + if (fullSubject.entity?.spend_limits?.some((s) => s.usage_limit != null)) { + ctx.logger.warn( + `entity-scoped usage windows are not enforced in v1; ignored for entity ${fullSubject.entity.id}`, + ); + } + + // set_usage carries no window provenance, so it would silently bypass the hard + // cap; reject it when the feature has an enforced usage window. + if (notNullish(targetBalance) && usageWindowLimits.length > 0) { + throw new RecaseError({ + message: `Cannot set usage for feature ${feature.id}: it has an active usage limit. Remove or adjust the limit, or record usage normally instead of using set_usage.`, + code: ErrCode.SetUsageNotAllowedWithUsageLimit, + }); + } const nativeUsageAllowedFeatureIds = new Set( customerEntitlements @@ -213,6 +258,8 @@ export const prepareFeatureDeductionV2 = ({ Object.keys(usageBasedCusEntIdsByFeatureId).length > 0 ? usageBasedCusEntIdsByFeatureId : undefined, + usageWindowLimits: + usageWindowLimits.length > 0 ? usageWindowLimits : undefined, rollovers: sortedRollovers.map((rollover) => ({ id: rollover.id, credit_cost: rollover.credit_cost, diff --git a/server/src/internal/balances/utils/types/deductionTypes.ts b/server/src/internal/balances/utils/types/deductionTypes.ts index 58a4c5e5d..800e25ae2 100644 --- a/server/src/internal/balances/utils/types/deductionTypes.ts +++ b/server/src/internal/balances/utils/types/deductionTypes.ts @@ -2,6 +2,7 @@ import type { CustomerEntitlementFilters, DbSpendLimit, FullCusEntWithFullCusProduct, + UsageWindowLimit, } from "@autumn/shared"; /** Behavior options for deduction */ @@ -42,6 +43,9 @@ export type PreparedFeatureDeduction = { customerEntitlementDeductions: CustomerEntitlementDeduction[]; spendLimitByFeatureId?: Record; usageBasedCusEntIdsByFeatureId?: Record; + // Resolved windowed usage-limit caps (PR2: passed to Lua but not yet + // enforced; enforcement lands with the deduction-script changes). + usageWindowLimits?: UsageWindowLimit[]; // rolloverIds: string[]; rollovers: RolloverDeduction[]; unlimitedFeatureIds: string[]; diff --git a/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts b/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts index 363f3c859..b08776582 100644 --- a/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts +++ b/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts @@ -21,6 +21,7 @@ export const buildDeductFromSubjectBalancesKeys = ({ idempotencyKey, customerEntitlementDeductions, fallbackFeatureId, + anchorFeatureIds = [], }: { orgId: string; env: AppEnv; @@ -30,17 +31,26 @@ export const buildDeductFromSubjectBalancesKeys = ({ idempotencyKey?: string | null; customerEntitlementDeductions: { feature_id?: string }[]; fallbackFeatureId: string; + // Features owning a usage-window anchor counter. Their balance hash keys must + // be declared in KEYS[] so Lua can load the anchor even when it is not in the + // deduction set. + anchorFeatureIds?: string[]; }) => { const balanceKeysByFeatureId: Record = {}; - for (const deductionEntry of customerEntitlementDeductions) { - const targetFeatureId = deductionEntry.feature_id ?? fallbackFeatureId; - if (balanceKeysByFeatureId[targetFeatureId]) continue; - balanceKeysByFeatureId[targetFeatureId] = buildSharedFullSubjectBalanceKey({ + const addFeatureKey = (featureId: string) => { + if (balanceKeysByFeatureId[featureId]) return; + balanceKeysByFeatureId[featureId] = buildSharedFullSubjectBalanceKey({ orgId, env, customerId, - featureId: targetFeatureId, + featureId, }); + }; + for (const deductionEntry of customerEntitlementDeductions) { + addFeatureKey(deductionEntry.feature_id ?? fallbackFeatureId); + } + for (const anchorFeatureId of anchorFeatureIds) { + addFeatureKey(anchorFeatureId); } const balanceFeatureIds = Object.keys(balanceKeysByFeatureId); diff --git a/server/src/internal/entities/handlers/handleListEntities.ts b/server/src/internal/entities/handlers/handleListEntities.ts index ffe183821..8676fa584 100644 --- a/server/src/internal/entities/handlers/handleListEntities.ts +++ b/server/src/internal/entities/handlers/handleListEntities.ts @@ -1,5 +1,5 @@ -import { createRoute } from "../../../honoMiddlewares/routeHandler.js"; import { Scopes } from "@autumn/shared"; +import { createRoute } from "../../../honoMiddlewares/routeHandler.js"; import { CusService } from "../../customers/CusService.js"; export const handleListEntities = createRoute({ diff --git a/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts new file mode 100644 index 000000000..4f7b551b4 --- /dev/null +++ b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts @@ -0,0 +1,566 @@ +import { describe, expect, test } from "bun:test"; +import { + buildUsageWindowKey, + CusProductStatus, + type DbSpendLimit, + EntInterval, + type Feature, + FeatureType, + type FullSubject, + fullSubjectToUsageWindowLimits, + getUsageWindowBounds, +} from "@autumn/shared"; + +const NOW = Date.UTC(2026, 5, 15, 12, 0, 0); + +const meteredAction1 = { id: "action1", type: FeatureType.Metered } as Feature; +const creditsFeature = { + id: "credits", + type: FeatureType.CreditSystem, +} as Feature; +// Credit system whose schema contains action1, for the membership-anchor path. +const creditsContainingAction1 = { + id: "credits", + type: FeatureType.CreditSystem, + config: { schema: [{ metered_feature_id: "action1", credit_amount: 5 }] }, +} as unknown as Feature; +const credits2ContainingAction1 = { + id: "credits2", + type: FeatureType.CreditSystem, + config: { schema: [{ metered_feature_id: "action1", credit_amount: 3 }] }, +} as unknown as Feature; + +// Test-input shape for a windowed usage cap. usage_limit arms the cap; `interval` +// is the optional override (omit it to test inheriting from the entitlement). +type UsageCap = { + feature_id: string; + limit: number; + interval?: EntInterval; +}; + +const toSpendLimit = (cap: UsageCap): DbSpendLimit => ({ + feature_id: cap.feature_id, + // Entry-level enabled gates the (absent) overage cap, not the usage window. + enabled: false, + usage_limit: cap.limit, + usage_limit_interval: cap.interval, +}); + +// Minimal loose (product-less) customer entitlement for anchor/inherit tests. +// `interval` is the entitlement's reset interval (the inherited window source). +const looseEntitlement = ({ + id, + featureId, + usageLimit, + interval = EntInterval.Month, +}: { + id: string; + featureId: string; + usageLimit?: number; + interval?: EntInterval | null; +}) => + ({ + id, + feature_id: featureId, + internal_entity_id: null, + internal_feature_id: featureId, + customer_product_id: null, + entitlement_id: `ent_${id}`, + created_at: 1000, + balance: 0, + expires_at: null, + entitlement: { + id: `ent_${id}`, + feature_id: featureId, + interval, + usage_limit: usageLimit ?? null, + feature: { id: featureId, internal_id: featureId }, + }, + rollovers: [], + replaceables: [], + }) as unknown as FullSubject["extra_customer_entitlements"][number]; + +// A customer product wrapping one entitlement, with an optional billing-cycle +// anchor. Unlike loose entitlements, product-backed ones keep their +// customer_product through fullSubjectToCustomerEntitlements, so the resolver can +// read the cycle anchor from it. +const customerProductWithEntitlement = ({ + id, + featureId, + usageLimit, + cycleAnchor, +}: { + id: string; + featureId: string; + usageLimit?: number; + cycleAnchor?: number; +}) => + ({ + id: `cusprod_${id}`, + status: CusProductStatus.Active, + created_at: 1000, + product: { is_add_on: false }, + billing_cycle_anchor_resets_at: cycleAnchor ?? null, + customer_entitlements: [ + { + id, + feature_id: featureId, + internal_entity_id: null, + internal_feature_id: featureId, + customer_product_id: `cusprod_${id}`, + entitlement_id: `ent_${id}`, + created_at: 1000, + balance: 0, + expires_at: null, + entitlement: { + id: `ent_${id}`, + feature_id: featureId, + interval: EntInterval.Month, + usage_limit: usageLimit ?? null, + feature: { id: featureId, internal_id: featureId }, + }, + rollovers: [], + replaceables: [], + }, + ], + }) as unknown as FullSubject["customer_products"][number]; + +const buildSubject = ({ + customerLimits = [], + entityLimits, + looseEntitlements = [], + extraCustomerSpendLimits = [], + customerProducts = [], +}: { + customerLimits?: UsageCap[]; + entityLimits?: UsageCap[]; + looseEntitlements?: FullSubject["extra_customer_entitlements"]; + // Raw spend-limit entries (e.g. overage-only or both-cap) injected as-is. + extraCustomerSpendLimits?: DbSpendLimit[]; + customerProducts?: FullSubject["customer_products"]; +}): FullSubject => + ({ + customer: { + spend_limits: [ + ...customerLimits.map(toSpendLimit), + ...extraCustomerSpendLimits, + ], + }, + customer_products: customerProducts, + extra_customer_entitlements: looseEntitlements, + entity: entityLimits + ? { + id: "ent_1", + internal_id: "ient_1", + spend_limits: entityLimits.map(toSpendLimit), + } + : undefined, + }) as unknown as FullSubject; + +describe("fullSubjectToUsageWindowLimits", () => { + test("resolves a customer-level metered-feature cap", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + const { windowStartAt, windowEndAt } = getUsageWindowBounds({ + interval: EntInterval.Month, + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + feature_id: "action1", + dimension_type: "metered_feature", + dimension_feature_id: "action1", + scope_type: "customer", + entity_id: null, + interval: EntInterval.Month, + window_start_at: windowStartAt, + window_end_at: windowEndAt, + limit: 5, + key: buildUsageWindowKey({ + scopeType: "customer", + internalEntityId: null, + dimensionType: "metered_feature", + dimensionFeatureId: "action1", + interval: EntInterval.Month, + windowStartAt, + }), + }); + }); + + test("a credit-system feature resolves to the balance dimension", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + dimension_type: "balance", + dimension_feature_id: null, + }); + }); + + test("inherits the interval from the anchor entitlement when no override", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + // No `interval` => inherit the entitlement's reset interval (Month). + customerLimits: [{ feature_id: "credits", limit: 5 }], + looseEntitlements: [ + looseEntitlement({ id: "ce_credits", featureId: "credits" }), + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + limit: 5, + interval: EntInterval.Month, + anchor_customer_entitlement_id: "ce_credits", + }); + }); + + test("an explicit usage_limit_interval overrides the inherited interval", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + // Entitlement interval is Month; the cap overrides to Day. + customerLimits: [ + { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + ], + looseEntitlements: [ + looseEntitlement({ id: "ce_credits", featureId: "credits" }), + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ limit: 3, interval: EntInterval.Day }); + }); + + test("a usage_limit with no override and a null-interval entitlement resolves nothing", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [{ feature_id: "credits", limit: 3 }], + looseEntitlements: [ + looseEntitlement({ + id: "ce_credits", + featureId: "credits", + interval: null, + }), + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + + test("a usage_limit of 0 is a valid hard cap (blocks all usage)", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "credits", limit: 0, interval: EntInterval.Day }, + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ limit: 0 }); + }); + + test("aligns window bounds to the billing-cycle anchor when present", () => { + // Anchor: a non-midnight, non-first-of-month timestamp the cycle aligns to. + const cycleAnchor = Date.UTC(2026, 0, 9, 15, 30, 0); + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + ], + customerProducts: [ + customerProductWithEntitlement({ + id: "ce_credits", + featureId: "credits", + cycleAnchor, + }), + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + const aligned = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + anchor: cycleAnchor, + }); + const calendar = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0].window_start_at).toBe(aligned.windowStartAt); + expect(limits[0].window_end_at).toBe(aligned.windowEndAt); + // Sanity: the anchored window genuinely differs from calendar alignment. + expect(aligned.windowStartAt).not.toBe(calendar.windowStartAt); + }); + + test("a spend_limit with usage_limit_interval but no usage_limit is not armed", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + extraCustomerSpendLimits: [ + { + feature_id: "action1", + enabled: false, + usage_limit_interval: EntInterval.Month, + }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + + test("ignores an overage-only spend_limit (no usage_limit)", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + extraCustomerSpendLimits: [ + { feature_id: "action1", enabled: true, overage_limit: 20 }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + + test("resolves the window when one entry carries both overage and usage caps", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + extraCustomerSpendLimits: [ + { + feature_id: "action1", + enabled: true, + overage_limit: 20, + usage_limit: 5, + usage_limit_interval: EntInterval.Month, + }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ feature_id: "action1", limit: 5 }); + }); + + test("ignores entity-scoped usage windows in v1; the customer cap applies", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + entityLimits: [ + { feature_id: "action1", limit: 2, interval: EntInterval.Month }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + scope_type: "customer", + entity_id: null, + internal_entity_id: null, + limit: 5, + }); + }); + + test("an entity-only usage window resolves nothing in v1", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + entityLimits: [ + { feature_id: "action1", limit: 2, interval: EntInterval.Month }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + + test("returns nothing when no cap matches the feature", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ customerLimits: [] }), + featureIds: ["action1"], + features: [meteredAction1], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + + test("returns one limit per feature when caps exist on multiple features", () => { + const meteredAction2 = { + id: "action2", + type: FeatureType.Metered, + } as Feature; + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + { feature_id: "action2", limit: 9, interval: EntInterval.Day }, + ], + }), + featureIds: ["action1", "action2"], + features: [meteredAction1, meteredAction2], + now: NOW, + }); + + expect(limits).toHaveLength(2); + expect(limits.map((limit) => limit.feature_id).sort()).toEqual([ + "action1", + "action2", + ]); + }); + + test("resolves the anchor to the owning entitlement (balance dim)", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + ], + looseEntitlements: [ + looseEntitlement({ id: "ce_credits", featureId: "credits" }), + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + anchor_customer_entitlement_id: "ce_credits", + anchor_feature_id: "credits", + }); + }); + + test("metered cap with no native entitlement anchors to the containing credit system", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + looseEntitlements: [ + looseEntitlement({ id: "ce_credits", featureId: "credits" }), + ], + }), + featureIds: ["action1"], + features: [meteredAction1, creditsContainingAction1], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0]).toMatchObject({ + dimension_type: "metered_feature", + dimension_feature_id: "action1", + anchor_customer_entitlement_id: "ce_credits", + anchor_feature_id: "credits", + }); + }); + + test("anchor is null when no owning entitlement exists (fail-closed signal)", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + ], + }), + featureIds: ["credits"], + features: [creditsFeature], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0].anchor_customer_entitlement_id).toBeNull(); + }); + + test("metered cap with a containing credit system but no entitlement resolves a null anchor", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + }), + featureIds: ["action1"], + features: [meteredAction1, creditsContainingAction1], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0].anchor_customer_entitlement_id).toBeNull(); + }); + + test("metered cap contained by two credit systems anchors deterministically", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + looseEntitlements: [ + looseEntitlement({ id: "ce_credits", featureId: "credits" }), + looseEntitlement({ id: "ce_credits2", featureId: "credits2" }), + ], + }), + featureIds: ["action1"], + features: [ + meteredAction1, + creditsContainingAction1, + credits2ContainingAction1, + ], + now: NOW, + }); + + expect(limits).toHaveLength(1); + expect(limits[0].anchor_customer_entitlement_id).toBe("ce_credits"); + }); +}); diff --git a/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts b/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts new file mode 100644 index 000000000..dc8589994 --- /dev/null +++ b/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, test } from "bun:test"; +import { EntInterval, getUsageWindowBounds } from "@autumn/shared"; + +// 2026-06-15T12:34:56Z (a Monday; month index 5 = June) +const NOW = Date.UTC(2026, 5, 15, 12, 34, 56); + +describe("getUsageWindowBounds", () => { + test("day window floors to UTC midnight and spans one day", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Day, now: NOW }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 5, 15), + windowEndAt: Date.UTC(2026, 5, 16), + }); + }); + + test("week window floors to Monday 00:00 UTC and spans 7 days", () => { + // Wednesday -> floors back to Monday June 15. + const wednesday = Date.UTC(2026, 5, 17, 8, 0, 0); + expect( + getUsageWindowBounds({ interval: EntInterval.Week, now: wednesday }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 5, 15), + windowEndAt: Date.UTC(2026, 5, 22), + }); + }); + + test("month window floors to the 1st and spans one month", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Month, now: NOW }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 5, 1), + windowEndAt: Date.UTC(2026, 6, 1), + }); + }); + + test("quarter window floors to the start of the quarter and spans 3 months", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Quarter, now: NOW }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 3, 1), + windowEndAt: Date.UTC(2026, 6, 1), + }); + }); + + test("semi_annual floors to Jan 1 in the first half and spans 6 months", () => { + const may = Date.UTC(2026, 4, 15); + expect( + getUsageWindowBounds({ interval: EntInterval.SemiAnnual, now: may }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 0, 1), + windowEndAt: Date.UTC(2026, 6, 1), + }); + }); + + test("semi_annual floors to Jul 1 in the second half", () => { + const august = Date.UTC(2026, 7, 15); + expect( + getUsageWindowBounds({ interval: EntInterval.SemiAnnual, now: august }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 6, 1), + windowEndAt: Date.UTC(2027, 0, 1), + }); + }); + + test("year window floors to Jan 1 and spans one year", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Year, now: NOW }), + ).toEqual({ + windowStartAt: Date.UTC(2026, 0, 1), + windowEndAt: Date.UTC(2027, 0, 1), + }); + }); + + test("lifetime never resets", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Lifetime, now: NOW }), + ).toEqual({ windowStartAt: 0, windowEndAt: Number.MAX_SAFE_INTEGER }); + }); + + test("is deterministic across the same window", () => { + expect( + getUsageWindowBounds({ interval: EntInterval.Month, now: NOW }), + ).toEqual( + getUsageWindowBounds({ interval: EntInterval.Month, now: NOW + 1000 }), + ); + }); + + test("aligns a day window to the anchor's time-of-day, not UTC midnight", () => { + const anchor = Date.UTC(2026, 0, 9, 15, 30, 0); // 15:30, not midnight + const { windowStartAt, windowEndAt } = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + anchor, + }); + const calendar = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + }); + + // Spans one day, contains now, and rolls at the anchor's 15:30. + expect(windowEndAt - windowStartAt).toBe(24 * 60 * 60 * 1000); + expect(windowStartAt).toBeLessThanOrEqual(NOW); + expect(NOW).toBeLessThan(windowEndAt); + expect(new Date(windowStartAt).getUTCHours()).toBe(15); + expect(new Date(windowStartAt).getUTCMinutes()).toBe(30); + expect(windowStartAt).not.toBe(calendar.windowStartAt); + }); + + test("aligns a month window to the anchor's day-of-month, not the 1st", () => { + const anchor = Date.UTC(2026, 0, 9); // the 9th + const { windowStartAt } = getUsageWindowBounds({ + interval: EntInterval.Month, + now: NOW, // June 15 + anchor, + }); + + expect(new Date(windowStartAt).getUTCDate()).toBe(9); + expect(windowStartAt).toBeLessThanOrEqual(NOW); + }); + + test("lifetime ignores the anchor", () => { + expect( + getUsageWindowBounds({ + interval: EntInterval.Lifetime, + now: NOW, + anchor: Date.UTC(2026, 0, 9), + }), + ).toEqual({ windowStartAt: 0, windowEndAt: Number.MAX_SAFE_INTEGER }); + }); +}); diff --git a/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts b/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts new file mode 100644 index 000000000..6fea01f1b --- /dev/null +++ b/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, test } from "bun:test"; +import { + type AnchorCandidate, + pickAnchorCustomerEntitlementId, +} from "@autumn/shared"; + +const candidate = (overrides: Partial): AnchorCandidate => ({ + id: "ce_1", + is_entity_scoped: false, + is_add_on: false, + status_rank: 0, + created_at: 1000, + ...overrides, +}); + +describe("pickAnchorCustomerEntitlementId", () => { + test("returns null when there are no candidates", () => { + expect( + pickAnchorCustomerEntitlementId({ + candidates: [], + scopeType: "customer", + }), + ).toBeNull(); + }); + + test("customer scope excludes entity-scoped candidates", () => { + const id = pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_entity", is_entity_scoped: true }), + candidate({ id: "ce_customer", is_entity_scoped: false }), + ], + scopeType: "customer", + }); + + expect(id).toBe("ce_customer"); + }); + + test("customer scope returns null when only entity-scoped candidates exist (fail closed)", () => { + const id = pickAnchorCustomerEntitlementId({ + candidates: [candidate({ id: "ce_entity", is_entity_scoped: true })], + scopeType: "customer", + }); + + expect(id).toBeNull(); + }); + + test("entity scope falls back to customer-level candidates when none are entity-scoped", () => { + const id = pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_a", is_entity_scoped: false }), + candidate({ id: "ce_b", is_entity_scoped: false, created_at: 2000 }), + ], + scopeType: "entity", + }); + + expect(id).toBe("ce_a"); + }); + + test("entity scope prefers an entity-scoped candidate over a customer-level one", () => { + const id = pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_customer", is_entity_scoped: false }), + candidate({ id: "ce_entity", is_entity_scoped: true }), + ], + scopeType: "entity", + }); + + expect(id).toBe("ce_entity"); + }); + + test("prefers lower status_rank, then non-add-on, then oldest, then id", () => { + // All customer-scope candidates; tie-break ladder. + expect( + pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_pastdue", status_rank: 1 }), + candidate({ id: "ce_active", status_rank: 0 }), + ], + scopeType: "customer", + }), + ).toBe("ce_active"); + + expect( + pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_addon", is_add_on: true }), + candidate({ id: "ce_base", is_add_on: false }), + ], + scopeType: "customer", + }), + ).toBe("ce_base"); + + expect( + pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_new", created_at: 2000 }), + candidate({ id: "ce_old", created_at: 1000 }), + ], + scopeType: "customer", + }), + ).toBe("ce_old"); + + expect( + pickAnchorCustomerEntitlementId({ + candidates: [candidate({ id: "ce_b" }), candidate({ id: "ce_a" })], + scopeType: "customer", + }), + ).toBe("ce_a"); + }); + + test("is deterministic regardless of input order", () => { + const candidates = [ + candidate({ id: "ce_b", status_rank: 0, created_at: 1000 }), + candidate({ id: "ce_a", status_rank: 0, created_at: 1000 }), + candidate({ id: "ce_c", status_rank: 1, created_at: 500 }), + ]; + + const forward = pickAnchorCustomerEntitlementId({ + candidates, + scopeType: "customer", + }); + const reversed = pickAnchorCustomerEntitlementId({ + candidates: [...candidates].reverse(), + scopeType: "customer", + }); + + expect(forward).toBe("ce_a"); + expect(reversed).toBe("ce_a"); + }); +}); diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index fe63c3cfe..53c4e8c27 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -5,7 +5,8 @@ import { ApiUsageAlertSchema } from "./usageAlert.js"; export const ApiEntityBillingControlsSchema = z.object({ spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ - description: "List of overage spend limits per feature.", + description: + "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", diff --git a/shared/enums/ErrCode.ts b/shared/enums/ErrCode.ts index 32c70fe4b..77f2bb653 100644 --- a/shared/enums/ErrCode.ts +++ b/shared/enums/ErrCode.ts @@ -104,6 +104,7 @@ export const ErrCode = { CreateEntitlementFailed: "create_entitlement_failed", DeleteEntitlementFailed: "delete_entitlement_failed", InsufficientBalance: "insufficient_balance", + SetUsageNotAllowedWithUsageLimit: "set_usage_not_allowed_with_usage_limit", // Invoice CreateInvoiceFailed: "create_invoice_failed", diff --git a/shared/index.ts b/shared/index.ts index 41a9e6d06..8b58fe33d 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -211,6 +211,8 @@ export * from "./utils/cusEntUtils/balanceUtils/cusEntToUsageAllowed"; export * from "./utils/cusEntUtils/index"; // Utils export * from "./utils/usageWindowUtils/buildUsageWindowKey"; +export * from "./utils/usageWindowUtils/getUsageWindowBounds"; +export * from "./utils/usageWindowUtils/pickAnchorCustomerEntitlementId"; export * from "./utils/displayUtils"; export * from "./utils/fullSubjectUtils"; export * from "./utils/index"; diff --git a/shared/models/cusModels/billingControls/customerBillingControls.ts b/shared/models/cusModels/billingControls/customerBillingControls.ts index 59efa6c9d..f112fd324 100644 --- a/shared/models/cusModels/billingControls/customerBillingControls.ts +++ b/shared/models/cusModels/billingControls/customerBillingControls.ts @@ -100,7 +100,8 @@ export const CustomerBillingControlsSchema = z.object({ description: "List of auto top-up configurations per feature.", }), spend_limits: z.array(DbSpendLimitSchema).optional().meta({ - description: "List of overage spend limits per feature.", + description: + "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", @@ -125,7 +126,8 @@ export const CustomerBillingControlsResponseSchema = z.object({ description: "List of auto top-up configurations per feature.", }), spend_limits: z.array(DbSpendLimitSchema).optional().meta({ - description: "List of overage spend limits per feature.", + description: + "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", diff --git a/shared/models/cusModels/billingControls/entityBillingControls.ts b/shared/models/cusModels/billingControls/entityBillingControls.ts index b1efb832e..58716f0b2 100644 --- a/shared/models/cusModels/billingControls/entityBillingControls.ts +++ b/shared/models/cusModels/billingControls/entityBillingControls.ts @@ -5,7 +5,8 @@ import { DbUsageAlertSchema } from "./usageAlert.js"; export const EntityBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ - description: "List of overage spend limits per feature.", + description: + "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", diff --git a/shared/models/cusModels/billingControls/spendLimit.ts b/shared/models/cusModels/billingControls/spendLimit.ts index 7e94ee15d..d274aaea0 100644 --- a/shared/models/cusModels/billingControls/spendLimit.ts +++ b/shared/models/cusModels/billingControls/spendLimit.ts @@ -1,4 +1,5 @@ import { z } from "zod/v4"; +import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; export const DbSpendLimitSchema = z .object({ @@ -6,22 +7,27 @@ export const DbSpendLimitSchema = z description: "Optional feature ID this spend limit applies to.", }), enabled: z.boolean().default(false).meta({ - description: "Whether this spend limit is enabled.", + description: "Whether the overage spend limit is enabled.", }), overage_limit: z.number().min(0).optional().meta({ description: "Maximum allowed overage spend for the target feature.", }), + usage_limit: z.number().min(0).optional().meta({ + description: + "Windowed usage cap: max units allowed per window. Its presence arms the cap (hard pre-write reject); absent means no usage cap.", + }), + usage_limit_interval: z.enum(EntInterval).optional().meta({ + description: + "Optional window/reset interval for the usage cap, aligned to the customer's billing cycle. When omitted, defaults to the feature entitlement's own reset interval. Only meaningful with usage_limit set.", + }), }) .refine( - (data) => { - if (data.overage_limit === undefined) { - return true; - } - - return data.feature_id !== undefined; - }, + (data) => + !(data.overage_limit !== undefined || data.usage_limit !== undefined) || + data.feature_id !== undefined, { - message: "feature_id is required when overage_limit is provided", + message: + "feature_id is required when overage_limit or usage_limit is provided", path: ["feature_id"], }, ); diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index a762395ee..307216ce4 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -65,11 +65,7 @@ export const entities = pgTable( table.internal_customer_id, sql`${table.internal_id} DESC`, ), - index("idx_entities_org_env_id").on( - table.org_id, - table.env, - table.id, - ), + index("idx_entities_org_env_id").on(table.org_id, table.env, table.id), index("idx_entities_cursor").on( table.org_id, table.env, diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts index ee1bad79d..0eea6fb20 100644 --- a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts +++ b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts @@ -52,7 +52,7 @@ export type UsageWindows = z.infer; /** * A resolved, enforceable usage-window limit: the runtime input handed to the * deduction script. Built each deduction from the windowed usage cap - * (`usage_limit_interval` + inherited/override limit) on a `spend_limit` billing + * (`usage_limit` + optional `usage_limit_interval` override) on a `spend_limit` billing * control plus the current window bounds (NOT stored). * Carries the resolved `limit` and `key`/window so Lua can find-or-create the * matching counter. diff --git a/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts new file mode 100644 index 000000000..0356afde8 --- /dev/null +++ b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts @@ -0,0 +1,191 @@ +import type { FullSubject } from "../../models/cusModels/fullSubject/fullSubjectModel.js"; +import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels/cusEntModels/cusEntWithProduct.js"; +import type { + UsageWindowDimension, + UsageWindowLimit, +} from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; +import { FeatureType } from "../../models/featureModels/featureEnums.js"; +import type { Feature } from "../../models/featureModels/featureModels.js"; +import { getRelevantFeatures } from "../featureUtils.js"; +import { buildUsageWindowKey } from "../usageWindowUtils/buildUsageWindowKey.js"; +import { getUsageWindowBounds } from "../usageWindowUtils/getUsageWindowBounds.js"; +import { + type AnchorCandidate, + pickAnchorCustomerEntitlementId, +} from "../usageWindowUtils/pickAnchorCustomerEntitlementId.js"; +import { fullSubjectToCustomerEntitlements } from "./fullSubjectToCustomerEntitlements.js"; + +// Lower rank wins in the anchor tie-break. Loose entitlements (no product) are +// treated as active grants. +const customerProductStatusToAnchorRank = ( + status: CusProductStatus | undefined, +): number => { + switch (status) { + case undefined: + case CusProductStatus.Active: + return 0; + case CusProductStatus.PastDue: + return 1; + case CusProductStatus.Scheduled: + return 2; + case CusProductStatus.Trialing: + return 3; + default: + return 999; + } +}; + +const toAnchorCandidate = ( + customerEntitlement: FullCusEntWithFullCusProduct, +): AnchorCandidate => ({ + id: customerEntitlement.id, + is_entity_scoped: customerEntitlement.internal_entity_id !== null, + is_add_on: customerEntitlement.customer_product?.product.is_add_on ?? false, + status_rank: customerProductStatusToAnchorRank( + customerEntitlement.customer_product?.status, + ), + created_at: + customerEntitlement.customer_product?.created_at ?? + customerEntitlement.created_at, +}); + +/** + * Resolves the enforceable usage-window limits for the requested features from a + * FullSubject. A windowed cap is armed by setting `usage_limit` on a customer + * `spend_limit` entry (flat config; its presence is the switch, independent of the + * entry-level `enabled` which gates the overage cap). + * v1 reads ONLY customer-scoped spend_limits; entity usage windows are out of scope. + * + * The window interval is the entry's `usage_limit_interval` if set, else inherited + * from the anchor entitlement's reset interval (`entitlement.interval`) - so a cap + * defaults to the billing cycle and you usually set only `usage_limit`. No + * resolvable interval (e.g. a boolean entitlement with no interval and no override) + * means no enforceable cap, so the feature is skipped. + * + * A cap on a credit-system feature targets the credit pool (`balance` dimension); + * a cap on any other feature targets that feature's usage (`metered_feature`). + * Window bounds align to the customer's billing cycle (the anchor entitlement's + * `billing_cycle_anchor_resets_at`), falling back to UTC calendar when absent. + * + * Each limit gets a single owning `anchor_customer_entitlement_id`, resolved + * deduction-order-independently so one counter never splits across pools. Null + * anchor means no eligible owner; the enforcement layer fails closed. + */ +export const fullSubjectToUsageWindowLimits = ({ + fullSubject, + featureIds, + features, + now, + inStatuses, +}: { + fullSubject: FullSubject; + featureIds: string[]; + features: Feature[]; + now: number; + // Status filter for entitlement lookups; pass the caller's orgToInStatuses so + // the cap's value/anchor resolution matches what the deduction can act on. + inStatuses?: CusProductStatus[]; +}): UsageWindowLimit[] => { + // v1: customer-scoped caps only; entity-scoped usage windows are out of scope. + const customerSpendLimits = fullSubject.customer.spend_limits ?? []; + const limits: UsageWindowLimit[] = []; + + for (const featureId of [...new Set(featureIds)]) { + const spendLimit = customerSpendLimits.find( + (candidate) => + candidate.feature_id === featureId && candidate.usage_limit != null, + ); + const limit = spendLimit?.usage_limit; + if (spendLimit == null || limit == null) continue; + + const scopeType = "customer" as const; + const entityId = null; + const internalEntityId = null; + + const isCreditSystem = + features.find((feature) => feature.id === featureId)?.type === + FeatureType.CreditSystem; + const dimensionType: UsageWindowDimension = isCreditSystem + ? "balance" + : "metered_feature"; + const dimensionFeatureId = isCreditSystem ? null : featureId; + + // Balance dim is owned by the credit-system entitlement. Metered dim + // prefers the member feature's own entitlement, then falls back to a + // credit system that contains it. + const containingCreditSystemFeatureIds = getRelevantFeatures({ + features, + featureId, + }) + .map((feature) => feature.id) + .filter((relevantFeatureId) => relevantFeatureId !== featureId); + const ownerFeatureIdsByPreference = isCreditSystem + ? [[featureId]] + : [[featureId], containingCreditSystemFeatureIds]; + + let anchorId: string | null = null; + let anchorFeatureId: string | null = null; + let anchorCustomerEntitlement: FullCusEntWithFullCusProduct | undefined; + for (const ownerFeatureIds of ownerFeatureIdsByPreference) { + if (ownerFeatureIds.length === 0) continue; + const candidateEntitlements = fullSubjectToCustomerEntitlements({ + fullSubject, + featureIds: ownerFeatureIds, + inStatuses, + }); + anchorId = pickAnchorCustomerEntitlementId({ + candidates: candidateEntitlements.map(toAnchorCandidate), + scopeType, + }); + if (anchorId) { + anchorCustomerEntitlement = candidateEntitlements.find( + (customerEntitlement) => customerEntitlement.id === anchorId, + ); + anchorFeatureId = anchorCustomerEntitlement?.feature_id ?? null; + break; + } + } + + const interval = + spendLimit.usage_limit_interval ?? + anchorCustomerEntitlement?.entitlement.interval; + if (interval == null) continue; + + // Align window bounds to the customer's billing cycle when the anchor has a + // cycle anchor; otherwise getUsageWindowBounds falls back to UTC calendar. + const cycleAnchor = + anchorCustomerEntitlement?.customer_product + ?.billing_cycle_anchor_resets_at ?? null; + const { windowStartAt, windowEndAt } = getUsageWindowBounds({ + interval, + now, + anchor: cycleAnchor, + }); + + limits.push({ + feature_id: featureId, + key: buildUsageWindowKey({ + scopeType, + internalEntityId, + dimensionType, + dimensionFeatureId, + interval, + windowStartAt, + }), + dimension_type: dimensionType, + dimension_feature_id: dimensionFeatureId, + scope_type: scopeType, + entity_id: entityId, + internal_entity_id: internalEntityId, + interval, + window_start_at: windowStartAt, + window_end_at: windowEndAt, + limit, + anchor_customer_entitlement_id: anchorId, + anchor_feature_id: anchorFeatureId, + }); + } + + return limits; +}; diff --git a/shared/utils/fullSubjectUtils/index.ts b/shared/utils/fullSubjectUtils/index.ts index 59652d9b5..ecd248198 100644 --- a/shared/utils/fullSubjectUtils/index.ts +++ b/shared/utils/fullSubjectUtils/index.ts @@ -9,6 +9,7 @@ export { fullSubjectToSpendLimitByFeatureId, fullSubjectToUsageBasedCusEntsByFeatureId, } from "./fullSubjectToSpendLimit.js"; +export { fullSubjectToUsageWindowLimits } from "./fullSubjectToUsageWindowLimits.js"; export { logFullSubject } from "./logFullSubject.js"; export { mergeCustomerBillingControlsForCheck } from "./mergeCustomerBillingControlsForCheck.js"; export { normalizedToFullSubject } from "./normalizedToFullSubject.js"; diff --git a/shared/utils/usageWindowUtils/getUsageWindowBounds.ts b/shared/utils/usageWindowUtils/getUsageWindowBounds.ts new file mode 100644 index 000000000..4c6fcb802 --- /dev/null +++ b/shared/utils/usageWindowUtils/getUsageWindowBounds.ts @@ -0,0 +1,98 @@ +import { UTCDate } from "@date-fns/utc"; +import { + startOfDay, + startOfHour, + startOfMinute, + startOfMonth, + startOfQuarter, + startOfWeek, + startOfYear, +} from "date-fns"; +import { EntInterval } from "../../models/productModels/intervals/entitlementInterval.js"; +import { getCycleEnd } from "../billingUtils/cycleUtils/getCycleEnd.js"; +import { getCycleStart } from "../billingUtils/cycleUtils/getCycleStart.js"; +import { addInterval } from "../billingUtils/intervalUtils/intervalArithmetic.js"; + +const LIFETIME_WINDOW_END = Number.MAX_SAFE_INTEGER; + +// UTC-calendar-aligned start of the interval containing `now`. Used only as the +// fallback when no billing-cycle anchor is available; alignment keeps the window +// (and its key) deterministic from `now` alone so TS and Lua never disagree. +const startOfWindow = ({ + interval, + now, +}: { + interval: EntInterval; + now: number; +}): number => { + const from = new UTCDate(now); + + switch (interval) { + case EntInterval.Minute: + return startOfMinute(from).getTime(); + case EntInterval.Hour: + return startOfHour(from).getTime(); + case EntInterval.Day: + return startOfDay(from).getTime(); + case EntInterval.Week: + return startOfWeek(from, { weekStartsOn: 1 }).getTime(); + case EntInterval.Month: + return startOfMonth(from).getTime(); + case EntInterval.Quarter: + return startOfQuarter(from).getTime(); + case EntInterval.SemiAnnual: { + const yearStart = startOfYear(from).getTime(); + return from.getUTCMonth() < 6 + ? yearStart + : addInterval({ + from: yearStart, + interval: EntInterval.Month, + intervalCount: 6, + }); + } + case EntInterval.Year: + return startOfYear(from).getTime(); + default: + return now; + } +}; + +/** + * Current usage-window bounds for an interval. When a billing-cycle `anchor` is + * given, bounds align to the customer's cycle (so a "daily" cap rolls on their + * billing time-of-day, not UTC midnight). Without an anchor, falls back to UTC + * calendar alignment. `lifetime` never resets. Each window spans one interval. + */ +export const getUsageWindowBounds = ({ + interval, + now, + anchor, +}: { + interval: EntInterval; + now: number; + anchor?: number | null; +}): { windowStartAt: number; windowEndAt: number } => { + if (interval === EntInterval.Lifetime) { + return { windowStartAt: 0, windowEndAt: LIFETIME_WINDOW_END }; + } + + if (anchor != null && Number.isFinite(anchor)) { + const cycleStartAt = getCycleStart({ anchor, interval, now }); + const cycleEndAt = getCycleEnd({ anchor, interval, now }); + // Only trust cycle bounds that are finite and actually bracket `now`; a bad + // billing anchor must fall back to calendar, never poison the deduction. + if ( + Number.isFinite(cycleStartAt) && + Number.isFinite(cycleEndAt) && + cycleStartAt <= now && + now < cycleEndAt + ) { + return { windowStartAt: cycleStartAt, windowEndAt: cycleEndAt }; + } + } + + const windowStartAt = startOfWindow({ interval, now }); + const windowEndAt = addInterval({ from: windowStartAt, interval }); + + return { windowStartAt, windowEndAt }; +}; diff --git a/shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts b/shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts new file mode 100644 index 000000000..7baba13d0 --- /dev/null +++ b/shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts @@ -0,0 +1,55 @@ +import type { UsageWindowScope } from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; + +/** + * A candidate customer entitlement for owning a usage-window counter, reduced to + * just the fields the canonical pick depends on. Deliberately decoupled from the + * deduction set and its ordering so the chosen anchor is stable regardless of + * deduction order, filters, or reverse_deduction_order. + */ +export type AnchorCandidate = { + id: string; + is_entity_scoped: boolean; + is_add_on: boolean; + // Lower rank = higher priority (e.g. active before past_due). + status_rank: number; + created_at: number; +}; + +/** + * Deterministically picks the single customer entitlement that owns a usage + * window, so one logical counter is never split across entitlements. + * + * Customer-scope counters must live on a customer-level entitlement (returns + * null if only entity-scoped ones exist, so the caller can fail closed rather + * than split the cap per entity). Entity-scope prefers an entity-owned one. + */ +export const pickAnchorCustomerEntitlementId = ({ + candidates, + scopeType, +}: { + candidates: AnchorCandidate[]; + scopeType: UsageWindowScope; +}): string | null => { + let eligible: AnchorCandidate[]; + if (scopeType === "customer") { + eligible = candidates.filter((candidate) => !candidate.is_entity_scoped); + } else { + const entityScoped = candidates.filter( + (candidate) => candidate.is_entity_scoped, + ); + eligible = entityScoped.length > 0 ? entityScoped : candidates; + } + + if (eligible.length === 0) { + return null; + } + + const sorted = [...eligible].sort((a, b) => { + if (a.status_rank !== b.status_rank) return a.status_rank - b.status_rank; + if (a.is_add_on !== b.is_add_on) return a.is_add_on ? 1 : -1; + if (a.created_at !== b.created_at) return a.created_at - b.created_at; + return a.id < b.id ? -1 : 1; + }); + + return sorted[0].id; +}; From 2b63e7de1a33507a371d47dbd87bd49657f216f4 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Tue, 2 Jun 2026 19:21:38 +0100 Subject: [PATCH 03/41] enforce windowed usage cap in V2 deduction lua --- .../fullSubjectDeduction/contextUtilsV2.lua | 18 + .../deductFromSubjectBalances.lua | 78 ++++- .../readSubjectBalances.lua | 77 +++-- .../runDeductionOnContextV2.lua | 5 + .../usageWindowUtilsV2.lua | 163 +++++++++ server/src/_luaScriptsV2/luaScriptsV2.ts | 2 + .../balances/check/runCheckWithTrackV2.ts | 6 +- .../track/v3/handleRedisTrackErrorV3.ts | 9 +- .../applyDeductionUpdateToFullSubject.ts | 1 + .../deductionV2/executeRedisDeductionV2.ts | 1 + .../balances/utils/types/deductionUpdate.ts | 2 + .../utils/types/redisDeductionError.ts | 5 + .../track-customer-usage-limit.test.ts | 319 ++++++++++++++++++ .../api/errors/classes/balancesErrClasses.ts | 19 ++ shared/api/errors/codes/balancesErrCodes.ts | 1 + 15 files changed, 678 insertions(+), 28 deletions(-) create mode 100644 server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua create mode 100644 server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua index 8e30a8a22..25b75b3e0 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua @@ -24,6 +24,7 @@ local function init_context(params) env = params.env, customer_id = params.customer_id, customer_entitlement_deductions = params.customer_entitlement_deductions, + anchor_entitlements = params.anchor_entitlements, balance_keys_by_feature_id = params.balance_keys_by_feature_id, }) @@ -96,6 +97,23 @@ local function init_context(params) end end + -- Register usage-window anchor cus_ents that are not in the deduction set so + -- their counter can be read/mutated and persisted (HSET) on apply. + for customer_entitlement_id, balance_entry in pairs(read_result.balances_by_id) do + if balance_entry.anchor_only + and is_nil(context.customer_entitlements[customer_entitlement_id]) + then + context.customer_entitlements[customer_entitlement_id] = { + base_path = customer_entitlement_id, + balance_key = balance_entry.balance_key, + subject_balance = balance_entry.subject_balance, + customer_entitlement_id = customer_entitlement_id, + feature_id = balance_entry.feature_id, + is_anchor_only = true, + } + end + end + return context end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua index ede381166..9d3abc3c7 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua @@ -111,6 +111,30 @@ local idempotency_ttl_ms = params.idempotency_ttl_ms local lock = params.lock local unwind_value = params.unwind_value local lock_receipt_key = lock_receipt_key_from_keys +local usage_window_limits = params.usage_window_limits +local usage_window_now = params.usage_window_now +local is_consumption = params.is_consumption + +-- Distinct usage-window anchor cus_ents to force-load into context (they own +-- the counters and may not be in the deduction set). +local anchor_entitlements = {} +if not is_nil(usage_window_limits) then + local seen_anchor_ids = {} + for _, usage_window_limit in ipairs(usage_window_limits) do + local anchor_id = usage_window_limit.anchor_customer_entitlement_id + local anchor_feature_id = usage_window_limit.anchor_feature_id + if not is_nil(anchor_id) + and not is_nil(anchor_feature_id) + and not seen_anchor_ids[anchor_id] + then + seen_anchor_ids[anchor_id] = true + table.insert(anchor_entitlements, { + customer_entitlement_id = anchor_id, + feature_id = anchor_feature_id, + }) + end + end +end if not is_nil(idempotency_key) then if redis.call('EXISTS', idempotency_key) == 1 then @@ -144,6 +168,7 @@ local context = init_context({ env = env, customer_id = customer_id, customer_entitlement_deductions = customer_entitlement_deductions, + anchor_entitlements = anchor_entitlements, balance_keys_by_feature_id = params.balance_keys_by_feature_id, debug = params.debug, }) @@ -235,11 +260,6 @@ for _, cus_ent_id in ipairs(unwind_modified_cus_ent_ids) do end end -local modified_customer_entitlement_ids = collect_modified_customer_entitlement_ids({ - context = context, - extra_customer_entitlement_ids = unwind_modified_cus_ent_ids, -}) - logger.log(" remaining_amount: %s", tostring(remaining_amount or "nil")) logger.log(" is_refund: %s", tostring(remaining_amount < 0 or false)) local mutation_logs = context.mutation_logs @@ -259,6 +279,54 @@ if remaining_amount > 0 and overage_behaviour == 'reject' then }) end +-- Hard windowed usage-limit enforcement, on ACTUAL consumed amounts, before any +-- writes. Only for positive consumption (refunds / target_balance / granted +-- balance edits never trip or move counters). v1 also excludes lock-based and +-- unwind flows: counter reversal on partial unwind is not implemented yet, so +-- enforcing there could drift the counter. +local enforce_usage_windows = is_consumption + and is_nil(unwind_value) + and (is_nil(lock) or not lock.enabled) + and not is_nil(usage_window_limits) + and #usage_window_limits > 0 + +if enforce_usage_windows then + local exceeded_feature_id = check_usage_window_limits({ + context = context, + usage_window_limits = usage_window_limits, + updates = updates, + amount_to_deduct = amount_to_deduct, + remaining_amount = remaining_amount, + }) + + if not is_nil(exceeded_feature_id) then + return cjson.encode({ + error = 'USAGE_LIMIT_EXCEEDED', + feature_id = exceeded_feature_id, + remaining = remaining_amount, + updates = {}, + rollover_updates = {}, + modified_customer_entitlement_ids = new_empty_array(), + mutation_logs = mutation_logs, + logs = context.logs, + }) + end + + increment_usage_window_counters({ + context = context, + usage_window_limits = usage_window_limits, + updates = updates, + amount_to_deduct = amount_to_deduct, + remaining_amount = remaining_amount, + now = usage_window_now, + }) +end + +local modified_customer_entitlement_ids = collect_modified_customer_entitlement_ids({ + context = context, + extra_customer_entitlement_ids = unwind_modified_cus_ent_ids, +}) + if not is_nil(lock) and not is_nil(lock.enabled) and lock.enabled diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua index d4e485815..b118c39b5 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua @@ -18,29 +18,61 @@ local function read_subject_balances(params) local balances_by_id = {} local missing_customer_entitlement_ids = {} local entries_by_balance_key = {} + local seen_ids_by_balance_key = {} + -- Anchor-only cus_ents (usage-window owners not in the deduction set) must + -- not abort the deduction when absent; a missing anchor fails closed later. + local anchor_only_ids = {} local balance_keys_by_feature_id = safe_table(params.balance_keys_by_feature_id) + local function queue_balance_read(customer_entitlement_id, feature_id) + if not (customer_entitlement_id and feature_id) then + return false + end + + local balance_key = balance_keys_by_feature_id[feature_id] + if not balance_key then + return false + end + + if entries_by_balance_key[balance_key] == nil then + entries_by_balance_key[balance_key] = { + feature_id = feature_id, + customer_entitlement_ids = {}, + } + seen_ids_by_balance_key[balance_key] = {} + end + + if seen_ids_by_balance_key[balance_key][customer_entitlement_id] then + return true + end + seen_ids_by_balance_key[balance_key][customer_entitlement_id] = true + + table.insert( + entries_by_balance_key[balance_key].customer_entitlement_ids, + customer_entitlement_id + ) + return true + end + + local deduction_ids = {} for _, ent_obj in ipairs(params.customer_entitlement_deductions or {}) do local customer_entitlement_id = ent_obj.customer_entitlement_id - local feature_id = ent_obj.feature_id - - if customer_entitlement_id and feature_id then - local balance_key = balance_keys_by_feature_id[feature_id] - if not balance_key then + if customer_entitlement_id then + local queued = queue_balance_read(customer_entitlement_id, ent_obj.feature_id) + if not queued then table.insert(missing_customer_entitlement_ids, customer_entitlement_id) - else - if entries_by_balance_key[balance_key] == nil then - entries_by_balance_key[balance_key] = { - feature_id = feature_id, - customer_entitlement_ids = {}, - } - end - - table.insert( - entries_by_balance_key[balance_key].customer_entitlement_ids, - customer_entitlement_id - ) end + deduction_ids[customer_entitlement_id] = true + end + end + + -- A deduction target must never be marked anchor_only, or a cache miss on it is + -- swallowed instead of surfacing as missing + triggering the Postgres fallback. + for _, anchor in ipairs(params.anchor_entitlements or {}) do + local customer_entitlement_id = anchor.customer_entitlement_id + if customer_entitlement_id and not deduction_ids[customer_entitlement_id] then + anchor_only_ids[customer_entitlement_id] = true + queue_balance_read(customer_entitlement_id, anchor.feature_id) end end @@ -57,16 +89,19 @@ local function read_subject_balances(params) local subject_balance = decode_subject_balance(raw_value) if subject_balance == nil then - table.insert( - missing_customer_entitlement_ids, - customer_entitlement_id - ) + if not anchor_only_ids[customer_entitlement_id] then + table.insert( + missing_customer_entitlement_ids, + customer_entitlement_id + ) + end else balances_by_id[customer_entitlement_id] = { balance_key = balance_key, customer_entitlement_id = customer_entitlement_id, feature_id = entry.feature_id, subject_balance = subject_balance, + anchor_only = anchor_only_ids[customer_entitlement_id] or nil, } end end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua index e092ee3a3..5e9901b33 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua @@ -274,6 +274,11 @@ local function run_deduction_on_context(params) update.adjustment = ent_data.adjustment or 0 update.additional_balance = 0 + + if ent_data.subject_balance + and type(ent_data.subject_balance.usage_windows) == 'table' then + update.usage_windows = ent_data.subject_balance.usage_windows + end end end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua new file mode 100644 index 000000000..9d7b003fc --- /dev/null +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua @@ -0,0 +1,163 @@ +-- ============================================================================ +-- USAGE WINDOW UTILITIES (V2) +-- Hard windowed usage-limit enforcement, evaluated at the orchestration layer +-- against ACTUAL consumed amounts (post-deduction, pre-write). +-- +-- Counters live inline on the anchor cus_ent's subject_balance.usage_windows, +-- keyed by the deterministic window key built in TS. The window key includes +-- window_start_at, so the current window's counter is found-or-created at +-- limit.key and a rolled window is simply a different (absent) key. +-- ============================================================================ + +-- Tolerance for float drift (credit-ratio conversions leave sub-nano noise). +local USAGE_WINDOW_EPSILON = 1e-9 + +local function get_anchor_usage_windows(context, anchor_customer_entitlement_id) + if is_nil(anchor_customer_entitlement_id) then + return nil + end + + local ent_data = context.customer_entitlements[anchor_customer_entitlement_id] + if not ent_data or not ent_data.subject_balance then + return nil + end + + if type(ent_data.subject_balance.usage_windows) ~= 'table' then + ent_data.subject_balance.usage_windows = {} + end + + return ent_data.subject_balance.usage_windows +end + +-- Actually-consumed amount for a limit, in its native unit. metered_feature +-- counts feature units (the tracked total); balance counts credits drained from +-- the anchor pool (its `deducted`, which is in credits). +local function usage_window_consumed(params) + local limit = params.limit + local updates = params.updates + + if limit.dimension_type == 'balance' then + local anchor_update = updates[limit.anchor_customer_entitlement_id] + return anchor_update and safe_number(anchor_update.deducted) or 0 + end + + return safe_number(params.amount_to_deduct) - safe_number(params.remaining_amount) +end + +-- Returns the feature_id of the first limit that would be exceeded (so the +-- caller can hard-reject), or nil if every limit has room. Null/missing anchor +-- fails closed: a cap that cannot resolve an owner must not silently allow. +local function check_usage_window_limits(params) + local context = params.context + local limits = params.usage_window_limits or {} + + for _, limit in ipairs(limits) do + local windows = get_anchor_usage_windows( + context, + limit.anchor_customer_entitlement_id + ) + if is_nil(windows) then + return limit.feature_id + end + + local consumed = usage_window_consumed({ + limit = limit, + updates = params.updates, + amount_to_deduct = params.amount_to_deduct, + remaining_amount = params.remaining_amount, + }) + + if consumed > USAGE_WINDOW_EPSILON then + local existing = windows[limit.key] + local current_usage = existing and safe_number(existing.usage_amount) or 0 + if current_usage + consumed + > safe_number(limit.limit) + USAGE_WINDOW_EPSILON then + return limit.feature_id + end + end + end + + return nil +end + +-- Applies the consumed amount to each anchor counter (find-or-create at the +-- current window key), prunes closed sibling windows, and marks the anchor dirty +-- so apply_pending_writes persists it (even when the anchor's balance did not +-- change, or when only a prune happened). +local function increment_usage_window_counters(params) + local context = params.context + local limits = params.usage_window_limits or {} + local now = params.now + + for _, limit in ipairs(limits) do + local windows = get_anchor_usage_windows( + context, + limit.anchor_customer_entitlement_id + ) + if not is_nil(windows) then + -- Prune closed windows every pass (not only when consuming) so the map + -- does not grow for sporadically-active features (lifetime never closes). + local pruned = false + for window_key, window in pairs(windows) do + if window_key ~= limit.key + and type(window) == 'table' + and safe_number(window.window_end_at) < now + then + windows[window_key] = nil + pruned = true + end + end + + local consumed = usage_window_consumed({ + limit = limit, + updates = params.updates, + amount_to_deduct = params.amount_to_deduct, + remaining_amount = params.remaining_amount, + }) + + if consumed > USAGE_WINDOW_EPSILON then + -- balance_amount is audit-only; for metered caps it captures just the + -- anchor pool's credits, not every pool the track touched. + local consumed_credits = 0 + local anchor_update = params.updates[limit.anchor_customer_entitlement_id] + if anchor_update then + consumed_credits = safe_number(anchor_update.deducted) + end + + local existing = windows[limit.key] + if is_nil(existing) then + existing = { + key = limit.key, + dimension_type = limit.dimension_type, + dimension_feature_id = limit.dimension_feature_id or cjson.null, + scope_type = limit.scope_type, + entity_id = limit.entity_id or cjson.null, + internal_entity_id = limit.internal_entity_id or cjson.null, + interval = limit.interval, + window_start_at = limit.window_start_at, + window_end_at = limit.window_end_at, + usage_amount = 0, + balance_amount = 0, + } + windows[limit.key] = existing + end + + existing.usage_amount = safe_number(existing.usage_amount) + consumed + existing.balance_amount = + safe_number(existing.balance_amount) + consumed_credits + existing.limit_snapshot = safe_number(limit.limit) + existing.updated_at = now + + mark_customer_entitlement_for_update( + context, + limit.anchor_customer_entitlement_id + ) + elseif pruned then + mark_customer_entitlement_for_update( + context, + limit.anchor_customer_entitlement_id + ) + end + end + end +end diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index e08cb37bd..93d91c7b3 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -44,6 +44,7 @@ import READ_SUBJECT_BALANCES from "./fullSubjectDeduction/readSubjectBalances.lu import RUN_DEDUCTION_ON_CONTEXT_V2 from "./fullSubjectDeduction/runDeductionOnContextV2.lua"; import SPEND_LIMIT_UTILS_V2 from "./fullSubjectDeduction/spendLimitUtilsV2.lua"; import UPDATE_AGGREGATED_BALANCES from "./fullSubjectDeduction/updateAggregatedBalances.lua"; +import USAGE_WINDOW_UTILS_V2 from "./fullSubjectDeduction/usageWindowUtilsV2.lua"; // ============================================================================ // UPDATE SUBJECT BALANCES HELPERS (V2 cache — per-feature hash updates) @@ -205,6 +206,7 @@ ${GET_TOTAL_BALANCE} ${DEDUCT_FROM_ROLLOVERS_V2} ${DEDUCT_FROM_MAIN_BALANCE_V2} ${SPEND_LIMIT_UTILS_V2} +${USAGE_WINDOW_UTILS_V2} ${RUN_DEDUCTION_ON_CONTEXT_V2} ${MUTATION_ITEM_UTILS} ${LOCK_RECEIPT_UTILS_V2} diff --git a/server/src/internal/balances/check/runCheckWithTrackV2.ts b/server/src/internal/balances/check/runCheckWithTrackV2.ts index 331194177..13f20c567 100644 --- a/server/src/internal/balances/check/runCheckWithTrackV2.ts +++ b/server/src/internal/balances/check/runCheckWithTrackV2.ts @@ -10,6 +10,7 @@ import { type ParsedCheckParams, RecaseError, type TrackParams, + UsageLimitExceededError, } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { getTrackFeatureDeductions } from "@/internal/balances/track/utils/getFeatureDeductions.js"; @@ -94,7 +95,10 @@ export const runCheckWithTrackV2 = async ({ checkData.evaluationApiBalance = trackedBalance ?? undefined; trackBalances = response.balances; } catch (error) { - if (error instanceof InsufficientBalanceError) { + if ( + error instanceof InsufficientBalanceError || + error instanceof UsageLimitExceededError + ) { allowed = false; } else { throw error; diff --git a/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts b/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts index 5df489ac0..62b79af28 100644 --- a/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts +++ b/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts @@ -5,9 +5,10 @@ import { RecaseError, type TrackParams, type TrackResponseV3, + UsageLimitExceededError, } from "@autumn/shared"; -import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { RedisUnavailableError } from "@/external/redis/utils/errors.js"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import { RedisDeductionError, @@ -39,6 +40,12 @@ export const handleRedisTrackErrorV3 = async ({ }); } + if (error.code === RedisDeductionErrorCode.UsageLimitExceeded) { + throw new UsageLimitExceededError({ + featureId: error.featureId ?? body.feature_id, + }); + } + if (error.code === RedisDeductionErrorCode.LockAlreadyExists) { throw new RecaseError({ message: "A lock with this ID already exists", diff --git a/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts b/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts index 4a4fe11a7..26de4db00 100644 --- a/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts +++ b/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts @@ -45,6 +45,7 @@ const applyUpdate = ({ additional_balance: update.additional_balance, adjustment: update.adjustment, entities: update.entities, + usage_windows: update.usage_windows ?? customerEntitlement.usage_windows, replaceables: getUpdatedReplaceables({ replaceables: customerEntitlement.replaceables, update, diff --git a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts index ac26b243b..c0c67a96c 100644 --- a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts @@ -272,6 +272,7 @@ export const executeRedisDeductionV2 = async ({ throw new RedisDeductionError({ message: `Redis deduction failed: ${resultJson.error}`, code: resultJson.error as RedisDeductionErrorCode, + featureId: resultJson.feature_id, }); } diff --git a/server/src/internal/balances/utils/types/deductionUpdate.ts b/server/src/internal/balances/utils/types/deductionUpdate.ts index d06d00dee..47e3d107c 100644 --- a/server/src/internal/balances/utils/types/deductionUpdate.ts +++ b/server/src/internal/balances/utils/types/deductionUpdate.ts @@ -2,6 +2,7 @@ import type { EntityBalance, InsertReplaceable, Replaceable, + UsageWindows, } from "@autumn/shared"; export interface DeductionUpdate { @@ -14,6 +15,7 @@ export interface DeductionUpdate { additional_deducted?: number; newReplaceables?: InsertReplaceable[]; deletedReplaceables?: Replaceable[]; + usage_windows?: UsageWindows | null; } export type DeductionUpdates = Record; diff --git a/server/src/internal/balances/utils/types/redisDeductionError.ts b/server/src/internal/balances/utils/types/redisDeductionError.ts index 2487c23cc..83449fbbb 100644 --- a/server/src/internal/balances/utils/types/redisDeductionError.ts +++ b/server/src/internal/balances/utils/types/redisDeductionError.ts @@ -9,6 +9,7 @@ export enum RedisDeductionErrorCode { SkipCache = "SKIP_CACHE", LockAlreadyExists = "LOCK_ALREADY_EXISTS", DuplicateIdempotencyKey = "DUPLICATE_IDEMPOTENCY_KEY", + UsageLimitExceeded = "USAGE_LIMIT_EXCEEDED", } /** Errors that should trigger a fallback to Postgres */ @@ -23,17 +24,21 @@ export const FALLBACK_ERROR_CODES = [ /** Error thrown by Redis deduction operations */ export class RedisDeductionError extends Error { code: RedisDeductionErrorCode; + featureId?: string; constructor({ message, code, + featureId, }: { message: string; code: RedisDeductionErrorCode; + featureId?: string; }) { super(message); this.name = "RedisDeductionError"; this.code = code; + this.featureId = featureId; } isRedisUnavailable(): boolean { diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts new file mode 100644 index 000000000..601ab0c4f --- /dev/null +++ b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts @@ -0,0 +1,319 @@ +import { expect, test } from "bun:test"; +import { type CustomerBillingControls, EntInterval } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; + +type AutumnV2_1Client = Awaited>["autumnV2_1"]; + +// Arms a windowed usage cap via spend_limits[].usage_limit (overage off); +// `interval` sets the explicit window override. +const setCustomerUsageLimit = async ({ + autumn, + customerId, + featureId, + limit, + interval = EntInterval.Month, +}: { + autumn: AutumnV2_1Client; + customerId: string; + featureId: string; + limit: number; + interval?: EntInterval; +}) => { + const billingControls: CustomerBillingControls = { + spend_limits: [ + { + feature_id: featureId, + enabled: false, + usage_limit: limit, + usage_limit_interval: interval, + }, + ], + }; + + await timeout(2000); + await autumn.customers.update(customerId, { + billing_controls: billingControls, + }); + await timeout(3000); +}; + +// Credit system: 100 credits, 1 action1 = 0.2 credits (see v2Features.ts). +// A cap of 5 action1 units consumes only 1 credit, so the cap must block the +// 6th unit while ~99 credits remain, proving it's a second, independent +// dimension, not a balance check. +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit1: per-feature cap blocks deduction while credits remain")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-usage-limit", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-usage-limit-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + // Consume exactly up to the cap: 5 action1 units = 1 credit deducted. Assert + // the synchronous track response; a re-read races the async write-through. + const consumed = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + expect(consumed.balances?.[TestFeature.Credits]).toMatchObject({ + feature_id: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + + // The 6th unit exceeds the cap. It must be hard-blocked BEFORE any + // deduction, even though ~99 credits remain. + let blocked = false; + let blockedCode: string | undefined; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + } catch (error) { + blocked = true; + blockedCode = (error as { code?: string }).code; + } + + expect(blocked).toBe(true); + // 400 not 429: clients flatten a 429 to a generic rate_limit_exceeded. + expect(blockedCode).toBe("usage_limit_exceeded"); + }, +); + +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit2: credit-pool sub-interval cap (1 credit/day) blocks while monthly credits remain")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-credit-day-cap", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-credit-day-cap-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + // 1 action1 = 0.2 credits, so 5 action1 = exactly 1 credit (the daily cap). + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Credits, + limit: 1, + interval: EntInterval.Day, + }); + + const consumed = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + expect(consumed.balances?.[TestFeature.Credits]).toMatchObject({ + feature_id: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + + let blocked = false; + let blockedCode: string | undefined; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + } catch (error) { + blocked = true; + blockedCode = (error as { code?: string }).code; + } + + expect(blocked).toBe(true); + expect(blockedCode).toBe("usage_limit_exceeded"); + }, +); + +// set_usage must be rejected when the feature has an enforced usage window; +// otherwise it bypasses the hard cap (it carries no window provenance). +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit3: set_usage is rejected when the feature has a usage window")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-setusage-guard", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-setusage-guard-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + let blockedCode: string | undefined; + try { + await autumnV2_1.balances.update({ + customer_id: customerId, + feature_id: TestFeature.Messages, + current_balance: 50, + }); + } catch (error) { + blockedCode = (error as { code?: string }).code; + } + + expect(blockedCode).toBe("set_usage_not_allowed_with_usage_limit"); + }, +); + +// A single spend_limit entry carrying BOTH an overage_limit and a windowed usage +// cap must still enforce the window (the two caps are independent). +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit4: a spend_limit with both overage_limit and a usage window still enforces the window")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-compound-cap", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-compound-cap-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const billingControls: CustomerBillingControls = { + spend_limits: [ + { + feature_id: TestFeature.Action1, + enabled: true, + overage_limit: 20, + usage_limit: 5, + usage_limit_interval: EntInterval.Month, + }, + ], + }; + await timeout(2000); + await autumnV2_1.customers.update(customerId, { + billing_controls: billingControls, + }); + await timeout(3000); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + let blockedCode: string | undefined; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + } catch (error) { + blockedCode = (error as { code?: string }).code; + } + + expect(blockedCode).toBe("usage_limit_exceeded"); + }, +); + +// Two concurrent tracks on the SAME customer's SAME window must serialize (Redis +// runs each deduction Lua atomically): combined value exceeds the cap, so exactly +// one succeeds and one is rejected, and the counter reflects only the winner. +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit6: concurrent tracks on one window serialize, one rejected")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-concurrent-cap", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-concurrent-cap-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + // Cap action1 at 5/month; two concurrent tracks of 5 each => combined 10 > 5. + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + const results = await Promise.allSettled([ + autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }), + autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }), + ]); + + const fulfilled = results.filter((result) => result.status === "fulfilled"); + const rejected = results.filter( + (result): result is PromiseRejectedResult => result.status === "rejected", + ); + + expect(fulfilled).toHaveLength(1); + expect(rejected).toHaveLength(1); + expect((rejected[0].reason as { code?: string }).code).toBe( + "usage_limit_exceeded", + ); + }, +); diff --git a/shared/api/errors/classes/balancesErrClasses.ts b/shared/api/errors/classes/balancesErrClasses.ts index e768d9af1..86c46ad7f 100644 --- a/shared/api/errors/classes/balancesErrClasses.ts +++ b/shared/api/errors/classes/balancesErrClasses.ts @@ -18,3 +18,22 @@ export class InsufficientBalanceError extends RecaseError { this.name = "InsufficientBalanceError"; } } + +export class UsageLimitExceededError extends RecaseError { + constructor(opts?: { + message?: string; + featureId?: string; + limit?: number; + }) { + super({ + message: + opts?.message || + `Usage limit exceeded${opts?.featureId ? ` for feature ${opts.featureId}` : ""}${opts?.limit !== undefined ? ` (limit ${opts.limit})` : ""}`, + code: BalancesErrorCode.UsageLimitExceeded, + // 400 (mirrors InsufficientBalanceError): clients flatten any 429 to a + // generic rate-limit error, which would hide the usage_limit_exceeded code. + statusCode: 400, + }); + this.name = "UsageLimitExceededError"; + } +} diff --git a/shared/api/errors/codes/balancesErrCodes.ts b/shared/api/errors/codes/balancesErrCodes.ts index 2a05243d0..1bfde8070 100644 --- a/shared/api/errors/codes/balancesErrCodes.ts +++ b/shared/api/errors/codes/balancesErrCodes.ts @@ -1,5 +1,6 @@ export const BalancesErrorCode = { InsufficientBalance: "insufficient_balance", + UsageLimitExceeded: "usage_limit_exceeded", } as const; export type BalancesErrorCode = From 09184a5c8e0d4e507b304458edc839574dc48ca8 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Tue, 2 Jun 2026 19:21:43 +0100 Subject: [PATCH 04/41] persist usage_windows via sync_balances_v2 write-through --- .../balances/utils/sql/syncBalancesV2.sql | 19 +++++++++++++++---- .../balances/utils/sync/syncItemV4.ts | 3 +++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/server/src/internal/balances/utils/sql/syncBalancesV2.sql b/server/src/internal/balances/utils/sql/syncBalancesV2.sql index 7ec674583..b87643c4a 100644 --- a/server/src/internal/balances/utils/sql/syncBalancesV2.sql +++ b/server/src/internal/balances/utils/sql/syncBalancesV2.sql @@ -6,6 +6,7 @@ -- - balance: number -- - adjustment: number -- - entities: jsonb (the full entities object) +-- - usage_windows: jsonb (the full usage-window counters object) -- - next_reset_at: bigint/number (unix timestamp, for conflict detection) -- - entity_count: number (for conflict detection) -- - cache_version: number (if defined, skip write if DB cache_version differs) @@ -38,6 +39,7 @@ DECLARE ent_balance numeric; ent_adjustment numeric; ent_entities jsonb; + ent_usage_windows jsonb; ent_next_reset_at bigint; ent_entity_count int; ent_cache_version int; @@ -99,6 +101,7 @@ BEGIN ent_balance := (ent_obj->>'balance')::numeric; ent_adjustment := (ent_obj->>'adjustment')::numeric; ent_entities := ent_obj->'entities'; + ent_usage_windows := ent_obj->'usage_windows'; ent_next_reset_at := (ent_obj->>'next_reset_at')::bigint; ent_entity_count := COALESCE((ent_obj->>'entity_count')::int, 0); ent_cache_version := COALESCE((ent_obj->>'cache_version')::int, 0); @@ -134,14 +137,21 @@ BEGIN ent_id, ent_cache_version, db_cache_version; END IF; - -- Update the customer_entitlement row directly + -- Update the customer_entitlement row directly. + -- usage_windows is written from the synced object, which carries the FRESH + -- Redis value (re-read at sync time), so this is a full replace, not a + -- lost-update risk: concurrent counter increments are serialized atomically + -- in Redis and the latest cumulative map is what reaches here. The COALESCE + -- only avoids wiping the column when a balance-only sync carries no + -- usage_windows (null). Postgres is a mirror; Redis is authoritative. UPDATE customer_entitlements ce SET balance = COALESCE(ent_balance, ce.balance), adjustment = COALESCE(ent_adjustment, ce.adjustment), - entities = COALESCE(ent_entities, ce.entities) + entities = COALESCE(ent_entities, ce.entities), + usage_windows = COALESCE(NULLIF(ent_usage_windows, 'null'::jsonb), ce.usage_windows) WHERE ce.id = ent_id; - + -- Track update IF FOUND THEN updates_json := jsonb_set( @@ -150,7 +160,8 @@ BEGIN jsonb_build_object( 'balance', ent_balance, 'adjustment', ent_adjustment, - 'entities', ent_entities + 'entities', ent_entities, + 'usage_windows', ent_usage_windows ) ); END IF; diff --git a/server/src/internal/balances/utils/sync/syncItemV4.ts b/server/src/internal/balances/utils/sync/syncItemV4.ts index d17670c30..5460556e8 100644 --- a/server/src/internal/balances/utils/sync/syncItemV4.ts +++ b/server/src/internal/balances/utils/sync/syncItemV4.ts @@ -4,6 +4,7 @@ import { type EntityRolloverBalance, type SubjectBalance, tryCatch, + type UsageWindows, } from "@autumn/shared"; import { sql } from "drizzle-orm"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; @@ -76,6 +77,7 @@ export interface SyncEntry { balance: number; adjustment: number; entities: Record | null; + usage_windows: UsageWindows | null; next_reset_at: number | null; entity_count: number; cache_version: number | null; @@ -98,6 +100,7 @@ const subjectBalanceToSyncEntry = ({ balance: subjectBalance.balance ?? 0, adjustment: subjectBalance.adjustment ?? 0, entities: subjectBalance.entities ?? null, + usage_windows: subjectBalance.usage_windows ?? null, next_reset_at: subjectBalance.next_reset_at ?? null, entity_count: subjectBalance.entities ? Object.keys(subjectBalance.entities).length From 80d959aaaff887fb00b54c2a231e6a669f66b007 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 4 Jun 2026 12:21:53 +0100 Subject: [PATCH 05/41] usage window tables --- .../normalizedSubjectCacheExperiment.ts | 1 + .../balances/utils/sync/syncItemV4.ts | 4 +- .../balances/utils/types/deductionUpdate.ts | 4 +- .../getFullSubject/getFullSubjectRowsQuery.ts | 22 + .../subjectQueryRowToNormalized.ts | 10 + .../utils/fullSubjectScenarioBuilders.ts | 1 - .../setSharedFullSubjectBalances.test.ts | 1 + shared/db/schema.ts | 2 + .../drizzle/0002_jittery_dexter_bennett.sql | 1 - shared/drizzle/0004_lucky_electro.sql | 2 +- shared/drizzle/0005_legal_logan.sql | 16 + shared/drizzle/meta/0002_snapshot.json | 4 - shared/drizzle/meta/0005_snapshot.json | 7485 +++++++++++++++++ shared/drizzle/meta/_journal.json | 87 +- shared/index.ts | 1 + .../fullSubject/normalizedFullSubjectModel.ts | 4 +- .../cusModels/fullSubject/subjectQueryRow.ts | 2 + .../cusEntModels/cusEntModels.ts | 10 +- .../cusEntModels/cusEntTable.ts | 6 - .../cusEntModels/usageWindowModels.ts | 32 - .../cusEntModels/usageWindowTable.ts | 70 + .../normalizedToFullSubject.ts | 6 + 22 files changed, 7675 insertions(+), 96 deletions(-) delete mode 100644 shared/drizzle/0002_jittery_dexter_bennett.sql create mode 100644 shared/drizzle/0005_legal_logan.sql create mode 100644 shared/drizzle/meta/0005_snapshot.json create mode 100644 shared/models/cusProductModels/cusEntModels/usageWindowTable.ts diff --git a/server/experiments/normalizedSubjectCacheExperiment.ts b/server/experiments/normalizedSubjectCacheExperiment.ts index f16b0016e..9fc8daecc 100644 --- a/server/experiments/normalizedSubjectCacheExperiment.ts +++ b/server/experiments/normalizedSubjectCacheExperiment.ts @@ -267,6 +267,7 @@ const generateNormalized = (): NormalizedFullSubject => { }, }, rollovers: [] as any, + usage_windows: [] as any, replaceables: [] as any, customerPrice: null as any, customerProductOptions: [] as any, diff --git a/server/src/internal/balances/utils/sync/syncItemV4.ts b/server/src/internal/balances/utils/sync/syncItemV4.ts index 5460556e8..3450a85f5 100644 --- a/server/src/internal/balances/utils/sync/syncItemV4.ts +++ b/server/src/internal/balances/utils/sync/syncItemV4.ts @@ -4,7 +4,7 @@ import { type EntityRolloverBalance, type SubjectBalance, tryCatch, - type UsageWindows, + type UsageWindow, } from "@autumn/shared"; import { sql } from "drizzle-orm"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; @@ -77,7 +77,7 @@ export interface SyncEntry { balance: number; adjustment: number; entities: Record | null; - usage_windows: UsageWindows | null; + usage_windows: UsageWindow[] | null; next_reset_at: number | null; entity_count: number; cache_version: number | null; diff --git a/server/src/internal/balances/utils/types/deductionUpdate.ts b/server/src/internal/balances/utils/types/deductionUpdate.ts index 47e3d107c..a1a80aa43 100644 --- a/server/src/internal/balances/utils/types/deductionUpdate.ts +++ b/server/src/internal/balances/utils/types/deductionUpdate.ts @@ -2,7 +2,7 @@ import type { EntityBalance, InsertReplaceable, Replaceable, - UsageWindows, + UsageWindow, } from "@autumn/shared"; export interface DeductionUpdate { @@ -15,7 +15,7 @@ export interface DeductionUpdate { additional_deducted?: number; newReplaceables?: InsertReplaceable[]; deletedReplaceables?: Replaceable[]; - usage_windows?: UsageWindows | null; + usage_windows?: UsageWindow[] | null; } export type DeductionUpdates = Record; diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts index 5e876fecc..dd6534bdf 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts @@ -201,6 +201,12 @@ export const getFullSubjectRowsQuery = ({ AND (ro.expires_at IS NULL OR ro.expires_at > EXTRACT(EPOCH FROM now()) * 1000) ), + cus_usage_windows AS ( + SELECT uw.* + FROM usage_windows uw + WHERE uw.customer_entitlement_id IN (SELECT id FROM all_cus_ent_ids) + ), + cus_replaceables AS ( SELECT rep.* FROM replaceables rep @@ -393,6 +399,22 @@ export const getFullSubjectRowsQuery = ({ '[]'::json ) AS rollovers, + COALESCE( + ( + SELECT json_agg( + row_to_json(uw) + ORDER BY uw.window_start_at ASC, uw.id ASC + ) + FROM cus_usage_windows uw + WHERE uw.customer_entitlement_id IN ( + SELECT ace.id + FROM all_cus_ent_ids ace + WHERE ace.subject_key = sr.subject_key + ) + ), + '[]'::json + ) AS usage_windows, + COALESCE( ( SELECT json_agg((row_to_json(p)::jsonb - 'internal_customer_id' - 'subject_key')::json) diff --git a/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts b/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts index eff589158..2e17b3262 100644 --- a/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts +++ b/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts @@ -10,6 +10,7 @@ import { type DbPrice, type DbProduct, type DbRollover, + type DbUsageWindow, type EntitlementWithFeature, type Entity, type EntityAggregations, @@ -72,6 +73,14 @@ export const subjectQueryRowToNormalized = ({ replaceablesByCusEntId.set(replaceable.cus_ent_id, existing); } + const usageWindowsByCusEntId = new Map(); + for (const usageWindow of row.usage_windows) { + const existing = + usageWindowsByCusEntId.get(usageWindow.customer_entitlement_id) ?? []; + existing.push(usageWindow); + usageWindowsByCusEntId.set(usageWindow.customer_entitlement_id, existing); + } + const customerProductsById = new Map( row.customer_products.map( (customerProduct) => [customerProduct.id, customerProduct] as const, @@ -208,6 +217,7 @@ export const subjectQueryRowToNormalized = ({ entitlement: catalogEntitlement as EntitlementWithFeature, replaceables: replaceablesByCusEntId.get(customerEntitlement.id) ?? [], rollovers: rolloversByCusEntId.get(customerEntitlement.id) ?? [], + usage_windows: usageWindowsByCusEntId.get(customerEntitlement.id) ?? [], customerPrice: resolveCustomerPrice({ customerEntitlement, entitlement: catalogEntitlement as EntitlementWithFeature, diff --git a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts index 3b2c30deb..c007c2710 100644 --- a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts +++ b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts @@ -337,7 +337,6 @@ const buildCustomerEntitlement = ({ adjustment: 0, additional_balance: 0, entities: null, - usage_windows: null, expires_at: expiresAt, cache_version: 0, customer_id: customer.id ?? null, diff --git a/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts b/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts index 13310ddbb..0afd22cb4 100644 --- a/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts +++ b/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts @@ -84,6 +84,7 @@ const buildNormalized = (): NormalizedFullSubject => }, }, rollovers: [], + usage_windows: [], replaceables: [], customerPrice: null, customerProductOptions: null, diff --git a/shared/db/schema.ts b/shared/db/schema.ts index 622ccf4f3..02d6d9602 100644 --- a/shared/db/schema.ts +++ b/shared/db/schema.ts @@ -25,6 +25,7 @@ import { replaceableRelations } from "../models/cusProductModels/cusEntModels/re import { replaceables } from "../models/cusProductModels/cusEntModels/replaceableTable.js"; import { rolloverRelations } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverRelations.js"; import { rollovers } from "../models/cusProductModels/cusEntModels/rolloverModels/rolloverTable.js"; +import { usageWindows } from "../models/cusProductModels/cusEntModels/usageWindowTable.js"; import { customerPricesRelations } from "../models/cusProductModels/cusPriceModels/cusPriceRelations.js"; import { customerPrices } from "../models/cusProductModels/cusPriceModels/cusPriceTable.js"; // CusProduct Relations @@ -175,6 +176,7 @@ export { schedules, session, subscriptions, + usageWindows, // Auth user, // Auth Relations diff --git a/shared/drizzle/0002_jittery_dexter_bennett.sql b/shared/drizzle/0002_jittery_dexter_bennett.sql deleted file mode 100644 index fe92af437..000000000 --- a/shared/drizzle/0002_jittery_dexter_bennett.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "customer_entitlements" ADD COLUMN "usage_windows" jsonb; \ No newline at end of file diff --git a/shared/drizzle/0004_lucky_electro.sql b/shared/drizzle/0004_lucky_electro.sql index 9dbd9531d..9ebe7f17b 100644 --- a/shared/drizzle/0004_lucky_electro.sql +++ b/shared/drizzle/0004_lucky_electro.sql @@ -11,4 +11,4 @@ CREATE TABLE "invoice_templates" ( ); --> statement-breakpoint ALTER TABLE "invoice_templates" ADD CONSTRAINT "invoice_templates_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "idx_invoice_templates_org_id" ON "invoice_templates" USING btree ("org_id"); \ No newline at end of file +CREATE INDEX CONCURRENTLY "idx_invoice_templates_org_id" ON "invoice_templates" USING btree ("org_id"); \ No newline at end of file diff --git a/shared/drizzle/0005_legal_logan.sql b/shared/drizzle/0005_legal_logan.sql new file mode 100644 index 000000000..3f83055fb --- /dev/null +++ b/shared/drizzle/0005_legal_logan.sql @@ -0,0 +1,16 @@ +CREATE TABLE "usage_windows" ( + "id" text PRIMARY KEY NOT NULL, + "customer_entitlement_id" text NOT NULL, + "feature_id" text NOT NULL, + "internal_feature_id" text NOT NULL, + "window_start_at" numeric NOT NULL, + "window_end_at" numeric NOT NULL, + "usage" numeric DEFAULT 0 NOT NULL, + "updated_at" numeric NOT NULL +); +--> statement-breakpoint +ALTER TABLE "usage_windows" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_customer_entitlement_id_fkey" FOREIGN KEY ("customer_entitlement_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX CONCURRENTLY "idx_usage_windows_customer_entitlement_id" ON "usage_windows" USING btree ("customer_entitlement_id");--> statement-breakpoint +CREATE UNIQUE INDEX CONCURRENTLY "idx_usage_windows_cus_ent_feature_window" ON "usage_windows" USING btree ("customer_entitlement_id","feature_id","window_start_at"); \ No newline at end of file diff --git a/shared/drizzle/meta/0002_snapshot.json b/shared/drizzle/meta/0002_snapshot.json index 6abd3a93e..1466adb6b 100644 --- a/shared/drizzle/meta/0002_snapshot.json +++ b/shared/drizzle/meta/0002_snapshot.json @@ -7219,8 +7219,4 @@ "schemas": {}, "tables": {} } -<<<<<<< HEAD } -======= -} ->>>>>>> dev diff --git a/shared/drizzle/meta/0005_snapshot.json b/shared/drizzle/meta/0005_snapshot.json new file mode 100644 index 000000000..fa0fb3c1f --- /dev/null +++ b/shared/drizzle/meta/0005_snapshot.json @@ -0,0 +1,7485 @@ +{ + "id": "0aaa5367-a797-49fa-b0d1-ddb5677edeb7", + "prevId": "20fbfba1-ef02-4637-b7f8-4ae1ee5983d7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "nullsNotDistinct": false, + "columns": [ + "hashed_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_approvals": { + "name": "chat_approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_ts": { + "name": "message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_args": { + "name": "tool_args", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "preview": { + "name": "preview", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "decided_at": { + "name": "decided_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "decided_by_provider_user_id": { + "name": "decided_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chat_approvals_org_id_fkey": { + "name": "chat_approvals_org_id_fkey", + "tableFrom": "chat_approvals", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_installations": { + "name": "chat_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_access_token": { + "name": "bot_access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_env": { + "name": "default_env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_api_key_id": { + "name": "sandbox_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_api_key": { + "name": "sandbox_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key_id": { + "name": "live_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key": { + "name": "live_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_provider_user_id": { + "name": "installed_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_installations_org_id_fkey": { + "name": "chat_installations_org_id_fkey", + "tableFrom": "chat_installations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_installations_org_provider_key": { + "name": "chat_installations_org_provider_key", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "provider" + ] + }, + "chat_installations_provider_workspace_key": { + "name": "chat_installations_provider_workspace_key", + "nullsNotDistinct": false, + "columns": [ + "provider", + "workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id_btree": { + "name": "idx_customer_entitlements_internal_customer_id_btree", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_entity_id": { + "name": "idx_customer_entitlements_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_customer_entitlements_on_next_reset_at": { + "name": "idx_customer_entitlements_on_next_reset_at", + "columns": [ + { + "expression": "next_reset_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_internal_customer_id": { + "name": "idx_customer_prices_internal_customer_id", + "columns": [ + { + "expression": "\"internal_customer_id\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_prices\".\"internal_customer_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "prices", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "access_starts_at": { + "name": "access_starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_anchor_resets_at": { + "name": "billing_cycle_anchor_resets_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_customer_product_id": { + "name": "previous_customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_trial_end": { + "name": "on_trial_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_product_id": { + "name": "idx_customer_products_on_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_subscription_ids": { + "name": "idx_customer_products_subscription_ids", + "columns": [ + { + "expression": "subscription_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_scheduled_ids": { + "name": "idx_customer_products_scheduled_ids", + "columns": [ + { + "expression": "scheduled_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customer_products_stripe_checkout_session_id": { + "name": "idx_customer_products_stripe_checkout_session_id", + "columns": [ + { + "expression": "stripe_checkout_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_revenuecat_processor": { + "name": "idx_customer_products_revenuecat_processor", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "(\"customer_products\".\"processor\" ->> 'type') = 'revenuecat'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "tableTo": "free_trials", + "columnsFrom": [ + "free_trial_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processor_id": { + "name": "idx_customers_processor_id", + "columns": [ + { + "expression": "(\"processor\" ->> 'id')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_composite": { + "name": "idx_customers_composite", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_internal_id": { + "name": "idx_customers_org_env_internal_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_email_trgm": { + "name": "idx_customers_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_name_trgm": { + "name": "idx_customers_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_id_trgm": { + "name": "idx_customers_id_trgm", + "columns": [ + { + "expression": "\"id\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"id\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_customers_org_id_env_created_at": { + "name": "idx_customers_org_id_env_created_at", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_cursor": { + "name": "idx_customers_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_revenuecat": { + "name": "idx_customers_processors_revenuecat", + "columns": [ + { + "expression": "(\"processors\" ->> 'revenuecat')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_processors_vercel": { + "name": "idx_customers_processors_vercel", + "columns": [ + { + "expression": "(\"processors\" ->> 'vercel')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_org_env_id": { + "name": "idx_entities_org_env_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entities_cursor": { + "name": "idx_entities_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "expiry_duration": { + "name": "expiry_duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_length": { + "name": "expiry_length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id": { + "name": "idx_entitlements_internal_reward_id", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_reward_feature": { + "name": "idx_entitlements_reward_feature", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_entitlements_internal_reward_id_c_partial": { + "name": "idx_entitlements_internal_reward_id_c_partial", + "columns": [ + { + "expression": "\"internal_reward_id\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"entitlements\".\"internal_reward_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "entitlements_internal_reward_id_fkey": { + "name": "entitlements_internal_reward_id_fkey", + "tableFrom": "entitlements", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deductions": { + "name": "deductions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"events\".\"set_usage\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "on_end": { + "name": "on_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_templates": { + "name": "invoice_templates", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "footer": { + "name": "footer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "net_terms_days": { + "name": "net_terms_days", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_invoice_templates_org_id": { + "name": "idx_invoice_templates_org_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoice_templates_org_id_fkey": { + "name": "invoice_templates_org_id_fkey", + "tableFrom": "invoice_templates", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_templates_id_unique": { + "name": "invoice_templates_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor_type": { + "name": "processor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "refunded_amount": { + "name": "refunded_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_internal_entity_id": { + "name": "idx_invoices_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"invoices\".\"internal_entity_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "migration_jobs", + "columnsFrom": [ + "migration_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_item_runs": { + "name": "migration_item_runs", + "schema": "", + "columns": { + "migration_item_run_id": { + "name": "migration_item_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_run_id": { + "name": "migration_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "item_kind": { + "name": "item_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_item_runs_live_unique": { + "name": "migration_item_runs_live_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_dry_run_unique": { + "name": "migration_item_runs_dry_run_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_item_runs\".\"dry_run\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_item_runs_customer_recent_idx": { + "name": "migration_item_runs_customer_recent_idx", + "columns": [ + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"migration_item_runs\".\"item_kind\" = 'customer'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "from_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "to_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lazy_run": { + "name": "lazy_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_run_id": { + "name": "trigger_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "only_ids": { + "name": "only_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "target_limit": { + "name": "target_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_runs_active_per_migration_unique": { + "name": "migration_runs_active_per_migration_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"migration_runs\".\"status\" IN ('queued', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_runs_migration_internal_id_fkey": { + "name": "migration_runs_migration_internal_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "migrations", + "columnsFrom": [ + "migration_internal_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_org_id_fkey": { + "name": "migration_runs_org_id_fkey", + "tableFrom": "migration_runs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migrations": { + "name": "migrations", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prepared_state": { + "name": "prepared_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "no_billing_changes": { + "name": "no_billing_changes", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "retry_failed": { + "name": "retry_failed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migrations_org_env_id_unique": { + "name": "migrations_org_env_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migrations_org_id_fkey": { + "name": "migrations_org_id_fkey", + "tableFrom": "migrations", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redis_config": { + "name": "redis_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_organizations_name_trgm": { + "name": "idx_organizations_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_slug_trgm": { + "name": "idx_organizations_slug_trgm", + "columns": [ + { + "expression": "\"slug\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"organizations\".\"slug\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_organizations_created_at_id": { + "name": "idx_organizations_created_at_id", + "columns": [ + { + "expression": "\"createdAt\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "test_pkey" + ] + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "live_pkey" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_credentialId_idx": { + "name": "passkey_credentialId_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "passkey_credential_id_unique": { + "name": "passkey_credential_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credential_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_products_org_env_id_version": { + "name": "idx_products_org_env_id_version", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_referral_codes_internal_customer_id": { + "name": "idx_referral_codes_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_internal_id": { + "name": "reward_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_code": { + "name": "promo_code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_reward_redemptions_referral_code_id": { + "name": "idx_reward_redemptions_referral_code_id", + "columns": [ + { + "expression": "referral_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_reward_internal_id": { + "name": "idx_reward_redemptions_reward_internal_id", + "columns": [ + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reward_redemptions_customer_reward": { + "name": "idx_reward_redemptions_customer_reward", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "referral_codes", + "columnsFrom": [ + "referral_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.phases": { + "name": "phases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "phases_schedule_id_fkey": { + "name": "phases_schedule_id_fkey", + "tableFrom": "phases", + "tableTo": "schedules", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "phases_schedule_id_starts_at_key": { + "name": "phases_schedule_id_starts_at_key", + "nullsNotDistinct": false, + "columns": [ + "schedule_id", + "starts_at" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "schedules_customer_scope_unique": { + "name": "schedules_customer_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedules_entity_scope_unique": { + "name": "schedules_entity_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"schedules\".\"internal_entity_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_customer_id": { + "name": "idx_schedules_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_internal_entity_id": { + "name": "idx_schedules_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schedules_org_id_fkey": { + "name": "schedules_org_id_fkey", + "tableFrom": "schedules", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_customer_id_fkey": { + "name": "schedules_internal_customer_id_fkey", + "tableFrom": "schedules", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedules_internal_entity_id_fkey": { + "name": "schedules_internal_entity_id_fkey", + "tableFrom": "schedules", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_windows": { + "name": "usage_windows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "customer_entitlement_id": { + "name": "customer_entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "window_end_at": { + "name": "window_end_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_windows_customer_entitlement_id": { + "name": "idx_usage_windows_customer_entitlement_id", + "columns": [ + { + "expression": "customer_entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_windows_cus_ent_feature_window": { + "name": "idx_usage_windows_cus_ent_feature_window", + "columns": [ + { + "expression": "customer_entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_windows_customer_entitlement_id_fkey": { + "name": "usage_windows_customer_entitlement_id_fkey", + "tableFrom": "usage_windows", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "customer_entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "usage_windows_internal_feature_id_fkey": { + "name": "usage_windows_internal_feature_id_fkey", + "tableFrom": "usage_windows", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_name_trgm": { + "name": "idx_user_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"name\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_email_trgm": { + "name": "idx_user_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_user_created_at_id": { + "name": "idx_user_created_at_id", + "columns": [ + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "tableTo": "organizations", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "vercel_resources_installation_name_unique_idx": { + "name": "vercel_resources_installation_name_unique_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status <> 'uninstalled'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json index 89700a5ea..546021d8a 100644 --- a/shared/drizzle/meta/_journal.json +++ b/shared/drizzle/meta/_journal.json @@ -1,41 +1,48 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1779096275848, - "tag": "0000_bumpy_tinkerer", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1779971507895, - "tag": "0001_concerned_ravenous", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1780329256103, - "tag": "0002_aromatic_johnny_blaze", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1780423680857, - "tag": "0003_short_gargoyle", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1780493543535, - "tag": "0004_lucky_electro", - "breakpoints": true - } - ] -} + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1779096275848, + "tag": "0000_bumpy_tinkerer", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1779971507895, + "tag": "0001_concerned_ravenous", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1780329256103, + "tag": "0002_aromatic_johnny_blaze", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1780423680857, + "tag": "0003_short_gargoyle", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1780493543535, + "tag": "0004_lucky_electro", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1780570563830, + "tag": "0005_legal_logan", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/shared/index.ts b/shared/index.ts index 9038e14f6..d1ec8aca1 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -84,6 +84,7 @@ export * from "./models/cusProductModels/cusEntModels/replaceableTable"; export * from "./models/cusProductModels/cusEntModels/resetCusEnt"; export * from "./models/cusProductModels/cusEntModels/rolloverModels/rolloverTable"; export * from "./models/cusProductModels/cusEntModels/usageWindowModels"; +export * from "./models/cusProductModels/cusEntModels/usageWindowTable"; export * from "./models/cusProductModels/cusPriceModels/cusPriceModels"; export * from "./models/cusProductModels/cusPriceModels/cusPriceTable"; export * from "./models/cusProductModels/cusProductEnums"; diff --git a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts index 11dd2f25b..2e0736244 100644 --- a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts +++ b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts @@ -7,7 +7,7 @@ import { type EntityBalance, FullCustomerEntitlementSchema, } from "../../cusProductModels/cusEntModels/cusEntModels.js"; -import type { UsageWindows } from "../../cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindow } from "../../cusProductModels/cusEntModels/usageWindowTable.js"; import type { Replaceable } from "../../cusProductModels/cusEntModels/replaceableTable.js"; import type { DbRollover } from "../../cusProductModels/cusEntModels/rolloverModels/rolloverTable.js"; import type { FullCustomerPrice } from "../../cusProductModels/cusPriceModels/cusPriceModels.js"; @@ -90,7 +90,7 @@ export type SubjectBalance = { expires_at: number | null; external_id: string | null; entities: Record | null; - usage_windows?: UsageWindows | null; + usage_windows: UsageWindow[]; cache_version: number | null; created_at: number; customer_id?: string | null; diff --git a/shared/models/cusModels/fullSubject/subjectQueryRow.ts b/shared/models/cusModels/fullSubject/subjectQueryRow.ts index 57becb439..0eb1487bc 100644 --- a/shared/models/cusModels/fullSubject/subjectQueryRow.ts +++ b/shared/models/cusModels/fullSubject/subjectQueryRow.ts @@ -2,6 +2,7 @@ import type { AggregatedFeatureBalance } from "../../cusProductModels/cusEntMode import type { DbCustomerEntitlement } from "../../cusProductModels/cusEntModels/cusEntTable.js"; import type { Replaceable } from "../../cusProductModels/cusEntModels/replaceableTable.js"; import type { DbRollover } from "../../cusProductModels/cusEntModels/rolloverModels/rolloverTable.js"; +import type { DbUsageWindow } from "../../cusProductModels/cusEntModels/usageWindowTable.js"; import type { DbCustomerPrice } from "../../cusProductModels/cusPriceModels/cusPriceTable.js"; import type { DbCustomerProduct } from "../../cusProductModels/cusProductTable.js"; import type { DbFeature } from "../../featureModels/featureTable.js"; @@ -28,6 +29,7 @@ export type SubjectQueryRow = { extra_customer_entitlements: DbCustomerEntitlement[]; replaceables: Replaceable[]; rollovers: DbRollover[]; + usage_windows: DbUsageWindow[]; products: DbProduct[]; entitlements: EntitlementWithFeatureRow[]; prices: DbPrice[]; diff --git a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts index 119b42db4..1bdf6005c 100644 --- a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts +++ b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts @@ -3,7 +3,7 @@ import { EntitlementWithFeatureSchema } from "../../productModels/entModels/entM import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; import { ReplaceableSchema } from "./replaceableSchema.js"; import { RolloverSchema } from "./rolloverModels/rolloverTable.js"; -import { UsageWindowsSchema } from "./usageWindowModels.js"; +import { UsageWindowSchema } from "./usageWindowTable.js"; export const CustomerEntitlementFiltersSchema = z.object({ cusEntIds: z.array(z.string()).optional(), @@ -49,10 +49,6 @@ export const CustomerEntitlementSchema = z.object({ // Group by fields entities: z.record(z.string(), EntityBalanceSchema).nullish(), - // Windowed usage-limit counters scoped beneath this entitlement (second - // limit dimension on top of balance). Keyed by buildUsageWindowKey. - usage_windows: UsageWindowsSchema.nullish(), - external_id: z.string().nullable(), }); @@ -60,6 +56,10 @@ export const FullCustomerEntitlementSchema = CustomerEntitlementSchema.extend({ entitlement: EntitlementWithFeatureSchema, replaceables: z.array(ReplaceableSchema), rollovers: z.array(RolloverSchema), + + // Windowed usage-limit counters, persisted as their own rows (second limit + // dimension on top of balance). + usage_windows: z.array(UsageWindowSchema).nullish(), }); export type CustomerEntitlementFilters = z.infer< diff --git a/shared/models/cusProductModels/cusEntModels/cusEntTable.ts b/shared/models/cusProductModels/cusEntModels/cusEntTable.ts index 24f1287ae..fd0adbb6f 100644 --- a/shared/models/cusProductModels/cusEntModels/cusEntTable.ts +++ b/shared/models/cusProductModels/cusEntModels/cusEntTable.ts @@ -15,7 +15,6 @@ import { features } from "../../featureModels/featureTable.js"; import { entitlements } from "../../productModels/entModels/entTable.js"; import { customerProducts } from "../cusProductTable.js"; import type { EntityBalance } from "./cusEntModels.js"; -import type { UsageWindows } from "./usageWindowModels.js"; export const customerEntitlements = pgTable( "customer_entitlements", @@ -42,11 +41,6 @@ export const customerEntitlements = pgTable( // Need to work on free balance... entities: jsonb("entities").$type>(), - // Windowed usage-limit counters (windowKey -> UsageWindow). Embedded - // here so they live in the same SubjectBalance hot object as the balance - // and are mutated atomically by the deduction script. - usage_windows: jsonb("usage_windows").$type(), - // Expiry for loose entitlements (entitlements without reset intervals) expires_at: numeric({ mode: "number" }), cache_version: integer("cache_version").default(0), diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts index 0eea6fb20..4854c6f25 100644 --- a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts +++ b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts @@ -17,38 +17,6 @@ export type UsageWindowDimension = z.infer; export const UsageWindowScopeSchema = z.enum(["customer", "entity"]); export type UsageWindowScope = z.infer; -/** - * A single windowed usage counter scoped beneath a customer entitlement. - * - * This is the embedded counter state. The enforced limit is resolved at - * deduction time (mirroring spend limits), so `limit_snapshot` is audit-only, - * never the enforcement source. `key` is built by `buildUsageWindowKey`. - * - * `usage_amount` is in the dimension's native units (e.g. workflow count); - * `balance_amount` records the pool/credit units consumed, for attribution. - */ -export const UsageWindowSchema = z.object({ - key: z.string(), - dimension_type: UsageWindowDimensionSchema, - dimension_feature_id: z.string().nullable(), - scope_type: UsageWindowScopeSchema, - entity_id: z.string().nullable(), - internal_entity_id: z.string().nullable(), - interval: z.enum(EntInterval), - window_start_at: z.number(), - window_end_at: z.number(), - usage_amount: z.number(), - balance_amount: z.number(), - limit_snapshot: z.number().nullish(), - updated_at: z.number(), -}); - -export type UsageWindow = z.infer; - -/** Map of windowKey -> UsageWindow, embedded on a customer entitlement. */ -export const UsageWindowsSchema = z.record(z.string(), UsageWindowSchema); -export type UsageWindows = z.infer; - /** * A resolved, enforceable usage-window limit: the runtime input handed to the * deduction script. Built each deduction from the windowed usage cap diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts b/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts new file mode 100644 index 000000000..dd40f5a4e --- /dev/null +++ b/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts @@ -0,0 +1,70 @@ +import { + foreignKey, + index, + numeric, + pgTable, + text, + uniqueIndex, +} from "drizzle-orm/pg-core"; +import { z } from "zod/v4"; +import { features } from "../../featureModels/featureTable.js"; +import { customerEntitlements } from "./cusEntTable.js"; + +/** + * A single windowed usage counter, persisted as its own row beneath a customer + * entitlement. `usage` is the running total consumed within [window_start_at, + * window_end_at). The enforced limit is resolved at deduction time, so it is not + * stored here. + */ +export const UsageWindowSchema = z.object({ + id: z.string(), + customer_entitlement_id: z.string(), + feature_id: z.string(), + internal_feature_id: z.string(), + window_start_at: z.number(), + window_end_at: z.number(), + usage: z.number(), + updated_at: z.number(), +}); + +export const usageWindows = pgTable( + "usage_windows", + { + id: text("id").primaryKey().notNull(), + customer_entitlement_id: text("customer_entitlement_id").notNull(), + feature_id: text("feature_id").notNull(), + internal_feature_id: text("internal_feature_id").notNull(), + window_start_at: numeric({ mode: "number" }).notNull(), + window_end_at: numeric({ mode: "number" }).notNull(), + usage: numeric({ mode: "number" }).notNull().default(0), + updated_at: numeric({ mode: "number" }).notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.customer_entitlement_id], + foreignColumns: [customerEntitlements.id], + name: "usage_windows_customer_entitlement_id_fkey", + }) + .onUpdate("cascade") + .onDelete("cascade"), + foreignKey({ + columns: [table.internal_feature_id], + foreignColumns: [features.internal_id], + name: "usage_windows_internal_feature_id_fkey", + }).onDelete("cascade"), + + index("idx_usage_windows_customer_entitlement_id").on( + table.customer_entitlement_id, + ), + uniqueIndex("idx_usage_windows_cus_ent_feature_window").on( + table.customer_entitlement_id, + table.feature_id, + table.window_start_at, + ), + ], +).enableRLS(); + +export type UsageWindow = typeof usageWindows.$inferSelect; +export type InsertUsageWindow = typeof usageWindows.$inferInsert; +export type DbUsageWindow = typeof usageWindows.$inferSelect; +export type InsertDbUsageWindow = typeof usageWindows.$inferInsert; diff --git a/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts b/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts index cd6766e72..4f056aeaa 100644 --- a/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts +++ b/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts @@ -48,6 +48,11 @@ const subjectBalanceToFullCustomerEntitlement = ({ const rollovers = getArrayEntries({ value: subjectBalance.rollovers, }); + const usageWindows = getArrayEntries< + SubjectBalance["usage_windows"][number] + >({ + value: subjectBalance.usage_windows, + }); return { id: subjectBalance.id, @@ -76,6 +81,7 @@ const subjectBalanceToFullCustomerEntitlement = ({ getRolloverSortValue({ rollover: left }) - getRolloverSortValue({ rollover: right }), ), + usage_windows: usageWindows, } as FullCustomerEntitlement; }; From 81a9e049dd3b20ee98952d37b206f13286bced58 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 4 Jun 2026 20:37:05 +0100 Subject: [PATCH 06/41] jsonb to table refactor --- .../usageWindowUtilsV2.lua | 118 +++++++----- .../deductionV2/executePostgresDeductionV2.ts | 10 +- .../balances/utils/sql/syncBalancesV2.sql | 47 +++-- .../track-customer-usage-limit.test.ts | 168 ++++++++++++++++++ .../fullSubjectToUsageWindowLimits.test.ts | 26 ++- .../cusEntModels/usageWindowModels.ts | 1 + .../fullSubjectToUsageWindowLimits.ts | 9 +- 7 files changed, 315 insertions(+), 64 deletions(-) diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua index 9d7b003fc..074047430 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua @@ -3,10 +3,14 @@ -- Hard windowed usage-limit enforcement, evaluated at the orchestration layer -- against ACTUAL consumed amounts (post-deduction, pre-write). -- --- Counters live inline on the anchor cus_ent's subject_balance.usage_windows, --- keyed by the deterministic window key built in TS. The window key includes --- window_start_at, so the current window's counter is found-or-created at --- limit.key and a rolled window is simply a different (absent) key. +-- Counters live inline on the anchor cus_ent's subject_balance.usage_windows as +-- a lean ARRAY of rows mirroring the usage_windows table (DbUsageWindow): +-- { id, customer_entitlement_id, feature_id, internal_feature_id, +-- window_start_at, window_end_at, usage, updated_at } +-- A window is identified by (customer_entitlement_id, feature_id, +-- window_start_at) -- the table's unique key. The current window is +-- found-or-created; a rolled window has a different window_start_at, so its +-- counter starts fresh at 0 and old windows are pruned. -- ============================================================================ -- Tolerance for float drift (credit-ratio conversions leave sub-nano noise). @@ -22,11 +26,37 @@ local function get_anchor_usage_windows(context, anchor_customer_entitlement_id) return nil end - if type(ent_data.subject_balance.usage_windows) ~= 'table' then - ent_data.subject_balance.usage_windows = {} + -- Reset a non-array blob to []: a legacy keyed-map blob (pre-array deploy) whose + -- string keys ipairs would skip and table.insert would corrupt into a + -- sync-breaking JSON object. The current window restarts at 0 (one-time cost). + local windows = ent_data.subject_balance.usage_windows + if type(windows) ~= 'table' + or (next(windows) ~= nil and windows[1] == nil) then + windows = new_empty_array() + ent_data.subject_balance.usage_windows = windows end - return ent_data.subject_balance.usage_windows + return windows +end + +-- The array is one anchor cus_ent's rows, so customer_entitlement_id is implied; +-- a window is the row matching (feature_id, window_start_at). +local function find_usage_window(windows, feature_id, window_start_at) + for _, window in ipairs(windows) do + if window.feature_id == feature_id + and safe_number(window.window_start_at) == window_start_at then + return window + end + end + return nil +end + +-- Stable id matching the table's unique key, so the async sync upserts the same +-- row each window rather than inserting duplicates. +local function build_usage_window_id(limit) + return limit.anchor_customer_entitlement_id + .. ':' .. limit.feature_id + .. ':' .. string.format('%.0f', limit.window_start_at) end -- Actually-consumed amount for a limit, in its native unit. metered_feature @@ -68,8 +98,12 @@ local function check_usage_window_limits(params) }) if consumed > USAGE_WINDOW_EPSILON then - local existing = windows[limit.key] - local current_usage = existing and safe_number(existing.usage_amount) or 0 + local existing = find_usage_window( + windows, + limit.feature_id, + limit.window_start_at + ) + local current_usage = existing and safe_number(existing.usage) or 0 if current_usage + consumed > safe_number(limit.limit) + USAGE_WINDOW_EPSILON then return limit.feature_id @@ -80,33 +114,39 @@ local function check_usage_window_limits(params) return nil end --- Applies the consumed amount to each anchor counter (find-or-create at the --- current window key), prunes closed sibling windows, and marks the anchor dirty --- so apply_pending_writes persists it (even when the anchor's balance did not --- change, or when only a prune happened). +-- Applies the consumed amount to each anchor counter (find-or-create the current +-- window row), prunes closed windows, and marks the anchor dirty so +-- apply_pending_writes persists it. local function increment_usage_window_counters(params) local context = params.context local limits = params.usage_window_limits or {} local now = params.now for _, limit in ipairs(limits) do + local ent_data = + context.customer_entitlements[limit.anchor_customer_entitlement_id] local windows = get_anchor_usage_windows( context, limit.anchor_customer_entitlement_id ) if not is_nil(windows) then - -- Prune closed windows every pass (not only when consuming) so the map - -- does not grow for sporadically-active features (lifetime never closes). + -- Rebuild (rather than nil-out) so the array stays hole-free and cjson + -- re-encodes it as [] not {}; prune every pass so it never grows for + -- sporadically-active features. + local kept = new_empty_array() local pruned = false - for window_key, window in pairs(windows) do - if window_key ~= limit.key - and type(window) == 'table' - and safe_number(window.window_end_at) < now - then - windows[window_key] = nil + for _, window in ipairs(windows) do + if type(window) == 'table' + and safe_number(window.window_end_at) < now then pruned = true + else + table.insert(kept, window) end end + if pruned then + ent_data.subject_balance.usage_windows = kept + windows = kept + end local consumed = usage_window_consumed({ limit = limit, @@ -116,36 +156,26 @@ local function increment_usage_window_counters(params) }) if consumed > USAGE_WINDOW_EPSILON then - -- balance_amount is audit-only; for metered caps it captures just the - -- anchor pool's credits, not every pool the track touched. - local consumed_credits = 0 - local anchor_update = params.updates[limit.anchor_customer_entitlement_id] - if anchor_update then - consumed_credits = safe_number(anchor_update.deducted) - end - - local existing = windows[limit.key] + local existing = find_usage_window( + windows, + limit.feature_id, + limit.window_start_at + ) if is_nil(existing) then existing = { - key = limit.key, - dimension_type = limit.dimension_type, - dimension_feature_id = limit.dimension_feature_id or cjson.null, - scope_type = limit.scope_type, - entity_id = limit.entity_id or cjson.null, - internal_entity_id = limit.internal_entity_id or cjson.null, - interval = limit.interval, + id = build_usage_window_id(limit), + customer_entitlement_id = limit.anchor_customer_entitlement_id, + feature_id = limit.feature_id, + internal_feature_id = limit.internal_feature_id, window_start_at = limit.window_start_at, window_end_at = limit.window_end_at, - usage_amount = 0, - balance_amount = 0, + usage = 0, + updated_at = now, } - windows[limit.key] = existing + table.insert(windows, existing) end - existing.usage_amount = safe_number(existing.usage_amount) + consumed - existing.balance_amount = - safe_number(existing.balance_amount) + consumed_credits - existing.limit_snapshot = safe_number(limit.limit) + existing.usage = safe_number(existing.usage) + consumed existing.updated_at = now mark_customer_entitlement_for_update( diff --git a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts index 48e469d4c..6fa2996b1 100644 --- a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts @@ -328,11 +328,11 @@ export const executePostgresDeductionV2 = async ({ const deductionResult = resolvedOptions.paidAllocated ? await withLock({ - lockKey: `lock:deduction:${org.id}:${env}:${customerId}`, - ttlMs: 60000, - errorMessage: `Deduction for paid feature ${deductions[0]?.feature?.name} already in progress for customer ${customerId}.`, - fn: executeDeduction, - }) + lockKey: `lock:deduction:${org.id}:${env}:${customerId}`, + ttlMs: 60000, + errorMessage: `Deduction for paid feature ${deductions[0]?.feature?.name} already in progress for customer ${customerId}.`, + fn: executeDeduction, + }) : await executeDeduction(); return { diff --git a/server/src/internal/balances/utils/sql/syncBalancesV2.sql b/server/src/internal/balances/utils/sql/syncBalancesV2.sql index b87643c4a..7553aae52 100644 --- a/server/src/internal/balances/utils/sql/syncBalancesV2.sql +++ b/server/src/internal/balances/utils/sql/syncBalancesV2.sql @@ -6,7 +6,7 @@ -- - balance: number -- - adjustment: number -- - entities: jsonb (the full entities object) --- - usage_windows: jsonb (the full usage-window counters object) +-- - usage_windows: jsonb array of DbUsageWindow rows, mirrored to the usage_windows table (null = skip) -- - next_reset_at: bigint/number (unix timestamp, for conflict detection) -- - entity_count: number (for conflict detection) -- - cache_version: number (if defined, skip write if DB cache_version differs) @@ -137,23 +137,48 @@ BEGIN ent_id, ent_cache_version, db_cache_version; END IF; - -- Update the customer_entitlement row directly. - -- usage_windows is written from the synced object, which carries the FRESH - -- Redis value (re-read at sync time), so this is a full replace, not a - -- lost-update risk: concurrent counter increments are serialized atomically - -- in Redis and the latest cumulative map is what reaches here. The COALESCE - -- only avoids wiping the column when a balance-only sync carries no - -- usage_windows (null). Postgres is a mirror; Redis is authoritative. UPDATE customer_entitlements ce SET balance = COALESCE(ent_balance, ce.balance), adjustment = COALESCE(ent_adjustment, ce.adjustment), - entities = COALESCE(ent_entities, ce.entities), - usage_windows = COALESCE(NULLIF(ent_usage_windows, 'null'::jsonb), ce.usage_windows) + entities = COALESCE(ent_entities, ce.entities) WHERE ce.id = ent_id; - -- Track update IF FOUND THEN + -- Mirror the windowed-usage counters into the usage_windows table (Redis is + -- authoritative and already prunes closed windows): full-replace the cus_ent's + -- rows. Clear on ANY present blob -- an emptied window array re-encodes as {} + -- (lua-cjson encodes an empty table as an object), so guarding the DELETE on + -- 'array' would leave stale closed rows. Only a real array has rows to INSERT; + -- a null/object blob simply clears, so the shared sync never reaches + -- jsonb_array_elements on a non-array. null = balance-only sync (untouched). + -- The internal_feature_id filters keep a stray null/orphan row from aborting + -- the whole batch on the NOT NULL + FK column. + IF ent_usage_windows IS NOT NULL AND ent_usage_windows != 'null'::jsonb THEN + DELETE FROM usage_windows WHERE customer_entitlement_id = ent_id; + IF jsonb_typeof(ent_usage_windows) = 'array' THEN + INSERT INTO usage_windows ( + id, customer_entitlement_id, feature_id, internal_feature_id, + window_start_at, window_end_at, usage, updated_at + ) + SELECT + w->>'id', + ent_id, + w->>'feature_id', + w->>'internal_feature_id', + (w->>'window_start_at')::numeric, + (w->>'window_end_at')::numeric, + (w->>'usage')::numeric, + (w->>'updated_at')::numeric + FROM jsonb_array_elements(ent_usage_windows) AS w + WHERE w->>'internal_feature_id' IS NOT NULL + AND EXISTS ( + SELECT 1 FROM features f + WHERE f.internal_id = w->>'internal_feature_id' + ); + END IF; + END IF; + updates_json := jsonb_set( updates_json, ARRAY[ent_id], diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts index 601ab0c4f..85af165a9 100644 --- a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts +++ b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts @@ -6,6 +6,14 @@ import { products } from "@tests/utils/fixtures/products.js"; import { timeout } from "@tests/utils/genUtils.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; +import { sql } from "drizzle-orm"; +import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; + +// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped +const queryRows = (result: unknown): any[] => + // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped + Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); type AutumnV2_1Client = Awaited>["autumnV2_1"]; @@ -317,3 +325,163 @@ test.concurrent( ); }, ); + +// Write-through: the Redis counter must reach the usage_windows table via the +// shared sync (the other tests assert only the synchronous Redis response). +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit-sync: window counter writes through to the usage_windows table")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-sync", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-sync-1-${Date.now()}`; + const { autumnV2_1, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Credits, + limit: 5, + interval: EntInterval.Day, + }); + + // 5 action1 = 1 credit; under the 5-credit/day cap. The counter lives on the + // credits cus-ent (balance dimension). + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + const creditsEnt = queryRows( + await ctx.db.execute(sql` + SELECT id, internal_feature_id FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} + LIMIT 1 + `), + )[0]; + expect(creditsEnt?.id).toBeTruthy(); + + // Drive the async write-through synchronously, then assert the mirrored row. + await syncItemV4({ + ctx, + payload: { + customerId, + orgId: ctx.org.id, + env: ctx.env, + timestamp: Date.now(), + modifiedCusEntIdsByFeatureId: { + [TestFeature.Credits]: [creditsEnt.id], + }, + }, + }); + + const windowRows = queryRows( + await ctx.db.execute(sql` + SELECT feature_id, internal_feature_id, usage + FROM usage_windows WHERE customer_entitlement_id = ${creditsEnt.id} + `), + ); + expect(windowRows).toHaveLength(1); + expect(windowRows[0].feature_id).toBe(TestFeature.Credits); + expect(windowRows[0].internal_feature_id).toBe( + creditsEnt.internal_feature_id, + ); + expect(Number(windowRows[0].usage)).toBeCloseTo(1, 5); + }, +); + +// Deploy-migration safety: a leftover pre-array keyed-map blob must be reset to a +// clean array, never iterated-then-corrupted into a JSON object that wedges sync. +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit-legacy: a pre-array keyed-map blob is reset, not corrupted into an object")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-legacy", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-legacy-1-${Date.now()}`; + const { autumnV2_1, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Credits, + limit: 5, + interval: EntInterval.Day, + }); + + // One track creates a proper array blob on the credits cus-ent. + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + + const creditsEnt = queryRows( + await ctx.db.execute(sql` + SELECT id FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} + LIMIT 1 + `), + )[0]; + expect(creditsEnt?.id).toBeTruthy(); + + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Credits, + }); + + // Overwrite usage_windows with a LEGACY keyed-map shape (the pre-array format + // ipairs would skip and table.insert would corrupt into a JSON object). + const blobJson = await ctx.redisV2.hget(balanceKey, creditsEnt.id); + expect(blobJson).toBeTruthy(); + const blob = JSON.parse(blobJson as string); + blob.usage_windows = { + "customer:balance:credits:day:legacy": { + key: "customer:balance:credits:day:legacy", + usage_amount: 0.2, + window_start_at: 1_700_000_000_000, + window_end_at: 9_999_999_999_999, + dimension_type: "balance", + interval: "day", + }, + }; + await ctx.redisV2.hset(balanceKey, creditsEnt.id, JSON.stringify(blob)); + + // The next track must RESET the map blob to a clean array, not corrupt it. + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + + const after = JSON.parse( + (await ctx.redisV2.hget(balanceKey, creditsEnt.id)) as string, + ); + // The poison case is a JSON OBJECT (string keys); it must be a clean array. + expect(Array.isArray(after.usage_windows)).toBe(true); + expect(after.usage_windows).toHaveLength(1); + expect(after.usage_windows[0].feature_id).toBe(TestFeature.Credits); + expect(typeof after.usage_windows[0].id).toBe("string"); + }, +); diff --git a/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts index 4f7b551b4..a0c7e97b9 100644 --- a/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts +++ b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts @@ -13,19 +13,26 @@ import { const NOW = Date.UTC(2026, 5, 15, 12, 0, 0); -const meteredAction1 = { id: "action1", type: FeatureType.Metered } as Feature; +const meteredAction1 = { + id: "action1", + internal_id: "iaction1", + type: FeatureType.Metered, +} as Feature; const creditsFeature = { id: "credits", + internal_id: "icredits", type: FeatureType.CreditSystem, } as Feature; // Credit system whose schema contains action1, for the membership-anchor path. const creditsContainingAction1 = { id: "credits", + internal_id: "icredits", type: FeatureType.CreditSystem, config: { schema: [{ metered_feature_id: "action1", credit_amount: 5 }] }, } as unknown as Feature; const credits2ContainingAction1 = { id: "credits2", + internal_id: "icredits2", type: FeatureType.CreditSystem, config: { schema: [{ metered_feature_id: "action1", credit_amount: 3 }] }, } as unknown as Feature; @@ -178,6 +185,7 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits).toHaveLength(1); expect(limits[0]).toMatchObject({ feature_id: "action1", + internal_feature_id: "iaction1", dimension_type: "metered_feature", dimension_feature_id: "action1", scope_type: "customer", @@ -197,6 +205,21 @@ describe("fullSubjectToUsageWindowLimits", () => { }); }); + test("skips a cap whose feature is absent from the catalog (unresolvable internal_feature_id)", () => { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject: buildSubject({ + customerLimits: [ + { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + ], + }), + featureIds: ["action1"], + features: [], + now: NOW, + }); + + expect(limits).toHaveLength(0); + }); + test("a credit-system feature resolves to the balance dimension", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ @@ -441,6 +464,7 @@ describe("fullSubjectToUsageWindowLimits", () => { test("returns one limit per feature when caps exist on multiple features", () => { const meteredAction2 = { id: "action2", + internal_id: "iaction2", type: FeatureType.Metered, } as Feature; const limits = fullSubjectToUsageWindowLimits({ diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts index 4854c6f25..c0bf88d91 100644 --- a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts +++ b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts @@ -27,6 +27,7 @@ export type UsageWindowScope = z.infer; */ export const UsageWindowLimitSchema = z.object({ feature_id: z.string(), + internal_feature_id: z.string(), key: z.string(), dimension_type: UsageWindowDimensionSchema, dimension_feature_id: z.string().nullable(), diff --git a/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts index 0356afde8..c29bd7f89 100644 --- a/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts +++ b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts @@ -103,9 +103,11 @@ export const fullSubjectToUsageWindowLimits = ({ const entityId = null; const internalEntityId = null; - const isCreditSystem = - features.find((feature) => feature.id === featureId)?.type === - FeatureType.CreditSystem; + const featureObject = features.find((feature) => feature.id === featureId); + // No catalog feature => no internal_feature_id (a NOT NULL FK on the windows + // table); the cap is unenforceable and unstorable, so skip it. + if (featureObject?.internal_id == null) continue; + const isCreditSystem = featureObject.type === FeatureType.CreditSystem; const dimensionType: UsageWindowDimension = isCreditSystem ? "balance" : "metered_feature"; @@ -165,6 +167,7 @@ export const fullSubjectToUsageWindowLimits = ({ limits.push({ feature_id: featureId, + internal_feature_id: featureObject.internal_id, key: buildUsageWindowKey({ scopeType, internalEntityId, From 8071b1b06d05bf3be2e5f280ca8e21a1b332bd8f Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 4 Jun 2026 20:42:46 +0100 Subject: [PATCH 07/41] deploy allowlist --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fdbe6d84..029f8b405 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ env: # staging repo (autumn-staging) -> us-east-1 # Branches allowed to deploy to staging via workflow_dispatch with tag=deploy-staging. # Add short-lived PR branches here when you need staging without merging to dev. - STAGING_DEPLOY_BRANCH_ALLOWLIST: fix-health-check-redis-disabled-detection feat/track-rate-limit-redis feat/events-hourly-rollup + STAGING_DEPLOY_BRANCH_ALLOWLIST: fix-health-check-redis-disabled-detection feat/track-rate-limit-redis feat/events-hourly-rollup uw-1-storage jobs: checks: From 34f904833e93a3d33340c46d2bb7be67ab4c51f0 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Fri, 5 Jun 2026 11:51:28 +0100 Subject: [PATCH 08/41] add usage limit billing control ui --- vite/src/hooks/stores/useSheetStore.ts | 2 + .../CustomerBillingControlsSection.tsx | 76 ++++- .../sheets/BillingUsageLimitSheet.tsx | 289 ++++++++++++++++++ .../customers2/customer/CustomerSheets.tsx | 4 + .../sheets/billing-usage-limit-sheet.test.ts | 57 ++++ 5 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx create mode 100644 vite/tests/views/customers2/components/sheets/billing-usage-limit-sheet.test.ts diff --git a/vite/src/hooks/stores/useSheetStore.ts b/vite/src/hooks/stores/useSheetStore.ts index ca3f1ffbf..fe1eb7311 100644 --- a/vite/src/hooks/stores/useSheetStore.ts +++ b/vite/src/hooks/stores/useSheetStore.ts @@ -29,6 +29,8 @@ export type SheetType = | "billing-auto-topup-edit" | "billing-spend-limit-add" | "billing-spend-limit-edit" + | "billing-usage-limit-add" + | "billing-usage-limit-edit" | "billing-usage-alert-add" | "billing-usage-alert-edit" | "billing-overage-allowed-add" diff --git a/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx b/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx index 1773eb010..0b748eab8 100644 --- a/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx +++ b/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx @@ -150,6 +150,34 @@ const SpendLimitRow = ({ ); +const UsageLimitRow = ({ + usageLimit, + featureNameById, + onClick, +}: { + usageLimit: DbSpendLimit; + featureNameById: Map; + onClick: () => void; +}) => ( + +); + const UsageAlertRow = ({ usageAlert, featureNameById, @@ -243,9 +271,22 @@ export function CustomerBillingControlsSection() { }, [features]); const autoTopups = selectedEntity ? [] : (fullCustomer?.auto_topups ?? []); - const spendLimits = selectedEntity + const allSpendLimits = selectedEntity ? (selectedEntity.spend_limits ?? []) : (fullCustomer?.spend_limits ?? []); + // Usage caps are folded into spend_limits (usage_limit set); surface them as a + // separate "Usage limits" control. Keep each entry's original index so edit/delete + // target the right slot in the full spend_limits array. + const indexedSpendLimits = allSpendLimits.map((item, index) => ({ + item, + index, + })); + const spendLimits = indexedSpendLimits.filter( + ({ item }) => item.usage_limit == null, + ); + const usageLimits = indexedSpendLimits.filter( + ({ item }) => item.usage_limit != null, + ); const usageAlerts = selectedEntity ? (selectedEntity.usage_alerts ?? []) : (fullCustomer?.usage_alerts ?? []); @@ -256,6 +297,7 @@ export function CustomerBillingControlsSection() { const hasAnyControls = autoTopups.length > 0 || spendLimits.length > 0 || + usageLimits.length > 0 || usageAlerts.length > 0 || overageAllowed.length > 0; @@ -305,6 +347,11 @@ export function CustomerBillingControlsSection() { > Spend limit + setSheet({ type: "billing-usage-limit-add" })} + > + Usage limit + setSheet({ type: "billing-usage-alert-add" })} > @@ -358,6 +405,11 @@ export function CustomerBillingControlsSection() { > Spend limit + setSheet({ type: "billing-usage-limit-add" })} + > + Usage limit + setSheet({ type: "billing-usage-alert-add" })} > @@ -410,7 +462,7 @@ export function CustomerBillingControlsSection() { {spendLimits.length > 0 && (
- {spendLimits.map((spendLimit, index) => ( + {spendLimits.map(({ item: spendLimit, index }) => ( )} + {usageLimits.length > 0 && ( + +
+ {usageLimits.map(({ item: usageLimit, index }) => ( + + setSheet({ + type: "billing-usage-limit-edit", + data: { index, item: usageLimit }, + }) + } + /> + ))} +
+
+ )} + {usageAlerts.length > 0 && (
diff --git a/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx b/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx new file mode 100644 index 000000000..4165ac38e --- /dev/null +++ b/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx @@ -0,0 +1,289 @@ +import { + type DbSpendLimit, + EntInterval, + type Feature, + FeatureType, + type FullCustomer, +} from "@autumn/shared"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/v2/buttons/Button"; +import { FeatureSearchDropdown } from "@/components/v2/dropdowns/FeatureSearchDropdown"; +import { FormLabel } from "@/components/v2/form/FormLabel"; +import { Input } from "@/components/v2/inputs/Input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/v2/selects/Select"; +import { + LayoutGroup, + SheetFooter, + SheetHeader, + SheetSection, +} from "@/components/v2/sheets/SharedSheetComponents"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; +import { useSheetStore } from "@/hooks/stores/useSheetStore"; +import { CusService } from "@/services/customers/CusService"; +import { useAxiosInstance } from "@/services/useAxiosInstance"; +import { getBackendErr } from "@/utils/genUtils"; +import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery"; +import { useCustomerContext } from "../../customer/CustomerContext"; + +// The empty value means "inherit the feature entitlement's reset interval" +// (usage_limit_interval omitted -> backend defaults to the billing cycle). +export const INHERIT_WINDOW = "inherit"; + +const WINDOW_OPTIONS: Record = { + [INHERIT_WINDOW]: "Inherit (billing cycle)", + [EntInterval.Day]: "Day", + [EntInterval.Week]: "Week", + [EntInterval.Month]: "Month", + [EntInterval.Year]: "Year", +}; + +/** + * Build the spend_limit entry for a usage cap. The cap is folded into spend_limits + * (presence of usage_limit arms it); window === INHERIT_WINDOW omits the interval so + * the backend inherits the entitlement's reset interval. Any co-located overage limit + * on an edited entry is preserved. + */ +export const buildUsageLimitItem = ({ + existing, + featureId, + usageLimit, + window, +}: { + existing?: DbSpendLimit; + featureId: string; + usageLimit: number; + window: string; +}): DbSpendLimit => ({ + ...existing, + feature_id: featureId || undefined, + enabled: existing?.enabled ?? false, + usage_limit: usageLimit, + usage_limit_interval: + window === INHERIT_WINDOW ? undefined : (window as EntInterval), +}); + +export function BillingUsageLimitSheet() { + const closeSheet = useSheetStore((s) => s.closeSheet); + const sheetData = useSheetStore((s) => s.data); + const sheetType = useSheetStore((s) => s.type); + const { customer, refetch } = useCusQuery(); + const { entityId } = useCustomerContext(); + const { features } = useFeaturesQuery(); + const axiosInstance = useAxiosInstance(); + + const isEdit = sheetType === "billing-usage-limit-edit"; + const existingItem = sheetData?.item as DbSpendLimit | undefined; + const existingIndex = sheetData?.index as number | undefined; + + const fullCustomer = customer as FullCustomer | undefined; + const selectedEntity = entityId + ? fullCustomer?.entities?.find( + (e) => e.id === entityId || e.internal_id === entityId, + ) + : null; + + const [isSaving, setIsSaving] = useState(false); + const [featureId, setFeatureId] = useState(existingItem?.feature_id ?? ""); + const [usageLimit, setUsageLimit] = useState( + existingItem?.usage_limit?.toString() ?? "", + ); + const [windowInterval, setWindowInterval] = useState( + existingItem?.usage_limit_interval ?? INHERIT_WINDOW, + ); + + const nonArchivedFeatures = (features ?? []).filter( + (f: Feature) => !f.archived && f.type !== FeatureType.Boolean, + ); + + const getCurrentSpendLimits = (): DbSpendLimit[] => { + if (selectedEntity) return [...(selectedEntity.spend_limits ?? [])]; + return [...(fullCustomer?.spend_limits ?? [])]; + }; + + const saveBillingControls = async (spendLimits: DbSpendLimit[]) => { + const customerId = fullCustomer?.id || fullCustomer?.internal_id; + if (!customerId) return; + + if (selectedEntity) { + await CusService.updateEntity({ + axios: axiosInstance, + customerId, + entityId: selectedEntity.id || selectedEntity.internal_id, + billingControls: { spend_limits: spendLimits }, + }); + } else { + await CusService.updateCustomer({ + axios: axiosInstance, + customer_id: customerId, + data: { billing_controls: { spend_limits: spendLimits } }, + }); + } + }; + + const handleSave = async () => { + const parsedLimit = + usageLimit.trim() === "" ? Number.NaN : Number.parseFloat(usageLimit); + if (Number.isNaN(parsedLimit) || parsedLimit < 0) { + toast.error("Please enter a valid usage limit"); + return; + } + if (!featureId) { + toast.error("Feature is required for a usage limit"); + return; + } + + const item = buildUsageLimitItem({ + existing: existingItem, + featureId, + usageLimit: parsedLimit, + window: windowInterval, + }); + + const spendLimits = getCurrentSpendLimits(); + if (isEdit && existingIndex !== undefined) { + spendLimits[existingIndex] = item; + } else { + spendLimits.push(item); + } + + setIsSaving(true); + try { + await saveBillingControls(spendLimits); + await refetch(); + closeSheet(); + toast.success(isEdit ? "Usage limit updated" : "Usage limit added"); + } catch (error) { + toast.error(getBackendErr(error, "Failed to save usage limit")); + } finally { + setIsSaving(false); + } + }; + + const handleDelete = async () => { + if (existingIndex === undefined) return; + + const spendLimits = getCurrentSpendLimits(); + const existing = spendLimits[existingIndex]; + // Preserve a co-located overage limit; otherwise drop the entry entirely. + if (existing?.overage_limit != null || existing?.enabled) { + spendLimits[existingIndex] = { + ...existing, + usage_limit: undefined, + usage_limit_interval: undefined, + }; + } else { + spendLimits.splice(existingIndex, 1); + } + + setIsSaving(true); + try { + await saveBillingControls(spendLimits); + await refetch(); + closeSheet(); + toast.success("Usage limit deleted"); + } catch (error) { + toast.error(getBackendErr(error, "Failed to delete usage limit")); + } finally { + setIsSaving(false); + } + }; + + return ( + +
+ + + + Feature + {isEdit ? ( +
+ {nonArchivedFeatures.find((f: Feature) => f.id === featureId) + ?.name ?? featureId} +
+ ) : ( + + )} +
+ + +
+
+ Limit + setUsageLimit(e.target.value)} + /> +
+ +
+ Window + +
+
+
+ +
+ + {isEdit && ( +
+ +
+ )} + + + + + +
+ + ); +} diff --git a/vite/src/views/customers2/customer/CustomerSheets.tsx b/vite/src/views/customers2/customer/CustomerSheets.tsx index 3616ba1d5..60a6eaf0a 100644 --- a/vite/src/views/customers2/customer/CustomerSheets.tsx +++ b/vite/src/views/customers2/customer/CustomerSheets.tsx @@ -20,6 +20,7 @@ import { BillingAutoTopupSheet } from "../components/sheets/BillingAutoTopupShee import { BillingOverageAllowedSheet } from "../components/sheets/BillingOverageAllowedSheet"; import { BillingSpendLimitSheet } from "../components/sheets/BillingSpendLimitSheet"; import { BillingUsageAlertSheet } from "../components/sheets/BillingUsageAlertSheet"; +import { BillingUsageLimitSheet } from "../components/sheets/BillingUsageLimitSheet"; import { CheckBalanceSheet } from "../components/sheets/CheckBalanceSheet"; import { CreateScheduleSheet } from "../components/sheets/CreateScheduleSheet"; import { InvoiceDetailSheet } from "../components/sheets/InvoiceDetailSheet"; @@ -83,6 +84,9 @@ export function CustomerSheets() { case "billing-spend-limit-add": case "billing-spend-limit-edit": return ; + case "billing-usage-limit-add": + case "billing-usage-limit-edit": + return ; case "billing-usage-alert-add": case "billing-usage-alert-edit": return ; diff --git a/vite/tests/views/customers2/components/sheets/billing-usage-limit-sheet.test.ts b/vite/tests/views/customers2/components/sheets/billing-usage-limit-sheet.test.ts new file mode 100644 index 000000000..544d915af --- /dev/null +++ b/vite/tests/views/customers2/components/sheets/billing-usage-limit-sheet.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "bun:test"; +import { type DbSpendLimit, EntInterval } from "@autumn/shared"; +import { + buildUsageLimitItem, + INHERIT_WINDOW, +} from "@/views/customers2/components/sheets/BillingUsageLimitSheet"; + +describe("buildUsageLimitItem", () => { + test("new cap with inherited window omits the interval (cap armed by usage_limit)", () => { + const item = buildUsageLimitItem({ + featureId: "credits", + usageLimit: 5, + window: INHERIT_WINDOW, + }); + expect(item.feature_id).toBe("credits"); + expect(item.usage_limit).toBe(5); + expect(item.usage_limit_interval).toBeUndefined(); + expect(item.enabled).toBe(false); + }); + + test("explicit window sets usage_limit_interval", () => { + const item = buildUsageLimitItem({ + featureId: "credits", + usageLimit: 10, + window: EntInterval.Day, + }); + expect(item.usage_limit).toBe(10); + expect(item.usage_limit_interval).toBe(EntInterval.Day); + }); + + test("editing preserves a co-located overage limit + enabled", () => { + const existing: DbSpendLimit = { + feature_id: "credits", + enabled: true, + overage_limit: 100, + }; + const item = buildUsageLimitItem({ + existing, + featureId: "credits", + usageLimit: 3, + window: EntInterval.Month, + }); + expect(item.overage_limit).toBe(100); + expect(item.enabled).toBe(true); + expect(item.usage_limit).toBe(3); + expect(item.usage_limit_interval).toBe(EntInterval.Month); + }); + + test("empty feature falls back to undefined feature_id", () => { + const item = buildUsageLimitItem({ + featureId: "", + usageLimit: 1, + window: INHERIT_WINDOW, + }); + expect(item.feature_id).toBeUndefined(); + }); +}); From f35f0d00b2660be82b2aad6701bad702c158729d Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Fri, 5 Jun 2026 18:03:47 +0100 Subject: [PATCH 09/41] fix usage-window cap counter durability + day-31 cycle bounds --- .../balances/track/v3/runRedisTrackV3.ts | 36 ++++- .../deductionV2/executePostgresDeductionV2.ts | 3 + .../track-customer-usage-limit.test.ts | 136 ++++++++++++++++++ .../getUsageWindowBounds.test.ts | 19 +++ .../billingUtils/cycleUtils/getCycleEnd.ts | 26 ++-- .../billingUtils/cycleUtils/getCycleStart.ts | 27 ++-- 6 files changed, 208 insertions(+), 39 deletions(-) diff --git a/server/src/internal/balances/track/v3/runRedisTrackV3.ts b/server/src/internal/balances/track/v3/runRedisTrackV3.ts index 494a393b6..577ea1150 100644 --- a/server/src/internal/balances/track/v3/runRedisTrackV3.ts +++ b/server/src/internal/balances/track/v3/runRedisTrackV3.ts @@ -18,6 +18,7 @@ import { projectMutationLogsToTrackDeductionsV2, } from "@/internal/balances/utils/deductionV2/index.js"; import { globalSyncBatchingManagerV3 } from "@/internal/balances/utils/sync/SyncBatchingManagerV3.js"; +import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import type { RolloverUpdate } from "../../utils/types/rolloverUpdate.js"; import { handleRedisTrackErrorV3 } from "./handleRedisTrackErrorV3.js"; @@ -130,13 +131,34 @@ export const runRedisTrackV3 = async ({ mutationLogs, } = result; - queueSyncItem({ - ctx, - body, - fullSubject: updatedFullSubject, - rolloverUpdates, - modifiedCusEntIdsByFeatureId, - }); + // Write the cap counter through to PG now; a later mutation rebuilds the cache from it. + const hasUsageCap = (fullSubject.customer.spend_limits ?? []).some( + (limit) => limit.usage_limit != null, + ); + if (hasUsageCap) { + await tryCatch( + syncItemV4({ + ctx, + payload: { + customerId: body.customer_id, + orgId: ctx.org.id, + env: ctx.env, + timestamp: Date.now(), + entityId: updatedFullSubject.entityId, + rolloverIds: Object.keys(rolloverUpdates), + modifiedCusEntIdsByFeatureId, + }, + }), + ); + } else { + queueSyncItem({ + ctx, + body, + fullSubject: updatedFullSubject, + rolloverUpdates, + modifiedCusEntIdsByFeatureId, + }); + } const deductions = projectMutationLogsToTrackDeductionsV2({ fullSubject: updatedFullSubject, diff --git a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts index 6fa2996b1..646975eda 100644 --- a/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executePostgresDeductionV2.ts @@ -157,6 +157,9 @@ export const executePostgresDeductionV2 = async ({ sql`SELECT * FROM deduct_from_cus_ents( ${JSON.stringify({ sorted_entitlements: customerEntitlementDeductions, + // No usage_window_limits here: the hard usage cap is enforced only on the + // Redis/Lua path, so this Postgres fallback intentionally fails open + // (availability over strict cap enforcement during a Redis outage). spend_limit_by_feature_id: spendLimitByFeatureId ?? null, usage_based_cus_ent_ids_by_feature_id: usageBasedCusEntIdsByFeatureId ?? null, diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts index 85af165a9..39377da9e 100644 --- a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts +++ b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts @@ -485,3 +485,139 @@ test.concurrent( expect(typeof after.usage_windows[0].id).toBe("string"); }, ); + +// No manual sync flush: the counter must survive the mutation's cache invalidation on +// its own, else the cap silently resets and hands out fresh headroom. +test( + `${chalk.yellowBright("track-customer-usage-limit-lowercap: lowering the cap below current usage keeps blocking (no counter reset)")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-lowercap", + items: [items.monthlyMessages({ includedUsage: 1000 })], + }); + + const customerId = `track-customer-uw-lowercap-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 10, + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 8, + }); + + await autumnV2_1.customers.update(customerId, { + billing_controls: { + spend_limits: [ + { + feature_id: TestFeature.Messages, + enabled: false, + usage_limit: 3, + usage_limit_interval: EntInterval.Month, + }, + ], + }, + }); + + let blocked = false; + let blockedCode: string | undefined; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + } catch (error) { + blocked = true; + blockedCode = (error as { code?: string }).code; + } + + expect(blocked).toBe(true); + expect(blockedCode).toBe("usage_limit_exceeded"); + }, +); + +// Bug 1: a second balance grant (balances.create) is a cache-invalidating mutation; +// the cap counter must survive it. It used to reset to 0, opening fresh headroom. +test( + `${chalk.yellowBright("track-customer-usage-limit-regrant: re-granting a balance does not reset the cap")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-regrant", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-regrant-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + + let blockedBefore = false; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + } catch (error) { + blockedBefore = + (error as { code?: string }).code === "usage_limit_exceeded"; + } + expect(blockedBefore).toBe(true); + + // Re-grant a second balance for the same feature while at the cap. + await autumnV2_1.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Messages, + included_grant: 100, + reset: { interval: EntInterval.Month }, + }); + + let blockedAfter = false; + let blockedCode: string | undefined; + try { + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + } catch (error) { + blockedAfter = true; + blockedCode = (error as { code?: string }).code; + } + + expect(blockedAfter).toBe(true); + expect(blockedCode).toBe("usage_limit_exceeded"); + }, +); diff --git a/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts b/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts index dc8589994..44c094280 100644 --- a/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts +++ b/server/tests/unit/usage-windows/getUsageWindowBounds.test.ts @@ -128,4 +128,23 @@ describe("getUsageWindowBounds", () => { }), ).toEqual({ windowStartAt: 0, windowEndAt: Number.MAX_SAFE_INTEGER }); }); + + // Day-31 anchor on the 30th of a 30-day month must stay cycle-aligned, not drop to + // the UTC-calendar fallback (which would key a fresh counter mid-cycle). + test("day-31 anchor stays cycle-aligned on the 30th of a 30-day month (no calendar fallback)", () => { + const anchor = Date.UTC(2026, 0, 31, 8, 30, 0); // Jan 31 08:30 UTC + const now = Date.UTC(2026, 3, 30, 9, 0, 0); // Apr 30 09:00 UTC (after the anchor time) + + const { windowStartAt, windowEndAt } = getUsageWindowBounds({ + interval: EntInterval.Month, + now, + anchor, + }); + + expect(windowStartAt).toBe(Date.UTC(2026, 3, 30, 8, 30, 0)); // Apr 30 08:30 + expect(windowEndAt).toBe(Date.UTC(2026, 4, 31, 8, 30, 0)); // May 31 08:30 + expect(windowStartAt).toBeLessThanOrEqual(now); + expect(now).toBeLessThan(windowEndAt); + expect(windowStartAt).not.toBe(Date.UTC(2026, 3, 1)); // not the calendar fallback + }); }); diff --git a/shared/utils/billingUtils/cycleUtils/getCycleEnd.ts b/shared/utils/billingUtils/cycleUtils/getCycleEnd.ts index 4c57655de..17cfe5efd 100644 --- a/shared/utils/billingUtils/cycleUtils/getCycleEnd.ts +++ b/shared/utils/billingUtils/cycleUtils/getCycleEnd.ts @@ -51,24 +51,18 @@ export const getCycleEnd = ({ const { add, difference } = intervalFunctions; const intervalsPassed = difference(nowDate, anchorDate); + let cyclesPassed = Math.floor(intervalsPassed / intervalCount); - // How many complete cycles have passed? - // e.g., if intervalCount=2 and 5 months passed, that's 2 complete cycles - const cyclesPassed = Math.floor(intervalsPassed / intervalCount); + // Same clamped-boundary correction as getCycleStart: land on the cycle that + // brackets `now`, then the end is the next boundary after it (always > now). + while (add(anchorDate, (cyclesPassed + 1) * intervalCount).getTime() <= now) { + cyclesPassed += 1; + } + while (add(anchorDate, cyclesPassed * intervalCount).getTime() > now) { + cyclesPassed -= 1; + } - // Next cycle end is (cyclesPassed + 1) * intervalCount months from anchor - const nextCycleEnd = add(anchorDate, (cyclesPassed + 1) * intervalCount); - - /** - * Handling edge case with date-fns anchor in the future - * Example: anchorDate: 28 Feb, nowDate: 15 Jan -> Next cycle end will be 28 Feb - * This is because of how differenceInMonths rounds down - * (28 Feb will see cycles passes as -1, so next cycle will be anchorDate + (-1 + 1) months) - */ - - const candidate = add(anchorDate, cyclesPassed * intervalCount); - const result = - candidate.getTime() > now ? candidate.getTime() : nextCycleEnd.getTime(); + const result = add(anchorDate, (cyclesPassed + 1) * intervalCount).getTime(); // If floor is provided and result is before floor, return floor if (floor !== undefined && result < floor) { diff --git a/shared/utils/billingUtils/cycleUtils/getCycleStart.ts b/shared/utils/billingUtils/cycleUtils/getCycleStart.ts index fbfbec7c4..0f371a673 100644 --- a/shared/utils/billingUtils/cycleUtils/getCycleStart.ts +++ b/shared/utils/billingUtils/cycleUtils/getCycleStart.ts @@ -49,25 +49,20 @@ export const getCycleStart = ({ const { add, difference } = intervalFunctions; const intervalsPassed = difference(nowDate, anchorDate); + let cyclesPassed = Math.floor(intervalsPassed / intervalCount); - // How many complete cycles have passed? - const cyclesPassed = Math.floor(intervalsPassed / intervalCount); - - // Cycle start is cyclesPassed * intervalCount from anchor - const cycleStart = add(anchorDate, cyclesPassed * intervalCount); - - /** - * Handling edge case with date-fns anchor in the future - * Example: anchorDate: 28 Apr, nowDate: 15 Jan -> differenceInMonths gives -3 - * cyclesPassed = floor(-3/3) = -1, so cycleStart = Apr 28 - 3 = Jan 28 - * But Jan 28 > Jan 15, so we overshot - need to go back one more cycle to Oct 28 - */ - let finalCycleStart = cycleStart; - if (cycleStart.getTime() > now) { - finalCycleStart = add(anchorDate, (cyclesPassed - 1) * intervalCount); + // date-fns shaves a cycle on a clamped end-of-month boundary (e.g. + // differenceInMonths(Apr 30, Jan 31) === 2, not 3) and on future anchors, so the + // estimate can land in the wrong cycle. Walk to the one that brackets `now`; + // boundaries are monotonic, so this is a bounded correction. + while (add(anchorDate, (cyclesPassed + 1) * intervalCount).getTime() <= now) { + cyclesPassed += 1; + } + while (add(anchorDate, cyclesPassed * intervalCount).getTime() > now) { + cyclesPassed -= 1; } - const result = finalCycleStart.getTime(); + const result = add(anchorDate, cyclesPassed * intervalCount).getTime(); // If floor is provided and result is before floor, return floor if (floor !== undefined && result < floor) { From bbcb68ba0810d35b483e4696a242d7a9d25b1eba Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Mon, 8 Jun 2026 11:25:54 +0100 Subject: [PATCH 10/41] expose usage_limit_used --- .../deductFromSubjectBalances.lua | 43 ++- .../usageWindowUtilsV2.lua | 44 ++++ .../apiCusUtils/getApiCustomerBase.ts | 10 +- .../getApiCustomerV2/getApiCustomerBaseV2.ts | 9 +- .../track-customer-usage-limit.test.ts | 247 +++++++++++------- .../billingControls/entityBillingControls.ts | 17 +- shared/api/billingControls/spendLimit.ts | 4 +- shared/api/common/entityData.ts | 4 +- .../customerBillingControls.ts | 8 +- .../cusModels/billingControls/spendLimit.ts | 9 + .../fullSubjectToApiSpendLimits.ts | 91 +++++++ shared/utils/fullSubjectUtils/index.ts | 1 + 12 files changed, 376 insertions(+), 111 deletions(-) create mode 100644 shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua index 9d3abc3c7..e36a1c362 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua @@ -219,6 +219,38 @@ if not is_nil(unwind_value) and safe_number(unwind_value) > 0 then end local logger = context.logger + +-- Usage windows are enforced only for positive consumption, never for refunds, +-- target_balance, granted-balance edits, locks, or unwinds. +local enforce_usage_windows = is_consumption + and is_nil(unwind_value) + and (is_nil(lock) or not lock.enabled) + and not is_nil(usage_window_limits) + and #usage_window_limits > 0 + +if enforce_usage_windows then + local clamp_result = clamp_amount_to_usage_windows({ + context = context, + usage_window_limits = usage_window_limits, + amount_to_deduct = amount_to_deduct, + }) + + if not is_nil(clamp_result.exceeded_feature_id) then + return cjson.encode({ + error = 'USAGE_LIMIT_EXCEEDED', + feature_id = clamp_result.exceeded_feature_id, + remaining = safe_number(amount_to_deduct), + updates = {}, + rollover_updates = {}, + modified_customer_entitlement_ids = new_empty_array(), + mutation_logs = new_empty_array(), + logs = context.logs, + }) + end + + amount_to_deduct = clamp_result.amount_to_deduct +end + logger.log("=== LUA DEDUCTION START ===") logger.log("=== PARAMS ===") logger.log(" amount_to_deduct: %s", tostring(amount_to_deduct or "nil")) @@ -279,17 +311,6 @@ if remaining_amount > 0 and overage_behaviour == 'reject' then }) end --- Hard windowed usage-limit enforcement, on ACTUAL consumed amounts, before any --- writes. Only for positive consumption (refunds / target_balance / granted --- balance edits never trip or move counters). v1 also excludes lock-based and --- unwind flows: counter reversal on partial unwind is not implemented yet, so --- enforcing there could drift the counter. -local enforce_usage_windows = is_consumption - and is_nil(unwind_value) - and (is_nil(lock) or not lock.enabled) - and not is_nil(usage_window_limits) - and #usage_window_limits > 0 - if enforce_usage_windows then local exceeded_feature_id = check_usage_window_limits({ context = context, diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua index 074047430..5cded80e7 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua @@ -74,6 +74,50 @@ local function usage_window_consumed(params) return safe_number(params.amount_to_deduct) - safe_number(params.remaining_amount) end +-- Clamps metered-feature usage caps before deduction so over-cap tracks apply +-- only the remaining headroom. Balance caps are credit-denominated, so unit +-- clamping would need credit conversion; they stay on the post-deduction check. +local function clamp_amount_to_usage_windows(params) + local context = params.context + local limits = params.usage_window_limits or {} + local clamped_amount = safe_number(params.amount_to_deduct) + + for _, limit in ipairs(limits) do + local windows = get_anchor_usage_windows( + context, + limit.anchor_customer_entitlement_id + ) + if is_nil(windows) then + return { + amount_to_deduct = clamped_amount, + exceeded_feature_id = limit.feature_id, + } + end + + if limit.dimension_type ~= 'balance' then + local existing = find_usage_window( + windows, + limit.feature_id, + limit.window_start_at + ) + local current_usage = existing and safe_number(existing.usage) or 0 + local headroom = safe_number(limit.limit) - current_usage + if headroom < 0 then + headroom = 0 + end + + if clamped_amount > headroom then + clamped_amount = headroom + end + end + end + + return { + amount_to_deduct = clamped_amount, + exceeded_feature_id = nil, + } +end + -- Returns the feature_id of the first limit that would be exceeded (so the -- caller can hard-reject), or nil if every limit has room. Null/missing anchor -- fails closed: a cap that cannot resolve an owner must not silently allow. diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts index d22af2607..f57dcb695 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts @@ -5,6 +5,9 @@ import { CustomerExpand, type CustomerLegacyData, type FullCustomer, + fullCustomerToFullSubject, + fullSubjectToApiSpendLimits, + orgToInStatuses, scopeExpandForCtx, } from "@autumn/shared"; import { z } from "zod/v4"; @@ -45,6 +48,11 @@ export const getApiCustomerBase = async ({ ctx: subscriptionsScopedCtx, fullCus, }); + const spendLimits = fullSubjectToApiSpendLimits({ + fullSubject: fullCustomerToFullSubject({ fullCustomer: fullCus }), + features: ctx.features, + inStatuses: orgToInStatuses({ org: ctx.org }), + }); const apiCustomer = ApiCustomerV5Schema.extend({ autumn_id: z.string().optional(), @@ -68,7 +76,7 @@ export const getApiCustomerBase = async ({ send_email_receipts: fullCus.send_email_receipts ?? false, billing_controls: { auto_topups: fullCus.auto_topups ?? undefined, - spend_limits: fullCus.spend_limits ?? undefined, + spend_limits: spendLimits, usage_alerts: fullCus.usage_alerts ?? undefined, overage_allowed: fullCus.overage_allowed ?? undefined, }, diff --git a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts index a3be20f65..ea38770a7 100644 --- a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts +++ b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts @@ -4,6 +4,8 @@ import { CustomerExpand, type CustomerLegacyData, type FullSubject, + fullSubjectToApiSpendLimits, + orgToInStatuses, scopeExpandForCtx, } from "@autumn/shared"; import { z } from "zod/v4"; @@ -46,6 +48,11 @@ export const getApiCustomerBaseV2 = async ({ }); const customer = fullSubject.customer; + const spendLimits = fullSubjectToApiSpendLimits({ + fullSubject, + features: ctx.features, + inStatuses: orgToInStatuses({ org: ctx.org }), + }); const apiCustomer = ApiCustomerV5Schema.extend({ autumn_id: z.string().optional(), @@ -66,7 +73,7 @@ export const getApiCustomerBaseV2 = async ({ send_email_receipts: customer.send_email_receipts ?? false, billing_controls: { auto_topups: customer.auto_topups ?? undefined, - spend_limits: customer.spend_limits ?? undefined, + spend_limits: spendLimits, usage_alerts: customer.usage_alerts ?? undefined, overage_allowed: customer.overage_allowed ?? undefined, }, diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts index 39377da9e..237444ec0 100644 --- a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts +++ b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts @@ -1,5 +1,9 @@ import { expect, test } from "bun:test"; -import { type CustomerBillingControls, EntInterval } from "@autumn/shared"; +import { + type ApiCustomerV5, + type CustomerBillingControls, + EntInterval, +} from "@autumn/shared"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; @@ -51,11 +55,11 @@ const setCustomerUsageLimit = async ({ }; // Credit system: 100 credits, 1 action1 = 0.2 credits (see v2Features.ts). -// A cap of 5 action1 units consumes only 1 credit, so the cap must block the +// A cap of 5 action1 units consumes only 1 credit, so the cap must clamp the // 6th unit while ~99 credits remain, proving it's a second, independent // dimension, not a balance check. test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit1: per-feature cap blocks deduction while credits remain")}`, + `${chalk.yellowBright("track-customer-usage-limit1: per-feature cap clamps the over-cap unit while credits remain")}`, async () => { const customerProduct = products.base({ id: "track-customer-usage-limit", @@ -93,24 +97,18 @@ test.concurrent( usage: 1, }); - // The 6th unit exceeds the cap. It must be hard-blocked BEFORE any - // deduction, even though ~99 credits remain. - let blocked = false; - let blockedCode: string | undefined; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - } catch (error) { - blocked = true; - blockedCode = (error as { code?: string }).code; - } - - expect(blocked).toBe(true); - // 400 not 429: clients flatten a 429 to a generic rate_limit_exceeded. - expect(blockedCode).toBe("usage_limit_exceeded"); + // The 6th unit is over the cap, so it clamps to 0: the track succeeds but + // applies nothing, leaving credits unchanged. + const overCap = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + expect(overCap.balances?.[TestFeature.Credits]).toMatchObject({ + granted: 100, + remaining: 99, + usage: 1, + }); }, ); @@ -214,9 +212,9 @@ test.concurrent( ); // A single spend_limit entry carrying BOTH an overage_limit and a windowed usage -// cap must still enforce the window (the two caps are independent). +// cap must still clamp on the window (the two caps are independent). test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit4: a spend_limit with both overage_limit and a usage window still enforces the window")}`, + `${chalk.yellowBright("track-customer-usage-limit4: a spend_limit with both overage_limit and a usage window clamps the window")}`, async () => { const customerProduct = products.base({ id: "track-customer-compound-cap", @@ -256,26 +254,25 @@ test.concurrent( value: 5, }); - let blockedCode: string | undefined; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - } catch (error) { - blockedCode = (error as { code?: string }).code; - } - - expect(blockedCode).toBe("usage_limit_exceeded"); + // The window cap clamps the over-cap unit to 0 (the overage path is separate), + // so the track succeeds and credits are unchanged. + const overCap = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + expect(overCap.balances?.[TestFeature.Credits]).toMatchObject({ + remaining: 99, + usage: 1, + }); }, ); // Two concurrent tracks on the SAME customer's SAME window must serialize (Redis -// runs each deduction Lua atomically): combined value exceeds the cap, so exactly -// one succeeds and one is rejected, and the counter reflects only the winner. +// runs each deduction Lua atomically): combined value exceeds the cap, so the +// second track clamps and the counter reflects exactly the capped usage. test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit6: concurrent tracks on one window serialize, one rejected")}`, + `${chalk.yellowBright("track-customer-usage-limit6: concurrent tracks on one window serialize, total clamped to the cap")}`, async () => { const customerProduct = products.base({ id: "track-customer-concurrent-cap", @@ -313,16 +310,17 @@ test.concurrent( }), ]); - const fulfilled = results.filter((result) => result.status === "fulfilled"); - const rejected = results.filter( - (result): result is PromiseRejectedResult => result.status === "rejected", - ); + // Both succeed now (clamp, not reject), but the window clamps the combined + // applied usage to the cap: one applies 5, the other clamps to 0. + expect(results.every((result) => result.status === "fulfilled")).toBe(true); - expect(fulfilled).toHaveLength(1); - expect(rejected).toHaveLength(1); - expect((rejected[0].reason as { code?: string }).code).toBe( - "usage_limit_exceeded", - ); + await timeout(2000); + const final = await autumnV2_1.customers.get(customerId); + expect(final.balances?.[TestFeature.Credits]).toMatchObject({ + feature_id: TestFeature.Credits, + remaining: 99, + usage: 1, + }); }, ); @@ -489,7 +487,7 @@ test.concurrent( // No manual sync flush: the counter must survive the mutation's cache invalidation on // its own, else the cap silently resets and hands out fresh headroom. test( - `${chalk.yellowBright("track-customer-usage-limit-lowercap: lowering the cap below current usage keeps blocking (no counter reset)")}`, + `${chalk.yellowBright("track-customer-usage-limit-lowercap: lowering the cap below current usage keeps the counter (clamps, no reset)")}`, async () => { const customerProduct = products.base({ id: "track-customer-uw-lowercap", @@ -532,28 +530,22 @@ test( }, }); - let blocked = false; - let blockedCode: string | undefined; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - } catch (error) { - blocked = true; - blockedCode = (error as { code?: string }).code; - } - - expect(blocked).toBe(true); - expect(blockedCode).toBe("usage_limit_exceeded"); + const clamped = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + expect(clamped.balance).toMatchObject({ + remaining: 992, + usage: 8, + }); }, ); // Bug 1: a second balance grant (balances.create) is a cache-invalidating mutation; // the cap counter must survive it. It used to reset to 0, opening fresh headroom. test( - `${chalk.yellowBright("track-customer-usage-limit-regrant: re-granting a balance does not reset the cap")}`, + `${chalk.yellowBright("track-customer-usage-limit-regrant: the cap counter survives a re-grant (clamps)")}`, async () => { const customerProduct = products.base({ id: "track-customer-uw-regrant", @@ -583,18 +575,12 @@ test( value: 5, }); - let blockedBefore = false; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - } catch (error) { - blockedBefore = - (error as { code?: string }).code === "usage_limit_exceeded"; - } - expect(blockedBefore).toBe(true); + const clampedBefore = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + expect(clampedBefore.balance).toMatchObject({ usage: 5 }); // Re-grant a second balance for the same feature while at the cap. await autumnV2_1.post("/balances.create", { @@ -604,20 +590,99 @@ test( reset: { interval: EntInterval.Month }, }); - let blockedAfter = false; - let blockedCode: string | undefined; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - } catch (error) { - blockedAfter = true; - blockedCode = (error as { code?: string }).code; - } - - expect(blockedAfter).toBe(true); - expect(blockedCode).toBe("usage_limit_exceeded"); + const clampedAfter = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + expect(clampedAfter.balance).toMatchObject({ usage: 5 }); + }, +); + +// Q1 clamp: an over-cap track applies what fits (the remaining headroom) instead of +// rejecting the whole track. cap 5, track 10 from 0 -> applies 5 (not 10, not a 400). +test( + `${chalk.yellowBright("track-customer-usage-limit-clamp: over-cap track applies what fits (clamp, not reject)")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-clamp", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-clamp-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Track 10 against a cap of 5 (from 0): clamps to 5, returns 200, not a reject. + const clamped = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 10, + }); + expect(clamped.value).toBe(10); + expect(clamped.balance).toMatchObject({ remaining: 95, usage: 5 }); + + // At the cap: a further track applies 0 (fully clamped), still 200. + const atCap = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + expect(atCap.balance).toMatchObject({ remaining: 95, usage: 5 }); + }, +); + +// Q2: the spend_limit in the customer response exposes the current window usage. +test( + `${chalk.yellowBright("track-customer-usage-limit-counter: spend_limit exposes the current window usage")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-counter", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-counter-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + const customer = (await autumnV2_1.get( + `/customers/${customerId}`, + )) as ApiCustomerV5; + const limit = customer.billing_controls?.spend_limits?.find( + (entry) => entry.feature_id === TestFeature.Messages, + ); + expect(limit?.usage_limit_used).toBe(3); }, ); diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index 53c4e8c27..b267214dd 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -1,4 +1,5 @@ import { z } from "zod/v4"; +import { DbSpendLimitSchema } from "../../models/cusModels/billingControls/spendLimit.js"; import { ApiOverageAllowedSchema } from "./overageAllowed.js"; import { ApiSpendLimitSchema } from "./spendLimit.js"; import { ApiUsageAlertSchema } from "./usageAlert.js"; @@ -17,8 +18,22 @@ export const ApiEntityBillingControlsSchema = z.object({ }), }); +const ApiEntityBillingControlsParamsBaseSchema = z.object({ + spend_limits: z.array(DbSpendLimitSchema).optional().meta({ + description: + "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", + }), + usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), + overage_allowed: z.array(ApiOverageAllowedSchema).optional().meta({ + description: + "List of overage allowed controls per feature. When enabled, usage can exceed balance.", + }), +}); + export const ApiEntityBillingControlsParamsSchema = - ApiEntityBillingControlsSchema.check((ctx) => { + ApiEntityBillingControlsParamsBaseSchema.check((ctx) => { const billingControls = ctx.value; const spendLimitFeatureIds = new Set(); diff --git a/shared/api/billingControls/spendLimit.ts b/shared/api/billingControls/spendLimit.ts index 15cf7121d..dc7d6d949 100644 --- a/shared/api/billingControls/spendLimit.ts +++ b/shared/api/billingControls/spendLimit.ts @@ -1,6 +1,6 @@ import type { z } from "zod/v4"; -import { DbSpendLimitSchema } from "../../models/cusModels/billingControls/spendLimit.js"; +import { SpendLimitResponseSchema } from "../../models/cusModels/billingControls/spendLimit.js"; -export const ApiSpendLimitSchema = DbSpendLimitSchema; +export const ApiSpendLimitSchema = SpendLimitResponseSchema; export type ApiSpendLimit = z.infer; diff --git a/shared/api/common/entityData.ts b/shared/api/common/entityData.ts index 6b416ba24..9308222c8 100644 --- a/shared/api/common/entityData.ts +++ b/shared/api/common/entityData.ts @@ -1,5 +1,5 @@ import { z } from "zod/v4"; -import { ApiEntityBillingControlsSchema } from "../billingControls/entityBillingControls.js"; +import { ApiEntityBillingControlsParamsSchema } from "../billingControls/entityBillingControls.js"; export const EntityDataSchema = z .object({ @@ -9,7 +9,7 @@ export const EntityDataSchema = z name: z.string().optional().meta({ description: "Name of the entity", }), - billing_controls: ApiEntityBillingControlsSchema.optional().meta({ + billing_controls: ApiEntityBillingControlsParamsSchema.optional().meta({ description: "Billing controls for the entity.", }), }) diff --git a/shared/models/cusModels/billingControls/customerBillingControls.ts b/shared/models/cusModels/billingControls/customerBillingControls.ts index f112fd324..182bf98dc 100644 --- a/shared/models/cusModels/billingControls/customerBillingControls.ts +++ b/shared/models/cusModels/billingControls/customerBillingControls.ts @@ -9,7 +9,11 @@ import { DbOverageAllowedSchema, } from "./overageAllowed.js"; import { PurchaseLimitIntervalEnum } from "./purchaseLimitInterval.js"; -import { type DbSpendLimit, DbSpendLimitSchema } from "./spendLimit.js"; +import { + type DbSpendLimit, + DbSpendLimitSchema, + SpendLimitResponseSchema, +} from "./spendLimit.js"; import { type DbUsageAlert, DbUsageAlertSchema } from "./usageAlert.js"; export const AutoTopupPurchaseLimitSchema = z.object({ @@ -125,7 +129,7 @@ export const CustomerBillingControlsResponseSchema = z.object({ auto_topups: z.array(AutoTopupResponseSchema).optional().meta({ description: "List of auto top-up configurations per feature.", }), - spend_limits: z.array(DbSpendLimitSchema).optional().meta({ + spend_limits: z.array(SpendLimitResponseSchema).optional().meta({ description: "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), diff --git a/shared/models/cusModels/billingControls/spendLimit.ts b/shared/models/cusModels/billingControls/spendLimit.ts index d274aaea0..a429db15e 100644 --- a/shared/models/cusModels/billingControls/spendLimit.ts +++ b/shared/models/cusModels/billingControls/spendLimit.ts @@ -33,3 +33,12 @@ export const DbSpendLimitSchema = z ); export type DbSpendLimit = z.infer; + +export const SpendLimitResponseSchema = DbSpendLimitSchema.extend({ + usage_limit_used: z.number().min(0).optional().meta({ + description: + "Current usage already consumed in the active usage_limit window. Response-only; not stored on billing controls.", + }), +}); + +export type SpendLimitResponse = z.infer; diff --git a/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts new file mode 100644 index 000000000..890821688 --- /dev/null +++ b/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts @@ -0,0 +1,91 @@ +import type { SpendLimitResponse } from "../../models/cusModels/billingControls/spendLimit.js"; +import type { FullSubject } from "../../models/cusModels/fullSubject/fullSubjectModel.js"; +import type { FullCustomerEntitlement } from "../../models/cusProductModels/cusEntModels/cusEntModels.js"; +import type { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; +import type { Feature } from "../../models/featureModels/featureModels.js"; +import { fullSubjectToUsageWindowLimits } from "./fullSubjectToUsageWindowLimits.js"; + +const fullSubjectToAllCustomerEntitlements = ({ + fullSubject, +}: { + fullSubject: FullSubject; +}): FullCustomerEntitlement[] => [ + ...fullSubject.customer_products.flatMap( + (customerProduct) => customerProduct.customer_entitlements, + ), + ...(fullSubject.extra_customer_entitlements ?? []), +]; + +/** + * Response decorator for customer spend limits. `usage_limit_used` is runtime + * state read from the current usage-window counter, not stored billing config. + */ +export const fullSubjectToApiSpendLimits = ({ + fullSubject, + features, + now = Date.now(), + inStatuses, +}: { + fullSubject: FullSubject; + features: Feature[]; + now?: number; + inStatuses?: CusProductStatus[]; +}): SpendLimitResponse[] | undefined => { + const spendLimits = fullSubject.customer.spend_limits; + if (spendLimits == null) return undefined; + + const usageLimitFeatureIds = spendLimits + .filter( + (spendLimit) => + spendLimit.feature_id != null && spendLimit.usage_limit != null, + ) + .map((spendLimit) => spendLimit.feature_id!); + + const usageWindowLimits = + usageLimitFeatureIds.length > 0 + ? fullSubjectToUsageWindowLimits({ + fullSubject, + featureIds: usageLimitFeatureIds, + features, + now, + inStatuses, + }) + : []; + + const allCustomerEntitlements = fullSubjectToAllCustomerEntitlements({ + fullSubject, + }); + const usageLimitUsedByFeatureId = new Map(); + + for (const limit of usageWindowLimits) { + if (limit.anchor_customer_entitlement_id == null) continue; + + const anchorCustomerEntitlement = allCustomerEntitlements.find( + (customerEntitlement) => + customerEntitlement.id === limit.anchor_customer_entitlement_id, + ); + const usageWindow = anchorCustomerEntitlement?.usage_windows?.find( + (window) => + window.feature_id === limit.feature_id && + Number(window.window_start_at) === limit.window_start_at, + ); + const usage = Number(usageWindow?.usage ?? 0); + + usageLimitUsedByFeatureId.set( + limit.feature_id, + Number.isFinite(usage) ? Math.max(0, usage) : 0, + ); + } + + return spendLimits.map((spendLimit) => { + if (spendLimit.usage_limit == null) return spendLimit; + + return { + ...spendLimit, + usage_limit_used: + spendLimit.feature_id == null + ? 0 + : (usageLimitUsedByFeatureId.get(spendLimit.feature_id) ?? 0), + }; + }); +}; diff --git a/shared/utils/fullSubjectUtils/index.ts b/shared/utils/fullSubjectUtils/index.ts index ecd248198..fafd46ba9 100644 --- a/shared/utils/fullSubjectUtils/index.ts +++ b/shared/utils/fullSubjectUtils/index.ts @@ -2,6 +2,7 @@ export * from "./aggregatedUtils/index.js"; export { fullSubjectHasUsageBasedAllocated } from "./classifyFullSubject.js"; export { fullCustomerToFullSubject } from "./fullCustomerToFullSubject.js"; export { fullSubjectToApiCustomerProducts } from "./fullSubjectToApiCustomerProducts.js"; +export { fullSubjectToApiSpendLimits } from "./fullSubjectToApiSpendLimits.js"; export { fullSubjectToCustomerEntitlements } from "./fullSubjectToCustomerEntitlements.js"; export { fullSubjectToFullCustomer } from "./fullSubjectToFullCustomer.js"; export { fullSubjectToOverageAllowedByFeatureId } from "./fullSubjectToOverageAllowed.js"; From d25ad88f1ae788a65fac5630687c556c14e782a5 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Tue, 9 Jun 2026 17:35:30 +0100 Subject: [PATCH 11/41] Make usage_windows PG sync monotonic to stop cap over-grant on rebuil --- .../balances/utils/sql/syncBalancesV2.sql | 33 +- .../balances/utils/sync/syncItemV4.ts | 5 +- .../track-customer-usage-limit.test.ts | 462 ++++++++++++++++++ .../balances/syncItemV4-cache-miss.test.ts | 113 +++++ 4 files changed, 601 insertions(+), 12 deletions(-) create mode 100644 server/tests/unit/balances/syncItemV4-cache-miss.test.ts diff --git a/server/src/internal/balances/utils/sql/syncBalancesV2.sql b/server/src/internal/balances/utils/sql/syncBalancesV2.sql index 7553aae52..74cfd68d6 100644 --- a/server/src/internal/balances/utils/sql/syncBalancesV2.sql +++ b/server/src/internal/balances/utils/sql/syncBalancesV2.sql @@ -145,18 +145,22 @@ BEGIN WHERE ce.id = ent_id; IF FOUND THEN - -- Mirror the windowed-usage counters into the usage_windows table (Redis is - -- authoritative and already prunes closed windows): full-replace the cus_ent's - -- rows. Clear on ANY present blob -- an emptied window array re-encodes as {} - -- (lua-cjson encodes an empty table as an object), so guarding the DELETE on - -- 'array' would leave stale closed rows. Only a real array has rows to INSERT; - -- a null/object blob simply clears, so the shared sync never reaches - -- jsonb_array_elements on a non-array. null = balance-only sync (untouched). - -- The internal_feature_id filters keep a stray null/orphan row from aborting - -- the whole batch on the NOT NULL + FK column. + -- Mirror the windowed-usage counters into the usage_windows table. Usage is + -- monotonic within a window, so stale cache snapshots must not lower the + -- persisted counter. A present non-array blob still means "no active windows" + -- because lua-cjson encodes an empty table as an object; null remains a + -- balance-only sync (untouched). The internal_feature_id filters keep a stray + -- null/orphan row from aborting the whole batch on the NOT NULL + FK column. IF ent_usage_windows IS NOT NULL AND ent_usage_windows != 'null'::jsonb THEN - DELETE FROM usage_windows WHERE customer_entitlement_id = ent_id; IF jsonb_typeof(ent_usage_windows) = 'array' THEN + DELETE FROM usage_windows uw + WHERE uw.customer_entitlement_id = ent_id + AND uw.window_start_at < ( + SELECT MIN((w->>'window_start_at')::numeric) + FROM jsonb_array_elements(ent_usage_windows) AS w + WHERE w->>'window_start_at' IS NOT NULL + ); + INSERT INTO usage_windows ( id, customer_entitlement_id, feature_id, internal_feature_id, window_start_at, window_end_at, usage, updated_at @@ -175,7 +179,14 @@ BEGIN AND EXISTS ( SELECT 1 FROM features f WHERE f.internal_id = w->>'internal_feature_id' - ); + ) + ON CONFLICT (customer_entitlement_id, feature_id, window_start_at) + DO UPDATE SET + usage = GREATEST(usage_windows.usage, EXCLUDED.usage), + window_end_at = EXCLUDED.window_end_at, + updated_at = EXCLUDED.updated_at; + ELSE + DELETE FROM usage_windows WHERE customer_entitlement_id = ent_id; END IF; END IF; diff --git a/server/src/internal/balances/utils/sync/syncItemV4.ts b/server/src/internal/balances/utils/sync/syncItemV4.ts index 3450a85f5..2d3b1f202 100644 --- a/server/src/internal/balances/utils/sync/syncItemV4.ts +++ b/server/src/internal/balances/utils/sync/syncItemV4.ts @@ -134,6 +134,9 @@ export const syncItemV4 = async ({ }); if (outcome.kind !== "ok") { + ctx.logger.warn( + `[SYNC V4] (${customerId}) Cache miss for feature ${featureId}; skipping this feature only.`, + ); logSyncItem({ ctx, result: { @@ -142,7 +145,7 @@ export const syncItemV4 = async ({ feature: featureId, }, }); - return; + continue; } allSubjectBalances.push(...outcome.value.balances); diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts index 237444ec0..cbe8eda6c 100644 --- a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts +++ b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts @@ -20,6 +20,111 @@ const queryRows = (result: unknown): any[] => Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); type AutumnV2_1Client = Awaited>["autumnV2_1"]; +type TestContext = Awaited>["ctx"]; + +type UsageWindowSyncEntry = { + customer_entitlement_id: string; + feature_id: string; + balance: number; + adjustment: number; + entities: null; + usage_windows: { + id: string; + feature_id: string; + internal_feature_id: string; + window_start_at: number; + window_end_at: number; + usage: number; + updated_at: number; + }[]; + next_reset_at: null; + entity_count: number; + cache_version: number; +}; + +const callSyncBalancesV2 = async ({ + ctx, + entry, +}: { + ctx: TestContext; + entry: UsageWindowSyncEntry; +}) => { + await ctx.db.execute(sql` + SELECT * FROM sync_balances_v2(${JSON.stringify({ + customer_entitlement_updates: [entry], + rollover_updates: [], + })}::jsonb) + `); +}; + +const buildUsageWindowSyncEntry = ({ + customerEntitlementId, + featureId, + balance = 0, + adjustment = 0, + cacheVersion = 0, + windows, +}: { + customerEntitlementId: string; + featureId: string; + balance?: number; + adjustment?: number; + cacheVersion?: number; + windows: UsageWindowSyncEntry["usage_windows"]; +}): UsageWindowSyncEntry => ({ + customer_entitlement_id: customerEntitlementId, + feature_id: featureId, + balance, + adjustment, + entities: null, + usage_windows: windows, + next_reset_at: null, + entity_count: 0, + cache_version: cacheVersion, +}); + +const getUsageLimitCustomerEntitlement = async ({ + ctx, + customerId, + featureId, +}: { + ctx: TestContext; + customerId: string; + featureId: string; +}) => { + const row = queryRows( + await ctx.db.execute(sql` + SELECT id, internal_feature_id, balance, adjustment, cache_version + FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${featureId} + LIMIT 1 + `), + )[0]; + expect(row?.id).toBeTruthy(); + return row as { + id: string; + internal_feature_id: string; + balance: string | number | null; + adjustment: string | number | null; + cache_version: number | null; + }; +}; + +const getUsageWindowRows = async ({ + ctx, + customerEntitlementId, +}: { + ctx: TestContext; + customerEntitlementId: string; +}) => + queryRows( + await ctx.db.execute(sql` + SELECT feature_id, internal_feature_id, window_start_at, window_end_at, usage + FROM usage_windows + WHERE customer_entitlement_id = ${customerEntitlementId} + ORDER BY window_start_at ASC + `), + ); // Arms a windowed usage cap via spend_limits[].usage_limit (overage off); // `interval` sets the explicit window override. @@ -398,6 +503,262 @@ test.concurrent( }, ); +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit-sync: stale sync cannot lower a usage_window counter")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-monotonic", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-monotonic-1-${Date.now()}`; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const messagesEnt = await getUsageLimitCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const windowStart = 1_900_000_000_000; + const windowEnd = 1_902_592_000_000; + const baseEntry = { + customerEntitlementId: messagesEnt.id, + featureId: TestFeature.Messages, + balance: Number(messagesEnt.balance ?? 0), + adjustment: Number(messagesEnt.adjustment ?? 0), + cacheVersion: messagesEnt.cache_version ?? 0, + }; + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [ + { + id: `${messagesEnt.id}:messages:${windowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: windowStart, + window_end_at: windowEnd, + usage: 10, + updated_at: windowStart + 10, + }, + ], + }), + }); + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [ + { + id: `${messagesEnt.id}:messages:${windowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: windowStart, + window_end_at: windowEnd, + usage: 8, + updated_at: windowStart + 20, + }, + ], + }), + }); + + const windowRows = await getUsageWindowRows({ + ctx, + customerEntitlementId: messagesEnt.id, + }); + expect(windowRows).toHaveLength(1); + expect(Number(windowRows[0].usage)).toBe(10); + expect(Number(windowRows[0].window_end_at)).toBe(windowEnd); + }, +); + +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit-sync: sync prunes windows older than the incoming window")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-prune", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-prune-1-${Date.now()}`; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const messagesEnt = await getUsageLimitCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const closedWindowStart = 1_900_000_000_000; + const currentWindowStart = 1_902_592_000_000; + const baseEntry = { + customerEntitlementId: messagesEnt.id, + featureId: TestFeature.Messages, + balance: Number(messagesEnt.balance ?? 0), + adjustment: Number(messagesEnt.adjustment ?? 0), + cacheVersion: messagesEnt.cache_version ?? 0, + }; + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [ + { + id: `${messagesEnt.id}:messages:${closedWindowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: closedWindowStart, + window_end_at: currentWindowStart, + usage: 4, + updated_at: closedWindowStart + 10, + }, + { + id: `${messagesEnt.id}:messages:${currentWindowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: currentWindowStart, + window_end_at: currentWindowStart + 2_592_000_000, + usage: 6, + updated_at: currentWindowStart + 10, + }, + ], + }), + }); + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [ + { + id: `${messagesEnt.id}:messages:${currentWindowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: currentWindowStart, + window_end_at: currentWindowStart + 2_592_000_000, + usage: 7, + updated_at: currentWindowStart + 20, + }, + ], + }), + }); + + const windowRows = await getUsageWindowRows({ + ctx, + customerEntitlementId: messagesEnt.id, + }); + expect(windowRows).toHaveLength(1); + expect(Number(windowRows[0].window_start_at)).toBe(currentWindowStart); + expect(Number(windowRows[0].usage)).toBe(7); + }, +); + +test.concurrent( + `${chalk.yellowBright("track-customer-usage-limit-sync: stale closed-window sync cannot delete the current window")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-boundary", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = `track-customer-uw-boundary-1-${Date.now()}`; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const messagesEnt = await getUsageLimitCustomerEntitlement({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const closedWindowStart = 1_900_000_000_000; + const currentWindowStart = 1_902_592_000_000; + const currentWindowEnd = currentWindowStart + 2_592_000_000; + const baseEntry = { + customerEntitlementId: messagesEnt.id, + featureId: TestFeature.Messages, + balance: Number(messagesEnt.balance ?? 0), + adjustment: Number(messagesEnt.adjustment ?? 0), + cacheVersion: messagesEnt.cache_version ?? 0, + }; + const closedWindow = { + id: `${messagesEnt.id}:messages:${closedWindowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: closedWindowStart, + window_end_at: currentWindowStart, + usage: 4, + updated_at: closedWindowStart + 10, + }; + const currentWindow = { + id: `${messagesEnt.id}:messages:${currentWindowStart}`, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + window_start_at: currentWindowStart, + window_end_at: currentWindowEnd, + usage: 6, + updated_at: currentWindowStart + 10, + }; + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [closedWindow], + }), + }); + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [currentWindow], + }), + }); + + await callSyncBalancesV2({ + ctx, + entry: buildUsageWindowSyncEntry({ + ...baseEntry, + windows: [closedWindow], + }), + }); + + const windowRows = await getUsageWindowRows({ + ctx, + customerEntitlementId: messagesEnt.id, + }); + const currentWindowRow = windowRows.find( + (row) => Number(row.window_start_at) === currentWindowStart, + ); + expect(currentWindowRow).toBeTruthy(); + expect(Number(currentWindowRow.usage)).toBe(6); + expect(Number(currentWindowRow.window_end_at)).toBe(currentWindowEnd); + }, +); + // Deploy-migration safety: a leftover pre-array keyed-map blob must be reset to a // clean array, never iterated-then-corrupted into a JSON object that wedges sync. test.concurrent( @@ -686,3 +1047,104 @@ test( expect(limit?.usage_limit_used).toBe(3); }, ); + +// Q1 clamp - partial fill: a track larger than the remaining headroom applies only +// what fits (not the whole value, not 0), fractional remainders included. +test( + `${chalk.yellowBright("track-customer-usage-limit-partial: an over-cap track fills the remaining headroom")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-partial", + items: [items.monthlyMessages({ includedUsage: 1000 })], + }); + + const customerId = `track-customer-uw-partial-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Only 2 headroom left; a fractional 2.5 fills exactly 2 (usage -> 5), not 2.5. + const partial = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2.5, + }); + expect(partial.balance).toMatchObject({ usage: 5, remaining: 995 }); + + // At the cap, a huge over-cap track applies 0. + const large = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1000, + }); + expect(large.balance).toMatchObject({ usage: 5, remaining: 995 }); + }, +); + +// Q1 clamp under concurrency: many simultaneous over-cap tracks all succeed (clamp, +// not reject), but the window applies at most the cap total - no over-count. +test( + `${chalk.yellowBright("track-customer-usage-limit-clamp-race: concurrent over-cap tracks clamp to the cap total")}`, + async () => { + const customerProduct = products.base({ + id: "track-customer-uw-clamp-race", + items: [items.monthlyMessages({ includedUsage: 100000 })], + }); + + const customerId = `track-customer-uw-clamp-race-1-${Date.now()}`; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + limit: 10, + }); + + const results = await Promise.allSettled( + Array.from({ length: 40 }, () => + autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }), + ), + ); + // Every track succeeds (clamp, never a usage_limit_exceeded reject)... + expect(results.every((result) => result.status === "fulfilled")).toBe(true); + + // ...but the window applied exactly the cap of 10 - no over-count from the race. + await timeout(2000); + const final = await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 0, + }); + expect(final.balance).toMatchObject({ usage: 10, remaining: 99990 }); + }, +); diff --git a/server/tests/unit/balances/syncItemV4-cache-miss.test.ts b/server/tests/unit/balances/syncItemV4-cache-miss.test.ts new file mode 100644 index 000000000..5ac47ed64 --- /dev/null +++ b/server/tests/unit/balances/syncItemV4-cache-miss.test.ts @@ -0,0 +1,113 @@ +import { afterAll, describe, expect, mock, test } from "bun:test"; +import { AppEnv } from "@autumn/shared"; + +const mockState = { + cacheReads: [] as string[], + executeCalls: [] as unknown[], +}; + +mock.module( + "@/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.js", + () => ({ + getCachedFeatureBalancesBatch: async () => ({ kind: "ok", value: [] }), + getCachedFeatureBalance: async ({ featureId }: { featureId: string }) => { + mockState.cacheReads.push(featureId); + if (featureId === "missing_feature") { + return { kind: "missing", reason: "single_field_null" }; + } + + return { + kind: "ok", + value: { + featureId, + balances: [ + { + id: "cus_ent_present", + feature_id: featureId, + balance: 42, + adjustment: 0, + entities: null, + usage_windows: null, + next_reset_at: null, + entity_count: 0, + cache_version: 0, + isEntityLevel: false, + rollovers: [], + }, + ], + }, + }; + }, + }), +); + +const { syncItemV4 } = await import( + "@/internal/balances/utils/sync/syncItemV4.js" +); + +describe("syncItemV4 cache misses", () => { + test("skips one missing feature without dropping another feature sync", async () => { + mockState.cacheReads = []; + mockState.executeCalls = []; + + const ctx = { + org: { id: "org_1" }, + env: AppEnv.Sandbox, + features: [], + extraLogs: {}, + logger: { warn: mock(() => {}) }, + db: { + execute: mock(async (query: unknown) => { + mockState.executeCalls.push(query); + return [ + { + sync_balances_v2: { + updates: { cus_ent_present: {} }, + rollover_updates: {}, + }, + }, + ]; + }), + }, + }; + + await syncItemV4({ + ctx: ctx as never, + payload: { + customerId: "cus_1", + orgId: "org_1", + env: AppEnv.Sandbox, + timestamp: 1, + modifiedCusEntIdsByFeatureId: { + missing_feature: ["cus_ent_missing"], + present_feature: ["cus_ent_present"], + }, + }, + }); + + expect(mockState.cacheReads).toEqual(["missing_feature", "present_feature"]); + expect(mockState.executeCalls).toHaveLength(1); + + const query = mockState.executeCalls[0] as { + queryChunks?: unknown[]; + }; + const payloadJson = query.queryChunks?.find( + (chunk): chunk is string => + typeof chunk === "string" && + chunk.includes("customer_entitlement_updates"), + ); + expect(payloadJson).toBeTruthy(); + + const payload = JSON.parse(payloadJson!); + expect(payload.customer_entitlement_updates).toHaveLength(1); + expect(payload.customer_entitlement_updates[0]).toMatchObject({ + customer_entitlement_id: "cus_ent_present", + feature_id: "present_feature", + balance: 42, + }); + }); +}); + +afterAll(() => { + mock.restore(); +}); From 6ff593703be506535fde3c3d01222f90a181ad7f Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Wed, 10 Jun 2026 20:12:40 +0100 Subject: [PATCH 12/41] customer-leading events_customer_hourly_mv for per-customer aggregates --- .../analytics/actions/getCountAndSum.ts | 2 +- .../events_customer_hourly_mv_backfill.pipe | 34 +++++++++++++++++++ .../events_customer_hourly_mv.datasource | 31 +++++++++++++++++ .../events_customer_hourly_mv_pipe.pipe | 22 ++++++++++++ .../tinybird/pipes/aggregate_groupable.pipe | 2 +- server/tinybird/pipes/aggregate_simple.pipe | 2 +- 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 server/tinybird/copies/events_customer_hourly_mv_backfill.pipe create mode 100644 server/tinybird/materializations/events_customer_hourly_mv.datasource create mode 100644 server/tinybird/materializations/events_customer_hourly_mv_pipe.pipe diff --git a/server/src/internal/analytics/actions/getCountAndSum.ts b/server/src/internal/analytics/actions/getCountAndSum.ts index 92e345e98..6a2e4c332 100644 --- a/server/src/internal/analytics/actions/getCountAndSum.ts +++ b/server/src/internal/analytics/actions/getCountAndSum.ts @@ -129,7 +129,7 @@ export const getCountAndSum = async ({ ` : ` SELECT event_name, sum(event_count) as count, sum(total_value) as sum - FROM ${useOrgRollup ? "events_org_hourly_mv" : "events_hourly_no_properties_two_mv"} + FROM ${useOrgRollup ? "events_org_hourly_mv" : "events_customer_hourly_mv"} WHERE org_id = {org_id:String} AND env = {env:String} ${!useOrgRollup && !params.aggregateAll ? "AND customer_id = {customer_id:String}" : ""} ${!useOrgRollup && params.entity_id ? "AND entity_id = {entity_id:String}" : ""} diff --git a/server/tinybird/copies/events_customer_hourly_mv_backfill.pipe b/server/tinybird/copies/events_customer_hourly_mv_backfill.pipe new file mode 100644 index 000000000..19256f0ac --- /dev/null +++ b/server/tinybird/copies/events_customer_hourly_mv_backfill.pipe @@ -0,0 +1,34 @@ +DESCRIPTION > + History backfill for events_customer_hourly_mv (deployed with BACKFILL skip). Fills the window before + the MV's forward-materialization seam, in bounded half-open [start_date, end_date) chunks on on-demand + compute + (tb copy run events_customer_hourly_mv_backfill --on-demand-compute --param start_date=... --param end_date=...). + SQL mirrors events_customer_hourly_mv_pipe EXACTLY (same projection + GROUP BY, no ARRAY JOIN) so + backfilled rows are identical to forward-materialized ones. No fan-out, so chunks can be wide. + + Seam safety: MergeTree does NOT dedup — keep every end_date <= the MV's actual promote time + (capture it empirically: min(hour) once forward events land), else overlapping hours double-count. + Only ~100 days of history is needed to cover the max servable window (the 92-day custom_range clamp + plus billing-cycle headroom); backfilling further back is wasted storage. + +NODE migrate +SQL > + % + SELECT + org_id, + env, + customer_id, + event_name, + coalesce(entity_id, '') as entity_id, + internal_product_id, + toStartOfHour(timestamp) as hour, + sum(toFloat64(coalesce(value, 1))) as total_value, + count() as event_count + FROM events + WHERE timestamp >= {{DateTime(start_date, '2026-06-01 00:00:00')}} + AND timestamp < {{DateTime(end_date, '2026-06-02 00:00:00')}} + GROUP BY org_id, env, customer_id, event_name, entity_id, internal_product_id, hour + +TYPE COPY +TARGET_DATASOURCE events_customer_hourly_mv +COPY_MODE append diff --git a/server/tinybird/materializations/events_customer_hourly_mv.datasource b/server/tinybird/materializations/events_customer_hourly_mv.datasource new file mode 100644 index 000000000..cc89319ef --- /dev/null +++ b/server/tinybird/materializations/events_customer_hourly_mv.datasource @@ -0,0 +1,31 @@ +DESCRIPTION > + Customer-leading hourly rollup of `events`, keyed (org_id, env, customer_id, entity_id, event_name, hour). + Same grain and projection as events_hourly_no_properties_two_mv, but with customer_id LEADING the sort key + so a per-customer (or per-customer+entity) query prunes straight to that customer's slice instead of + scanning the whole org's hourly partitions — events_hourly_no_properties_two_mv leads with `hour`, so a + per-customer query there cannot prune by customer and scans the entire org/env date window. + + Serves the per-customer ungrouped timeseries + count/sum and the group-by customer_id/entity_id/plan_id + paths once the read pipes (aggregate_simple, aggregate_groupable, getCountAndSum) are repointed at it. + Totals reconcile exactly with events_hourly_no_properties_two_mv because both pre-aggregate one row per + event at insert and the read pipe re-sums by (period, event_name, group_value). + + BACKFILL skip: deploy empty + forward-materialization trigger only (no deploy-time repopulate of the + existing events history); fill history in bounded chunks via events_customer_hourly_mv_backfill. + +SCHEMA > + `org_id` String, + `env` String, + `customer_id` String, + `event_name` String, + `entity_id` String DEFAULT '', + `internal_product_id` Nullable(String), + `hour` DateTime, + `total_value` Float64, + `event_count` UInt64 + +ENGINE "MergeTree" +ENGINE_PARTITION_KEY "toYYYYMM(hour)" +ENGINE_SORTING_KEY "org_id, env, customer_id, entity_id, event_name, hour" + +BACKFILL skip diff --git a/server/tinybird/materializations/events_customer_hourly_mv_pipe.pipe b/server/tinybird/materializations/events_customer_hourly_mv_pipe.pipe new file mode 100644 index 000000000..225e844d2 --- /dev/null +++ b/server/tinybird/materializations/events_customer_hourly_mv_pipe.pipe @@ -0,0 +1,22 @@ +DESCRIPTION > + Materializes events into customer-leading hourly aggregates without properties. + Projection + GROUP BY are identical to events_hourly_no_properties_two_mv_pipe, so the two rollups + are row-for-row equivalent; only the destination sort key differs (customer-leading here). + +NODE materialize +SQL > + SELECT + org_id, + env, + customer_id, + event_name, + coalesce(entity_id, '') as entity_id, + internal_product_id, + toStartOfHour(timestamp) as hour, + sum(toFloat64(coalesce(value, 1))) as total_value, + count() as event_count + FROM events + GROUP BY org_id, env, customer_id, event_name, entity_id, internal_product_id, hour + +TYPE materialized +DATASOURCE events_customer_hourly_mv diff --git a/server/tinybird/pipes/aggregate_groupable.pipe b/server/tinybird/pipes/aggregate_groupable.pipe index c289a3489..0da6fcbbf 100644 --- a/server/tinybird/pipes/aggregate_groupable.pipe +++ b/server/tinybird/pipes/aggregate_groupable.pipe @@ -41,7 +41,7 @@ SQL > sum(total_value) as total_value FROM {% if use_no_props %} - events_hourly_no_properties_two_mv + events_customer_hourly_mv {% elif use_property_rollup %} events_property_mv {% else %} diff --git a/server/tinybird/pipes/aggregate_simple.pipe b/server/tinybird/pipes/aggregate_simple.pipe index b5dc9a086..e0f4dc90d 100644 --- a/server/tinybird/pipes/aggregate_simple.pipe +++ b/server/tinybird/pipes/aggregate_simple.pipe @@ -20,7 +20,7 @@ SQL > {% end %} event_name, sum(total_value) as total_value - FROM {% if not no_property_filters %}events_hourly_mv{% elif use_org_rollup %}events_org_hourly_mv{% else %}events_hourly_no_properties_two_mv{% end %} + FROM {% if not no_property_filters %}events_hourly_mv{% elif use_org_rollup %}events_org_hourly_mv{% else %}events_customer_hourly_mv{% end %} WHERE org_id = {{ String(org_id, '') }} AND env = {{ String(env, 'test') }} From 2c6522f49040ed31a15b21fd0998dd0fb074bc2d Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 09:25:36 +0100 Subject: [PATCH 13/41] fix: ink/react in autumn/scripts --- scripts/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/package.json b/scripts/package.json index f64d8ed9e..39e8fe44b 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -15,17 +15,17 @@ "chalk": "^5.3.0", "dotenv": "^16.5.0", "drizzle-orm": "catalog:", - "ink": "^5.1.0", + "ink": "^6.6.0", "ioredis": "^5.10.0", "inquirer": "^12.6.3", "p-limit": "^7.2.0", "pg": "8.20.0", - "react": "^18.3.1" + "react": "^19.2.1" }, "devDependencies": { "@types/bun": "^1.3.11", "@types/pg": "8.20.0", - "@types/react": "^18.3.1", + "@types/react": "^19.2.1", "tsx": "^4.19.2", "typescript": "^5.7.3" } From f14052819f4390ba120f5bfe75c22ce38be055ce Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 09:59:54 +0100 Subject: [PATCH 14/41] fix: typecheck --- ai | 2 +- bun.lock | 308 ++++++-------------- packages/mcp/tests/utils/eval-test-utils.ts | 4 +- 3 files changed, 94 insertions(+), 220 deletions(-) diff --git a/ai b/ai index a84bc7418..bca809a30 160000 --- a/ai +++ b/ai @@ -1 +1 @@ -Subproject commit a84bc7418cd985f3b831b24a5e49f447b0eff0d4 +Subproject commit bca809a3078696361300cc65dc201fc077f91915 diff --git a/bun.lock b/bun.lock index 1244c9c82..cac27ce71 100644 --- a/bun.lock +++ b/bun.lock @@ -414,17 +414,17 @@ "chalk": "^5.3.0", "dotenv": "^16.5.0", "drizzle-orm": "catalog:", - "ink": "^5.1.0", + "ink": "^6.6.0", "inquirer": "^12.6.3", "ioredis": "^5.10.0", "p-limit": "^7.2.0", "pg": "8.20.0", - "react": "^18.3.1", + "react": "^19.2.1", }, "devDependencies": { "@types/bun": "^1.3.11", "@types/pg": "8.20.0", - "@types/react": "^18.3.1", + "@types/react": "^19.2.1", "tsx": "^4.19.2", "typescript": "^5.7.3", }, @@ -740,7 +740,7 @@ "@ai-sdk/ui-utils-v5": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], - "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.1.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="], + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -2660,7 +2660,7 @@ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], @@ -3216,7 +3216,7 @@ "cli-testing-library": ["cli-testing-library@3.0.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "picocolors": "^1.1.1", "redent": "^4.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "strip-final-newline": "^4.0.0", "tree-kill": "^1.2.2" }, "peerDependencies": { "@jest/expect": "^29.0.0", "@jest/globals": "^29.0.0", "vitest": "^3.0.0" }, "optionalPeers": ["@jest/expect", "@jest/globals", "vitest"] }, "sha512-fkQ8D2hQS53RP3s0yuCMHmTfPUMEqtVtJG0rs13MNE2khnkSaY8MsNxN7rSJZAzOOVsSg2I2F2XjEITJwf5dFg=="], - "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + "cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], @@ -3592,7 +3592,7 @@ "emittery": ["emittery@1.2.1", "", {}, "sha512-sFz64DCRjirhwHLxofFqxYQm6DCp6o0Ix7jwKQvuCHPn4GMRZNuBZyLPu9Ccmk/QSCAMZt6FOUqA8JZCQvA9fw=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], @@ -4158,7 +4158,7 @@ "ini": ["ini@5.0.0", "", {}, "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw=="], - "ink": ["ink@5.2.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="], + "ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], "ink-big-text": ["ink-big-text@2.0.0", "", { "dependencies": { "cfonts": "^3.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { "ink": ">=4", "react": ">=18" } }, "sha512-Juzqv+rIOLGuhMJiE50VtS6dg6olWfzFdL7wsU/EARSL5Eaa5JNXMogMBm9AkjgzO2Y3UwWCOh87jbhSn8aNdw=="], @@ -4256,7 +4256,7 @@ "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], @@ -4266,7 +4266,7 @@ "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], - "is-in-ci": ["is-in-ci@1.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="], + "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], @@ -5238,7 +5238,7 @@ "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-day-picker": ["react-day-picker@8.10.2", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-LK68OTbHB3oJNhl9cA0qVizzp3o26w61YSjAFkYi67N86iro32wx86kSNeFU/hq+gI8m1yzWhnomMLfZ041RzQ=="], @@ -5256,7 +5256,7 @@ "react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="], - "react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="], + "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], "react-redux": ["react-redux@9.3.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g=="], @@ -5462,7 +5462,7 @@ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], @@ -5536,7 +5536,7 @@ "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], - "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + "slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], "slug": ["slug@6.1.0", "", {}, "sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA=="], @@ -5612,7 +5612,7 @@ "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5880,7 +5880,7 @@ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -6074,7 +6074,7 @@ "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], - "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], "wildcard-match": ["wildcard-match@5.1.4", "", {}, "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g=="], @@ -6220,11 +6220,13 @@ "@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@autumn/server/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], - "@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], + "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -6234,16 +6236,16 @@ "@autumn/vite/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + "@autumn/vite/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], + "@autumn/vite/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "@autumn/website/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@autumn/website/eslint": ["eslint@10.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ=="], - "@autumn/website/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@autumn/website/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "@autumn/website/shiki": ["shiki@4.1.0", "", { "dependencies": { "@shikijs/core": "4.1.0", "@shikijs/engine-javascript": "4.1.0", "@shikijs/engine-oniguruma": "4.1.0", "@shikijs/langs": "4.1.0", "@shikijs/themes": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q=="], @@ -6524,6 +6526,8 @@ "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + "@mintlify/mdx/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], @@ -6570,10 +6574,6 @@ "@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], - "@mishieck/ink-titled-box/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], - - "@mishieck/ink-titled-box/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "@modelcontextprotocol/sdk/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -6766,8 +6766,6 @@ "@orpc/server/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "@orpc/shared/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], "@paralleldrive/cuid2/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -7120,7 +7118,7 @@ "@types/pg/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], - "@types/react-syntax-highlighter/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@types/react-dom/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], "@types/responselike/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], @@ -7196,18 +7194,12 @@ "atmn/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], - "atmn/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "atmn/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], "atmn/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "atmn/eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@4.6.2", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ=="], - "atmn/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], - - "atmn/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "atmn/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], @@ -7216,8 +7208,6 @@ "autumn-js/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], - "autumn-js/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "autumn-js/next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="], "autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -7262,6 +7252,12 @@ "boxen/camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], + "boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "boxen/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "boxen/widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "braintrust/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "bun-types/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], @@ -7282,14 +7278,10 @@ "checkout/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], - "checkout/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "checkout/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "checkout/decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], - "checkout/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "checkout/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "checkout/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -7304,7 +7296,7 @@ "cli-table3/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + "cli-testing-library/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -7338,6 +7330,8 @@ "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + "dot-prop/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "eciesjs/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -7406,8 +7400,6 @@ "eslint-plugin-import/tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - "eslint-plugin-jsx-a11y/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "eslint-plugin-n/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -7482,6 +7474,8 @@ "got/form-data-encoder": ["form-data-encoder@4.1.0", "", {}, "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw=="], + "got/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "gradient-string/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], @@ -7502,14 +7496,12 @@ "ink-confirm-input/ink-text-input": ["ink-text-input@3.3.0", "", { "dependencies": { "chalk": "^3.0.0", "prop-types": "^15.5.10" }, "peerDependencies": { "ink": "^2.0.0", "react": "^16.5.2" } }, "sha512-gO4wrOf2ie3YuEARTIwGlw37lMjFn3Gk6CKIDrMlHb46WFMagZU7DplohjM24zynlqfnXA5UDEIfC2NBcvD8kg=="], - "ink-scroll-list/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], - - "ink-scroll-list/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "ink-select-input/figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], "ink-table/object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], + "ink-text-input/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "is-get-set-prop/lowercase-keys": ["lowercase-keys@1.0.1", "", {}, "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="], "is-number/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], @@ -7532,12 +7524,12 @@ "line-column-path/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], - "listr2/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "log-update/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + "log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + "matcher-collection/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -7580,12 +7572,12 @@ "msw/tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], - "msw/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - "next/@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="], "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "next-mdx-remote-client/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "next-mdx-remote-client/serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], "ngrok/got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], @@ -7702,6 +7694,10 @@ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "react-dom/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "react-email/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], @@ -7750,8 +7746,6 @@ "sdk-test/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="], - "sdk-test/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "sdk-test/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "sdk-test/next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], @@ -7782,8 +7776,6 @@ "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], - "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "socket.io-adapter/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], @@ -7870,6 +7862,8 @@ "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -8034,28 +8028,6 @@ "@autumn/server/autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@autumn/server/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - - "@autumn/server/ink/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - - "@autumn/server/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], - - "@autumn/server/ink/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - - "@autumn/server/ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - - "@autumn/server/ink/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "@autumn/server/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "@autumn/server/ink/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "@autumn/server/ink/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - - "@autumn/server/ink/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - - "@autumn/server/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - "@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "@autumn/website/eslint/@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], @@ -8078,8 +8050,6 @@ "@autumn/website/eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "@autumn/website/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "@autumn/website/shiki/@shikijs/core": ["@shikijs/core@4.1.0", "", { "dependencies": { "@shikijs/primitive": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ=="], "@autumn/website/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ=="], @@ -8194,16 +8164,22 @@ "@mastra/braintrust/braintrust/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "@mintlify/cli/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - "@mintlify/cli/ink/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@mintlify/cli/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "@mintlify/cli/ink/cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], "@mintlify/cli/ink/react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], "@mintlify/cli/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "@mintlify/cli/ink/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "@mintlify/cli/ink/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "@mintlify/cli/ink/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "@mintlify/cli/ink/widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "@mintlify/cli/inquirer/@inquirer/prompts": ["@inquirer/prompts@7.10.1", "", { "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", "@inquirer/editor": "^4.2.23", "@inquirer/expand": "^4.0.23", "@inquirer/input": "^4.3.1", "@inquirer/number": "^3.0.23", "@inquirer/password": "^4.0.23", "@inquirer/rawlist": "^4.1.11", "@inquirer/search": "^3.2.2", "@inquirer/select": "^4.4.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="], "@mintlify/cli/inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], @@ -8320,16 +8296,22 @@ "@mintlify/previewing/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], - "@mintlify/previewing/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - "@mintlify/previewing/ink/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@mintlify/previewing/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "@mintlify/previewing/ink/cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], "@mintlify/previewing/ink/react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], "@mintlify/previewing/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "@mintlify/previewing/ink/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "@mintlify/previewing/ink/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "@mintlify/previewing/ink/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "@mintlify/previewing/ink/widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "@mintlify/previewing/socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "@mintlify/previewing/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -8342,26 +8324,6 @@ "@mintlify/scraping/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@mishieck/ink-titled-box/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - - "@mishieck/ink-titled-box/ink/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - - "@mishieck/ink-titled-box/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], - - "@mishieck/ink-titled-box/ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - - "@mishieck/ink-titled-box/ink/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "@mishieck/ink-titled-box/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "@mishieck/ink-titled-box/ink/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "@mishieck/ink-titled-box/ink/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - - "@mishieck/ink-titled-box/ink/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - - "@mishieck/ink-titled-box/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], @@ -8588,8 +8550,6 @@ "@react-grab/cli/ora/stdin-discarder": ["stdin-discarder@0.3.2", "", {}, "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A=="], - "@react-grab/cli/ora/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - "@sentry/bundler-plugin-core/glob/foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "@sentry/bundler-plugin-core/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -8832,26 +8792,6 @@ "atmn/eslint-plugin-react-hooks/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], - "atmn/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - - "atmn/ink/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - - "atmn/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], - - "atmn/ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - - "atmn/ink/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "atmn/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "atmn/ink/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "atmn/ink/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - - "atmn/ink/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - - "atmn/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - "autumn-js/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "autumn-js/next/@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="], @@ -8874,10 +8814,6 @@ "autumn-js/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "autumn-js/react-dom/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - - "autumn-js/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "ava/cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "ava/cli-truncate/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -8894,6 +8830,8 @@ "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "boxen/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "braintrust/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "braintrust/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], @@ -8920,8 +8858,6 @@ "checkout/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.100.11", "", {}, "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw=="], - "checkout/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "cli-progress/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "cli-progress/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -9062,30 +8998,6 @@ "ink-confirm-input/ink-text-input/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "ink-confirm-input/ink-text-input/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], - - "ink-confirm-input/ink-text-input/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - - "ink-scroll-list/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - - "ink-scroll-list/ink/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - - "ink-scroll-list/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], - - "ink-scroll-list/ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - - "ink-scroll-list/ink/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "ink-scroll-list/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "ink-scroll-list/ink/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "ink-scroll-list/ink/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - - "ink-scroll-list/ink/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - - "ink-scroll-list/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - "ink-select-input/figures/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], @@ -9104,10 +9016,6 @@ "jest-worker/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - "listr2/cli-truncate/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "listr2/cli-truncate/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - "log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -9138,8 +9046,6 @@ "msw/tough-cookie/tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], - "next-mdx-remote-client/serialize-error/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - "next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "ngrok/got/@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], @@ -9266,6 +9172,8 @@ "react-email/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "react-email/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], @@ -9324,8 +9232,6 @@ "sdk-test/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "sdk-test/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "shadcn/cosmiconfig/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], @@ -9344,6 +9250,8 @@ "shadcn/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "shadcn/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "supertap/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], @@ -9400,6 +9308,8 @@ "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "xo/@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "xo/@eslint/eslintrc/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], @@ -9466,10 +9376,6 @@ "@autumn/server/@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - "@autumn/server/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - - "@autumn/server/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "@autumn/website/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], "@autumn/website/eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], @@ -9550,10 +9456,12 @@ "@mastra/braintrust/braintrust/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], - "@mintlify/cli/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "@mintlify/cli/ink/cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "@mintlify/cli/ink/react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + "@mintlify/cli/ink/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "@mintlify/cli/inquirer/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "@mintlify/cli/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -9576,10 +9484,12 @@ "@mintlify/previewing/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], - "@mintlify/previewing/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "@mintlify/previewing/ink/cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "@mintlify/previewing/ink/react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + "@mintlify/previewing/ink/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "@mintlify/previewing/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "@mintlify/previewing/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -9592,10 +9502,6 @@ "@mintlify/scraping/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@mishieck/ink-titled-box/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - - "@mishieck/ink-titled-box/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -9782,13 +9688,9 @@ "atmn/eslint-plugin-react-hooks/eslint/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "atmn/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - - "atmn/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "autumn-js/next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - "ava/cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "ava/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "braintrust/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -9834,38 +9736,10 @@ "ink-confirm-input/ink-text-input/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "ink-confirm-input/ink-text-input/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], - - "ink-confirm-input/ink-text-input/ink/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - - "ink-confirm-input/ink-text-input/ink/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], - - "ink-confirm-input/ink-text-input/ink/is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], - - "ink-confirm-input/ink-text-input/ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - - "ink-confirm-input/ink-text-input/ink/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "ink-confirm-input/ink-text-input/ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "ink-confirm-input/ink-text-input/ink/slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], - - "ink-confirm-input/ink-text-input/ink/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], - - "ink-confirm-input/ink-text-input/ink/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - - "ink-confirm-input/ink-text-input/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], - - "ink-scroll-list/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - - "ink-scroll-list/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "is-online/got/cacheable-request/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "is-online/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], - "listr2/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "log-symbols/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -9940,6 +9814,8 @@ "react-email/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "react-email/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "run-jxa/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], @@ -9950,6 +9826,8 @@ "shadcn/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "shadcn/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "trigger.dev/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw=="], "trigger.dev/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], @@ -10000,10 +9878,14 @@ "@mastra/braintrust/braintrust/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@mintlify/cli/ink/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + "@mintlify/cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@mintlify/common/sucrase/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@mintlify/previewing/ink/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + "@mintlify/previewing/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@mintlify/scraping/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -10050,10 +9932,6 @@ "ink-confirm-input/ink-text-input/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "ink-confirm-input/ink-text-input/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - - "ink-confirm-input/ink-text-input/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], - "meow/read-pkg-up/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "meow/read-pkg-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], @@ -10118,8 +9996,6 @@ "@mintlify/common/sucrase/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@sentry/bundler-plugin-core/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "atmn/eslint-plugin-react-hooks/eslint/chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "atmn/eslint-plugin-react-hooks/eslint/file-entry-cache/flat-cache/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -10134,8 +10010,6 @@ "meow/read-pkg-up/read-pkg/normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "mocha/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "ora/log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "trigger.dev/c12/giget/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], diff --git a/packages/mcp/tests/utils/eval-test-utils.ts b/packages/mcp/tests/utils/eval-test-utils.ts index de24f5c27..57b14b919 100644 --- a/packages/mcp/tests/utils/eval-test-utils.ts +++ b/packages/mcp/tests/utils/eval-test-utils.ts @@ -271,7 +271,7 @@ export const initMcpEval = ({ }; const generate = async ( message: string | string[], - maxSteps = leafChatAgentDefaults.maxSteps, + maxSteps: number = leafChatAgentDefaults.maxSteps, ) => { messages.push({ role: "user", @@ -293,7 +293,7 @@ export const initMcpEval = ({ generate, approve: async ( message: string, - maxSteps = leafChatAgentDefaults.maxSteps, + maxSteps: number = leafChatAgentDefaults.maxSteps, ) => { if (!pendingApproval) await generate(message, maxSteps); if (!pendingApproval) { From 0170ddf2735b6644e1394014da47fe0335f72808 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Thu, 11 Jun 2026 09:58:13 +0100 Subject: [PATCH 15/41] fix: ends at propogation --- .../handleCheckoutSessionMetadataV2.ts | 28 +++- .../withClaimedCheckoutSessionMetadata.ts | 84 ++++++++++ .../handleStripeSubscriptionCanceled.ts | 15 +- .../handleStripeSubscriptionRenewed.ts | 3 + .../src/internal/metadata/MetadataService.ts | 25 +++ .../stripe-checkout-ends-at.test.ts | 156 ++++++++++++++++++ shared/models/otherModels/metadataTable.ts | 1 + 7 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts create mode 100644 server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-ends-at.test.ts diff --git a/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/handleCheckoutSessionMetadataV2.ts b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/handleCheckoutSessionMetadataV2.ts index b72217626..1124d1669 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/handleCheckoutSessionMetadataV2.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/handleCheckoutSessionMetadataV2.ts @@ -7,9 +7,9 @@ import { createStripeScheduleFromCheckout } from "@/external/stripe/webhookHandl import { modifyStripeSubscriptionFromCheckout } from "@/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/modifyStripeSubscriptionFromCheckout"; import { syncSubscriptionItemMetadataFromCheckout } from "@/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/syncSubscriptionItemMetadataFromCheckout"; import { updateBillingPlanFromCheckout } from "@/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/updateBillingPlanFromCheckout"; +import { withClaimedCheckoutSessionMetadata } from "@/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata"; import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext"; import { persistDeferredCreateSchedule } from "@/internal/billing/v2/actions/createSchedule/utils/persistDeferredCreateSchedule"; -import { checkoutSessionLock } from "@/internal/billing/v2/actions/locks/checkoutSessionLock/checkoutSessionLock"; import { addStripeSubscriptionScheduleIdToBillingPlan } from "@/internal/billing/v2/execute/addStripeSubscriptionScheduleIdToBillingPlan"; import { executeAutumnBillingPlan } from "@/internal/billing/v2/execute/executeAutumnBillingPlan"; import { logAutumnBillingPlan } from "@/internal/billing/v2/utils/logs/logAutumnBillingPlan"; @@ -34,6 +34,24 @@ export const handleCheckoutSessionMetadataV2 = async ({ `[checkout.completed] Handling checkout session metadata V2: ${metadata.id}`, ); + await withClaimedCheckoutSessionMetadata({ + ctx, + checkoutContext, + metadata, + execute: () => + executeCheckoutSessionMetadataV2({ ctx, checkoutContext, metadata }), + }); +}; + +const executeCheckoutSessionMetadataV2 = async ({ + ctx, + checkoutContext, + metadata, +}: { + ctx: StripeWebhookContext; + checkoutContext: CheckoutSessionCompletedContext; + metadata: NonNullable; +}): Promise => { const deferredData = metadata.data as DeferredAutumnBillingPlanData; // 1. Sync Autumn metadata onto subscription items created by checkout @@ -96,14 +114,6 @@ export const handleCheckoutSessionMetadataV2 = async ({ billingPlan: updatedDeferredData.billingPlan, }); - // Clear checkout session lock now that customer_product rows exist - const lockCustomerId = - updatedDeferredData.billingContext.fullCustomer.id ?? - updatedDeferredData.billingContext.fullCustomer.internal_id; - if (lockCustomerId) { - await checkoutSessionLock.clear({ ctx, customerId: lockCustomerId }); - } - // Queue customer.products.updated webhook (mirrors executeBillingPlan) await billingPlanToSendProductsUpdated({ ctx, diff --git a/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts new file mode 100644 index 000000000..745780ee1 --- /dev/null +++ b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts @@ -0,0 +1,84 @@ +import { + type DeferredAutumnBillingPlanData, + MetadataType, +} from "@autumn/shared"; +import { setStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils/lockStripeSubscriptionUtils"; +import type { CheckoutSessionCompletedContext } from "@/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/setupCheckoutSessionCompletedContext"; +import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext"; +import { checkoutSessionLock } from "@/internal/billing/v2/actions/locks/checkoutSessionLock/checkoutSessionLock"; +import { MetadataService } from "@/internal/metadata/MetadataService"; + +/** + * Runs `execute` exactly once across concurrent executors of the same deferred + * plan. The subscription lock marks the resulting subscription.updated events + * as Autumn-initiated; the checkout session lock is cleared even on failure + * since the Stripe session is already paid. + */ +export const withClaimedCheckoutSessionMetadata = async ({ + ctx, + checkoutContext, + metadata, + execute, +}: { + ctx: StripeWebhookContext; + checkoutContext: CheckoutSessionCompletedContext; + metadata: NonNullable; + execute: () => Promise; +}): Promise => { + const claimed = await MetadataService.claim({ + db: ctx.db, + id: metadata.id, + fromType: MetadataType.CheckoutSessionV2, + toType: MetadataType.CheckoutSessionV2Processing, + }); + + if (!claimed) { + ctx.logger.info( + `[checkout.completed] Metadata ${metadata.id} already claimed by another executor, skipping`, + ); + return; + } + + if (checkoutContext.stripeSubscription) { + await setStripeSubscriptionLock({ + stripeSubscriptionId: checkoutContext.stripeSubscription.id, + lockedAtMs: Date.now(), + }); + } + + const deferredData = metadata.data as DeferredAutumnBillingPlanData; + const lockCustomerId = + deferredData?.billingContext?.fullCustomer?.id ?? + deferredData?.billingContext?.fullCustomer?.internal_id; + + try { + await execute(); + } catch (error) { + await revertMetadataClaim({ ctx, metadataId: metadata.id }); + throw error; + } finally { + if (lockCustomerId) { + await checkoutSessionLock.clear({ ctx, customerId: lockCustomerId }); + } + } +}; + +const revertMetadataClaim = async ({ + ctx, + metadataId, +}: { + ctx: StripeWebhookContext; + metadataId: string; +}): Promise => { + await MetadataService.claim({ + db: ctx.db, + id: metadataId, + fromType: MetadataType.CheckoutSessionV2Processing, + toType: MetadataType.CheckoutSessionV2, + }).catch((revertError) => { + ctx.logger.error( + `[checkout.completed] Failed to revert metadata claim for ${metadataId}`, + { revertError }, + ); + }); +}; diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionCanceled/handleStripeSubscriptionCanceled.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionCanceled/handleStripeSubscriptionCanceled.ts index 897947271..03d61a435 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionCanceled/handleStripeSubscriptionCanceled.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionCanceled/handleStripeSubscriptionCanceled.ts @@ -1,4 +1,10 @@ -import { AttachScenario, cp, type FullCusProduct } from "@autumn/shared"; +import { + AttachScenario, + cp, + type FullCusProduct, + notNullish, +} from "@autumn/shared"; +import { msToSeconds } from "@shared/utils/common/unixUtils"; import { getStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils/lockStripeSubscriptionUtils"; import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext"; import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated"; @@ -68,6 +74,13 @@ export const handleStripeSubscriptionCanceled = async ({ if (!isActiveRecurringAndOnSub) continue; + // attach-set ends_at, not an external cancellation + const endedAtMatchesCancelAt = + notNullish(customerProduct.ended_at) && + notNullish(cancelsAtMs) && + msToSeconds(customerProduct.ended_at!) === msToSeconds(cancelsAtMs!); + if (endedAtMatchesCancelAt) continue; + const updates = { canceled_at: canceledAtMs ?? Date.now(), canceled: true, diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts index e5ee4764a..b5e04a89a 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts @@ -77,6 +77,9 @@ export const handleStripeSubscriptionRenewed = async ({ if (!valid) continue; + // attach-set ends_at expiry, not a cancellation + if (!customerProduct.canceled && !customerProduct.canceled_at) continue; + // Clear cancellation fields const updates = { canceled_at: null, diff --git a/server/src/internal/metadata/MetadataService.ts b/server/src/internal/metadata/MetadataService.ts index 8679fc2f1..2f7c31d30 100644 --- a/server/src/internal/metadata/MetadataService.ts +++ b/server/src/internal/metadata/MetadataService.ts @@ -54,6 +54,31 @@ export class MetadataService { return meta as Metadata; } + /** + * Atomically transitions a metadata row from one type to another. + * Returns true only for the caller whose update matched the `fromType` + * predicate — concurrent executors racing on the same row get false. + */ + static async claim({ + db, + id, + fromType, + toType, + }: { + db: DrizzleCli; + id: string; + fromType: MetadataType; + toType: MetadataType; + }): Promise { + const claimedRows = await db + .update(metadata) + .set({ type: toType }) + .where(and(eq(metadata.id, id), eq(metadata.type, fromType))) + .returning({ id: metadata.id }); + + return claimedRows.length > 0; + } + static async delete({ db, id }: { db: DrizzleCli; id: string }) { await db.delete(metadata).where(eq(metadata.id, id)); } diff --git a/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-ends-at.test.ts b/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-ends-at.test.ts new file mode 100644 index 000000000..e272e890a --- /dev/null +++ b/server/tests/integration/billing/attach/checkout/stripe-checkout/stripe-checkout-ends-at.test.ts @@ -0,0 +1,156 @@ +/** + * Stripe Checkout + ends_at Tests (Attach V2) + * + * Regression tests for ends_at surviving the Stripe Checkout deferred flow. + * + * Previously, after checkout completion: + * - A concurrent execution of the same deferred plan crashed the + * checkout.session.completed handler on a duplicate customer_products insert + * - The handler took no Stripe subscription lock, so the subscription.updated + * events it generated were misread as customer-initiated cancel/renew and + * handleStripeSubscriptionRenewed wiped ended_at off the customer product + * + * Expected behavior: + * - cancel_at lands on the Stripe subscription and stays there + * - customer_product.ended_at persists with canceled=false (an Autumn-owned + * expiry, not a cancellation) + */ + +import { expect, test } from "bun:test"; +import { + type ApiCustomerV5, + type AttachParamsV1Input, + MetadataType, +} from "@autumn/shared"; +import { getCustomerProduct } from "@tests/integration/billing/attach/params/start-date/utils"; +import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { completeStripeCheckoutFormV2 as completeStripeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2"; +import { items } from "@tests/utils/fixtures/items"; +import { products } from "@tests/utils/fixtures/products"; +import { timeout } from "@tests/utils/genUtils"; +import testContext from "@tests/utils/testInitUtils/createTestContext"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; +import chalk from "chalk"; +import { addDays } from "date-fns"; +import { MetadataService } from "@/internal/metadata/MetadataService"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 1: Stripe Checkout attach with ends_at → cancel_at + ended_at persist +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent( + `${chalk.yellowBright("stripe-checkout: ends_at sets cancel_at and persists ended_at")}`, + async () => { + const customerId = "stripe-checkout-ends-at"; + + const pro = products.pro({ + id: "pro-checkout-ends-at", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const { autumnV2_2, ctx, advancedTo } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: true }), // No payment method → stripe_checkout + s.products({ list: [pro] }), + ], + actions: [], + }); + + const endsAt = addDays(advancedTo, 7).getTime(); + + // 1. Attach with ends_at — should defer to Stripe Checkout + const result = await autumnV2_2.billing.attach({ + customer_id: customerId, + plan_id: pro.id, + ends_at: endsAt, + }); + + expect(result.payment_url).toBeDefined(); + expect(result.payment_url).toContain("checkout.stripe.com"); + + // 2. Complete checkout, then wait past the trailing subscription.updated + // events that previously wiped the cancellation fields + await completeStripeCheckoutForm({ url: result.payment_url }); + await timeout(15000); + + // 3. Product attached + const customer = await autumnV2_2.customers.get(customerId); + await expectProductActive({ customer, productId: pro.id }); + + // 4. ended_at persisted as an Autumn-owned expiry — not a cancellation + const customerProduct = await getCustomerProduct({ + ctx, + customerId, + productId: pro.id, + }); + expect(customerProduct.ended_at).toBe(endsAt); + expect(customerProduct.canceled).toBe(false); + expect(customerProduct.canceled_at ?? null).toBeNull(); + expect(customerProduct.subscription_ids).toHaveLength(1); + + // 5. cancel_at propagated onto the Stripe subscription and not cleared + const stripeSubscription = await ctx.stripeCli.subscriptions.retrieve( + customerProduct.subscription_ids![0]!, + ); + expect(stripeSubscription.cancel_at).toBe(Math.floor(endsAt / 1000)); + }, +); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 2: Metadata claim — exactly one concurrent executor wins +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent( + `${chalk.yellowBright("checkout metadata claim: only one concurrent executor wins")}`, + async () => { + const metadataId = `meta_claim_race_${Date.now()}`; + + await MetadataService.insert({ + db: testContext.db, + data: { + id: metadataId, + type: MetadataType.CheckoutSessionV2, + data: {}, + }, + }); + + try { + const claimResults = await Promise.all([ + MetadataService.claim({ + db: testContext.db, + id: metadataId, + fromType: MetadataType.CheckoutSessionV2, + toType: MetadataType.CheckoutSessionV2Processing, + }), + MetadataService.claim({ + db: testContext.db, + id: metadataId, + fromType: MetadataType.CheckoutSessionV2, + toType: MetadataType.CheckoutSessionV2Processing, + }), + ]); + + expect(claimResults.filter(Boolean)).toHaveLength(1); + + // Reverting the claim re-arms it for exactly one retry + const reverted = await MetadataService.claim({ + db: testContext.db, + id: metadataId, + fromType: MetadataType.CheckoutSessionV2Processing, + toType: MetadataType.CheckoutSessionV2, + }); + expect(reverted).toBe(true); + + const reclaimed = await MetadataService.claim({ + db: testContext.db, + id: metadataId, + fromType: MetadataType.CheckoutSessionV2, + toType: MetadataType.CheckoutSessionV2Processing, + }); + expect(reclaimed).toBe(true); + } finally { + await MetadataService.delete({ db: testContext.db, id: metadataId }); + } + }, +); diff --git a/shared/models/otherModels/metadataTable.ts b/shared/models/otherModels/metadataTable.ts index 8683aa7e3..45103c1d8 100644 --- a/shared/models/otherModels/metadataTable.ts +++ b/shared/models/otherModels/metadataTable.ts @@ -9,6 +9,7 @@ export enum MetadataType { DeferredInvoice = "deferred_invoice", CheckoutSessionV2 = "checkout_session_v2", + CheckoutSessionV2Processing = "checkout_session_v2_processing", CheckoutSessionEnabledImmediately = "checkout_session_enabled_immediately", SetupPaymentV2 = "setup_payment_v2", } From 5535368adb2563604b9061105af6429981859214 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:01:06 +0100 Subject: [PATCH 16/41] latest --- apps/leaf/tests/evals/harness/context/types.ts | 1 + apps/leaf/tests/evals/utils/scorers.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/leaf/tests/evals/harness/context/types.ts b/apps/leaf/tests/evals/harness/context/types.ts index 116408aab..5758ca79d 100644 --- a/apps/leaf/tests/evals/harness/context/types.ts +++ b/apps/leaf/tests/evals/harness/context/types.ts @@ -10,6 +10,7 @@ export type AutumnEvalToolName = | "getCustomer" | "getEntity" | "getOrCreateCustomer" + | "getCurrentOrganization" | "getPlan" | "listCustomers" | "listEntities" diff --git a/apps/leaf/tests/evals/utils/scorers.ts b/apps/leaf/tests/evals/utils/scorers.ts index daeabc318..f103524df 100644 --- a/apps/leaf/tests/evals/utils/scorers.ts +++ b/apps/leaf/tests/evals/utils/scorers.ts @@ -180,9 +180,21 @@ const matchesApiCall = ({ actual.toolName === expected.toolName && (!expected.body || includesObject(actual.body, expected.body)); -const valuesAtPath = ({ path, value }: { path: string; value: unknown }) => { +const valuesAtPath = ({ + path, + value, +}: { + path: string; + value: unknown; +}): unknown[] => { const parts = path.split("."); - const walk = ({ index, current }: { index: number; current: unknown }) => { + const walk = ({ + index, + current, + }: { + index: number; + current: unknown; + }): unknown[] => { if (index === parts.length) return [current]; const part = parts[index]; if (part === "*") { @@ -308,7 +320,7 @@ export const expectedApiBodyNumberFields = ({ const values = valuesAtPath({ path, value: call.body }); return ( values.length > 0 && - values.every((value) => typeof value === "number") + values.every((value: unknown) => typeof value === "number") ); }), ) From 47a8fec48ecdc33909e44173f681ad8b3ef49f44 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:05:07 +0100 Subject: [PATCH 17/41] latest --- server/src/internal/emails/OTPEmail.tsx | 58 +++++++++++++++---------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/server/src/internal/emails/OTPEmail.tsx b/server/src/internal/emails/OTPEmail.tsx index 86d6be1d8..285c3564d 100644 --- a/server/src/internal/emails/OTPEmail.tsx +++ b/server/src/internal/emails/OTPEmail.tsx @@ -8,44 +8,58 @@ import { Tailwind, Text, } from "@react-email/components"; +import type { ReactElement, ReactNode } from "react"; + +type EmailComponent = (props: { + children?: ReactNode; + [key: string]: unknown; +}) => ReactElement | null; + +const EmailBody = Body as unknown as EmailComponent; +const EmailContainer = Container as unknown as EmailComponent; +const EmailHead = Head as unknown as EmailComponent; +const EmailHeading = Heading as unknown as EmailComponent; +const EmailHtml = Html as unknown as EmailComponent; +const EmailSection = Section as unknown as EmailComponent; +const EmailTailwind = Tailwind as unknown as EmailComponent; +const EmailText = Text as unknown as EmailComponent; const OTPEmail = (props: { otpCode: string }) => { return ( - - - - - - + + + + + + Verification code - + - + Enter the following verification code when prompted: - + - + {props.otpCode} - + - + To protect your account, do not share this code. - + - {/* Footer */} -
- + + Autumn
2261 Market Street STE 22390
San Francisco, CA, US, 94114 -
-
-
- -
- + + + + + + ); }; From 1c6bf0fd488251b320ba0e455a49c08dea01440a Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:07:24 +0100 Subject: [PATCH 18/41] fix: typecheck --- bun.lock | 42 +++++++++--------- server/package.json | 8 ++-- server/src/internal/emails/OTPEmail.tsx | 57 +++++++++---------------- 3 files changed, 46 insertions(+), 61 deletions(-) diff --git a/bun.lock b/bun.lock index cac27ce71..4d1b40361 100644 --- a/bun.lock +++ b/bun.lock @@ -528,8 +528,8 @@ "posthog-node": "^5.20.0", "puppeteer-core": "^24.14.0", "qs": "^6.14.0", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "19.2.3", + "react-dom": "19.2.3", "resend": "4.8.0", "semver": "^7.7.2", "stripe": "catalog:", @@ -547,8 +547,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "18.3.28", - "@types/react-dom": "18.3.7", + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", @@ -2662,7 +2662,7 @@ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], @@ -5244,7 +5244,7 @@ "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="], - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], "react-email": ["react-email@4.0.16", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/traverse": "^7.27.0", "chalk": "^5.0.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "debounce": "^2.0.0", "esbuild": "^0.25.0", "glob": "^11.0.0", "log-symbols": "^7.0.0", "mime-types": "^3.0.0", "next": "^15.3.1", "normalize-path": "^3.0.0", "ora": "^8.0.0", "socket.io": "^4.8.1" }, "bin": { "email": "dist/cli/index.mjs" } }, "sha512-auhFU+nQxAkKkP6lQhPyGsa9exwfUEzp2BwZnjHokCwphZlg30tu4t1LgdKRwGPYsi7XNGy6asbVLAUhOVpzzg=="], @@ -6220,13 +6220,11 @@ "@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], - "@autumn/server/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], - "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], - "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@autumn/server/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], "@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -6238,10 +6236,14 @@ "@autumn/vite/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/vite/@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "@autumn/vite/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@autumn/vite/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], "@autumn/website/eslint": ["eslint@10.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ=="], @@ -6528,6 +6530,8 @@ "@mintlify/mdx/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@mintlify/mdx/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], @@ -7118,8 +7122,6 @@ "@types/pg/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], - "@types/react-dom/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], - "@types/responselike/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], "@types/send/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], @@ -7278,8 +7280,6 @@ "checkout/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], - "checkout/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "checkout/decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], "checkout/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -7578,6 +7578,8 @@ "next-mdx-remote-client/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "next-mdx-remote-client/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "next-mdx-remote-client/serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], "ngrok/got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], @@ -7694,10 +7696,6 @@ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - "react-dom/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - - "react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "react-email/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], @@ -7746,14 +7744,10 @@ "sdk-test/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="], - "sdk-test/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "sdk-test/next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], "sdk-test/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], - "sdk-test/react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - "sdk-test/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -8030,6 +8024,8 @@ "@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@autumn/vite/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "@autumn/website/eslint/@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], "@autumn/website/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], @@ -8232,6 +8228,8 @@ "@mintlify/link-rot/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + "@mintlify/mdx/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "@mintlify/prebuild/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@mintlify/prebuild/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -9046,6 +9044,8 @@ "msw/tough-cookie/tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], + "next-mdx-remote-client/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "ngrok/got/@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], diff --git a/server/package.json b/server/package.json index d694e4511..94e32e35b 100644 --- a/server/package.json +++ b/server/package.json @@ -140,8 +140,8 @@ "posthog-node": "^5.20.0", "puppeteer-core": "^24.14.0", "qs": "^6.14.0", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "19.2.3", + "react-dom": "19.2.3", "resend": "4.8.0", "semver": "^7.7.2", "stripe": "catalog:", @@ -159,8 +159,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "18.3.28", - "@types/react-dom": "18.3.7", + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", diff --git a/server/src/internal/emails/OTPEmail.tsx b/server/src/internal/emails/OTPEmail.tsx index 285c3564d..66d685e37 100644 --- a/server/src/internal/emails/OTPEmail.tsx +++ b/server/src/internal/emails/OTPEmail.tsx @@ -8,58 +8,43 @@ import { Tailwind, Text, } from "@react-email/components"; -import type { ReactElement, ReactNode } from "react"; - -type EmailComponent = (props: { - children?: ReactNode; - [key: string]: unknown; -}) => ReactElement | null; - -const EmailBody = Body as unknown as EmailComponent; -const EmailContainer = Container as unknown as EmailComponent; -const EmailHead = Head as unknown as EmailComponent; -const EmailHeading = Heading as unknown as EmailComponent; -const EmailHtml = Html as unknown as EmailComponent; -const EmailSection = Section as unknown as EmailComponent; -const EmailTailwind = Tailwind as unknown as EmailComponent; -const EmailText = Text as unknown as EmailComponent; const OTPEmail = (props: { otpCode: string }) => { return ( - - - - - - + + + + + + Verification code - + - + Enter the following verification code when prompted: - + - + {props.otpCode} - + - + To protect your account, do not share this code. - + - - +
+ Autumn
2261 Market Street STE 22390
San Francisco, CA, US, 94114 - - - - - - +
+
+ + + + ); }; From 3c300bc8ae2bc7e2f258914df7d672d5e91c43ec Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Thu, 11 Jun 2026 10:28:01 +0100 Subject: [PATCH 19/41] fix: revert metadata claim if subscription lock write fails Co-Authored-By: Claude Fable 5 --- .../withClaimedCheckoutSessionMetadata.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts index 745780ee1..c89fe8f28 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeCheckoutSessionCompleted/tasks/handleCheckoutSessionMetadataV2/withClaimedCheckoutSessionMetadata.ts @@ -39,19 +39,19 @@ export const withClaimedCheckoutSessionMetadata = async ({ return; } - if (checkoutContext.stripeSubscription) { - await setStripeSubscriptionLock({ - stripeSubscriptionId: checkoutContext.stripeSubscription.id, - lockedAtMs: Date.now(), - }); - } - const deferredData = metadata.data as DeferredAutumnBillingPlanData; const lockCustomerId = deferredData?.billingContext?.fullCustomer?.id ?? deferredData?.billingContext?.fullCustomer?.internal_id; try { + if (checkoutContext.stripeSubscription) { + await setStripeSubscriptionLock({ + stripeSubscriptionId: checkoutContext.stripeSubscription.id, + lockedAtMs: Date.now(), + }); + } + await execute(); } catch (error) { await revertMetadataClaim({ ctx, metadataId: metadata.id }); From 852203c5e79ad317d7c4b4dbc187e9a1e0e67daa Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:32:50 +0100 Subject: [PATCH 20/41] fix: typecheck --- packages/ai-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 6907b1fb9..722aa3f95 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -19,7 +19,7 @@ } }, "scripts": { - "ts": "tsgo --noEmit --skipLibCheck", + "ts": "bunx tsgo --noEmit --skipLibCheck", "test": "bun test tests/unit", "build": "rm -rf dist && tsup", "prepublishOnly": "bun run build" From dc6f199611d4ab8c78e0f409d1433235905c589a Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:40:14 +0100 Subject: [PATCH 21/41] latest --- scripts/dw/commands/setup.ts | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/scripts/dw/commands/setup.ts b/scripts/dw/commands/setup.ts index bfd00cff4..e93d0e9af 100644 --- a/scripts/dw/commands/setup.ts +++ b/scripts/dw/commands/setup.ts @@ -14,6 +14,42 @@ import { ensureEmulateRunning } from "../helpers/emulate.ts"; import { PROJECT_ROOT } from "../constants.ts"; import type { RegistryEntry } from "../types.ts"; +function ensureAiSubmoduleSynced(): void { + const aiDir = `${PROJECT_ROOT}/ai`; + + log("ensuring ai submodule is initialized"); + const submoduleCode = shInherit( + "git", + ["submodule", "update", "--init", "--recursive"], + { cwd: PROJECT_ROOT }, + ); + if (submoduleCode !== 0) { + fatal( + `git submodule update --init --recursive failed (exit ${submoduleCode})`, + ); + } + + log("checking out ai submodule main branch"); + const checkoutCode = shInherit("git", ["checkout", "main"], { + cwd: aiDir, + }); + if (checkoutCode !== 0) { + fatal(`git checkout main failed in ai submodule (exit ${checkoutCode})`); + } + + log("ensuring ai deps installed (bun install)"); + const installCode = shInherit("bun", ["install"], { cwd: aiDir }); + if (installCode !== 0) { + fatal(`bun install failed in ai submodule (exit ${installCode})`); + } + + log("syncing ai skills"); + const syncCode = shInherit("bun", ["sync"], { cwd: aiDir }); + if (syncCode !== 0) { + fatal(`bun sync failed in ai submodule (exit ${syncCode})`); + } +} + export async function cmdSetup(): Promise { if (process.env.NODE_ENV === "production") { fatal("bun dw is disabled in production"); @@ -25,6 +61,8 @@ export async function cmdSetup(): Promise { const installCode = shInherit("bun", ["install"], { cwd: PROJECT_ROOT }); if (installCode !== 0) fatal(`bun install failed (exit ${installCode})`); + ensureAiSubmoduleSynced(); + const canonical = getCanonicalWorktree(); const cwd = getCurrentWorktree(); let registry = loadRegistry(); From 5af305c472a2e89d692b1dfc04a85ff63640433f Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 10:48:50 +0100 Subject: [PATCH 22/41] latest --- bun.lock | 42 +++++++++++++++++++++--------------------- server/package.json | 8 ++++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/bun.lock b/bun.lock index 4d1b40361..cac27ce71 100644 --- a/bun.lock +++ b/bun.lock @@ -528,8 +528,8 @@ "posthog-node": "^5.20.0", "puppeteer-core": "^24.14.0", "qs": "^6.14.0", - "react": "19.2.3", - "react-dom": "19.2.3", + "react": "18.3.1", + "react-dom": "18.3.1", "resend": "4.8.0", "semver": "^7.7.2", "stripe": "catalog:", @@ -547,8 +547,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", + "@types/react": "18.3.28", + "@types/react-dom": "18.3.7", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", @@ -2662,7 +2662,7 @@ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], @@ -5244,7 +5244,7 @@ "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="], - "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], "react-email": ["react-email@4.0.16", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/traverse": "^7.27.0", "chalk": "^5.0.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "debounce": "^2.0.0", "esbuild": "^0.25.0", "glob": "^11.0.0", "log-symbols": "^7.0.0", "mime-types": "^3.0.0", "next": "^15.3.1", "normalize-path": "^3.0.0", "ora": "^8.0.0", "socket.io": "^4.8.1" }, "bin": { "email": "dist/cli/index.mjs" } }, "sha512-auhFU+nQxAkKkP6lQhPyGsa9exwfUEzp2BwZnjHokCwphZlg30tu4t1LgdKRwGPYsi7XNGy6asbVLAUhOVpzzg=="], @@ -6220,11 +6220,13 @@ "@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@autumn/server/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], - "@autumn/server/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -6236,14 +6238,10 @@ "@autumn/vite/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], - "@autumn/vite/@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], - "@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "@autumn/vite/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "@autumn/vite/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - "@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], "@autumn/website/eslint": ["eslint@10.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ=="], @@ -6530,8 +6528,6 @@ "@mintlify/mdx/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "@mintlify/mdx/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], @@ -7122,6 +7118,8 @@ "@types/pg/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/react-dom/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@types/responselike/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], "@types/send/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], @@ -7280,6 +7278,8 @@ "checkout/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], + "checkout/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "checkout/decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], "checkout/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -7578,8 +7578,6 @@ "next-mdx-remote-client/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "next-mdx-remote-client/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - "next-mdx-remote-client/serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], "ngrok/got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], @@ -7696,6 +7694,10 @@ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "react-dom/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "react-email/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], @@ -7744,10 +7746,14 @@ "sdk-test/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="], + "sdk-test/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "sdk-test/next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], "sdk-test/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + "sdk-test/react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + "sdk-test/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -8024,8 +8030,6 @@ "@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@autumn/vite/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "@autumn/website/eslint/@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], "@autumn/website/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], @@ -8228,8 +8232,6 @@ "@mintlify/link-rot/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], - "@mintlify/mdx/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "@mintlify/prebuild/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@mintlify/prebuild/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -9044,8 +9046,6 @@ "msw/tough-cookie/tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], - "next-mdx-remote-client/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "ngrok/got/@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], diff --git a/server/package.json b/server/package.json index 94e32e35b..d694e4511 100644 --- a/server/package.json +++ b/server/package.json @@ -140,8 +140,8 @@ "posthog-node": "^5.20.0", "puppeteer-core": "^24.14.0", "qs": "^6.14.0", - "react": "19.2.3", - "react-dom": "19.2.3", + "react": "18.3.1", + "react-dom": "18.3.1", "resend": "4.8.0", "semver": "^7.7.2", "stripe": "catalog:", @@ -159,8 +159,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", + "@types/react": "18.3.28", + "@types/react-dom": "18.3.7", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", From 6d3fd027b7a0c3275e1fd51b1938aed628d39059 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 11:05:53 +0100 Subject: [PATCH 23/41] latest --- apps/leaf/package.json | 3 +- apps/leaf/tests/harness/claudeCode.smoke.ts | 4 +- bun.lock | 1390 +++++++++---------- packages/ai-sdk/package.json | 1 + server/package.json | 4 +- 5 files changed, 645 insertions(+), 757 deletions(-) diff --git a/apps/leaf/package.json b/apps/leaf/package.json index 8877d845a..f97fcc44b 100644 --- a/apps/leaf/package.json +++ b/apps/leaf/package.json @@ -34,8 +34,7 @@ "e2b": "^2.8.4", "hono": "4.12.7", "postgres": "catalog:", - "zod": "^3.25.23", - "zod-v4": "npm:zod@^4.4.3" + "zod": "^3.25.23" }, "devDependencies": { "@ngrok/ngrok": "^1.7.0", diff --git a/apps/leaf/tests/harness/claudeCode.smoke.ts b/apps/leaf/tests/harness/claudeCode.smoke.ts index 7783a4d4a..e6c47bc65 100644 --- a/apps/leaf/tests/harness/claudeCode.smoke.ts +++ b/apps/leaf/tests/harness/claudeCode.smoke.ts @@ -4,8 +4,8 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk"; -// SDK tool() needs zod v4 internals; leaf is on zod 3, so use the aliased package. -import { z } from "zod-v4"; +// SDK tool() needs v4 schemas from the same zod copy its peer resolves to (leaf's zod 3.25.x). +import { z } from "zod/v4"; import { createClaudeCodeHarness } from "../../src/harness/index.js"; import type { HarnessEvent, diff --git a/bun.lock b/bun.lock index cac27ce71..a687953df 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,6 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "autumn", @@ -111,7 +111,6 @@ "hono": "4.12.7", "postgres": "catalog:", "zod": "^3.25.23", - "zod-v4": "npm:zod@^4.4.3", }, "devDependencies": { "@ngrok/ngrok": "^1.7.0", @@ -190,6 +189,7 @@ }, "devDependencies": { "@types/node": "^24.9.1", + "@typescript/native-preview": "catalog:", "tsup": "^8.4.0", "typescript": "^5.8.3", }, @@ -547,8 +547,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "18.3.28", - "@types/react-dom": "18.3.7", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", @@ -720,9 +720,9 @@ "packages": { "@a2a-js/sdk": ["@a2a-js/sdk@0.3.13", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "@bufbuild/protobuf": "^2.10.2", "@grpc/grpc-js": "^1.11.0", "express": "^4.21.2 || ^5.1.0" }, "optionalPeers": ["@bufbuild/protobuf", "@grpc/grpc-js", "express"] }, "sha512-BZr0f9JVNQs3GKOM9xINWCh6OKIJWZFPyqqVqTym5mxO2Eemc6I/0zL7zWnljHzGdaf5aZQyQN5xa6PSH62q+A=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.81", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.116", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-k8P17w7Eho5Y4l3tZrYxqQdffkI4xwtl8GCxkZs+JdMWZhyrLLlozqWkKLaWrCSlEYQOeIhEnQLhqQgYYU86Rw=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.125", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tocl7cUDoTpmhZqeW8XVKMMznZQwwQAEunF0VyNKmf64qt8NbMIAEiet/vRMzh7Jr9WcFeb6EZjmhLTP4Qx2Og=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], @@ -736,9 +736,7 @@ "@ai-sdk/provider-v6": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@ai-sdk/react": ["@ai-sdk/react@3.0.187", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.185", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-TJBhR18F7BOLj/mBLYoZNZVkQgDc7DBVz2ZyQEecpKnO+EAhdx3QA2q8BnEVqwNlDfOKOa6dr7ka4hU0wy/wDw=="], - - "@ai-sdk/ui-utils-v5": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], + "@ai-sdk/react": ["@ai-sdk/react@3.0.199", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.197", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-0QmG6nd1iDTTWpWbQbE5qgSpEm0XkBvrOn1L1rSzBhG5+7BasckcjTF3CQMwUxdvozMMYRNOGXLQODs/1+a3NQ=="], "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], @@ -828,83 +826,75 @@ "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - "@aws-sdk/client-cloudwatch": ["@aws-sdk/client-cloudwatch@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/middleware-compression": "^4.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lklRpzp5ZVXcVmGmZ9LUZN2jnJm4HZoB4WVC8yDMWolf5oyQZoC68+on2+F9gwNlux+7y6SD5FhXWhOQUTDqEg=="], + "@aws-sdk/checksums": ["@aws-sdk/checksums@3.1000.2", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-PIha+kauTbp6IRmOpYktPTrlfrrSqDVixvhO/EUOFOf62DPX81CaJoHJreuA1m9HYpSKyXf99BKjU1dvJPeUfw=="], - "@aws-sdk/client-cloudwatch-logs": ["@aws-sdk/client-cloudwatch-logs@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0ck+MgIMIfM+VY2LJTo3Nwwxe2skjmmCoFmuR6k6ZeLCi3xp6oKKJtJbl3UJN/vrWmEmZp8JhtBR9w09TV5O5g=="], + "@aws-sdk/client-cloudwatch": ["@aws-sdk/client-cloudwatch@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/middleware-compression": "^4.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-A/PG9D709oSFwutfP5CyATQJzc2IyhicMirByvkLkrj8ezSunB0/+ZRJdPibONolE0P2+kneQqHttanspULpAw=="], - "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-eJUSxEwhz9fKmjhRgOvFTuJ+SjCu15SpgYo3aSJ6rjs2IaWi2F8NcvOuejHb0a01a/J71/9bdjUDrQLH2kUmkg=="], + "@aws-sdk/client-cloudwatch-logs": ["@aws-sdk/client-cloudwatch-logs@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-oVYWCAjcK6hq8atqfouNSu0jygOdhMcrH2sZIxNXICLzVD5jeOHD4pgr+W08uwN5Dfzq9iEKR7VFCV6l05Dj1A=="], - "@aws-sdk/client-ec2": ["@aws-sdk/client-ec2@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/middleware-sdk-ec2": "^3.972.25", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Ajd2vwz889wEaSC37o4NndqQA6hd3nAONxu6evtkWPVM7pJyhulgcRocmdBef7N0erwTUfaPD1GD1xgNrBiG0w=="], + "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-fLwNblkowkRyuxdVehlHVOnr/7bBf8Y1UGYdhhpuMPHOQL2QTY6kLcQ+EV1BhTQG1p4ATwaONNJsIk44hxEGMA=="], - "@aws-sdk/client-ecs": ["@aws-sdk/client-ecs@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-HABKZRDWmyZTgRxmM2vMaTcaNyJg4PadF/Turc/t/4FVNxjiO2qjNEa13O+P9DM/vEYSgpMl5HOhWEw2acs0ww=="], + "@aws-sdk/client-ec2": ["@aws-sdk/client-ec2@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/middleware-sdk-ec2": "^3.972.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-RVVqRR6SBQwTHnttHXj/QbUwagA1a86tqJhyDik9/REviSfu7XGJ61RlXmDfyeU76bhv3NJ4mW2EZrhWO2/0hQ=="], - "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p6AJofTz9ehvKf02DhW4rJd8ivqYqz3VCnt3k7cyTFoCzveaAY0XZ4XgSHrE1BEdqClQxlHz1c8NLxgUdh7FMw=="], + "@aws-sdk/client-ecs": ["@aws-sdk/client-ecs@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-SVVmsVKJpy9cGoKAr6etP2i/MkymCgesZRlaHra3M2dNxTEldYRxQ5sqC3H5qj6qhF8XtWELl3G3loTAN7eJXA=="], - "@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ryEYNVdilyWkKsOs/7Xy/l7+qjtSz4sll8NpcWD6AtONxjG/5OMaAhxxDkQb4iBoNMKnISxsARzQAp/Wa8pXIg=="], + "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5BPya0CvFQvgI4Ru2rzoQ/GQLZ8LevVNXHPNT/q0otms1Yg4rRi2G4/yoN6zmsU3Og4CHSOSuS287zwFVNTlQA=="], - "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1048.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/middleware-bucket-endpoint": "^3.972.13", "@aws-sdk/middleware-expect-continue": "^3.972.12", "@aws-sdk/middleware-flexible-checksums": "^3.974.19", "@aws-sdk/middleware-location-constraint": "^3.972.10", "@aws-sdk/middleware-sdk-s3": "^3.972.40", "@aws-sdk/middleware-ssec": "^3.972.10", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-SrJn5FteqqtcDBgQIvqLKk3Qn/2vSsi5XR03I53EDDR4CbCdLysVSNgUnjVncEECMua9Pz+nxO0/lEx3TP+6mA=="], + "@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-xn2c+C2/le5Iya243PVsH+s4yhs0Oo5wK+CopVJfhZ2uH6WNEWre+wQ6D/q8FkFZaaPP/cwejVBVUEaMsD98Kw=="], - "@aws-sdk/client-scheduler": ["@aws-sdk/client-scheduler@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-9p9bHxb5E9pRm4gPIUVS9Zz248KzIr4AWHkEMqxbfG3M1shru5a7hH98RJxNvangz5sb81EhLEtULB6/9XmKGw=="], + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1063.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/middleware-flexible-checksums": "^3.974.27", "@aws-sdk/middleware-sdk-s3": "^3.972.48", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ETn+vvmZVK1MmOZwVBXmWANpmD5iTbzojIqyEIoZ86qo+8oWy35S8QyQNE/ZDI+WHgMU1dS+VSYbpRl1QkEySg=="], - "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/middleware-sdk-sqs": "^3.972.24", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-GGCSdU3rFc77KxgL7LmJeY169MEjsQmbLwHpnWpopavA1nxz2htJvLSPghuyGhTVpgqnp4HqwWiczlkjV3UADw=="], + "@aws-sdk/client-scheduler": ["@aws-sdk/client-scheduler@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QGepeFeLEkji0WNYJ5AUFd+9f/HIm0ZURjaUGCtACpyfj7Pdzbm1vsIb6MuLlQVHIV+X/+ptFlzX2blFuGuQqg=="], - "@aws-sdk/client-ssm": ["@aws-sdk/client-ssm@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Q/9t+BeHQnbsYCmvNwh7FDs5JQATuqoSRPdhYZ5hij/mG431VTDWVxqwD4Gze+4AMDVPcOTi9Jv0fXYc1BJVRg=="], + "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/middleware-sdk-sqs": "^3.972.29", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-2Oi4FpC1jJ10gpBDfXw0sMh6GvkYmnkaTo28AnjRUrCRe5JkvEWvDwKKksQM67UtwO5AoomKAqFKVzID1zW/fQ=="], + + "@aws-sdk/client-ssm": ["@aws-sdk/client-ssm@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-qLXNofwgCB7/3mhWR9pz/bnNgyvSxjp38SvhTazESrt3mLtX9+kMg8UOKTZol0mvZg209UVIS82xY1JhJDtGpA=="], "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.598.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw=="], "@aws-sdk/client-sso-oidc": ["@aws-sdk/client-sso-oidc@3.600.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw=="], - "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-CE/RhHaIoLmmlKva/rmNB0A0/WWta+GozzTGl5kNc8fAnlR5iA0ygz8zw6VQRwFWz2b8T56qA8lapKcslztHfA=="], + "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.1063.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5SZhiVKuufk/dUcfNr6hZymQSTSnh12paXRLq+YS8OmsozpChNG0wHaKH/hXA/mdGwLxgdudqHAHxygbCiwzqQ=="], - "@aws-sdk/core": ["@aws-sdk/core@3.974.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.24", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-qrqgioqYFjwR6LatVNS1L2Vk++EwRIxqSQXPKNv5Ofux2D8UNgqMQ1znnMyEImXquVPTtbf71fc128pvmU6y9A=="], + "@aws-sdk/core": ["@aws-sdk/core@3.974.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@aws-sdk/xml-builder": "^3.972.28", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g=="], - "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-fVfUCL/Xh2zINYMPZvj+iBn6XWouQf0DAnjaWCI9MkmqXzL2Iy5FoQB8O7syFe6gN6AH1ecDDU58T51Ou0kFkA=="], + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.42", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-94W7f8xVsdLEjv3TY8R+beoFL0pIRduiGZdqMfIVMvQfn6q9IA3SgE2mIQluu3VCULn8PopB/gx7Fns8ETn/1Q=="], - "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.35", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-mMQsBJv40oi5QdqRj4Xbc9jTlWMxqWfs5zWu+RhbOuF5F0AxxWXT70hm0abOmLbF2M/Tkuygs01H4eWIQMfoMw=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-m3WjZEgPtioMhPmwqUt+DhlTJ2i9ufR6DhfkyXojb9puEvfR+ur2U5shavu5/Cc9WHHsDCvALi6UFHgcqjhQ5w=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.40", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-D78L/m2Dr6cJnnSvWoAudPhQmCwmJ7j6APXsPYmFpPaKfQTfCSu0rdm8j14Np+VmXF9z8Aj8HE3xFpsrwtfgeg=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/credential-provider-env": "^3.972.38", "@aws-sdk/credential-provider-http": "^3.972.40", "@aws-sdk/credential-provider-login": "^3.972.42", "@aws-sdk/credential-provider-process": "^3.972.38", "@aws-sdk/credential-provider-sso": "^3.972.42", "@aws-sdk/credential-provider-web-identity": "^3.972.42", "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Mu5ESvFXeinafVM8jTIvRqcvK2Ehj4kz3auT39yUcHwu1Vfxo6xRlmUafdKLW4tusjAJukQwK09sCSMgOm7OKg=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-O6WkZga3kf0yqyJYd1dbeJqVhEgJx/x1UaLgtbR+XuL/YP+K5y6QTxQKL7ka9z3jnQASESKGAPnRyt4D5hQrxA=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.52", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-ini": "^3.972.50", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.43", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.38", "@aws-sdk/credential-provider-http": "^3.972.40", "@aws-sdk/credential-provider-ini": "^3.972.42", "@aws-sdk/credential-provider-process": "^3.972.38", "@aws-sdk/credential-provider-sso": "^3.972.42", "@aws-sdk/credential-provider-web-identity": "^3.972.42", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-D/DJmbrWRP5BXEO3FH+ar4el+2n6OlGofiud7dQun2jES+AQEJjczenp1jBb4MBN7CpGpS8nsWGQLtuzc9tQbA=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-EnbYVajGgbkb24s0K1eo4VNAPV5mHIET7LSvirTaFCwkfrfaOJxtSE+wY/tJdKDS21cEYkZs2ruCaAm+W4iblg=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/token-providers": "3.1063.0", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/token-providers": "3.1049.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-RVV/9NbFwI8ZHEH5dn39lGyFmSbSVj1+orZdr6QsOe1mW9DCglmlen0cFaNZmCcqkqc7erNRHNBduxbeZuHAnw=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.42", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-/67fXX0ddllD4u2Nujc5PvT4byHgpMUfz6+RxIKi/0nFIckeorm7JvXgzBuDyVKw0s58EbofmETDWUf9vTEuHQ=="], + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1063.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1063.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-cognito-identity": "^3.972.42", "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-ini": "^3.972.50", "@aws-sdk/credential-provider-login": "^3.972.49", "@aws-sdk/credential-provider-node": "^3.972.52", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ApW861WX8h7wKDKRNj7Dyne7awtq/PHrJVSdr3NsE/rmuFUxSha6BFJJ1H0S1MD7hCqZjYqz2VPPmCXo3IKC9A=="], - "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1048.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.34", "@aws-sdk/credential-provider-env": "^3.972.37", "@aws-sdk/credential-provider-http": "^3.972.39", "@aws-sdk/credential-provider-ini": "^3.972.41", "@aws-sdk/credential-provider-login": "^3.972.41", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/credential-provider-process": "^3.972.37", "@aws-sdk/credential-provider-sso": "^3.972.41", "@aws-sdk/credential-provider-web-identity": "^3.972.41", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/credential-provider-imds": "^4.3.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-qradm+eSLJTWQLd/TOxlETL1rMQ/ozvr2iU7wga5hqoox/FiXV9VLtomv3Cqwa6GdpYGWI8ebfSu6mS18I1PyQ=="], - - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Aaj0d+xbo1jJquBWJP0/9V/XZRYukO3LWIRp3dOLHmoFrYKb4YZ0aLefgVHfGcNOVBS2ZTq7L/n5JcrE7DaC+Q=="], - - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dA5pKTom/Ls9mgeyeaRBNQrRIVOLVjv4AmKOB0/e4yaiXEUy0gSz2d3liP8JHtYoCAEWySU1jWnyzwLOREN+4g=="], - - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.20", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.12", "@aws-sdk/crc64-nvme": "^3.972.8", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-NdnMVQCR1YjIcqFAiNLdBiOwr2DyQDB2IiXQrBhzolKOv32ae4d4Ll7IzLMi04eMHiq/o/Y/GjFuVjF9HuG0QA=="], + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.27", "", { "dependencies": { "@aws-sdk/checksums": "^3.1000.2", "tslib": "^2.6.2" } }, "sha512-bZqezPLdllFC4VAeV/f+EIc/hz56ab3TD/+4zNCgOgmG5ZHAE5dMHrX1gtTwdcQXbPr3KR7x3zTC3zuCTE6+ng=="], "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/protocol-http": "^4.0.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-WiaG059YBQwQraNejLIi0gMNkX7dfPZ8hDIhvMr5aVPRbaHH8AYF3iNSsXYCHvA2Cfa1O9haYXsuMF9flXnCmA=="], - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-bxBjf/VYiu3zfu8SYM2S9dQQc3tz5uBAOcPz/Bt8DyyK3GgOpjhschH/2XuUErsoUO1gDJqZSdGOmuHGZQn00Q=="], "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/protocol-http": "^4.0.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-vjT9BeFY9FeN0f8hm2l6F53tI0N5bUq6RcDkQXKNabXBnQxKptJRad6oP2X5y3FoVfBLOuDkQgiC2940GIPxtQ=="], - "@aws-sdk/middleware-sdk-ec2": ["@aws-sdk/middleware-sdk-ec2@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-sHc/vgigKtDZa1D19Go9jQT/IjMACinFnwg7I+vmhdie3rPFjB5VF57T9cDgcG0TAQnhBTkXSm1w4+ZlKR0bEA=="], + "@aws-sdk/middleware-sdk-ec2": ["@aws-sdk/middleware-sdk-ec2@3.972.32", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-pYIQfl8jN0HVuz6iDisD4wxA59MgLLp2mAs6ziPPA4OMGXkQ5eDQgZRU1K7d5b6tFcdTmQTYD+pcqqUpeG5Plw=="], - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.41", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-M4T2I2WPuH5WQpU8Tsp+u2bcO29zGRkU14ATzuqb9I4xh8tzsLqtp4hzaJM5aO2dhMZnHDzyQwSFVgc3XbnoGg=="], + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.48", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-MRTqx8wD/T3REt6LTT3/yN8rrp6+xIHrbUekkDYJTYWVch70mwtdJBovR4qKJz1jIPlbN+9R/Sn6R04BfsglzA=="], - "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.24", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-ej3vwWFzTP2B0FxlU2JelMaxplEncflFv2ARsoMQ9TXI/yfmsPEZw8zkOdsQp3rMEJ/vN7iKTYDYIcpeMmDRoQ=="], - - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw=="], + "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.29", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-huMx6RhC/tF9K82GZpnox8vK26Pt+6QASMrJiyup99ffr+HBT3asD4soa9BuD3WeLd64XYdOeuUjIjqKgVu5gA=="], "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@smithy/protocol-http": "^4.0.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-4tjESlHG5B5MdjUaLK7tQs/miUtHbb6deauQx8ryqSBYOhfHVgb1ZnzvQR0bTrhpqUg0WlybSkDaZAICf9xctg=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.10", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.12", "@aws-sdk/signature-v4-multi-region": "^3.996.27", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-FtQ/Bt327peZJuyo4WZSOLVUTw9ujRxntepiC7L65FxA2P82Xlq0g14T22BuqBUeMjDoxa9nvwiMHjLIfP3eUg=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.17", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig=="], "@aws-sdk/protocol-http": ["@aws-sdk/protocol-http@3.374.0", "", { "dependencies": { "@smithy/protocol-http": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9WpRUbINdGroV3HiZZIBoJvL2ndoWk39OfwxWs2otxByppJZNN14bg/lvCx5e8ggHUti7IBk5rb0nqQZ4m05pg=="], @@ -912,15 +902,15 @@ "@aws-sdk/signature-v4": ["@aws-sdk/signature-v4@3.374.0", "", { "dependencies": { "@smithy/signature-v4": "^1.0.1", "tslib": "^2.5.0" } }, "sha512-2xLJvSdzcZZAg0lsDLUAuSQuihzK0dcxIK7WmfuJeF7DGKJFmp9czQmz5f3qiDz6IDQzvgK1M9vtJSVCslJbyQ=="], - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/signature-v4": "^5.4.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg=="], + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.32", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1049.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.12", "@aws-sdk/nested-clients": "^3.997.10", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-r7+d0lQMTHKypkmaF5jRTBYLYHCUHzt3gaVoN9SidLhQeWhCmHk3AKrboDTpPF5b7Pt7vKu3+oeMjznM2Eu1ow=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1063.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + "@aws-sdk/types": ["@aws-sdk/types@3.973.11", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg=="], "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/types": "^3.1.0", "@smithy/util-endpoints": "^2.0.2", "tslib": "^2.6.2" } }, "sha512-Qo9UoiVVZxcOEdiOMZg3xb1mzkTxrhd4qSlg5QQrfWPJVx/QOg+Iy0NtGxPtHtVZNHZxohYwDwV/tfsnDSE2gQ=="], - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.6", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw=="], "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/types": "^3.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-36Sxo6F+ykElaL1mWzWjlg+1epMpSe8obwhCN1yGE7Js9ywy5U6k6l+A3q3YM9YRbm740sNxncbwLklMvuhTKw=="], @@ -928,7 +918,7 @@ "@aws-sdk/util-utf8-browser": ["@aws-sdk/util-utf8-browser@3.259.0", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.24", "", { "dependencies": { "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.28", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], @@ -942,7 +932,7 @@ "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], - "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="], + "@azure/core-client": ["@azure/core-client@1.10.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ=="], "@azure/core-http-compat": ["@azure/core-http-compat@2.4.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA=="], @@ -950,7 +940,7 @@ "@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="], - "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.24.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg=="], "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], @@ -962,89 +952,89 @@ "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], - "@azure/msal-browser": ["@azure/msal-browser@5.10.1", "", { "dependencies": { "@azure/msal-common": "16.6.1" } }, "sha512-hTbvOi9Ko2Jvn+G/fSmjzHf9WbNcf/o3epMtbeGx/pMwMrVAbi6OgCJVeCfsAb8IybSRpaCSc4EDRlYAhgngUQ=="], + "@azure/msal-browser": ["@azure/msal-browser@5.12.0", "", { "dependencies": { "@azure/msal-common": "16.7.0" } }, "sha512-eNf2aqx1C6I0yT1GEu5ukblFrmaBXGfe1bivpmlfqvK7giPZvoXLa404C8EfeHVsy6EIryfQuPRzuW1fPxWlHg=="], - "@azure/msal-common": ["@azure/msal-common@16.6.1", "", {}, "sha512-VxKdEtUwDuLD0F1hOQP7kye0YadZxFJfv37Em440geEf/w9uggKnHpRrqwZJOdxmPUOdhZ9kyRtKuAJW8wUcRg=="], + "@azure/msal-common": ["@azure/msal-common@16.7.0", "", {}, "sha512-Jb8Y7pX6KM42SIT7KWP6YbY3+vLbwB5b5m+tpiiOzMU1QeyelQzs9lO8jv1e7/Uj9r7tg7VjPvW4T0KB1jF3UQ=="], - "@azure/msal-node": ["@azure/msal-node@5.2.1", "", { "dependencies": { "@azure/msal-common": "16.6.1", "jsonwebtoken": "^9.0.0" } }, "sha512-tmQiQ2HvtzaeLqYGy3BemiPOSGPY4wCy1IW5zDWITKSs/s35WEd7Zij/hCxvUdAOzj6U3qnyaGbYXY91ortFEQ=="], + "@azure/msal-node": ["@azure/msal-node@5.2.3", "", { "dependencies": { "@azure/msal-common": "16.7.0", "jsonwebtoken": "^9.0.0" } }, "sha512-YYX4TchEVddVBiybKvKhV9QO/q22jgewP+BVxKG7Uh115voPcviGlypbKERDsqQdAiSTJrwi80gcWFjYKdo8+Q=="], - "@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="], + "@azure/storage-blob": ["@azure/storage-blob@12.32.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.4.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-80LzSNnFQye2LCCBFghAJS6jJQJ7N4bfgZ6qDMgVGRtugZ7TLDKQZ2hczMigmZH3jAcMRdma/IygsC5+0gT7Tw=="], - "@azure/storage-common": ["@azure/storage-common@12.3.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ=="], + "@azure/storage-common": ["@azure/storage-common@12.4.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-kNhJKMxQb374KOVt63CZnGIpDcrKNzJeyANLJymxE9mCJSdRGzb+Iv9oSIiCj6tNMLypr530b9ObOiA/5OvwOg=="], - "@azure/storage-queue": ["@azure/storage-queue@12.29.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.0.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.3", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.2.0", "tslib": "^2.8.1" } }, "sha512-p02H+TbPQWSI/SQ4CG+luoDvpenM+4837NARmOE4oPNOR5vAq7qRyeX72ffyYL2YLnkcyxETh28/bp/TiVIM+g=="], + "@azure/storage-queue": ["@azure/storage-queue@12.30.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.0.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.3", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.4.0", "tslib": "^2.8.1" } }, "sha512-204lc/W0nnZy0/JXGXAVsQG9LmRWGVrh28uxkWd6lV5/G/vHlFZOLxiTS5DUdLcnZ+OHhsClRJnMWgHX2X0vdA=="], - "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@babel/compat-data": ["@babel/compat-data@7.29.3", "", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], - "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], - "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], - "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], - "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA=="], + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], - "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], - "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], - "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], - "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], - "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], - "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], - "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], - "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="], - "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="], - "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="], + "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q=="], - "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="], + "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/types": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A=="], - "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="], + "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.29.7", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g=="], - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], - "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="], + "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA=="], - "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="], - "@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="], + "@babel/preset-react": ["@babel/preset-react@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-transform-react-display-name": "^7.29.7", "@babel/plugin-transform-react-jsx": "^7.29.7", "@babel/plugin-transform-react-jsx-development": "^7.29.7", "@babel/plugin-transform-react-pure-annotations": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA=="], - "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + "@babel/preset-typescript": ["@babel/preset-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ=="], - "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], - "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="], - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "@base-ui/react": ["@base-ui/react@1.5.0", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.2.9", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A=="], @@ -1096,6 +1086,20 @@ "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + "@braintrust/bt-darwin-arm64": ["@braintrust/bt-darwin-arm64@0.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jhL/X24ss4e4qMMdlXtxO8rA957Z77wJA59XXFHqpuaaVCd4pXE5JPUQBkms9dHcs4efJhY3Lx9zNQqoaeCqWg=="], + + "@braintrust/bt-darwin-x64": ["@braintrust/bt-darwin-x64@0.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-f4l25gVUpCJ99mFS9y+zmnYOcevtI7KLLvlLF/xOil2YCIx/KCM6AqS96jj6CJW74B7hYhd74dLnFKMFFL429w=="], + + "@braintrust/bt-linux-arm64": ["@braintrust/bt-linux-arm64@0.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-01GWsP/p17I3yy0kxkp+Yt8w5L28j3nFL+7iLCkQWtDdfCGkuRsnrIbrY8dbnqGo/wvgz7uPXBkCXoIuNsfoxw=="], + + "@braintrust/bt-linux-x64": ["@braintrust/bt-linux-x64@0.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-E/XwRuhPrZxD+IZgSbPCuj4gywBrim/g+tU0MjWxFohpMMkJJW2CxMCIO+6RVk8gTxlVh/ajCIjv2/Ph1Yugeg=="], + + "@braintrust/bt-linux-x64-musl": ["@braintrust/bt-linux-x64-musl@0.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-QLqlFsF6HKON5Vc0c8JfpQ4vrl4KjmInGF1Vsqy+ecVkgXVk8pwCVibb8Ea/udVpBQqctAaNMn7ZsmvWR2vO8Q=="], + + "@braintrust/bt-win32-arm64": ["@braintrust/bt-win32-arm64@0.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-0LSVXZ/tE79VVcpRjaTE+iTMQR4EKNC6tteAS01uKT34eID7OqJ7xJijZOthe11kGqMcX8nnNbdDrWHqLczDow=="], + + "@braintrust/bt-win32-x64": ["@braintrust/bt-win32-x64@0.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-JPo3xffJvW0OKowqpbh+XtlafpoZq8VXzhOcW2yQmHcN56Me2u9plPiXi4gzrpgz2cnFx6/EiJ1bTa3F25F0RA=="], + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.12.0", "", {}, "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA=="], "@bugsnag/cuid": ["@bugsnag/cuid@3.2.2", "", {}, "sha512-7onuYLTMqMmHE9BBPG0YER4nFsU1rB+me1/YIeMusqcLbVbKKuG9u9+BDVDpje5e0llkkrVNOKYwmzM9DRIo7A=="], @@ -1104,11 +1108,11 @@ "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="], - "@chat-adapter/shared": ["@chat-adapter/shared@4.29.0", "", { "dependencies": { "chat": "4.29.0" } }, "sha512-ARqTDoHJHKN9rpytbFPJbNmqqx3fOg5xwsTZdlingQPAssOSeHDBdqrFJkgDhyCRGbmDtG09cuS0FkVzeoh2qg=="], + "@chat-adapter/shared": ["@chat-adapter/shared@4.30.0", "", { "dependencies": { "chat": "4.30.0" } }, "sha512-IuYtbn/p1FBXvp7JYGEMLCt07GHOMlyjx7OlZXPJwLTravcyJuP7Q6N31r6c1yubMhM8PLb8eT8l/YnjwYjs9Q=="], - "@chat-adapter/slack": ["@chat-adapter/slack@4.29.0", "", { "dependencies": { "@chat-adapter/shared": "4.29.0", "@slack/socket-mode": "^2.0.5", "@slack/web-api": "^7.14.0", "chat": "4.29.0" } }, "sha512-s2DXAwkTpmiIKSATXgrO879s1pqFwS70Y0JPd+TRGRzDeh6nfqt5dnKt5Bug0P1zwkB6DoPurhnYS9nqhSmD/w=="], + "@chat-adapter/slack": ["@chat-adapter/slack@4.30.0", "", { "dependencies": { "@chat-adapter/shared": "4.30.0", "@slack/socket-mode": "^2.0.5", "@slack/web-api": "^7.14.0", "chat": "4.30.0" } }, "sha512-ZB+G/JBKmaXzvl+DuUQPBb/gCwXP3fOtUK5Cyj6wLbbeeDYzi3NQPvpvieVum/GI4iuR8OUVcNAnVQiH6P6DOQ=="], - "@chat-adapter/state-pg": ["@chat-adapter/state-pg@4.29.0", "", { "dependencies": { "chat": "4.29.0", "pg": "^8.20.0" } }, "sha512-ocKWDly9my4kftWdVdfE/b9UczRBUFSPrxEHzQAPDNfkt1PjcIKFjDjWIADYSCeUL1MqLjSnu8pM3BE96kUJGQ=="], + "@chat-adapter/state-pg": ["@chat-adapter/state-pg@4.30.0", "", { "dependencies": { "chat": "4.30.0", "pg": "^8.20.0" } }, "sha512-8qymxX34Fg7B0PJCoYi60Bck68Gnd4cW4U8hgvJBc5ZHm8K4XT4srV8G3AeDT7rVSmrW5diidcmROo8Z1ke6iw=="], "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="], @@ -1124,9 +1128,9 @@ "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], - "@clickhouse/client": ["@clickhouse/client@1.18.5", "", { "dependencies": { "@clickhouse/client-common": "1.18.5" } }, "sha512-4FfoyMkFWhsdNMuXsoEL6l3c12svA63BBJBtDo9SrxRZ14RdmN6jLr/rF3f84BK8cFoxETZCSeKlsbk6NNYebw=="], + "@clickhouse/client": ["@clickhouse/client@1.20.0", "", { "dependencies": { "@clickhouse/client-common": "1.20.0" } }, "sha512-LfHZ9bZZhc7KrNFVa9v73JMqwsP+m/5SgwdKMmxze4Urcw6pE0F7RNog3Lzx3GKRnJr3Hd15uDlIbqaDa9BbgA=="], - "@clickhouse/client-common": ["@clickhouse/client-common@1.18.5", "", {}, "sha512-g9LwcS1dvkatKDsIjT1PwUHldsiYzwdKAB0nXfd9APLd+t4PrNJa+my+dXcqJdmcWyhWjKLP/2/ztBwgxp+sbQ=="], + "@clickhouse/client-common": ["@clickhouse/client-common@1.20.0", "", {}, "sha512-s0oDSwxQyJO/Xwne6sNE7xTAlms72Hq2AHzHAB9oSOBfiaXTzQOyHQrhufJ9ldPJTwr4L47/RxG1i6I0I8Xy9A=="], "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], @@ -1138,7 +1142,7 @@ "@datadog/datadog-api-client": ["@datadog/datadog-api-client@1.58.0", "", { "dependencies": { "@types/buffer-from": "^1.1.0", "@types/node": "*", "@types/pako": "^1.0.3", "buffer-from": "^1.1.2", "cross-fetch": "^3.1.5", "form-data": "^4.0.4", "loglevel": "^1.8.1", "pako": "^2.0.4" } }, "sha512-aDCMu+qEXjr8PHkT8XvY7FTzVNi2z7yVJy39I4s0lVtb+sdth4ZppMC1aCDwGJQMHK4J0aect+xKScuHy/Xilg=="], - "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + "@date-fns/tz": ["@date-fns/tz@1.5.0", "", {}, "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg=="], "@date-fns/utc": ["@date-fns/utc@2.1.1", "", {}, "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="], @@ -1162,7 +1166,7 @@ "@depot/cli-win32-x64": ["@depot/cli-win32-x64@0.0.1-cli.2.80.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9CRcc7D0/x4UrBkDuc35WVPMQG5gKMD1JckGLEl6VREE0Ppdny6n+hunQ8prwVc8aqzKG134XCC2U4DUjYg18A=="], - "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.66.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-qlQFhHUjhRDybrinqLAD0MClVZDOrsq80O8eD5iSjz3Qa/4f3Jg7SQrOaSobrRyP1QaWIYLGtGpj2c7H0D8NUw=="], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-KEUw/mGu+EDRhYWRTNGHIimVCs9NvMFaIXOGrHSXoCteKLE5EsJnmPjOPpYorjXVg/0xG0fbdVw720azw1z4ag=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], @@ -1268,7 +1272,7 @@ "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], - "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="], @@ -1306,6 +1310,8 @@ "@hyperbrowser/sdk": ["@hyperbrowser/sdk@0.54.0", "", { "dependencies": { "form-data": "^4.0.1", "node-fetch": "2.7.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.1" } }, "sha512-QmbwMG6niqInlS0FfUWWzvv+DfyINHGnF8i/0oNuyyuiIdqSXnp+UG/74Ci+yz8QuyezmuKzBSp6dBXlVG0Glw=="], + "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.3", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="], @@ -1394,7 +1400,7 @@ "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], - "@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], + "@ioredis/commands": ["@ioredis/commands@1.10.0", "", {}, "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q=="], "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], @@ -1432,15 +1438,15 @@ "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], - "@langchain/core": ["@langchain/core@1.1.47", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "zod": "^3.25.76 || ^4" } }, "sha512-+fiPu6ZFnJMrZyKeM77OIVPoMPAY6OKWacnPlojHtXTbMMzb2cEOKAJV0U07cDl86NHSCIYYa0i4CyKZzXbHQQ=="], + "@langchain/core": ["@langchain/core@1.1.48", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "zod": "^3.25.76 || ^4" } }, "sha512-fQU6Guyb1pwc2fEplmA8FPbKfOMAofjnyJzExevro0FxEiuGHE18Ov/ZHmT9trWCDTZRI9eW1VIc6aChxV8pAQ=="], - "@langchain/langgraph": ["@langchain/langgraph@1.3.2", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.2", "@langchain/langgraph-sdk": "~1.9.4", "@langchain/protocol": "^0.0.15", "@standard-schema/spec": "1.1.0", "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.44", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-SL7Ktsr681R7da+1b2MVOWEbaCoFJOXEJPTGOjg4JIG4C7quWbTYC8DzxhcCxte6D/8cGp0rYDBnbKLXEpNqlA=="], + "@langchain/langgraph": ["@langchain/langgraph@1.3.6", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.4", "@langchain/langgraph-sdk": "~1.9.17", "@langchain/protocol": "^0.0.16", "@standard-schema/spec": "1.1.0", "uuid": "^14.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.48", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-OlpIhaMYs2IlN5hCGUMF9B/jgVacLK5nYbwri1AQatB+CcFIk1xVi9jHcD+fkAtn+7ExFbwiOAAdhuGeWQA9BA=="], - "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.0.2", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.44" } }, "sha512-F4E5Tr0nt8FGghgdscJtHw+ABzChOHeI80R7Y1pjIHdiJom6c2ieo76vL+FWiny80JmoGqhrVAEIWrw0cXKPxg=="], + "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.0.4", "", { "dependencies": { "uuid": "^14.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.44" } }, "sha512-1y5MgZ0gXXrtmoy56e3kaBChI3GwFPIKl27xkrHwN+VE/3iUsyr9gO3Jtp7kdKAe6diZGbcas5bdC/r0yUwTZA=="], - "@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@1.9.4", "", { "dependencies": { "@langchain/protocol": "^0.0.15", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1", "uuid": "^13.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.44", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-hhASJGKa2MDJDtDkuIFdWGysMTog/HkYe0r6B6Gn1XqsURWnF7FIFl9diITAPOv1tB8YpyjnbpsBj/NkT5d+jQ=="], + "@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@1.9.18", "", { "dependencies": { "@langchain/protocol": "^0.0.16", "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1", "uuid": "^14.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.48", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-EQLlop/GLlm52Rii06oZDv1bPvrWARnDuzSY6in4d1gnZX2KSQvxajeZH8GYfMjapDVhbo324DRYJgL9euo2fg=="], - "@langchain/protocol": ["@langchain/protocol@0.0.15", "", {}, "sha512-MllvbpMjqHevUm+v94M422mH7XKN+wGCvJRBVROTWBotEDOATYB4Ktk2UheYP859y9o2LlhtPek5t1T9eyfAbQ=="], + "@langchain/protocol": ["@langchain/protocol@0.0.16", "", {}, "sha512-ws+J7MaHyhO5dG7f0vdyHQiUn9hoCnki0f3crJPa4MCTGzcRC39jYSCghyrGtBPYQnZbUQiGyRVpW3z3M8IpJg=="], "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], @@ -1452,13 +1458,13 @@ "@mastra/braintrust": ["@mastra/braintrust@1.1.3", "", { "dependencies": { "@mastra/observability": "1.14.1", "braintrust": "^2.2.2" }, "peerDependencies": { "@mastra/core": ">=1.16.0-0 <2.0.0-0", "zod": "^3.25.34 || ^4.0.0" } }, "sha512-5NxE+7gFPXR3p+K947Dri0Ta7gDl53wsYCGj6KBfXsuEIaLlxwCu+vsWdAiuXDoyTj22bVQkr2jX92CfqitI8g=="], - "@mastra/core": ["@mastra/core@1.36.0", "", { "dependencies": { "@a2a-js/sdk": "~0.3.13", "@ai-sdk/provider-utils-v5": "npm:@ai-sdk/provider-utils@3.0.25", "@ai-sdk/provider-utils-v6": "npm:@ai-sdk/provider-utils@4.0.27", "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.3", "@ai-sdk/provider-v6": "npm:@ai-sdk/provider@3.0.10", "@ai-sdk/ui-utils-v5": "npm:@ai-sdk/ui-utils@1.2.11", "@isaacs/ttlcache": "^2.1.4", "@lukeed/uuid": "^2.0.1", "@mastra/schema-compat": "1.2.10", "@modelcontextprotocol/sdk": "^1.29.0", "@sindresorhus/slugify": "^2.2.1", "@standard-schema/spec": "^1.1.0", "ajv": "^8.18.0", "chat": "^4.29.0", "croner": "^10.0.1", "dotenv": "^17.3.1", "execa": "^9.6.1", "fastq": "^1.19.1", "gray-matter": "^4.0.3", "hono": "^4.12.8", "hono-openapi": "^1.3.0", "ignore": "^7.0.5", "json-schema": "^0.4.0", "lru-cache": "^11.2.7", "p-map": "^7.0.4", "p-retry": "^7.1.1", "picomatch": "^4.0.3", "posthog-node": "^5.30.6", "tokenx": "^1.3.0", "ws": "^8.20.0", "xxhash-wasm": "^1.1.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-BEhDZPQeDcJ6jQRHtpfFLuoRiWAuv9dTCIjeWbXokzwDamI3D9jkyNzpBFJwFwy2S/a4jBTu4+d61nOaP7knTQ=="], + "@mastra/core": ["@mastra/core@1.41.0", "", { "dependencies": { "@a2a-js/sdk": "~0.3.13", "@ai-sdk/provider-utils-v5": "npm:@ai-sdk/provider-utils@3.0.25", "@ai-sdk/provider-utils-v6": "npm:@ai-sdk/provider-utils@4.0.27", "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.3", "@ai-sdk/provider-v6": "npm:@ai-sdk/provider@3.0.10", "@isaacs/ttlcache": "^2.1.4", "@lukeed/uuid": "^2.0.1", "@mastra/schema-compat": "1.2.11", "@modelcontextprotocol/sdk": "^1.29.0", "@sindresorhus/slugify": "^2.2.1", "@standard-schema/spec": "^1.1.0", "ajv": "^8.18.0", "chat": "^4.29.0", "croner": "^10.0.1", "dotenv": "^17.3.1", "execa": "^9.6.1", "fastq": "^1.19.1", "gray-matter": "^4.0.3", "ignore": "^7.0.5", "json-schema": "^0.4.0", "lru-cache": "^11.2.7", "p-map": "^7.0.4", "p-retry": "^7.1.1", "picomatch": "^4.0.3", "posthog-node": "^5.30.6", "tokenx": "^1.3.0", "ws": "^8.20.0", "xxhash-wasm": "^1.1.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-A3gV8kdyO3xf4zIFgzUYutVIOhN4595mGoz0IpMrQPuGTYSVTMwSoUhqpKhvyRXt1UYZAOCt88qqI66lscNQtQ=="], - "@mastra/mcp": ["@mastra/mcp@1.8.0", "", { "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.29.0", "exit-hook": "^5.1.0", "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "@mastra/core": ">=1.0.0-0 <2.0.0-0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-kA1YhDa/W/ZuhZ/AZpUFuKKFhINSVvLf+hDNmbCZMsM46rYjyqqgQR0xgqNaysCwv3Anta6KqDz8fp6mJ7RyuA=="], + "@mastra/mcp": ["@mastra/mcp@1.9.1", "", { "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.1", "@modelcontextprotocol/sdk": "^1.29.0", "exit-hook": "^5.1.0", "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "@mastra/core": ">=1.0.0-0 <2.0.0-0" } }, "sha512-tQOBxBBpxWeLKxCpv6QgKUxwiH7bEDMkvSat126UIn9L5rr0+4igJ0zmJuGdFCBKPvz7CPo4ykQjkmu7rc/01w=="], "@mastra/observability": ["@mastra/observability@1.14.1", "", { "peerDependencies": { "@mastra/core": ">=1.16.0-0 <2.0.0-0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-VKfn3mE1mNFOXgY9Pr9sy5L4q4xLnoLj501gFMOr0teNnRMvAiANVB+p4rnooDfM4i8nzLDG9icM80uMsgXTzQ=="], - "@mastra/schema-compat": ["@mastra/schema-compat@1.2.10", "", { "dependencies": { "json-schema-to-zod": "^2.7.0", "zod-from-json-schema": "^0.5.2", "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-8Fg8PeO7GsRPOrEZAzc5udZgsF9ZDxih5JSoxjgnR79d0ImjKffhcoysPW6wIYXPEZ5i6/QDNR7rCazZZSD5Tg=="], + "@mastra/schema-compat": ["@mastra/schema-compat@1.2.11", "", { "dependencies": { "json-schema-to-zod": "^2.7.0", "zod-from-json-schema": "^0.5.2", "zod-from-json-schema-v3": "npm:zod-from-json-schema@^0.0.5", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-wN8eTy/g14Mg3kWukhoIjd5SpFtLQ8gOltbELe9nM2Ruzm4jK8tFr1ZZNZwvLYnpq7NJG5a8F0ZFCjXFOEwx0w=="], "@mdx-js/loader": ["@mdx-js/loader@3.1.1", "", { "dependencies": { "@mdx-js/mdx": "^3.0.0", "source-map": "^0.7.0" }, "peerDependencies": { "webpack": ">=5" }, "optionalPeers": ["webpack"] }, "sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ=="], @@ -1466,35 +1472,35 @@ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], - "@mendable/firecrawl-js": ["@mendable/firecrawl-js@4.25.1", "", { "dependencies": { "axios": "1.15.2", "firecrawl": "4.16.0", "typescript-event-target": "^1.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.0" } }, "sha512-Oo5tFCltCRMEJiaIsFOIBAzeRNq+OZ5qocrexvRcxXCnoEkqAoKunzbadSEEFZXYYayWj6yyYoGhRcGg5R14Dg=="], + "@mendable/firecrawl-js": ["@mendable/firecrawl-js@4.25.2", "", { "dependencies": { "axios": "1.16.1", "firecrawl": "4.16.0", "typescript-event-target": "^1.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.0" } }, "sha512-1dRs5qpfjfievBsUAxAWtnhtNiIVdXshAXdhAUQVmFUFOrOKR4XWs/76CxDIcEPQ44iD8ckInLwyewo6EIs+iQ=="], "@mermaid-js/parser": ["@mermaid-js/parser@1.1.1", "", { "dependencies": { "@chevrotain/types": "~11.1.1" } }, "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw=="], "@microsoft/fetch-event-source": ["@microsoft/fetch-event-source@2.0.1", "", {}, "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="], - "@mintlify/cli": ["@mintlify/cli@4.0.1172", "", { "dependencies": { "@inquirer/prompts": "7.9.0", "@mintlify/common": "1.0.901", "@mintlify/link-rot": "3.0.1080", "@mintlify/models": "0.0.311", "@mintlify/prebuild": "1.0.1045", "@mintlify/previewing": "4.0.1106", "@mintlify/validation": "0.1.707", "adm-zip": "0.5.16", "chalk": "5.2.0", "color": "4.2.3", "detect-port": "1.5.1", "front-matter": "4.0.2", "fs-extra": "11.2.0", "ink": "6.3.0", "inquirer": "12.3.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.2.0", "open": "8.4.2", "openid-client": "6.8.2", "posthog-node": "5.17.2", "react": "19.2.3", "semver": "7.7.2", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "4.3.6" }, "optionalDependencies": { "keytar": "7.9.0" }, "bin": { "mint": "bin/index.js", "mintlify": "bin/index.js" } }, "sha512-Ic3dUoeeOzSR+S9Izmbfr8eM1dMDVTkRp5TGlwa6/k9/BKExYFECvMfzZcN++W/+BDgMSbWqXRc76EwMXLv4Qw=="], + "@mintlify/cli": ["@mintlify/cli@4.0.1202", "", { "dependencies": { "@inquirer/prompts": "7.9.0", "@mintlify/common": "1.0.925", "@mintlify/link-rot": "3.0.1107", "@mintlify/models": "0.0.317", "@mintlify/prebuild": "1.0.1070", "@mintlify/previewing": "4.0.1132", "@mintlify/validation": "0.1.723", "adm-zip": "0.5.16", "chalk": "5.2.0", "color": "4.2.3", "detect-port": "1.5.1", "front-matter": "4.0.2", "fs-extra": "11.2.0", "ink": "6.3.0", "inquirer": "12.3.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.2.0", "open": "8.4.2", "openid-client": "6.8.2", "posthog-node": "5.17.2", "react": "19.2.3", "semver": "7.7.2", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "4.3.6" }, "optionalDependencies": { "keytar": "7.9.0" }, "bin": { "mint": "bin/index.js", "mintlify": "bin/index.js" } }, "sha512-/77Ot8sdVOlUW6H8kS88wPFyIxYl/P9RcpKjmK1rkdMWbAKWUPRtcoYrOYB6VISgtOaqBmptpZ/Uwa82/3u+vA=="], - "@mintlify/common": ["@mintlify/common@1.0.901", "", { "dependencies": { "@asyncapi/parser": "3.4.0", "@asyncapi/specs": "6.8.1", "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.311", "@mintlify/openapi-parser": "0.0.8", "@mintlify/validation": "0.1.707", "@sindresorhus/slugify": "2.2.0", "@types/mdast": "4.0.4", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "color-blend": "4.0.0", "estree-util-to-js": "2.0.0", "estree-walker": "3.0.3", "front-matter": "4.0.2", "hast-util-from-html": "2.0.3", "hast-util-to-html": "9.0.4", "hast-util-to-text": "4.0.2", "hex-rgb": "5.0.0", "ignore": "7.0.5", "js-yaml": "4.1.1", "lodash": "4.18.1", "mdast-util-from-markdown": "2.0.2", "mdast-util-gfm": "3.0.0", "mdast-util-mdx": "3.0.0", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-gfm": "3.0.0", "micromark-extension-mdx-jsx": "3.0.1", "micromark-extension-mdxjs": "3.0.0", "openapi-types": "12.1.3", "postcss": "8.5.14", "rehype-stringify": "10.0.1", "remark": "15.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.0", "remark-math": "6.0.0", "remark-mdx": "3.1.0", "remark-parse": "11.0.0", "remark-rehype": "11.1.1", "remark-stringify": "11.0.0", "sucrase": "3.34.0", "tailwindcss": "3.4.17", "unified": "11.0.5", "unist-builder": "4.0.0", "unist-util-map": "4.0.0", "unist-util-remove": "4.0.0", "unist-util-remove-position": "5.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1", "vfile": "6.0.3", "xss": "1.0.15" } }, "sha512-8CnUsvKT4hayjH2PooWJM9djPO6w/y6RBz5SEA1kRNu1YZt/kPhSRb7kQQCFHa2p8Q2qUnJ9KWI11SwerHdeMA=="], + "@mintlify/common": ["@mintlify/common@1.0.925", "", { "dependencies": { "@asyncapi/parser": "3.4.0", "@asyncapi/specs": "6.8.1", "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.317", "@mintlify/openapi-parser": "0.0.8", "@mintlify/validation": "0.1.723", "@sindresorhus/slugify": "2.2.0", "@types/mdast": "4.0.4", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "color-blend": "4.0.0", "estree-util-to-js": "2.0.0", "estree-walker": "3.0.3", "front-matter": "4.0.2", "hast-util-from-html": "2.0.3", "hast-util-to-html": "9.0.4", "hast-util-to-text": "4.0.2", "hex-rgb": "5.0.0", "ignore": "7.0.5", "js-yaml": "4.1.1", "lodash": "4.18.1", "mdast-util-from-markdown": "2.0.2", "mdast-util-gfm": "3.0.0", "mdast-util-mdx": "3.0.0", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-gfm": "3.0.0", "micromark-extension-mdx-jsx": "3.0.1", "micromark-extension-mdxjs": "3.0.0", "openapi-types": "12.1.3", "postcss": "8.5.14", "rehype-stringify": "10.0.1", "remark": "15.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.0", "remark-math": "6.0.0", "remark-mdx": "3.1.0", "remark-parse": "11.0.0", "remark-rehype": "11.1.1", "remark-stringify": "11.0.0", "sucrase": "3.34.0", "tailwindcss": "3.4.17", "unified": "11.0.5", "unist-builder": "4.0.0", "unist-util-map": "4.0.0", "unist-util-remove": "4.0.0", "unist-util-remove-position": "5.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1", "vfile": "6.0.3", "xss": "1.0.15" } }, "sha512-ell12iRylb06JNNdXIJukbFiKWF5Ip08LDfw25NpVjKJDqiCcMKyaDPf29rwjkicWaIzoGplSSix2T5nbR4V7Q=="], - "@mintlify/link-rot": ["@mintlify/link-rot@3.0.1080", "", { "dependencies": { "@mintlify/common": "1.0.901", "@mintlify/models": "0.0.311", "@mintlify/prebuild": "1.0.1045", "@mintlify/previewing": "4.0.1106", "@mintlify/scraping": "4.0.765", "@mintlify/validation": "0.1.707", "fs-extra": "11.1.0", "unist-util-visit": "4.1.2" } }, "sha512-vJwBkgNj/2J1AARviYSDca0BiQWbRet2Yv/QCCGK4ChMOGLvHUfbC4+Ay2RaGduHO7Xyi5e8m48gIa5UykwaUw=="], + "@mintlify/link-rot": ["@mintlify/link-rot@3.0.1107", "", { "dependencies": { "@mintlify/common": "1.0.925", "@mintlify/models": "0.0.317", "@mintlify/prebuild": "1.0.1070", "@mintlify/previewing": "4.0.1132", "@mintlify/scraping": "4.0.789", "@mintlify/validation": "0.1.723", "fs-extra": "11.1.0", "unist-util-visit": "4.1.2" } }, "sha512-Xh8PwkR8wba9fbDd1cKdPwGM2SI9XbchRn2KN0ODODFzLwHtYCpiGclSFjp5ViXXEk8G5Lv1cl/FKe3XANOxew=="], "@mintlify/mdx": ["@mintlify/mdx@3.0.4", "", { "dependencies": { "@shikijs/transformers": "^3.11.0", "@shikijs/twoslash": "^3.12.2", "arktype": "^2.1.26", "hast-util-to-string": "^3.0.1", "mdast-util-from-markdown": "^2.0.2", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "next-mdx-remote-client": "^1.0.3", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-smartypants": "^3.0.2", "shiki": "^3.11.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@radix-ui/react-popover": "^1.1.15", "react": "^18.3.1", "react-dom": "^18.3.1" } }, "sha512-tJhdpnM5ReJLNJ2fuDRIEr0zgVd6id7/oAIfs26V46QlygiLsc8qx4Rz3LWIX51rUXW/cfakjj0EATxIciIw+g=="], - "@mintlify/models": ["@mintlify/models@0.0.311", "", { "dependencies": { "axios": "1.16.1", "openapi-types": "12.1.3" } }, "sha512-WHvTcVxFpRnzHQewzk0RgfEZWKuYZ5ryZ8vJQRS5WtfIYhZ/wTZz7PtIsRaZsw/CQhTo1QTakne6tTrsmJuOwg=="], + "@mintlify/models": ["@mintlify/models@0.0.317", "", { "dependencies": { "axios": "1.16.1", "openapi-types": "12.1.3" } }, "sha512-FyRvuXTUsyC+KGa9uGtUSl6vYg+PLPC20POfmTO/8PkvfLiUIGXPLhFLNz6yPuLliExQo6CDOFhsSPZZUvWmew=="], "@mintlify/openapi-parser": ["@mintlify/openapi-parser@0.0.8", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-9MBRq9lS4l4HITYCrqCL7T61MOb20q9IdU7HWhqYMNMM1jGO1nHjXasFy61yZ8V6gMZyyKQARGVoZ0ZrYN48Og=="], - "@mintlify/prebuild": ["@mintlify/prebuild@1.0.1045", "", { "dependencies": { "@mintlify/common": "1.0.901", "@mintlify/openapi-parser": "0.0.8", "@mintlify/scraping": "4.0.765", "@mintlify/validation": "0.1.707", "chalk": "5.3.0", "favicons": "7.2.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "sharp": "0.33.5", "sharp-ico": "0.1.5", "unist-util-visit": "4.1.2", "uuid": "11.1.1" } }, "sha512-uJQtqUo8cWvAFa9j5ndI29bIyOHFFxPmVeyWhTTDfqelrJNmqcO0p6eGQf2EAFZ/ulewavEvn7IanWpq9Ob1Zw=="], + "@mintlify/prebuild": ["@mintlify/prebuild@1.0.1070", "", { "dependencies": { "@mintlify/common": "1.0.925", "@mintlify/openapi-parser": "0.0.8", "@mintlify/scraping": "4.0.789", "@mintlify/validation": "0.1.723", "chalk": "5.3.0", "favicons": "7.2.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "sharp": "0.33.5", "sharp-ico": "0.1.5", "unist-util-visit": "4.1.2", "uuid": "11.1.1" } }, "sha512-dCPZMNdRZoFKd0W92s7YMqlMczrhm8gIxv7tCm6AK+bR7QQ2+8o/bLb0jCqyesy+TliM0L+QgsqCZHHR6GBVwA=="], - "@mintlify/previewing": ["@mintlify/previewing@4.0.1106", "", { "dependencies": { "@mintlify/common": "1.0.901", "@mintlify/prebuild": "1.0.1045", "@mintlify/validation": "0.1.707", "adm-zip": "0.5.16", "better-opn": "3.0.2", "chalk": "5.2.0", "chokidar": "3.5.3", "express": "4.22.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "got": "13.0.0", "ink": "6.3.0", "ink-spinner": "5.0.0", "is-online": "10.0.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "react": "19.2.3", "socket.io": "4.8.0", "tar": "7.5.15", "unist-util-visit": "4.1.2", "yargs": "17.7.1" } }, "sha512-MtqFCHFGUUfF8RjRtaifeMLIzjDC30FIR8Wwqbo5NxwaNsraKErNsFhhdJczzQYP/GuCYcIIui8FQ4iJTN8/DA=="], + "@mintlify/previewing": ["@mintlify/previewing@4.0.1132", "", { "dependencies": { "@mintlify/common": "1.0.925", "@mintlify/prebuild": "1.0.1070", "@mintlify/validation": "0.1.723", "adm-zip": "0.5.16", "better-opn": "3.0.2", "chalk": "5.2.0", "chokidar": "3.5.3", "express": "4.22.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "got": "13.0.0", "ink": "6.3.0", "ink-spinner": "5.0.0", "is-online": "10.0.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "react": "19.2.3", "socket.io": "4.8.0", "tar": "7.5.15", "unist-util-visit": "4.1.2", "yargs": "17.7.1" } }, "sha512-F/MiPNIksOW9tPGrM2vp/b/Eb2w5Z8qMdAEdCKfGyin9g4nnm7A7ThySAEsuSfa3ZTGY6sAV/t+shC6dJ1FqAA=="], - "@mintlify/scraping": ["@mintlify/scraping@4.0.765", "", { "dependencies": { "@mintlify/common": "1.0.901", "@mintlify/openapi-parser": "0.0.8", "fs-extra": "11.1.1", "hast-util-to-mdast": "10.1.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.1.3", "neotraverse": "0.6.18", "puppeteer": "22.14.0", "rehype-parse": "9.0.1", "remark-gfm": "4.0.0", "remark-mdx": "3.0.1", "remark-parse": "11.0.0", "remark-stringify": "11.0.0", "unified": "11.0.5", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "3.24.0" }, "bin": { "mintlify-scrape": "bin/cli.js" } }, "sha512-CkghRhHaQWrw1SvGREvIaWVGiZ5AMsqKHPe1/y98xJe+pPofMTw0k1p9tgoJEbrNlwDa6L4ACatfF5a3au3AYg=="], + "@mintlify/scraping": ["@mintlify/scraping@4.0.789", "", { "dependencies": { "@mintlify/common": "1.0.925", "@mintlify/openapi-parser": "0.0.8", "fs-extra": "11.1.1", "hast-util-to-mdast": "10.1.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.1.3", "neotraverse": "0.6.18", "puppeteer": "22.14.0", "rehype-parse": "9.0.1", "remark-gfm": "4.0.0", "remark-mdx": "3.0.1", "remark-parse": "11.0.0", "remark-stringify": "11.0.0", "unified": "11.0.5", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "3.24.0" }, "bin": { "mintlify-scrape": "bin/cli.js" } }, "sha512-oOChtuikQx61XSDWVUL+8JaoSfmtymLtOo5KOrU2m1/hpru6Qvw7+WvHEUiV+bQJYU4GaXYkwrLTB5WXEmoN1Q=="], - "@mintlify/validation": ["@mintlify/validation@0.1.707", "", { "dependencies": { "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.311", "arktype": "2.1.27", "js-yaml": "4.1.1", "lcm": "0.0.3", "lodash": "4.18.1", "neotraverse": "0.6.18", "object-hash": "3.0.0", "openapi-types": "12.1.3", "uuid": "11.1.1", "zod": "3.24.0", "zod-to-json-schema": "3.20.4" } }, "sha512-4SyIGXaaz/7N3Slctr9CQI238tCOltbYIpmFwRRDBK8nPWeTBbF5x6DVQuqYtcgRShZ5BT1zuwJ4idaCxZt9bg=="], + "@mintlify/validation": ["@mintlify/validation@0.1.723", "", { "dependencies": { "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.317", "arktype": "2.1.27", "js-yaml": "4.1.1", "lcm": "0.0.3", "lodash": "4.18.1", "neotraverse": "0.6.18", "object-hash": "3.0.0", "openapi-types": "12.1.3", "uuid": "11.1.1", "zod": "3.24.0", "zod-to-json-schema": "3.20.4" } }, "sha512-QhPNokKUSjbEg0ozUwEBEaUcZH10X2Ku/bZzA9X4oyuY1O6KgSdb/u7lo+4hNidxsecHHXxLYPSzOAGZ6qtjpg=="], "@mishieck/ink-titled-box": ["@mishieck/ink-titled-box@0.3.0", "", { "peerDependencies": { "ink": "^6.0.0", "react": "^19.1.0", "typescript": "^5" } }, "sha512-ugzVH9hixp3hwKfQ8On/qnsrdAxS3y9rTu/aGOFed4zVUvtZyGZNIR4rxAwXult8HKI4vJEh0OM8wib9NPrwUg=="], - "@modelcontextprotocol/ext-apps": ["@modelcontextprotocol/ext-apps@1.7.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-OOWKDxdAjYDcgHkmzVzccyyag3FK+jBWPaWu4WvTxFsU4R/cgOX4eep66zPRA5n4v6WfxUNibPyvX4iJ7egYTg=="], + "@modelcontextprotocol/ext-apps": ["@modelcontextprotocol/ext-apps@1.7.4", "", { "dependencies": { "@standard-schema/spec": "^1.1.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-QQqysE549cf/Y0VabBmAACXhj92EhB3t8yVct2BHbkWiPTFA1S91EqTVjYXXcZEefXU0pmHcdObhsNMcomJIOQ=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], @@ -1504,17 +1510,17 @@ "@mrleebo/prisma-ast": ["@mrleebo/prisma-ast@0.13.1", "", { "dependencies": { "chevrotain": "^10.5.0", "lilconfig": "^2.1.0" } }, "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw=="], - "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], - "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], - "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], - "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], - "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], - "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], "@mswjs/interceptors": ["@mswjs/interceptors@0.41.9", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w=="], @@ -1524,7 +1530,7 @@ "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA=="], - "@next/mdx": ["@next/mdx@16.2.6", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-0hdoSkzRbyud1dNRRDiyqD9FrxR2wwdiW+ffhYx+n+fXrFOJ7Nwpi8o7nUz2LiiM44BB9M0eIO1Evy3BBrS50A=="], + "@next/mdx": ["@next/mdx@16.2.7", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-4RmM0KISxvfHr37/cn9TAGD2oy1nvTQ+ycgknz2xpd8IrY980N7XDU3CXhfKOXPhIVgbshxFF9HQEQC32ZVa9A=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], @@ -1578,8 +1584,6 @@ "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], - "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -1588,11 +1592,11 @@ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], - "@oclif/core": ["@oclif/core@4.11.3", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.3", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^10.2.5", "semver": "^7.8.0", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.16", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-gQCSYAtUhJilGKaSaZhqejH9X1dDu+jWQjLmtGOgN/XcKaAEPPSeT2mu1UvlvtPox1/NNRdlBcUa8KRKo2HnJQ=="], + "@oclif/core": ["@oclif/core@4.11.4", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.3", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^10.2.5", "semver": "^7.8.1", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.16", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-URwiQ5ALx/sJ2iH4vzXEd+H4K6NAI7LRs6Jag3hrgKEpGmaE6alfRC8qjO4GIgb6A3ACaJumqP9twi/M9ywdHQ=="], - "@oclif/plugin-help": ["@oclif/plugin-help@6.2.49", "", { "dependencies": { "@oclif/core": "^4" } }, "sha512-fEsO0YU7ThtzHE1RGuoHxFu/OGlqxm7PCfFp+U1PS8sde4E0cDqjVDuv78+VKrr45LpC5lWOApj7pm3FNfHrVA=="], + "@oclif/plugin-help": ["@oclif/plugin-help@6.2.50", "", { "dependencies": { "@oclif/core": "^4" } }, "sha512-rNCG4hUm+kPXFbhJfAVk/fZ3OdWJYwBDASlyX8CqOLP0MssjIGl7iEgfZz7TMuZFa+KucupKU5NRSc0KWfPTQA=="], - "@oclif/plugin-not-found": ["@oclif/plugin-not-found@3.2.86", "", { "dependencies": { "@inquirer/prompts": "^7.10.1", "@oclif/core": "^4.11.3", "ansis": "^3.17.0", "fast-levenshtein": "^3.0.0" } }, "sha512-BJhJSahwsYayZpo18f0fPTg8tKb9dIvydaz03NCK3eMfmcsT1MmXhXqh1KEV8J7mz0sQ6f0qFEb6BXy490/iUg=="], + "@oclif/plugin-not-found": ["@oclif/plugin-not-found@3.2.87", "", { "dependencies": { "@inquirer/prompts": "^7.10.1", "@oclif/core": "^4.11.4", "ansis": "^3.17.0", "fast-levenshtein": "^3.0.0" } }, "sha512-lKyZ4INrx5vB14HNWIkM6Vla/4rWVhOA2U7uCAj6gEBg36/KVmwYXxpZ9ckzZS0+jtLE84TVqS8NCYEhQiQojw=="], "@onkernel/sdk": ["@onkernel/sdk@0.36.1", "", {}, "sha512-DbpPja/+sYiZxBKS4bM8HOpFYCV1DirrTflElbMtLDCPzqO1oFQLtLGfgfAg7T4wnmth3/QYtekJpnTjxh0VGg=="], @@ -1714,35 +1718,35 @@ "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="], - "@orpc/client": ["@orpc/client@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-peer": "1.14.3" } }, "sha512-0HzeD/BgPctvFnd6ltjuQvx4/POXo0K01Tee/3whAm3ohXnlGqCfhzR2VMN8zBaGs1SYe6AFzjmGG928Ej3pAg=="], + "@orpc/client": ["@orpc/client@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "@orpc/standard-server-fetch": "1.14.5", "@orpc/standard-server-peer": "1.14.5" } }, "sha512-7kDiGJDyiwdHUnWJGGY2/evUenfAjvw6skrfuIKX+ZNtNrJecNNl3ytURmWOoZbcNZ4bX/1NKViO8BrtPgY0KA=="], - "@orpc/contract": ["@orpc/contract@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/shared": "1.14.3", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-docXs4ALK3TADAnscEywjqvV1Dy+4+B6ihfo33hayvJdxZdpVmxjHOf7pcAYaJFJ6+LgKYoskaVVKad6LLxFlg=="], + "@orpc/contract": ["@orpc/contract@1.14.5", "", { "dependencies": { "@orpc/client": "1.14.5", "@orpc/shared": "1.14.5", "@standard-schema/spec": "^1.1.0", "openapi-types": "^12.1.3" } }, "sha512-DARmTs1w5Z+bRtccMptc+d/k2DmZCY2pqtHhF3I2DxmN5IXEp9nch+MO/b5cnq1khjxy4XK6CIFL2psXyaXYTQ=="], - "@orpc/interop": ["@orpc/interop@1.14.3", "", {}, "sha512-B8ANHAGVI8Mjw7Co0p+qBlkFG84i38WTKjR01HMkMXd6g9bHbgaqOfHcMpJMFaZzqvxBnXH4zPra2w6J8sQmhQ=="], + "@orpc/interop": ["@orpc/interop@1.14.5", "", {}, "sha512-0cZuUVmBCkX1AsyjhDb5nG+odE/Zggl+kr3LZo3x+X4czUWlSSmPMomJ0sqtQdzv1LVyIRHmfHMWt5/9dY4BDQ=="], - "@orpc/json-schema": ["@orpc/json-schema@1.14.3", "", { "dependencies": { "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "json-schema-typed": "^8.0.2" } }, "sha512-Qcz2PzyZG2etpfB8ywy4Upf4SaI2x6x4fA8utVQXf5GcTPWfbTVz78MDDWnNtYEXSPj1BS95HqHC0AeGvYIB+g=="], + "@orpc/json-schema": ["@orpc/json-schema@1.14.5", "", { "dependencies": { "@orpc/contract": "1.14.5", "@orpc/interop": "1.14.5", "@orpc/openapi": "1.14.5", "@orpc/server": "1.14.5", "@orpc/shared": "1.14.5", "json-schema-typed": "^8.0.2" } }, "sha512-8vabQ3eWFpdk+ivwTm5lCy7mhFaTa7BlatjUIO0hiGlixgjtSc41v6zqK8bzV2QsyvPrHDuD1Q4Uasjzmk77+A=="], - "@orpc/openapi": ["@orpc/openapi@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/openapi-client": "1.14.3", "@orpc/server": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-0tZN91VoT6MEkOfw+ERKyozsnDXzmDSsBeMgEHN3Hl1WVU97T9l4aZzLlZILSNl3fat3HmduEDy/boNGmAWJkQ=="], + "@orpc/openapi": ["@orpc/openapi@1.14.5", "", { "dependencies": { "@orpc/client": "1.14.5", "@orpc/contract": "1.14.5", "@orpc/interop": "1.14.5", "@orpc/openapi-client": "1.14.5", "@orpc/server": "1.14.5", "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "json-schema-typed": "^8.0.2", "rou3": "^0.7.12" } }, "sha512-/wbjotmAbOKRxKEWW31jIp8/WwcbwTKEPtny+BNWGD2PrLWK+xiVKkmjjGFxbqIgETk3MZ2vLGRi1iwA6kZVyg=="], - "@orpc/openapi-client": ["@orpc/openapi-client@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-1vp+hi858XDrCwYdhONl15YKNlHtj5F5gI3dq/McRRZ45tZ9/Ma03hxzABOOcayaT1L9nX7gPxhX7l5EuRf2zw=="], + "@orpc/openapi-client": ["@orpc/openapi-client@1.14.5", "", { "dependencies": { "@orpc/client": "1.14.5", "@orpc/contract": "1.14.5", "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5" } }, "sha512-wfxXmQSXHdEsMjMr+mMxg6NKbtsyBCeCvbTAWdo9mr4I59HErGaiH/lVEuPpIH5G980Y5kewcdHexm+hNBefFw=="], - "@orpc/server": ["@orpc/server@1.14.3", "", { "dependencies": { "@orpc/client": "1.14.3", "@orpc/contract": "1.14.3", "@orpc/interop": "1.14.3", "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-aws-lambda": "1.14.3", "@orpc/standard-server-fastify": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-node": "1.14.3", "@orpc/standard-server-peer": "1.14.3", "cookie": "^1.1.1" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-VQG1sgruPhWdzT/ChltJ5Ju9v1A8F+s8EQ1MMSI33z0AthZ3IuuMZdqMIOo5YSuHROoFxzMJgCShOWYR9qXhQA=="], + "@orpc/server": ["@orpc/server@1.14.5", "", { "dependencies": { "@orpc/client": "1.14.5", "@orpc/contract": "1.14.5", "@orpc/interop": "1.14.5", "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "@orpc/standard-server-aws-lambda": "1.14.5", "@orpc/standard-server-fastify": "1.14.5", "@orpc/standard-server-fetch": "1.14.5", "@orpc/standard-server-node": "1.14.5", "@orpc/standard-server-peer": "1.14.5", "cookie": "^1.1.1" }, "peerDependencies": { "crossws": ">=0.3.4", "ws": ">=8.18.1" }, "optionalPeers": ["crossws", "ws"] }, "sha512-+yT4LEPnGdYCveBVPUwAwSTSuwsIsU7QywGEu+ug51KYKwtm5qCPNBMyoA7R1/J/Q4Q5a+qaHWBTDq8bElT0Ew=="], - "@orpc/shared": ["@orpc/shared@1.14.3", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-S7qmhZT4vchKEF6F6YduG5ub5lWnvQRVNq1/f5/kJkSnYMG5q6rWLcK7c3wYfDkeap05ZIiWTwksH+fv+yJOrw=="], + "@orpc/shared": ["@orpc/shared@1.14.5", "", { "dependencies": { "radash": "^12.1.1", "type-fest": "^5.4.4" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-D3rYULnYfTYC/ZcgSP6A2Fdypwkfm/vZQ7i2ycXXXfYNrnpAINQJfN32p6+M8GTDqmNkt0R7eALV1rrvxjmZCg=="], - "@orpc/standard-server": ["@orpc/standard-server@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3" } }, "sha512-qO6xJy+S15Wx0elQeVojo3p5EgBLJDTEtElPcUF9o4ac8hrikYZJBeSg7qGgu/elCIrVbaFk/16Lu8P4qatPWg=="], + "@orpc/standard-server": ["@orpc/standard-server@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5" } }, "sha512-bCd1BkShxknFjIDixUEjOj3fvP4hxN0IIV/hvTdtk6sdWApadBjw3UuK3iprkECgoBt/31zTOKwgHB/wnnUfhw=="], - "@orpc/standard-server-aws-lambda": ["@orpc/standard-server-aws-lambda@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3", "@orpc/standard-server-node": "1.14.3" } }, "sha512-/JpBBpLVcKTrALyhOB2zi5FfQi+X0uKNVkaZzGKd0iNLGLMYAQvfuWzdQRqWfnJb30yAPNVIjia+HFQgjyZBDA=="], + "@orpc/standard-server-aws-lambda": ["@orpc/standard-server-aws-lambda@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "@orpc/standard-server-fetch": "1.14.5", "@orpc/standard-server-node": "1.14.5" } }, "sha512-qHMSWp5d85WZzch2BAL8egrz7xQNorAElHNXC1S4q5W/Ssib5Krv5zLAXVKki4IUxwAHqfrO/EYnWgEdufQzNQ=="], - "@orpc/standard-server-fastify": ["@orpc/standard-server-fastify@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-node": "1.14.3" }, "peerDependencies": { "fastify": ">=5.6.1" }, "optionalPeers": ["fastify"] }, "sha512-hDQCazvnlXR8+27qkm/uBwGd82l8UAz3LbBGmJyhDK96Cfuyx9QX5oECC21CeRZJylpbdvSuwwDlSpm6IQ0uRQ=="], + "@orpc/standard-server-fastify": ["@orpc/standard-server-fastify@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "@orpc/standard-server-node": "1.14.5" }, "peerDependencies": { "fastify": ">=5.6.1" }, "optionalPeers": ["fastify"] }, "sha512-agxnwypSaS/LUkUFRzVxy+wO2EMBD0+paZw9SXk2rAsW+Gxl9/XTUundUqUoukusdhgZ7R2Fp7gQLt2p0wH3Qg=="], - "@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-IHpBpyd+CTav7ycftKkQax6qrMGdpQfYKCuTLK+P3xsBl1A07UXvlpzPi/8MjyNGDCRAQkTaN7JIr/uqLL1B8A=="], + "@orpc/standard-server-fetch": ["@orpc/standard-server-fetch@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5" } }, "sha512-7h83p6/TgogOpaTuEXMVMB+UkMuAMUyFQ0zw4HM2B5/XSiEOzVO1YyFzuC2EmeuuLIV11sEI3vcw5oWbNAQROQ=="], - "@orpc/standard-server-node": ["@orpc/standard-server-node@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3", "@orpc/standard-server-fetch": "1.14.3" } }, "sha512-jDMfxmicxwJq+UT3X9Ls/ijR1Inwv07Dkz+YIFiZ2MKlp3sXVZlxhleLqH5nxlsrANmNMpIENqosSBnaCcbQjg=="], + "@orpc/standard-server-node": ["@orpc/standard-server-node@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5", "@orpc/standard-server-fetch": "1.14.5" } }, "sha512-GrRGpCaZ8uQQRRCPCq/IWf30k/22gVsske3EP/vU93cQ8I+ArKHhmvnRQIiG2y4V/QvSTwvGpj/X279LmdnV4A=="], - "@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.14.3", "", { "dependencies": { "@orpc/shared": "1.14.3", "@orpc/standard-server": "1.14.3" } }, "sha512-Pk2Sccy+rnMYEDZnbO23NE6gP7ltk8pmlKABE4xlD1l87I/vENip94tiTG0QqmAmKCZ20Gec9vHtuLxtUqMOjQ=="], + "@orpc/standard-server-peer": ["@orpc/standard-server-peer@1.14.5", "", { "dependencies": { "@orpc/shared": "1.14.5", "@orpc/standard-server": "1.14.5" } }, "sha512-zp2QyfVzw4/Be5/D1NWB+r7+WDerxC39XZYsvD1+I1sT13P5HlVWfZpBIlF6eEig416O4wMwHYLOfnN4Ngom0g=="], - "@orpc/zod": ["@orpc/zod@1.14.3", "", { "dependencies": { "@orpc/json-schema": "1.14.3", "@orpc/openapi": "1.14.3", "@orpc/shared": "1.14.3", "escape-string-regexp": "^5.0.0", "wildcard-match": "^5.1.4" }, "peerDependencies": { "@orpc/contract": "1.14.3", "@orpc/server": "1.14.3", "zod": ">=3.25.0" } }, "sha512-+SIDmqfkTLCeeZVN6Cic4aWeiBqf2O9F4Vto9npqOEXT1szIpHKJtCmdZBsTOSD46LV5Tcg1emOde9eUeY2EBg=="], + "@orpc/zod": ["@orpc/zod@1.14.5", "", { "dependencies": { "@orpc/json-schema": "1.14.5", "@orpc/openapi": "1.14.5", "@orpc/shared": "1.14.5", "escape-string-regexp": "^5.0.0", "wildcard-match": "^5.1.4" }, "peerDependencies": { "@orpc/contract": "1.14.5", "@orpc/server": "1.14.5", "zod": ">=3.25.0" } }, "sha512-Al0Tim0CxO7o4UkZCmIPWJyNXnB0f0qL2YzB/MA0E65ss7qJydBmvyPUES9b3z8GeXgPQAmIOYl/Jq2jtx8DfQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -1754,87 +1758,85 @@ "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="], - "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.130.0", "", { "os": "android", "cpu": "arm" }, "sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.133.0", "", { "os": "android", "cpu": "arm" }, "sha512-l/44caGse+VpnY9gx0yvvc5QnnG3yG1FO3KZgYvNL1GZrfK86zIwAOgGEVlxDyRymzrU/KHiblPFpevKOmJmUA=="], - "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.130.0", "", { "os": "android", "cpu": "arm64" }, "sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg=="], + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.133.0", "", { "os": "android", "cpu": "arm64" }, "sha512-KUHmPMziLBp4u+zbrLdB7iWS7KshuZe+RAp7ELnY9SI9nNXBZ+dp8fiBqWOxhXqn+FQg3a4UcQhwmsJOKV8Jjg=="], - "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.130.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig=="], + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.133.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q8dWmnU/8ea2tga9w2f1PinQ5rcMPDUGkF64T189b65YMjUomET4oy5oRldOr4AwOQkneOG/Zttnz1Dvrc62wg=="], - "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.130.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg=="], + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.133.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOKeIELIB2bJnCKwqx4Rdj+1Lss/U6uCbLxRySZrhyOOQa1flKhwZFjEHRHxk8fU1NKmhK5OnTdPQ4CpjuFuVw=="], - "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.130.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw=="], + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.133.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-OpaSv4pW3KgFrMYQxTaS0aOE4T1DQF3qZE/4B6uqqv1KgPWWd4UQhJALi8PJPX1RRV5K7ThKXRfF7qGg2+3l1A=="], - "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.130.0", "", { "os": "linux", "cpu": "arm" }, "sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA=="], + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JGK1wlGrGwxBIlVSF7KWTX1/ru6BEtf28fRROztDRkLfiW+Kxa4onnriezMIiogfn9hVw2KzYcKiLjkLR2ns8A=="], - "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.130.0", "", { "os": "linux", "cpu": "arm" }, "sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w=="], + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.133.0", "", { "os": "linux", "cpu": "arm" }, "sha512-yuZO533Ftonxn/iyoqQzURzLQHMspvsIyfiCSNi1t/ER4eIQaR0SsmUOUm5b/lmSig7IWIUa5/BrbEkAPwcilQ=="], - "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.130.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q=="], + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-hvpbqT5pN2rR+3+xtWeizwfR/aZ0vGceg6TqYMl+ToxMpk9/tmnX7kSvQnfEUkoua8mhogzvIKsAkn0wxgblBA=="], - "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.130.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw=="], + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.133.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wJQGamIosQBoJHW9+S5XxrtKRo3eyJxsnS1XCPrqN0LHi8uw1pTqqTfn3t/NVuvbBg7Pumn4ez9Eidgcn0xbEg=="], - "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.130.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA=="], + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.133.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Koaz32/O5+abIfrNGdyndgRvdOZ9jEf5/z3Ep9h3h2QWpdDiUQpVwgH0OcMXCs+l9aXxPLtkupqyVig9W6FDKw=="], - "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.130.0", "", { "os": "linux", "cpu": "none" }, "sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw=="], + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-R4vOjWzxhnNWHnVLeiB6jNuIifdy9vcMXZGPc7StXcxBovI+U2zg1QhZ9o8OjV80oGivs1lX5NfPLzk4IPqlRA=="], - "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.130.0", "", { "os": "linux", "cpu": "none" }, "sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw=="], + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.133.0", "", { "os": "linux", "cpu": "none" }, "sha512-iwgBNUTHiMdxARLYuM0SBlnYeb19iw1Ea5M+4ERZupCsBMLArti6FyZ6UfFjJxIiTDr2oW2DGQFxlQVQ/dW9rA=="], - "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.130.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw=="], + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.133.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-ZwZNo8FZmB/gVfboQl+wXilBigGl+6nQQs+nITOeAP/HcAOjiHl6XZJL9F/KXNEspODQcbjAiyjUbeCJd9a0fA=="], - "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.130.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg=="], + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-govCvWx1dBlED3uu4qXctxpRcouu9I8Kn+DBktGCl760JtlGJzc9l/OmPJKlYWSbrRqKkMZehNeZ/4Wfma7uSA=="], - "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.130.0", "", { "os": "linux", "cpu": "x64" }, "sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A=="], + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.133.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ssTlpXD5Mq9uCssDJPzlRWqBt4Y7Zzd9i+XZhWmK/9Y6KUIuAxVYTYiI8lxcGWi0+3/Cz4A8q9UrD4NK9Y2j7g=="], - "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.130.0", "", { "os": "none", "cpu": "arm64" }, "sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw=="], + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.133.0", "", { "os": "none", "cpu": "arm64" }, "sha512-51aByfXhPtLEdWG4a2Ihdw6cPWV1ei1AarALpFdDP8MLWDLE2NuUMgbo3DERR2Kt8fT/ok1GUvBiLxVGke9uUQ=="], - "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.130.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA=="], + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.133.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-2e16tkKp+wDO2GTAmXfxbBcCmGEaFPIJEIRBBmVKNVXSc8/fJsSIaBGyFTPHM9ST5GNWgJcYIt94rDTks+PLwA=="], - "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.130.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA=="], + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.133.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-KPTNDKbxH1cglrqTyVeXHb4Pk4oksz8EcE1/v8zqU7N4UXbiHfA/IwtXZ2U77fnRAWBbgVkl/lZbL7o3hRdejg=="], - "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.130.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA=="], + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.133.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-Una1bNYv9zCavQrfnDR9wuZVB3itLjCEH4Oz7i6CwAJN/Xq9b+zbbcxmvdkKvvJt4Ngc/MBmIYlbLo3zS4TQ0A=="], - "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.130.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ=="], + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.133.0", "", { "os": "win32", "cpu": "x64" }, "sha512-kjBhCiOGSYTwDJQuuZa7a94JbP8htWu7J0X1KwH74kV2K5eYf6eyJRYmkpCDvr0XEL8tMxYI4WU1VekblFCLgg=="], - "@oxc-project/types": ["@oxc-project/types@0.130.0", "", {}, "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q=="], + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], - "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.20.0", "", { "os": "android", "cpu": "arm" }, "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg=="], - "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="], + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.20.0", "", { "os": "android", "cpu": "arm64" }, "sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q=="], - "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.19.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ=="], + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.20.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ=="], - "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.19.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ=="], + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.20.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg=="], - "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.19.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw=="], + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.20.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ=="], - "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A=="], + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0", "", { "os": "linux", "cpu": "arm" }, "sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg=="], - "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ=="], + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.20.0", "", { "os": "linux", "cpu": "arm" }, "sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg=="], - "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig=="], + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.20.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg=="], - "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew=="], + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.20.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw=="], - "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.19.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ=="], + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.20.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ=="], - "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w=="], + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.20.0", "", { "os": "linux", "cpu": "none" }, "sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw=="], - "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw=="], + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.20.0", "", { "os": "linux", "cpu": "none" }, "sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg=="], - "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.19.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA=="], + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.20.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g=="], - "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ=="], + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.20.0", "", { "os": "linux", "cpu": "x64" }, "sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g=="], - "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw=="], + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.20.0", "", { "os": "linux", "cpu": "x64" }, "sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ=="], - "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.19.1", "", { "os": "none", "cpu": "arm64" }, "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA=="], + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.20.0", "", { "os": "none", "cpu": "arm64" }, "sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ=="], - "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.19.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg=="], + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.20.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg=="], - "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.19.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ=="], + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.20.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA=="], - "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.19.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA=="], - - "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.19.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw=="], + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.20.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw=="], "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="], @@ -1876,11 +1878,11 @@ "@polka/url": ["@polka/url@0.5.0", "", {}, "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="], - "@posthog/ai": ["@posthog/ai@7.18.10", "", { "dependencies": { "@anthropic-ai/sdk": "^0.78.0", "@google/genai": "^1.43.0", "@langchain/core": "^1.1.29", "@posthog/core": "1.29.5", "langchain": "^1.2.28", "openai": "^6.25.0", "uuid": "^11.1.0", "zod": "^4.1.13" }, "peerDependencies": { "@ai-sdk/provider": "^2.0.0 || ^3.0.0", "@openai/agents": "^0.8.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0 <1.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "posthog-node": "^5.0.0" }, "optionalPeers": ["@ai-sdk/provider", "@openai/agents", "@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/sdk-trace-base"] }, "sha512-FSZOsU5QjoLH0hsRCkUO0pPeXyIWLqk50O/B0RBnhhhsFMnC2XpKDBstqmMmEVFIEaBd/sK4vQkhaLNaHjgnxw=="], + "@posthog/ai": ["@posthog/ai@7.20.14", "", { "dependencies": { "@anthropic-ai/sdk": "^0.78.0", "@google/genai": "^1.43.0", "@langchain/core": "^1.1.29", "@posthog/core": "1.30.10", "langchain": "^1.2.28", "openai": "^6.25.0", "uuid": "^11.1.0", "zod": "^4.1.13" }, "peerDependencies": { "@ai-sdk/provider": "^2.0.0 || ^3.0.0", "@openai/agents": "^0.8.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0 <1.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "posthog-node": "^5.0.0" }, "optionalPeers": ["@ai-sdk/provider", "@openai/agents", "@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/sdk-trace-base"] }, "sha512-xjzG2aSpqnSDUWYN1gHa6LHmrGwwqZi7E5DfRTpUq2YOwQbVk5mr8K+Hq5keck8bowApalVRySKMM6LZr/1JHA=="], - "@posthog/core": ["@posthog/core@1.29.5", "", { "dependencies": { "@posthog/types": "1.374.2" } }, "sha512-Jm5AE95EwBRqO6J8+skDufyf5rnEcmOvjYArCKCOzD4mWdH1xGpfcRXj5TEyZII3mD04Kr7pw9aP2ZbAHQGu2A=="], + "@posthog/core": ["@posthog/core@1.30.10", "", { "dependencies": { "@posthog/types": "1.382.0" } }, "sha512-R7Z5jDB3ugwfSujMmRd5osPPR6L6BqfcaSNcYOekzRMZ4Jklq74p05xByP09EnUvKXb5czI+RQVCITTWRWuFXw=="], - "@posthog/types": ["@posthog/types@1.374.2", "", {}, "sha512-ZghQSFMi+HFJNPvPjBoyY/jWQ+q6mSQVtWQxOHMSbBidUZjsyYbxYxBFbHy2qWLNe4mEpX+Wqir2Q4I/4AVvJQ=="], + "@posthog/types": ["@posthog/types@1.382.0", "", {}, "sha512-iK4OcSgvtmS9FZ9EUpvwlRZmHCLXaZ3+6dbRjkE7q9LL0zHLewxJH84H6uGvCw8aGzxs5rIliZqPHgimTcQEaw=="], "@prisma/client": ["@prisma/client@5.22.0", "", { "peerDependencies": { "prisma": "*" }, "optionalPeers": ["prisma"] }, "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA=="], @@ -1896,7 +1898,7 @@ "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], @@ -1914,125 +1916,125 @@ "@puzzmo/revenue-cat-webhook-types": ["@puzzmo/revenue-cat-webhook-types@1.1.0", "", {}, "sha512-ChEa8v4dHUlZyQ7mt5i8x2lBcZ7STRSe3D+YO+DhXicJI0Thjcy23Ehbn9l071bxgmbeH7OErOphJrCLpq/SEg=="], - "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + "@radix-ui/number": ["@radix-ui/number@1.1.2", "", {}, "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig=="], - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="], - "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], + "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.9", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5W9KzJz/3DeYbGJHbZv8Q6AkxMOKUmALfc+PRg9dWwJZMk6zD37Sz8sZrF7UD6CBkiJvn7dNeRzn5G7XiCMyig=="], - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collapsible": "1.1.13", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xITxBB2p5m5tAe7M0F95kb4uAh7jSIKGlExMEm93HlW+XxZHV2eXFbPWLktd4JhRiwcnXNbO7iekcrbZy6ZCvA=="], - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dialog": "1.1.16", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPaIgo0mxYlvcFaM9jB2Uot9TjGXMuAPEvrc6BOLeV+I5U8s1dkIoouYaa6lmSfc5SPMo5x5djOTOTvaigdGMQ=="], - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig=="], - "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], + "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Xy+Dpxt/5n9rVTdPrNFmf8GwG1NlT1pzCF/z1MgOGZMLZWdWl+km+ZRWGQAPEhbkzSwYEsfYmTca8NhUtVxqnw=="], - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.12", "", { "dependencies": { "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NQCQyWC7QrDPhjMn8hUqFeU0lUrprIgm1AyMgLbzuQJibNnatdc3SSMo3/UGFu/eUkJUU1cEcKCnyhXTQzq6tA=="], - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m3JmIOAX5ZzZ6VPjxEU2dbTOhoHi0nT5riwcDwe8idocsWf4a5DXJLDtZ6LfJwMBx7W+A2b7kp2TgPEKtaiF6A=="], - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F0s8+p2XNpfc3k02zBfB0jPWbkHVG162+p7BdUMyJ2308QMqZ+oaclX+FAzKFovgL5OqRU+Rvy6f/vbdlJVaqA=="], - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.9", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ=="], - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="], - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="], - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.3.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-menu": "2.1.17", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-d7CouXhAW+CGmFOqmB+IEvd3E9GcaqfgvfjCc3hfulp2pkaUCEVEGa0SN5nNWYA+IvQ6g1Pt+S5dpNn1AoY9hg=="], - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw=="], - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA=="], - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg=="], - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-menu": "2.1.17", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-S6b3Jm57sY5EdDyOMLkacbB0qMnKhy1RCKZCt795ZkmtUOAvojYIZ5p7dXHIh5Cyr3jCLLI5/g64V3FKLudZmw=="], - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="], - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.9", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ=="], - "@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="], + "@radix-ui/react-form": ["@radix-ui/react-form@0.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-label": "2.1.9", "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eTPyThIKDacJ3mJDvYwf/PSmsEYlOyA2Qcb+aGyWwYv+P5w57VPUkMVA2XJ9z0Du2KBY1HoHQzhPV9iYL/r4hg=="], - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hAileDBtd6CX7nlZOarOnISQ6PP4q0e16BX51ulzdZ+7IzjL0sDTVpFdmSYrIjw6zVNsfQBao5gG6AWr3qwfvA=="], - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="], - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-rDoTeMbCwRVcnmo7NGT9IlPo1yXmEI+xc1URP3oeewwZEV4mdTp1dYUhYbQdo4D1q2SjKVvv4N1gNY77QAQtjA=="], - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-callback-ref": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fmbNnFyf+JYCN0DhhWnEdUTDnZD1mXaPQWivdsPIb8oOSbARfD3LIQJbLCG8a8QLCwoMxiJ7GVPIFcC8Dw8v2Q=="], - "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="], + "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-menu": "2.1.17", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-AKtZ4O782yO7qwIyq73WpulYt1IHhQ0htDb6wNcxzxnSDCcSWMVBiU9ycpcA90XzQO4IVIxIErtak6Kg/Vt0rQ=="], - "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], + "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/fS8hKCcRt4DwCGa5QIB3juRXmfYSOk4a2AEe/BDIyy7Hm+eje2Y13oUx5zejl+wFt1owrM7E8NWlbaEl5EGpg=="], - "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="], + "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.9", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fvCzA9hm7yN5xxTPJIi4VhSmH5gv+76ILsxguBK3cm3icD5BR4vW7POQmu8Zio0yh91uuouG/Kang40IbMkaSQ=="], - "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="], + "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-is-hydrated": "0.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qoDSkObZ9faJlsjlwyBH6ia7kq9vaJ2QwWTowT3nQpzPvUTAKesmWuGJYpd91HIoJqS+5ZPXy5uFPp+HlwdaAg=="], - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-8brVpAU5Uq7Bh0c8EFc4ZTf2JJTYn0o+1L+CUJB3UYIOkTjKGMgoHvduylrahdmNlr3DfH0rFq2DrbNZXgaspw=="], - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.3.0", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-rect": "1.1.2", "@radix-ui/react-use-size": "1.1.2", "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ=="], - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.11", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw=="], - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="], - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.5", "", { "dependencies": { "@radix-ui/react-slot": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA=="], - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.9", "", { "dependencies": { "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+EOkvg1Zn1vI1+fRDfRSAiJ7BWfcDAo5ASMmbqrcLZ4s4USk2FGkoHgeb2X+CkUgo2zJMiyObwf1k44CrRWsyw=="], - "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="], + "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.4.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eHdV5bLx9sH+tBnbDjkIBdvQEH/c6MEtQYhTbxkaDK9qsIFFLtmJYEQFVdwhnruWotLfQmIuWEL/J+L3utE8rQ=="], - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg=="], - "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.11", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-DS39ziOgea75U/TrXKU2/oKp0be2jrDHnzFLvahg/0iNAT1Zq16e4Uw0WXwyXvsK+mG3BRyMb7A3NRZMDuEXtQ=="], - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.3.0", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.5", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mENc7WpJvJcW8hlMpzfFcHcEhTvYS5JMBmi9HVC1Q00uhBwML086MHYUV8QQdQv6lcu0Wg8dzd1RB8AFADcG/g=="], - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-gvgW+JV/Mbjj6darztTetnmElpQEzZrXpJvfj+dOxNAxiyHEAyUvEjjl4zxblvmjmKmi3jfPoy7ZdxzCuUBJSA=="], - "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.4.0", "", { "dependencies": { "@radix-ui/number": "1.1.2", "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-RHcPlLOThRJM51DSIC33ZnpDEBYhyEFroVWkd2P54PGGjkmAt14RboYUU9E1MFst666zFHM0tGtWvMjSOtU1pw=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg=="], - "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.3.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-previous": "1.1.2", "@radix-ui/react-use-size": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GP1EZwhoZO/GGnhM1P5/2Vpm8iN8EnngyU0oezn2l78kN8tj25pyrvjIaT7azBhK615KSt+P2w39y57YV5jVkA=="], - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-D5jwp9JNuwDeCw3CYD2Fz+sSHo0droQjC8u75dJHe4aWr5q6yBiXZU+hurXnKudRgEpUkD5TsI6bjHPo5ThUxA=="], - "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WUymDDiN2DpoGudRN1aW4wF5O3BNQjZZO/5nngPoNiEVqjyOzirvZZNO0R6dC1ifucSINVaSv8JX1aq47VGgiA=="], - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FikrKJemoBGZQ6uRID0HJqSPBP6D7OppdD2OhLl0ZYLlAyPXI7MezoYGmumwNkrAoRm35xXkb4C8JPfJZZzcaw=="], - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], + "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-toggle": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TEgECgJaWGAHJJZGzNNEYTNBdIXqX7LchANycpyP7DkfjmuiSN7ISt1k/ZRGVJgVJonsgP4vwaiKMn5utrcwWQ=="], - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], + "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-separator": "1.1.9", "@radix-ui/react-toggle-group": "1.1.12" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4wHtJVdIgqMmEwUvxA0BYg/2JMRbt0L3+8UD8Ml/nhKkfXtiZcM8u/S15gQ5xj9YEd/0qlrm5bE805LsjQ+J8A=="], - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-visually-hidden": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-u6F9MmTtBSLkiXNVDrtB/yPCZarM9smNswC24YYLV/M+bth6J3Gs3vlJezEoFwKZvPvxhCpUYdUnOsNG/0XOlA=="], - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="], - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.3", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA=="], - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.3", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA=="], - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.2", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw=="], - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A=="], - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA=="], - "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw=="], - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.2", "", { "dependencies": { "@radix-ui/rect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw=="], - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w=="], - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.5", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tPcHNI3FajdDBFpl/Ez1m2WL0ufJqBKyHxMDBvKitopamK36WwBGOMicuMEZKkM5Wce41QxUyv6BsiqfrWBiGg=="], - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@radix-ui/rect": ["@radix-ui/rect@1.1.2", "", {}, "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA=="], "@react-email/body": ["@react-email/body@0.0.11", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg=="], @@ -2076,61 +2078,61 @@ "@react-email/text": ["@react-email/text@0.1.5", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg=="], - "@react-grab/cli": ["@react-grab/cli@0.1.37", "", { "dependencies": { "commander": "^14.0.3", "ignore": "^7.0.5", "jsonc-parser": "^3.3.1", "ora": "^9.4.0", "package-manager-detector": "^1.6.0", "picocolors": "^1.1.1", "prompts": "^2.4.2", "smol-toml": "^1.6.1", "tinyexec": "^1.1.2" }, "bin": { "react-grab": "bin/cli.js" } }, "sha512-1ln28VkVHUbd5qy+ccXG68voWc0mgZMhBnwG0umxfD+wbkXUcvRzVrLjSqao7N8hCrDqp+Pt5j9Tsqef+9yQQQ=="], + "@react-grab/cli": ["@react-grab/cli@0.1.44", "", { "dependencies": { "agent-install": "^0.0.5", "commander": "^14.0.3", "ignore": "^7.0.5", "ora": "^9.4.0", "package-manager-detector": "^1.6.0", "picocolors": "^1.1.1", "prompts": "^2.4.2", "tinyexec": "^1.1.2" }, "bin": { "react-grab": "bin/cli.js" } }, "sha512-gMDYY2rw6OWajCcDlXSIgs2LC432YJXSb3Lm5yM187uhRgBYddoEVULi36h+IolX3r7jSb3ew7vn9FfI8NSo0A=="], "@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.61.1", "", { "os": "android", "cpu": "arm" }, "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.61.1", "", { "os": "android", "cpu": "arm64" }, "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.61.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.61.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.61.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.61.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.61.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.61.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.4", "", { "os": "none", "cpu": "arm64" }, "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.61.1", "", { "os": "none", "cpu": "arm64" }, "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.61.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.61.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], @@ -2140,39 +2142,39 @@ "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], - "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.53.1", "", { "dependencies": { "@sentry/core": "10.53.1" } }, "sha512-X4d6y8sBMjmNhcDW4eMBU3ASsNIMz8dqaFkhyIMN/dkYr/yZKnbRZPaVuVUGvHKjnlficPpIH0/HK9KBjrYxPw=="], + "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.56.0", "", { "dependencies": { "@sentry/core": "10.56.0" } }, "sha512-I8tZWAFg8SZpD8BFUpglEtSTzhZjacmcThB5/Mlq/iFiiT8mBPG4ZWDWssSfmIBKvZywJZJ83uDA0+uiJU73Tw=="], - "@sentry-internal/feedback": ["@sentry-internal/feedback@10.53.1", "", { "dependencies": { "@sentry/core": "10.53.1" } }, "sha512-vVpTI/aEYN5d9IgZeYJWMqVaN0+iFgidSrYNAsZTh1US5sJUzF/wrl+68KdpmCtFROrN3jiAn1oPSwL5CKvEJA=="], + "@sentry-internal/feedback": ["@sentry-internal/feedback@10.56.0", "", { "dependencies": { "@sentry/core": "10.56.0" } }, "sha512-fkRR9JroESTIlErkht3OrH4DXKd/DbPozr2KLdX7boMo31hPu4cL9fuqzwOrwyDPRq9B4j+qEgIWB8JrTbgvmg=="], - "@sentry-internal/replay": ["@sentry-internal/replay@10.53.1", "", { "dependencies": { "@sentry-internal/browser-utils": "10.53.1", "@sentry/core": "10.53.1" } }, "sha512-wZNzTBYkgGUPWMuUQv7L64+OJmoCnz7GQNiTrTFK6EVAjJXFBCSsPp/nhif0bLhbk8+0g4xz633uOhpXuQbFdw=="], + "@sentry-internal/replay": ["@sentry-internal/replay@10.56.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.56.0", "@sentry/core": "10.56.0" } }, "sha512-DjF09hpy3TF7Km/kOZc73YJmBqcbPCxuZ5rtRs+KtVHu3Vq48xeW83qKUcFEZv20ur9UD99OAJ/gaEt//1Qbwg=="], - "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.53.1", "", { "dependencies": { "@sentry-internal/replay": "10.53.1", "@sentry/core": "10.53.1" } }, "sha512-aueLaf/2prExwA76BGU5/bOXCKWqtt6jQXWA6WJQNrmKpPEtZJB4ypnpsou0McXQCF8tur2Y8U0TEkwQP13yJQ=="], + "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.56.0", "", { "dependencies": { "@sentry-internal/replay": "10.56.0", "@sentry/core": "10.56.0" } }, "sha512-SDg2K0CAZT/TnhrixQGwXoi6ZsWUB+DQy3UUk0bSQm6c/5k5zFBpGOiughQN+DYsDilKREfPKmUEEnqvUjm1HQ=="], "@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.9.1", "", {}, "sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg=="], - "@sentry/browser": ["@sentry/browser@10.53.1", "", { "dependencies": { "@sentry-internal/browser-utils": "10.53.1", "@sentry-internal/feedback": "10.53.1", "@sentry-internal/replay": "10.53.1", "@sentry-internal/replay-canvas": "10.53.1", "@sentry/core": "10.53.1" } }, "sha512-zXF373hzUOGzUOrqd8xb1U3LQi5uYC3mwv+z5OMKUUinQlu30tTWBs7ypy6YTchtix9QlYaHWlayUF8vBZ5UjA=="], + "@sentry/browser": ["@sentry/browser@10.56.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.56.0", "@sentry-internal/feedback": "10.56.0", "@sentry-internal/replay": "10.56.0", "@sentry-internal/replay-canvas": "10.56.0", "@sentry/core": "10.56.0" } }, "sha512-80X3NmsGB6tLmfzXYdjzWWdVAdL5CRukGKLcRWIcNhgGjtskOmnzaGb93egEZGI5bUTbtONJ0oyscQ3Z9yoAtQ=="], "@sentry/bun": ["@sentry/bun@10.38.0", "", { "dependencies": { "@sentry/core": "10.38.0", "@sentry/node": "10.38.0" } }, "sha512-8a2s+FVeqI2l12RNMFFEjAXpAUkqNZeGXTvHtjzcyWASW9szBNhOpiKN8oy0R/wUeIWgHpdnUeOSBhVKzH5YfQ=="], "@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.9.1", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.9.1", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^10.5.0", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ=="], - "@sentry/cli": ["@sentry/cli@2.58.5", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.5", "@sentry/cli-linux-arm": "2.58.5", "@sentry/cli-linux-arm64": "2.58.5", "@sentry/cli-linux-i686": "2.58.5", "@sentry/cli-linux-x64": "2.58.5", "@sentry/cli-win32-arm64": "2.58.5", "@sentry/cli-win32-i686": "2.58.5", "@sentry/cli-win32-x64": "2.58.5" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg=="], + "@sentry/cli": ["@sentry/cli@2.58.6", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.6", "@sentry/cli-linux-arm": "2.58.6", "@sentry/cli-linux-arm64": "2.58.6", "@sentry/cli-linux-i686": "2.58.6", "@sentry/cli-linux-x64": "2.58.6", "@sentry/cli-win32-arm64": "2.58.6", "@sentry/cli-win32-i686": "2.58.6", "@sentry/cli-win32-x64": "2.58.6" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg=="], - "@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.5", "", { "os": "darwin" }, "sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ=="], + "@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.6", "", { "os": "darwin" }, "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA=="], - "@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw=="], + "@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.6", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw=="], - "@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ=="], + "@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.6", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g=="], - "@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw=="], + "@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.6", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg=="], - "@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g=="], + "@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.6", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q=="], - "@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA=="], + "@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A=="], - "@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g=="], + "@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg=="], - "@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.5", "", { "os": "win32", "cpu": "x64" }, "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg=="], + "@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.6", "", { "os": "win32", "cpu": "x64" }, "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA=="], "@sentry/core": ["@sentry/core@10.38.0", "", {}, "sha512-1pubWDZE5y5HZEPMAZERP4fVl2NH3Ihp1A+vMoVkb3Qc66Diqj1WierAnStlZP7tCx0TBa0dK85GTW/ZFYyB9g=="], @@ -2182,7 +2184,7 @@ "@sentry/opentelemetry": ["@sentry/opentelemetry@10.38.0", "", { "dependencies": { "@sentry/core": "10.38.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.39.0" } }, "sha512-YPVhWfYmC7nD3EJqEHGtjp4fp5LwtAbE5rt9egQ4hqJlYFvr8YEz9sdoqSZxO0cZzgs2v97HFl/nmWAXe52G2Q=="], - "@sentry/react": ["@sentry/react@10.53.1", "", { "dependencies": { "@sentry/browser": "10.53.1", "@sentry/core": "10.53.1" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-lrwNq5T/zW84l60894TpKHPcvFuc1I/Hnohecc0TfYVpIcYYuw2orCHoU4v4wgkFaJUpegVetbgdOphViyLVjA=="], + "@sentry/react": ["@sentry/react@10.56.0", "", { "dependencies": { "@sentry/browser": "10.56.0", "@sentry/core": "10.56.0" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-HfPLyvnrydfyjRXw9Q0GMzj7w2YtEwuC9z5RrPUfarA2qpA0/J8cfGLzyFX2v0jBmA/kkj6J1uBUoSVhCTxFHg=="], "@sentry/vite-plugin": ["@sentry/vite-plugin@4.9.1", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.9.1", "unplugin": "1.0.1" } }, "sha512-Tlyg2cyFYp/icX58GWvfpvZr9NLdLs2/xyFVyS8pQ0faZWmoXic3FMzoXYHV1gsdMbL1Yy5WQvGJy8j1rS8LGA=="], @@ -2194,7 +2196,7 @@ "@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], - "@shikijs/primitive": ["@shikijs/primitive@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw=="], + "@shikijs/primitive": ["@shikijs/primitive@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA=="], "@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], @@ -2218,7 +2220,7 @@ "@simplewebauthn/browser": ["@simplewebauthn/browser@13.3.0", "", {}, "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ=="], - "@simplewebauthn/server": ["@simplewebauthn/server@13.3.0", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/x509": "^1.14.3" } }, "sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ=="], + "@simplewebauthn/server": ["@simplewebauthn/server@13.3.1", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/x509": "^1.14.3" } }, "sha512-GV/oM/qeycWn8p42JZIMJBsXWQcNFg+nJFzeQTnMA4gN8mXg0+HZFWJerHg8ZN/zlveMS3iV1wzuFpOVWS/46w=="], "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], @@ -2240,15 +2242,15 @@ "@smithy/abort-controller": ["@smithy/abort-controller@3.1.9", "", { "dependencies": { "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.5.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "tslib": "^2.6.2" } }, "sha512-TpS6Am5zSEtx3ow7VynThEL7UwRM06zZZcmFaP6Ij9hqKPfsFhTYCLcgU7gjFjw9QAI2kzwXrfS7InH8BivJTA=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.5.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-AXbvUX9aNY2qCLOMCikpl1Df5w2CNFEqbEb6XafG81FJbAbB8avIT7BOx1KDqiO86J/38qKQ3YuakfAfY3iBkQ=="], - "@smithy/core": ["@smithy/core@3.24.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg=="], + "@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="], "@smithy/eventstream-codec": ["@smithy/eventstream-codec@1.1.0", "", { "dependencies": { "@aws-crypto/crc32": "3.0.0", "@smithy/types": "^1.2.0", "@smithy/util-hex-encoding": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="], "@smithy/hash-node": ["@smithy/hash-node@3.0.11", "", { "dependencies": { "@smithy/types": "^3.7.2", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-emP23rwYyZhQBvklqTtwetkQlqbNYirDiEEwXl2v0GYWMnCzxst7ZaRAnWuy28njp5kAH54lvkdG37MblZzaHA=="], @@ -2256,7 +2258,7 @@ "@smithy/is-array-buffer": ["@smithy/is-array-buffer@1.1.0", "", { "dependencies": { "tslib": "^2.5.0" } }, "sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ=="], - "@smithy/middleware-compression": ["@smithy/middleware-compression@4.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "fflate": "0.8.1", "tslib": "^2.6.2" } }, "sha512-IuZ+ebi3OteVFprY33vV7oLfZxRx0YACjoGhex59PX7+sHgG0f75wyb5FZuOZhJoQPnWaDD5piirEwWzyAmb3A=="], + "@smithy/middleware-compression": ["@smithy/middleware-compression@4.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "fflate": "0.8.1", "tslib": "^2.6.2" } }, "sha512-wZQpnjrGSO2IFxhwWNaeRzHh2swSwRGWaCVgQN9zqYdtP98tcNYyqI7YvPeVTwf9CvQTas7xlmR3NY5L1i32mg=="], "@smithy/middleware-content-length": ["@smithy/middleware-content-length@3.0.13", "", { "dependencies": { "@smithy/protocol-http": "^4.1.8", "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw=="], @@ -2270,7 +2272,7 @@ "@smithy/node-config-provider": ["@smithy/node-config-provider@3.1.12", "", { "dependencies": { "@smithy/property-provider": "^3.1.11", "@smithy/shared-ini-file-loader": "^3.1.12", "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-O9LVEu5J/u/FuNlZs+L7Ikn3lz7VB9hb0GtPT9MQeiBmtK8RSY3ULmsZgXhe6VAlgTw0YO+paQx4p8xdbs43vQ=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="], "@smithy/property-provider": ["@smithy/property-provider@3.1.11", "", { "dependencies": { "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A=="], @@ -2284,11 +2286,11 @@ "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@3.1.12", "", { "dependencies": { "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.4.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="], "@smithy/smithy-client": ["@smithy/smithy-client@3.7.0", "", { "dependencies": { "@smithy/core": "^2.5.7", "@smithy/middleware-endpoint": "^3.2.8", "@smithy/middleware-stack": "^3.0.11", "@smithy/protocol-http": "^4.1.8", "@smithy/types": "^3.7.2", "@smithy/util-stream": "^3.3.4", "tslib": "^2.6.2" } }, "sha512-9wYrjAZFlqWhgVo3C4y/9kpc68jgiSsKUnsFPzr/MSiRL93+QRDafGTfhhKAb2wsr69Ru87WTiqSfQusSmWipA=="], - "@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], + "@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="], "@smithy/url-parser": ["@smithy/url-parser@3.0.11", "", { "dependencies": { "@smithy/querystring-parser": "^3.0.11", "@smithy/types": "^3.7.2", "tslib": "^2.6.2" } }, "sha512-TmlqXkSk8ZPhfc+SQutjmFr5FjC0av3GZP4B/10caK1SbRwe/v+Wzu/R6xEKxoNqL+8nY18s1byiy6HqPG37Aw=="], @@ -2332,10 +2334,6 @@ "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="], - "@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="], - - "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="], - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], @@ -2370,21 +2368,21 @@ "@stoplight/yaml-ast-parser": ["@stoplight/yaml-ast-parser@0.0.50", "", {}, "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ=="], - "@supabase/auth-js": ["@supabase/auth-js@2.106.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-JY7602OvjK2l3BjsQkpePpxR+6P0iG37gCrZNWAMhAuNh1iFnhGRwj/y5EshUG0INMPGFrj0UA9MErQ/kOEKFg=="], + "@supabase/auth-js": ["@supabase/auth-js@2.107.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-XA7x+WIeIvuC3GTZ2ey67QcBbGw4n+o5B7M+dMm9KT1lL3wX1B52DfEWW00WuPt/LnniJLLIn1WIm9YPtuxzKQ=="], - "@supabase/functions-js": ["@supabase/functions-js@2.106.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-ADIkJYH5w7HbnGVAAlCbyKoLF5QdfyezBLfYXpUqhxZOacK6YepOvnP/8p4p+50bhTPWp6VhDxu19KO7e/qU2g=="], + "@supabase/functions-js": ["@supabase/functions-js@2.107.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-iMtRUmEj1KOgQd/a3MR4hnBlPnZc62DW8+z8aPpnzbxWkexEZUVL2fSgvvp15gqFg1V55e2yMGqgK+yhSQxp5w=="], "@supabase/phoenix": ["@supabase/phoenix@0.4.2", "", {}, "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A=="], - "@supabase/postgrest-js": ["@supabase/postgrest-js@2.106.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vNKFAXQrtmUn7J3LbN+uMlt0jciAwRIBpdy6Do4DKrpf1xj0kJhbqXTX4y8ziewWUEEx8G5GPnDmprXXaO9f3w=="], + "@supabase/postgrest-js": ["@supabase/postgrest-js@2.107.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-7ARs47/tyIjX7T0Ive20d4NY8zQYXsP5/P07jJWxffSIM2gpnSnGRnL/Fe15GPbdjsW2sTYeckHcyaoKbM6yWQ=="], - "@supabase/realtime-js": ["@supabase/realtime-js@2.106.0", "", { "dependencies": { "@supabase/phoenix": "^0.4.2", "tslib": "2.8.1" } }, "sha512-mYZoaYpkyjlecixbvxCu0h3jw12uHfEcUqNdaRATNI8zQVI5arels+VJzAGcHwNiD+/Juv0OXIuk+M7SHsdI4A=="], + "@supabase/realtime-js": ["@supabase/realtime-js@2.107.0", "", { "dependencies": { "@supabase/phoenix": "^0.4.2", "tslib": "2.8.1" } }, "sha512-cF2KYdR3JIn9YlWGeluY9S0G+otqTdL6hB8GzpatlEIY6fZudCcyFo6Dc3+X9tjeb+x9XcIyNAk9qhNAknjH1A=="], - "@supabase/storage-js": ["@supabase/storage-js@2.106.0", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-BHc3nIjD3zfdDxBenphXrLJSoQ+qwo24VD96cVzmjBFbQVk5krvwRNUXrA5ozPplA3Vhlst2d/hy9R9ViqH2lg=="], + "@supabase/storage-js": ["@supabase/storage-js@2.107.0", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-/X8OOVwKBn8aVKuHAGOz2yLA0d2OauqhVuy4mNtN+o7wttHOgx1/j+pqOzlsjmhOHrYykF6AJNZhs3gKZzcMUw=="], "@supabase/stripe-sync-engine": ["@supabase/stripe-sync-engine@0.48.5", "", { "dependencies": { "pg": "^8.20.0", "pg-node-migrations": "0.0.8", "yesql": "^7.0.0" }, "peerDependencies": { "stripe": "> 18" } }, "sha512-+LbtJH8n5Xiu289AL3FuWFdKXd0K7kDF0z4Lm+zMYoImWmOuGd3TgSx9gm/nv4nzLooOmIxGZh6LojoYBcJM+g=="], - "@supabase/supabase-js": ["@supabase/supabase-js@2.106.0", "", { "dependencies": { "@supabase/auth-js": "2.106.0", "@supabase/functions-js": "2.106.0", "@supabase/postgrest-js": "2.106.0", "@supabase/realtime-js": "2.106.0", "@supabase/storage-js": "2.106.0" } }, "sha512-OOoo3sLj9iVXNp6b+fkyOfFeQrvvNy7nQbaONNf72dOaictUeS39hFDS9argIRTag6M3ZxIypNWcrDAwLgUihQ=="], + "@supabase/supabase-js": ["@supabase/supabase-js@2.107.0", "", { "dependencies": { "@supabase/auth-js": "2.107.0", "@supabase/functions-js": "2.107.0", "@supabase/postgrest-js": "2.107.0", "@supabase/realtime-js": "2.107.0", "@supabase/storage-js": "2.107.0" } }, "sha512-ChKzdlWVweMUUhr0U79JhMmgm1haS/C5JquaiCDr70JaGARRtjjoY9rkIheXWybXxTSNzRiQs3Sk8IAg1HS3ZA=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -2426,27 +2424,27 @@ "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.3", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw=="], - "@tanstack/form-core": ["@tanstack/form-core@1.32.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.9.1" } }, "sha512-Tn5VRDSjyqjmaet2tJMuEWDRFyrCaon03vxXPlSSaiSs6C/N7lCIwGCXJbZXEUq1kTj8jYN9qyXHbsz4LQHcow=="], + "@tanstack/form-core": ["@tanstack/form-core@1.33.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.11.0" } }, "sha512-AV4Pw9Dk4orFsuPBcDssfWMJFs+yMYBae7zZ4oTqrCf4ftNGQKxvrQRZeqKHG6A4TkiLeSvf2kzIjcVkrW7E6w=="], "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], "@tanstack/query-core": ["@tanstack/query-core@5.85.6", "", {}, "sha512-hCj0TktzdCv2bCepIdfwqVwUVWb+GSHm1Jnn8w+40lfhQ3m7lCO7ADRUJy+2unxQ/nzjh2ipC6ye69NDW3l73g=="], - "@tanstack/react-form": ["@tanstack/react-form@1.32.0", "", { "dependencies": { "@tanstack/form-core": "1.32.0", "@tanstack/react-store": "^0.9.1" }, "peerDependencies": { "@tanstack/react-start": "*", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@tanstack/react-start"] }, "sha512-6WP5SQTA6/H9crCpvpq3ZppYWqtrdE5NjOy6ebABi6uAQPqhfTzrdjS9t40mCZCFtGI5585OhJV6zBP/KN2zcw=="], + "@tanstack/react-form": ["@tanstack/react-form@1.33.0", "", { "dependencies": { "@tanstack/form-core": "1.33.0", "@tanstack/react-store": "^0.11.0" }, "peerDependencies": { "@tanstack/react-start": "*", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@tanstack/react-start"] }, "sha512-unaee+VS4MvKo+s1dmgGUXI4902VeAhuaUbKsQbhFe3MceOpB3JpAUGCDpyzjQPXVFkFY0COKfLrUNX2XZYW4g=="], "@tanstack/react-query": ["@tanstack/react-query@5.85.6", "", { "dependencies": { "@tanstack/query-core": "5.85.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-VUAag4ERjh+qlmg0wNivQIVCZUrYndqYu3/wPCVZd4r0E+1IqotbeyGTc+ICroL/PqbpSaGZg02zSWYfcvxbdA=="], - "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], + "@tanstack/react-store": ["@tanstack/react-store@0.11.0", "", { "dependencies": { "@tanstack/store": "0.11.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w=="], "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.24", "", { "dependencies": { "@tanstack/virtual-core": "3.14.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.14.2", "", { "dependencies": { "@tanstack/virtual-core": "3.17.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-IpWnmCLvuymRfeeLNVXIzNEYBFLpd3drVIS91sqV78VTZFyldlChkOocZRCPp1B+Wnk09bcLNme8WaMU/9/9bQ=="], - "@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="], + "@tanstack/store": ["@tanstack/store@0.11.0", "", {}, "sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw=="], "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.0", "", {}, "sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ=="], "@tinybirdco/sdk": ["@tinybirdco/sdk@0.0.69", "", { "dependencies": { "@clack/prompts": "^1.0.0", "chokidar": "^4.0.0", "commander": "^12.0.0", "dotenv": "^16.0.0", "esbuild": "^0.24.0", "picocolors": "^1.1.1", "zod": "^3.25.0" }, "bin": { "tinybird": "bin/tinybird.js" } }, "sha512-ScwHmj/bIjjxc7skTv+lRES8x0OnCQ8s8CHhS8kVjL5R1A0nrLsoCamBbFQUe0R1RL5Cm+ylWkTTCLyvCqkjnw=="], @@ -2472,17 +2470,17 @@ "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], - "@turbo/darwin-64": ["@turbo/darwin-64@2.9.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A=="], + "@turbo/darwin-64": ["@turbo/darwin-64@2.9.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-jLjApWTSNd7JZ5JaLYfelW1ytnGQOvB7ivl+2RD1xQvJTbi8I9gBjzcga7tDZVPyaxpl10YTfJt3BrYXR18KDw=="], - "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d23147mC9BsCPA9mJ0h/ubcpbRgcJBXbcG3+Vq7YLhjz3IXuvQsJ1UXH8f4MD76ZjJ4m/E4aRdJV+MW88CDfbw=="], + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YPgrn+5HIGzrx0O2a631SV4MBQUe4W/DafMFUuBVgaU32PW9/OTT0ehviF0QSxTXuRJlHvW2eUTemddF5/spmw=="], - "@turbo/linux-64": ["@turbo/linux-64@2.9.14", "", { "os": "linux", "cpu": "x64" }, "sha512-P3ZKB5tuUDdDQWuAsACGUR1qv9W7BNWxdxqVJ0kZNuNNPRaVYTPPikLcp79+GiEcW3npsR+KyP38lnQiBc5aSA=="], + "@turbo/linux-64": ["@turbo/linux-64@2.9.16", "", { "os": "linux", "cpu": "x64" }, "sha512-vAEf1H6l26lTpl9FJ/peQo1NUB8RC0sbEJJz5mPcUhHA2bPDup2x3CZPgo/bH8S4cUcBLm4FN3UHd5iUO2RAew=="], - "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZRTlzcUMrrPv9ZuDzRF9n60Ym13bKeG9jDB8WjxyLhWNzV+AJQN+zdpIk3NJYf2zQsGUm1mNar2P0elRzLw25g=="], + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-xDBLR2PZg4BrQOchfG6svgpv5FCNJ2TOtT2psLdEJcdKo1BH+pnPs9Xj6pvUjgfkHbuvBOfeE4R6tvxMoQKDHQ=="], - "@turbo/windows-64": ["@turbo/windows-64@2.9.14", "", { "os": "win32", "cpu": "x64" }, "sha512-exanwN6sIduZwykYeiTQj8kCmOhazP5WOz3bvXMcYtjhL6Z3iRWLewKrXCBq0bqwSP3iBMb/AerRCnHI4lx46A=="], + "@turbo/windows-64": ["@turbo/windows-64@2.9.16", "", { "os": "win32", "cpu": "x64" }, "sha512-NBAJnaUiGdgkSzQwUIdOvkCkcpTSu58G/sBGa0mvBtzfvFOOgrQwepKOOQ8cp6sWM6OcKDNFj2p1dsZA1OWjPg=="], - "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g=="], + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-Y7SJppD0Z8wjO3Ec0ZGd9KQ4Yv0BMnA8CIowj5Vp+OEVsosXDG2weK6/t1RRLfJmc2Ozrnd6y4DOgQys+mn3WQ=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], @@ -2586,8 +2584,6 @@ "@types/eslint": ["@types/eslint@7.29.0", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng=="], - "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], @@ -2620,7 +2616,7 @@ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], + "@types/mdx": ["@types/mdx@2.0.14", "", {}, "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg=="], "@types/methods": ["@types/methods@1.1.4", "", {}, "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ=="], @@ -2634,7 +2630,7 @@ "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], - "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], + "@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], @@ -2660,9 +2656,9 @@ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], - "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], @@ -2680,7 +2676,7 @@ "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], - "@types/superagent": ["@types/superagent@8.1.9", "", { "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ=="], + "@types/superagent": ["@types/superagent@8.1.10", "", { "dependencies": { "@types/cookiejar": "^2.1.5", "@types/methods": "^1.1.4", "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg=="], "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], @@ -2700,25 +2696,25 @@ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.4", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/type-utils": "8.59.4", "@typescript-eslint/utils": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.4", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/type-utils": "8.60.1", "@typescript-eslint/utils": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.4", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/types": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.4", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.4", "@typescript-eslint/types": "^8.59.4", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.1", "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.4", "", { "dependencies": { "@typescript-eslint/types": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4" } }, "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1" } }, "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.4", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.4", "", { "dependencies": { "@typescript-eslint/types": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/utils": "8.59.4", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.59.4", "", {}, "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.60.1", "", {}, "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.4", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.4", "@typescript-eslint/tsconfig-utils": "8.59.4", "@typescript-eslint/types": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.1", "@typescript-eslint/tsconfig-utils": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/types": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.4", "", { "dependencies": { "@typescript-eslint/types": "8.59.4", "eslint-visitor-keys": "^5.0.0" } }, "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag=="], "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260220.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260220.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260220.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-trYXlG98/C7Q7pqnPrKo+ksXrWqWVMncCy2x0VftD2llfL99Z//g2mpB9TmzWeKgb4d1659ESvxTowCGnzMccw=="], @@ -2738,7 +2734,7 @@ "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="], @@ -2806,7 +2802,7 @@ "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], - "@vercel/sdk": ["@vercel/sdk@1.21.5", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", "zod": "^3.25.0 || ^4.0.0" }, "bin": { "mcp": "bin/mcp-server.js" } }, "sha512-R1/j1ixylaHQ+d3y+QhG9848Ruv8XBH0g2MChzNek6F1SKNGvgHaG2hX0crndIYTVplB11Ynt3g2mKIkEKBAPQ=="], + "@vercel/sdk": ["@vercel/sdk@1.21.9", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", "zod": "^3.25.0 || ^4.0.0" }, "bin": { "mcp": "bin/mcp-server.js" } }, "sha512-HbmrcF/uwio8HwVA7oyvfzyqk2QxM2yqP2EakLqQ/9ZHA1VWi05D9mBZW5J/PnLs5sJLdFa5+J6mzGey6lq8KQ=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], @@ -2882,11 +2878,13 @@ "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "agent-install": ["agent-install@0.0.5", "", { "dependencies": { "@iarna/toml": "^2.2.5", "commander": "^14.0.0", "jsonc-parser": "^3.3.1", "picocolors": "^1.1.1", "prompts": "^2.4.2", "yaml": "^2.8.3" }, "bin": { "agent-install": "bin/agent-install.mjs" } }, "sha512-nHlms9BkP8ZiY79HrwCGiA2DcNaXrAaJrCM/BEqQ7MEsSKyCk+2A76xPGylIfASZSZE0SaU3T0bNSg4rBPIJAQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], "aggregate-error": ["aggregate-error@4.0.1", "", { "dependencies": { "clean-stack": "^4.0.0", "indent-string": "^5.0.0" } }, "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w=="], - "ai": ["ai@6.0.185", "", { "dependencies": { "@ai-sdk/gateway": "3.0.116", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oGsqscREaTlo75KHZLtwZxRyI+ZBwHV2wRX9B8smHjgOs13WwoCvUyr5aPUWpIBRz406wmIKy1RzoUEq0/WKJw=="], + "ai": ["ai@6.0.197", "", { "dependencies": { "@ai-sdk/gateway": "3.0.125", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "^1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-U3KsjkqwQXGHC0u0VeUDqUaNaBS/uQc7v4Vj92Cjv5lPx5DIyRBQYk4Hipy5vwD9AQKIG8uRvdaN9R+pAvrtcQ=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -2900,6 +2898,8 @@ "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -3012,9 +3012,9 @@ "avsc": ["avsc@5.7.9", "", {}, "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg=="], - "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + "axe-core": ["axe-core@4.12.0", "", {}, "sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg=="], - "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], + "axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -3024,17 +3024,17 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "bare-events": ["bare-events@2.8.3", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw=="], + "bare-events": ["bare-events@2.9.1", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg=="], - "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], + "bare-fs": ["bare-fs@4.7.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg=="], "bare-os": ["bare-os@3.9.1", "", {}, "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ=="], - "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + "bare-path": ["bare-path@3.0.1", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ=="], "bare-stream": ["bare-stream@2.13.1", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow=="], - "bare-url": ["bare-url@2.4.3", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ=="], + "bare-url": ["bare-url@2.4.5", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ=="], "base-convert-int-array": ["base-convert-int-array@1.0.1", "", {}, "sha512-NWqzaoXx8L/SS32R+WmKqnQkVXVYl2PwNJ68QV3RAlRRL1uV+yxJT66abXI1cAvqCXQTyXr7/9NN4Af90/zDVw=="], @@ -3042,7 +3042,7 @@ "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.31", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.34", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw=="], "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], @@ -3086,7 +3086,7 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "braintrust": ["braintrust@3.14.0", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.12.0", "@next/env": "^14.2.3", "@vercel/functions": "^1.0.2", "ajv": "^8.20.0", "argparse": "^2.0.1", "cli-progress": "^3.12.0", "cli-table3": "^0.6.5", "cors": "^2.8.5", "dc-browser": "^1.0.4", "dotenv": "^16.4.5", "esbuild": "0.28.0", "eventsource-parser": "^1.1.2", "express": "^5.2.1", "http-errors": "^2.0.0", "minimatch": "^10.2.5", "module-details-from-path": "^1.0.4", "mustache": "^4.2.0", "pluralize": "^8.0.0", "simple-git": "^3.36.0", "source-map": "^0.7.4", "termi-link": "^1.0.1", "unplugin": "^2.3.5", "uuid": "^11.1.1", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "zod": "^3.25.34 || ^4.0" }, "bin": { "braintrust": "dist/cli.js" } }, "sha512-B1ZvfYP4uWqCt39ACkfvglTtIg3VbiLXxs+diaqt90vTelABJ4B7tCMmv5hiSyQmt6HxIHCu+VmoBTnlsx30Xg=="], + "braintrust": ["braintrust@3.17.0", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.12.0", "@next/env": "^14.2.3", "@vercel/functions": "^1.0.2", "ajv": "^8.20.0", "argparse": "^2.0.1", "cli-progress": "^3.12.0", "cli-table3": "^0.6.5", "cors": "^2.8.5", "dc-browser": "^1.0.4", "dotenv": "^16.4.5", "esbuild": "0.28.0", "eventsource-parser": "^1.1.2", "express": "^5.2.1", "http-errors": "^2.0.0", "minimatch": "^10.2.5", "module-details-from-path": "^1.0.4", "mustache": "^4.2.0", "pluralize": "^8.0.0", "simple-git": "^3.36.0", "source-map": "^0.7.4", "termi-link": "^1.0.1", "unplugin": "^2.3.5", "uuid": "^11.1.1", "zod-to-json-schema": "^3.25.0" }, "optionalDependencies": { "@braintrust/bt-darwin-arm64": "0.11.1", "@braintrust/bt-darwin-x64": "0.11.1", "@braintrust/bt-linux-arm64": "0.11.1", "@braintrust/bt-linux-x64": "0.11.1", "@braintrust/bt-linux-x64-musl": "0.11.1", "@braintrust/bt-win32-arm64": "0.11.1", "@braintrust/bt-win32-x64": "0.11.1" }, "peerDependencies": { "zod": "^3.25.34 || ^4.0" }, "bin": { "braintrust": "dist/cli.js", "bt": "bin/bt" } }, "sha512-nyV+j/FJJJsWnkiSn9tAoNSTsMtDfbH4v8EQpBTYGj1120eXFPcPPs66kkkKcYuN0tEo/Ai7VO8Ujcy5j3SrUQ=="], "browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="], @@ -3104,7 +3104,7 @@ "builtins": ["builtins@5.1.0", "", { "dependencies": { "semver": "^7.0.0" } }, "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg=="], - "bullmq": ["bullmq@5.76.10", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.10.1", "msgpackr": "2.0.1", "node-abort-controller": "3.1.1", "semver": "7.8.0", "tslib": "2.8.1" } }, "sha512-LWve7SpQjYSpCP2GEsWmoyzTz2H37L8HRmSTu3YihYsTOr5kJxrfEX6aEV7m6eskEMWXSHZYTMZepX6qNaH6CQ=="], + "bullmq": ["bullmq@5.78.0", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.10.1", "msgpackr": "2.0.2", "node-abort-controller": "3.1.1", "semver": "7.8.0", "tslib": "2.8.1" }, "peerDependencies": { "redis": ">=5.0.0" }, "optionalPeers": ["redis"] }, "sha512-tT9jJmbobk9ueEfFc22egLmgwCcMGgOjZ5Y1cvgczBPv1JUmC7iHQVbQtqku2YBE5dE9uzdVpxIrBvL/YAjGwA=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], @@ -3138,7 +3138,7 @@ "camelcase-keys": ["camelcase-keys@8.0.2", "", { "dependencies": { "camelcase": "^7.0.0", "map-obj": "^4.3.0", "quick-lru": "^6.1.1", "type-fest": "^2.13.0" } }, "sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="], "cbor": ["cbor@8.1.0", "", { "dependencies": { "nofilter": "^3.1.0" } }, "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg=="], @@ -3164,7 +3164,7 @@ "charset": ["charset@1.0.1", "", {}, "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg=="], - "chat": ["chat@4.29.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" }, "peerDependencies": { "ai": "^6.0.182", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai", "zod"] }, "sha512-KdPfzaie5ivYytyRICTERg5xT+LeCbYefokvNAqTHe92eqkFaoTMXXkSitikxJVWhZIb2YoXF1b9UZHyzSzKzw=="], + "chat": ["chat@4.30.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" }, "peerDependencies": { "ai": "^6.0.182", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai", "zod"] }, "sha512-8LXrauKckMmR83FcYC/R8nNEda5VJDDdIhZwUUu+hzaSbk4lqsro0IWm7rB1GGYXONRrUOG2XJlkNr4C15vgMA=="], "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], @@ -3236,7 +3236,7 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + "cluster-key-slot": ["cluster-key-slot@1.1.1", "", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="], "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], @@ -3352,7 +3352,7 @@ "currently-unhandled": ["currently-unhandled@0.4.1", "", { "dependencies": { "array-find-index": "^1.0.1" } }, "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng=="], - "cytoscape": ["cytoscape@3.33.3", "", {}, "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g=="], + "cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="], "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], @@ -3436,13 +3436,13 @@ "datadog-metrics": ["datadog-metrics@0.12.1", "", { "dependencies": { "@datadog/datadog-api-client": "^1.17.0", "debug": "^4.1.0" } }, "sha512-Gy+17ia7m9Uy+nKQHDd7fljdq0fqqfpgkpxlwW0x1oFKI7RcgDV32pMCfHtv4HKychP6fHtncj3Lf4VN/g4G6A=="], - "date-fns": ["date-fns@4.2.1", "", {}, "sha512-37RhSdxaG1suen6VDCza6rNrQfooyQh57HFVPwQGEq2QWliVLzPQZ8Oa017weOu+HZCnzI7N3Pf/wyoBKfEqrA=="], + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], "date-time": ["date-time@3.1.0", "", { "dependencies": { "time-zone": "^1.0.0" } }, "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg=="], "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], - "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], "dc-browser": ["dc-browser@1.0.4", "", {}, "sha512-7oEtnzNlcE+hr4OvO3GR6Gndgw8BhW+wKOEwMqSleyY7N29jbAxzyW5BaJl7qBCw+6OIxfMWtY0T+6dxq8RWLw=="], @@ -3558,7 +3558,7 @@ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "dompurify": ["dompurify@3.4.5", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA=="], + "dompurify": ["dompurify@3.4.8", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ=="], "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], @@ -3574,7 +3574,7 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "e2b": ["e2b@2.27.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "chalk": "^5.3.0", "compare-versions": "^6.1.0", "dockerfile-ast": "^0.7.1", "glob": "^11.1.0", "openapi-fetch": "^0.14.1", "platform": "^1.3.6", "tar": "^7.5.11", "undici": "^7.25.0" } }, "sha512-xZ1vXSl4dpWxbvan5vihE2embXzHdlpK1N0CmFUIcj5kdGLpiQXGoQYsz1Dhy8wr9VO724DyRC7Y3iblMElLPQ=="], + "e2b": ["e2b@2.28.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "chalk": "^5.3.0", "compare-versions": "^6.1.0", "dockerfile-ast": "^0.7.1", "glob": "^11.1.0", "openapi-fetch": "^0.14.1", "platform": "^1.3.6", "tar": "^7.5.11", "undici": "^7.25.0" } }, "sha512-ptvySeKFFwz+bJbGIT6WGRkLr+Xwo1/oicf82cFuMepPXdRd3CrJoZ8FGnu+XWHRSJlOKBfCWENrDZmg4oKTtQ=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -3588,7 +3588,7 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.358", "", {}, "sha512-EO7tKm3QxRqTs1lSuPXzl6yRAwznehp0AH9OoMOIC+4mQzTFday8FJCO5KU6J/TFSQXEOahNq4vTKpz1jmCVOA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.368", "", {}, "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw=="], "emittery": ["emittery@1.2.1", "", {}, "sha512-sFz64DCRjirhwHLxofFqxYQm6DCp6o0Ix7jwKQvuCHPn4GMRZNuBZyLPu9Ccmk/QSCAMZt6FOUqA8JZCQvA9fw=="], @@ -3602,7 +3602,7 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "engine.io": ["engine.io@6.6.7", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3" } }, "sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ=="], + "engine.io": ["engine.io@6.6.8", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1" } }, "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g=="], "engine.io-client": ["engine.io-client@6.5.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.0.0" } }, "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ=="], @@ -3610,7 +3610,9 @@ "enhance-visitors": ["enhance-visitors@1.0.0", "", { "dependencies": { "lodash": "^4.13.1" } }, "sha512-+29eJLiUixTEDRaZ35Vu8jP3gPLNcQQkQkOQjLp2X+6cZGGPDD/uasbFzvLsJKnGZnvmyZ0srxudwOtskHeIDA=="], - "enhanced-resolve": ["enhanced-resolve@5.21.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A=="], + "enhanced-resolve": ["enhanced-resolve@5.23.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA=="], + + "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], "ensure-posix-path": ["ensure-posix-path@1.1.1", "", {}, "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw=="], @@ -3638,7 +3640,7 @@ "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -3646,7 +3648,7 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], + "es-toolkit": ["es-toolkit@1.47.0", "", {}, "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], @@ -3684,7 +3686,7 @@ "eslint-import-resolver-webpack": ["eslint-import-resolver-webpack@0.13.11", "", { "dependencies": { "debug": "^3.2.7", "enhanced-resolve": "^0.9.1", "find-root": "^1.1.0", "hasown": "^2.0.2", "interpret": "^1.4.0", "is-core-module": "^2.16.1", "is-regex": "^1.2.1", "lodash": "^4.18.1", "resolve": "^2.0.0-next.6", "semver": "^5.7.2" }, "peerDependencies": { "eslint-plugin-import": ">=1.4.0", "webpack": ">=1.11.0" } }, "sha512-RGFDrCHSmCKGuaoI1zmZT028weIFIEyfSy0nAwzp5rplutWDC+BBjvZS2l4bEgSOfjc+ILkSLxeszkslyNO6fQ=="], - "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" }, "peerDependencies": { "eslint": "*" }, "optionalPeers": ["eslint"] }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], + "eslint-module-utils": ["eslint-module-utils@2.13.0", "", { "dependencies": { "debug": "^3.2.7" }, "peerDependencies": { "eslint": "*" }, "optionalPeers": ["eslint"] }, "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ=="], "eslint-plugin-ava": ["eslint-plugin-ava@13.2.0", "", { "dependencies": { "enhance-visitors": "^1.0.0", "eslint-utils": "^3.0.0", "espree": "^9.0.0", "espurify": "^2.1.1", "import-modules": "^2.1.0", "micro-spelling-correcter": "^1.1.1", "pkg-dir": "^5.0.0", "resolve-from": "^5.0.0" }, "peerDependencies": { "eslint": ">=7.22.0" } }, "sha512-i5B5izsEdERKQLruk1nIWzTTE7C26/ju8qQf7JeyRv32XT2lRMW0zMFZNhIrEf5/5VvpSz2rqrV7UcjClGbKsw=="], @@ -3816,7 +3818,7 @@ "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], - "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="], "fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="], @@ -3898,7 +3900,7 @@ "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], - "framer-motion": ["framer-motion@12.39.0", "", { "dependencies": { "motion-dom": "^12.39.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w=="], + "framer-motion": ["framer-motion@12.40.0", "", { "dependencies": { "motion-dom": "^12.40.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -3926,7 +3928,7 @@ "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="], - "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + "gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="], "gcd": ["gcd@0.0.1", "", {}, "sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw=="], @@ -3984,7 +3986,7 @@ "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], - "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], + "google-auth-library": ["google-auth-library@10.7.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -3998,7 +4000,7 @@ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], - "graphql": ["graphql@16.14.0", "", {}, "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q=="], + "graphql": ["graphql@16.14.1", "", {}, "sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg=="], "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], @@ -4020,7 +4022,7 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], "hast": ["hast@1.0.0", "", {}, "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA=="], @@ -4088,8 +4090,6 @@ "hono": ["hono@4.12.7", "", {}, "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw=="], - "hono-openapi": ["hono-openapi@1.3.0", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig=="], - "hono-rate-limiter": ["hono-rate-limiter@0.4.2", "", { "peerDependencies": { "hono": "^4.1.1" } }, "sha512-AAtFqgADyrmbDijcRTT/HJfwqfvhalya2Zo+MgfdrMPas3zSMD8SU03cv+ZsYwRU1swv7zgVt0shwN059yzhjw=="], "hosted-git-info": ["hosted-git-info@5.2.1", "", { "dependencies": { "lru-cache": "^7.5.1" } }, "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw=="], @@ -4192,7 +4192,7 @@ "interpret": ["interpret@1.4.0", "", {}, "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="], - "ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], + "ioredis": ["ioredis@5.11.1", "", { "dependencies": { "@ioredis/commands": "1.10.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A=="], "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], @@ -4376,7 +4376,7 @@ "js-types": ["js-types@1.0.0", "", {}, "sha512-bfwqBW9cC/Lp7xcRpug7YrXm0IVw+T9e3g4mCYnv0Pjr3zIzU9PCQElYU9oSGAWzXlbdl9X5SAMPejO9sxkeUw=="], - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], "jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="], @@ -4438,15 +4438,15 @@ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - "knip": ["knip@6.14.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "minimist": "^1.2.8", "oxc-parser": "^0.130.0", "oxc-resolver": "^11.19.1", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.9.0", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-SN3Ly0ixzj5CQkY/rc4OPHpWrCC0XRIIjgdP76G9Cni5k72ur5jBYOyvJuF5oPTM14v8eHcMUgPbElHa+lnR0g=="], + "knip": ["knip@6.16.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "oxc-parser": "^0.133.0", "oxc-resolver": "^11.20.0", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.9.0", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w=="], "ksuid": ["ksuid@3.0.0", "", { "dependencies": { "base-convert-int-array": "^1.0.1" } }, "sha512-81CkBGn/06ZVAjGvFZi6fVG8VcPeMH0JpJ4V1Z9VwrMMaGIeAjY4jrVdrIcxhL9I2ZUU6t5uiyswcmkk+KZegA=="], "kysely": ["kysely@0.28.17", "", {}, "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q=="], - "langchain": ["langchain@1.4.1", "", { "dependencies": { "@langchain/langgraph": "^1.3.0", "@langchain/langgraph-checkpoint": "^1.0.1", "langsmith": ">=0.5.0 <1.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.1.47" } }, "sha512-LHGdj0OQV5pgyZgC2WWiEvNg5g16dg+c3j7pw7Iuw7tJXEvltNLVl6DjC6egxSsWT03FJN0eUJxJ13Dxhz2bBA=="], + "langchain": ["langchain@1.4.4", "", { "dependencies": { "@langchain/langgraph": "^1.3.2", "@langchain/langgraph-checkpoint": "^1.0.1", "langsmith": ">=0.5.0 <1.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.1.48" } }, "sha512-tepOCwUDaIZOYJ9Eo0O6o5dXEN/0KJheiFDnHHFL8Tx8rfkDLL4cOTSTln4Vpn9LpWzXYkjQ8lkHnnNDQWZPeg=="], - "langsmith": ["langsmith@0.7.1", "", { "dependencies": { "p-queue": "6.6.2" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-Wjk90UjNoY5cBHMlNAC/eZx5clI8jnjBOBW8uJu8+MWBtx0QesNjsUiLtjI+I3UnrpxFFpDqGXcnhBjH654Mqg=="], + "langsmith": ["langsmith@0.7.5", "", { "dependencies": { "p-queue": "6.6.2" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-OeD6+yKtWwy6sAboq25kD5DICzYv7j2KgtV2n4LsJ8nU2LpEdt1UwbjA6BON/zTggmS/YjV2TtLTHd7VEJhtEA=="], "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], @@ -4462,7 +4462,7 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "libphonenumber-js": ["libphonenumber-js@1.13.2", "", {}, "sha512-S3kmBrptp3yRTm83NUcHy9g1vbwiWMzI8WvY22+koBJ6zkRteLnedBL2VX0MIAGwx2yiyxX4J85pceZyQ6ffgg=="], + "libphonenumber-js": ["libphonenumber-js@1.13.6", "", {}, "sha512-NdB6O6QvlGMCoG003m0YIKG2+Xw7DjmCZhmc1RH+K6HncADUbRf8TZeLegxBBN1VFyPHcNpPTKpIhYLXzJVy1Q=="], "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], @@ -4560,7 +4560,7 @@ "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], - "lru-cache": ["lru-cache@11.4.0", "", {}, "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA=="], + "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="], @@ -4600,7 +4600,7 @@ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], @@ -4766,7 +4766,7 @@ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - "mintlify": ["mintlify@4.2.569", "", { "dependencies": { "@mintlify/cli": "4.0.1172" }, "bin": { "mintlify": "index.js" } }, "sha512-RadGvZlURzMpdr6Yy608a/EAm9lPT4rGpjOX3mTR0AvGwMRZjoEg6qxWcqbbjUsKKa5q7KERkcCNIRH09/bn3A=="], + "mintlify": ["mintlify@4.2.599", "", { "dependencies": { "@mintlify/cli": "4.0.1202" }, "bin": { "mintlify": "index.js" } }, "sha512-LyGieXwSu0F9xWfaN12LUDvV786wGQz6Kc9TP9GInW3cIBPvMty6ItSiYGv0OJm8Dx6lWnctzzGI8vVigeEvVg=="], "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], @@ -4790,7 +4790,7 @@ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - "mocha": ["mocha@11.7.5", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig=="], + "mocha": ["mocha@11.7.6", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA=="], "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], @@ -4798,17 +4798,17 @@ "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], - "motion": ["motion@12.39.0", "", { "dependencies": { "framer-motion": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-H4a+Ze+a9j+/NTla5ezfb/g9vmIOxC+viDj++NGDZyTZkdRKjiOz3kSv6TalRWM8ZmD2y/CfC6TkQc97ybyqSA=="], + "motion": ["motion@12.40.0", "", { "dependencies": { "framer-motion": "^12.40.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA=="], - "motion-dom": ["motion-dom@12.39.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ=="], + "motion-dom": ["motion-dom@12.40.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg=="], "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "msgpackr": ["msgpackr@2.0.1", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA=="], + "msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="], - "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], "msw": ["msw@2.14.6", "", { "dependencies": { "@inquirer/confirm": "^6.0.11", "@mswjs/interceptors": "^0.41.3", "@open-draft/deferred-promise": "^3.0.0", "@types/statuses": "^2.0.6", "cookie": "^1.1.1", "graphql": "^16.13.2", "headers-polyfill": "^5.0.1", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.11.11", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.1", "type-fest": "^5.5.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg=="], @@ -4844,7 +4844,7 @@ "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], - "next-mdx-remote-client": ["next-mdx-remote-client@1.1.7", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "remark-mdx-remove-esm": "^1.3.1", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 18.3.0 < 19.0.0", "react-dom": ">= 18.3.0 < 19.0.0" } }, "sha512-12Ap5Z/tFIETMXFSBTH2IFEhJAso7MvOJ5ICyesA4q6FM4vtAcmb+4ZKa4tV1IVQJLBVqOhaEfIESZzdwjmrQQ=="], + "next-mdx-remote-client": ["next-mdx-remote-client@1.1.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "@types/mdx": "^2.0.13", "remark-mdx-remove-esm": "^1.3.2", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 18.3.0 < 19.0.0", "react-dom": ">= 18.3.0 < 19.0.0" } }, "sha512-IElOrn02JjGQZxx+re7wMx/1AUG+Arte9aDImAtxjAfMw6xuSCaH5mTCunKelkWzFyFdRb565jO8jRICvvh96g=="], "ngrok": ["ngrok@5.0.0-beta.2", "", { "dependencies": { "extract-zip": "^2.0.1", "got": "^11.8.5", "lodash.clonedeep": "^4.5.0", "uuid": "^7.0.0 || ^8.0.0", "yaml": "^2.2.2" }, "optionalDependencies": { "hpagent": "^0.1.2" }, "bin": { "ngrok": "bin/ngrok" } }, "sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ=="], @@ -4872,7 +4872,7 @@ "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - "node-releases": ["node-releases@2.0.44", "", {}, "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ=="], + "node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="], "nodemon": ["nodemon@3.1.14", "", { "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" } }, "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw=="], @@ -4960,9 +4960,9 @@ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], - "oxc-parser": ["oxc-parser@0.130.0", "", { "dependencies": { "@oxc-project/types": "^0.130.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.130.0", "@oxc-parser/binding-android-arm64": "0.130.0", "@oxc-parser/binding-darwin-arm64": "0.130.0", "@oxc-parser/binding-darwin-x64": "0.130.0", "@oxc-parser/binding-freebsd-x64": "0.130.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.130.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.130.0", "@oxc-parser/binding-linux-arm64-gnu": "0.130.0", "@oxc-parser/binding-linux-arm64-musl": "0.130.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.130.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.130.0", "@oxc-parser/binding-linux-riscv64-musl": "0.130.0", "@oxc-parser/binding-linux-s390x-gnu": "0.130.0", "@oxc-parser/binding-linux-x64-gnu": "0.130.0", "@oxc-parser/binding-linux-x64-musl": "0.130.0", "@oxc-parser/binding-openharmony-arm64": "0.130.0", "@oxc-parser/binding-wasm32-wasi": "0.130.0", "@oxc-parser/binding-win32-arm64-msvc": "0.130.0", "@oxc-parser/binding-win32-ia32-msvc": "0.130.0", "@oxc-parser/binding-win32-x64-msvc": "0.130.0" } }, "sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw=="], + "oxc-parser": ["oxc-parser@0.133.0", "", { "dependencies": { "@oxc-project/types": "^0.133.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.133.0", "@oxc-parser/binding-android-arm64": "0.133.0", "@oxc-parser/binding-darwin-arm64": "0.133.0", "@oxc-parser/binding-darwin-x64": "0.133.0", "@oxc-parser/binding-freebsd-x64": "0.133.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", "@oxc-parser/binding-linux-arm64-musl": "0.133.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-musl": "0.133.0", "@oxc-parser/binding-openharmony-arm64": "0.133.0", "@oxc-parser/binding-wasm32-wasi": "0.133.0", "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw=="], - "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="], + "oxc-resolver": ["oxc-resolver@11.20.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.20.0", "@oxc-resolver/binding-android-arm64": "11.20.0", "@oxc-resolver/binding-darwin-arm64": "11.20.0", "@oxc-resolver/binding-darwin-x64": "11.20.0", "@oxc-resolver/binding-freebsd-x64": "11.20.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-musl": "11.20.0", "@oxc-resolver/binding-openharmony-arm64": "11.20.0", "@oxc-resolver/binding-wasm32-wasi": "11.20.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" } }, "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g=="], "p-any": ["p-any@4.0.0", "", { "dependencies": { "p-cancelable": "^3.0.0", "p-some": "^6.0.0" } }, "sha512-S/B50s+pAVe0wmEZHmBs/9yJXeZ5KhHzOsgKzt0hRdgkoR3DxW9ts46fcsWi/r3VnzsnkKS7q4uimze+zjdryw=="], @@ -5144,9 +5144,9 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "posthog-js": ["posthog-js@1.374.2", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.29.5", "@posthog/types": "1.374.2", "core-js": "^3.38.1", "dompurify": "^3.3.2", "fflate": "^0.4.8", "preact": "^10.28.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.1.0" } }, "sha512-6z1xGlVocd3NmSZlJNFfpedLIHLcejuuQPxvrpHDvtyVI9tN1NPqbM7T7coXw2It6gdZ/nAgDuZkNxfIut+Spw=="], + "posthog-js": ["posthog-js@1.382.0", "", { "dependencies": { "@posthog/core": "1.30.10", "@posthog/types": "1.382.0", "core-js": "^3.38.1", "dompurify": "^3.3.2", "fflate": "^0.4.8", "preact": "^10.28.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.1.0" } }, "sha512-lXwVlNdPLkhDft48ZgLQ5Jf4RsuQVwz7Hr7luD4DAG+4wDGA+k9eE2no36r3Z1w4uK0EmIf9lmav6+4RsmP7nA=="], - "posthog-node": ["posthog-node@5.34.6", "", { "dependencies": { "@posthog/core": "1.29.5" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-oDjagFRkmCbWJBxG1FVU3kOGC6dxNpR849q8ARrZSBK3zWz4zJox6V5EjrATKM9RXKvAmbCSFoxYaOYTzp3phA=="], + "posthog-node": ["posthog-node@5.36.4", "", { "dependencies": { "@posthog/core": "1.30.10" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-N+1WiypMHf3SO3NNoXTUFRzX98TuM5w4bDCm8RenYPf0rvX6r8v+yH6IzL1g/Me+wWA5+sfE1JZ6kGV6pWTZyQ=="], "powershell-utils": ["powershell-utils@0.2.0", "", {}, "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw=="], @@ -5178,11 +5178,11 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], "proto-props": ["proto-props@2.0.0", "", {}, "sha512-2yma2tog9VaRZY2mn3Wq51uiSW4NcPYT1cQdBagwyrznrilKSZwIZ0UG3ZPL/mx+axEns0hE35T5ufOYZXEnBQ=="], - "protobufjs": ["protobufjs@7.6.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ=="], + "protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], @@ -5210,11 +5210,9 @@ "qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], - "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], - "query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="], + "query-string": ["query-string@9.4.0", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-ivvWyHqU9K1Log4hJFhqVIIMoEi0nzmlRhvk2pPcTuQH/Y0K5iTTMxEx7R0PRHD2Z1hMVbWnjfsEWbIKIK+3IA=="], "queue-lit": ["queue-lit@1.5.2", "", {}, "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw=="], @@ -5226,7 +5224,7 @@ "radash": ["radash@12.1.1", "", {}, "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA=="], - "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], + "radix-ui": ["radix-ui@1.5.0", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-accessible-icon": "1.1.9", "@radix-ui/react-accordion": "1.2.13", "@radix-ui/react-alert-dialog": "1.1.16", "@radix-ui/react-arrow": "1.1.9", "@radix-ui/react-aspect-ratio": "1.1.9", "@radix-ui/react-avatar": "1.1.12", "@radix-ui/react-checkbox": "1.3.4", "@radix-ui/react-collapsible": "1.1.13", "@radix-ui/react-collection": "1.1.9", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-context-menu": "2.3.0", "@radix-ui/react-dialog": "1.1.16", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-dropdown-menu": "2.1.17", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-form": "0.1.9", "@radix-ui/react-hover-card": "1.1.16", "@radix-ui/react-label": "2.1.9", "@radix-ui/react-menu": "2.1.17", "@radix-ui/react-menubar": "1.1.17", "@radix-ui/react-navigation-menu": "1.2.15", "@radix-ui/react-one-time-password-field": "0.1.9", "@radix-ui/react-password-toggle-field": "0.1.4", "@radix-ui/react-popover": "1.1.16", "@radix-ui/react-popper": "1.3.0", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-progress": "1.1.9", "@radix-ui/react-radio-group": "1.4.0", "@radix-ui/react-roving-focus": "1.1.12", "@radix-ui/react-scroll-area": "1.2.11", "@radix-ui/react-select": "2.3.0", "@radix-ui/react-separator": "1.1.9", "@radix-ui/react-slider": "1.4.0", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-switch": "1.3.0", "@radix-ui/react-tabs": "1.1.14", "@radix-ui/react-toast": "1.2.16", "@radix-ui/react-toggle": "1.1.11", "@radix-ui/react-toggle-group": "1.1.12", "@radix-ui/react-toolbar": "1.1.12", "@radix-ui/react-tooltip": "1.2.9", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-effect-event": "0.0.3", "@radix-ui/react-use-escape-keydown": "1.1.2", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2", "@radix-ui/react-use-size": "1.1.2", "@radix-ui/react-visually-hidden": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nzh2HNpClgB31FBHRqt2xG8XNUfVfQRpf34hACC5PNrXTd5JdXdqOXwLs3BL+D8CNYiNQiJiT8QGr5Q4vq+00w=="], "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], @@ -5238,7 +5236,7 @@ "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], - "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], "react-day-picker": ["react-day-picker@8.10.2", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-LK68OTbHB3oJNhl9cA0qVizzp3o26w61YSjAFkYi67N86iro32wx86kSNeFU/hq+gI8m1yzWhnomMLfZ041RzQ=="], @@ -5248,7 +5246,7 @@ "react-email": ["react-email@4.0.16", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/traverse": "^7.27.0", "chalk": "^5.0.0", "chokidar": "^4.0.3", "commander": "^13.0.0", "debounce": "^2.0.0", "esbuild": "^0.25.0", "glob": "^11.0.0", "log-symbols": "^7.0.0", "mime-types": "^3.0.0", "next": "^15.3.1", "normalize-path": "^3.0.0", "ora": "^8.0.0", "socket.io": "^4.8.1" }, "bin": { "email": "dist/cli/index.mjs" } }, "sha512-auhFU+nQxAkKkP6lQhPyGsa9exwfUEzp2BwZnjHokCwphZlg30tu4t1LgdKRwGPYsi7XNGy6asbVLAUhOVpzzg=="], - "react-grab": ["react-grab@0.1.37", "", { "dependencies": { "@react-grab/cli": "0.1.37", "bippy": "^0.5.41" }, "peerDependencies": { "react": ">=17.0.0" }, "optionalPeers": ["react"], "bin": { "react-grab": "bin/cli.js" } }, "sha512-XVAc/qPyxsDT8Putu9UnP7iVHmAORMzvaN/3GDTOfbuLIRXWdOt7vebPOzM9RVTVUjucXv0135JI++kSVPSfYg=="], + "react-grab": ["react-grab@0.1.44", "", { "dependencies": { "@react-grab/cli": "0.1.44", "bippy": "^0.5.41" }, "peerDependencies": { "react": ">=17.0.0" }, "optionalPeers": ["react"], "bin": { "react-grab": "bin/cli.js" } }, "sha512-bDEwBdI90ljq2lhUtPqmWis/HwYB/CvfT0m5i+P9F83Pt0Ot8o9XL8v00s9jcWzdQUlsFDzmq2FO2CHUe8JY8A=="], "react-hotkeys-hook": ["react-hotkeys-hook@4.6.2", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q=="], @@ -5266,11 +5264,11 @@ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - "react-resizable-panels": ["react-resizable-panels@4.11.1", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-kA4w58V6wYdRLm2rg9pzroZwGlqBLul1FjMP0J8kqTo3zSHtjeH+LXmZaldCo6+HWqs1e5hOcPoajKXdOze37Q=="], + "react-resizable-panels": ["react-resizable-panels@4.11.2", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-+kfFbDZ8mygc7g0vxOcDzCVGuwiIUOnILqPoUHo6/uP+Mmyx6HzZU+kj1aOPDlktXuobYbr6BtQekvJwHRX4Eg=="], - "react-router": ["react-router@7.15.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A=="], + "react-router": ["react-router@7.17.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ=="], - "react-router-dom": ["react-router-dom@7.15.1", "", { "dependencies": { "react-router": "7.15.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg=="], + "react-router-dom": ["react-router-dom@7.17.0", "", { "dependencies": { "react-router": "7.17.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw=="], "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], @@ -5366,7 +5364,7 @@ "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], - "remark-mdx-remove-esm": ["remark-mdx-remove-esm@1.3.1", "", { "dependencies": { "@types/mdast": "^4.0.4", "mdast-util-mdxjs-esm": "^2.0.1", "unist-util-remove": "^4.0.0" }, "peerDependencies": { "unified": "^11" } }, "sha512-POa8abdiuicD2e+zQkclxzJa5JEGLtV8XIOFVvisnGuw4l4xd6dfQozedwqR8JTeXQmxLebvYhlbwHoQP9RWkw=="], + "remark-mdx-remove-esm": ["remark-mdx-remove-esm@1.3.2", "", { "dependencies": { "@types/mdast": "^4.0.4", "unist-util-remove": "^4.0.0" }, "peerDependencies": { "unified": "^11" } }, "sha512-BvL8VSdVXy9S7NlHP56nUJAHFc45h5E9HnHiLUGHe5tw3Yvm/3cVZvAzlkEEh2i+fkq2uKrf2xn5VmItBhMypA=="], "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -5424,7 +5422,7 @@ "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], - "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], + "rollup": ["rollup@4.61.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.61.1", "@rollup/rollup-android-arm64": "4.61.1", "@rollup/rollup-darwin-arm64": "4.61.1", "@rollup/rollup-darwin-x64": "4.61.1", "@rollup/rollup-freebsd-arm64": "4.61.1", "@rollup/rollup-freebsd-x64": "4.61.1", "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", "@rollup/rollup-linux-arm-musleabihf": "4.61.1", "@rollup/rollup-linux-arm64-gnu": "4.61.1", "@rollup/rollup-linux-arm64-musl": "4.61.1", "@rollup/rollup-linux-loong64-gnu": "4.61.1", "@rollup/rollup-linux-loong64-musl": "4.61.1", "@rollup/rollup-linux-ppc64-gnu": "4.61.1", "@rollup/rollup-linux-ppc64-musl": "4.61.1", "@rollup/rollup-linux-riscv64-gnu": "4.61.1", "@rollup/rollup-linux-riscv64-musl": "4.61.1", "@rollup/rollup-linux-s390x-gnu": "4.61.1", "@rollup/rollup-linux-x64-gnu": "4.61.1", "@rollup/rollup-linux-x64-musl": "4.61.1", "@rollup/rollup-openbsd-x64": "4.61.1", "@rollup/rollup-openharmony-arm64": "4.61.1", "@rollup/rollup-win32-arm64-msvc": "4.61.1", "@rollup/rollup-win32-ia32-msvc": "4.61.1", "@rollup/rollup-win32-x64-gnu": "4.61.1", "@rollup/rollup-win32-x64-msvc": "4.61.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA=="], "rou3": ["rou3@0.6.3", "", {}, "sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA=="], @@ -5478,7 +5476,7 @@ "semifies": ["semifies@1.0.0", "", {}, "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw=="], - "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + "semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], @@ -5546,7 +5544,7 @@ "socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="], - "socket.io-adapter": ["socket.io-adapter@2.5.6", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.18.3" } }, "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ=="], + "socket.io-adapter": ["socket.io-adapter@2.5.7", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.20.1" } }, "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg=="], "socket.io-client": ["socket.io-client@4.7.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ=="], @@ -5608,7 +5606,7 @@ "streamdown": ["streamdown@1.6.11", "", { "dependencies": { "clsx": "^2.1.1", "hast": "^1.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", "rehype-harden": "^1.1.6", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-cjk-friendly": "^1.2.3", "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.0.1", "shiki": "^3.12.2", "tailwind-merge": "^3.3.1", "unified": "^11.0.5", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-Y38fwRx5kCKTluwM+Gf27jbbi9q6Qy+WC9YrC1YbCpMkktT3PsRBJHMWiqYeF8y/JzLpB1IzDoeaB6qkQEDnAA=="], - "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], + "streamx": ["streamx@2.27.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA=="], "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], @@ -5622,9 +5620,9 @@ "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], - "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="], - "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="], "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], @@ -5680,7 +5678,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svix": ["svix@1.93.0", "", { "dependencies": { "standardwebhooks": "1.0.0" } }, "sha512-AeCcSs+CrHNejZytBuvD4hw2B14rB7+Sq7ggwYgF22TgXh0uJJ3T4uVJSbSYKFSbO1AA4o470XoGgOYqu2fbSA=="], + "svix": ["svix@1.95.1", "", { "dependencies": { "standardwebhooks": "1.0.0" } }, "sha512-Vtsbzsvs4lzXJneruB5HiZmV7dlhAjbo6dGid2Qxi9bv+LutLz7Yt3NORI4SYqRTNWPhVoFAA8TG/WXB+mIzNQ=="], "svix-react": ["svix-react@1.13.9", "", { "peerDependencies": { "react": ">=16", "react-dom": ">=16", "svix": ">=1.26.0" } }, "sha512-upKI64EwMiEUpG4+LRAOxlWzz/qR9wGcYCt0firKajqyPYblTp8Yfca1gf5a+rYdoE5MharloHNiQcZGdhemGw=="], @@ -5704,7 +5702,7 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "tar": ["tar@7.5.16", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w=="], "tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="], @@ -5722,61 +5720,61 @@ "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], - "terser": ["terser@5.47.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw=="], + "terser": ["terser@5.48.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q=="], - "terser-webpack-plugin": ["terser-webpack-plugin@5.6.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA=="], + "terser-webpack-plugin": ["terser-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "@minify-html/node": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "lightningcss": "*", "postcss": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "lightningcss", "postcss", "uglify-js"] }, "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ=="], - "text-camel-case": ["text-camel-case@1.2.10", "", { "dependencies": { "text-pascal-case": "1.2.10" } }, "sha512-KNrWeZzQT+gh73V1LnmgTkjK7V+tMRjLCc6VrGwkqbiRdnGVIWBUgIvVnvnaVCxIvZ/2Ke8DCmgPirlQcCqD3Q=="], + "text-camel-case": ["text-camel-case@1.2.11", "", { "dependencies": { "text-pascal-case": "^1.2.11" } }, "sha512-2ZsM/gOlB1tyza+8lGLvs6gtPuZ9qEYuKPa+gwo38m65wkY4k323SK4hT7ku8r5wIKyspUYIWSk1aB9/Jjxr7A=="], - "text-capital-case": ["text-capital-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-yvViUJKSSQcRO58je224bhPHg/Hij9MEY43zuKShtFzrPwW/fOAarUJ5UkTMSB81AOO1m8q+JiFdxMF4etKZbA=="], + "text-capital-case": ["text-capital-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-30A7B7+VUvevEmPE0xWK1Z2z0ncl/JTjSUBLfjpoXrkwuPpmNTVbjHShRTN3cX9GIuZn/P3jvR+TO9JiTZcl8A=="], - "text-case": ["text-case@1.2.10", "", { "dependencies": { "text-camel-case": "1.2.10", "text-capital-case": "1.2.10", "text-constant-case": "1.2.10", "text-dot-case": "1.2.10", "text-header-case": "1.2.10", "text-is-lower-case": "1.2.10", "text-is-upper-case": "1.2.10", "text-kebab-case": "1.2.10", "text-lower-case": "1.2.10", "text-lower-case-first": "1.2.10", "text-no-case": "1.2.10", "text-param-case": "1.2.10", "text-pascal-case": "1.2.10", "text-path-case": "1.2.10", "text-sentence-case": "1.2.10", "text-snake-case": "1.2.10", "text-swap-case": "1.2.10", "text-title-case": "1.2.10", "text-upper-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-5bY3Ks/u7OJ5YO69iyXrG5Xf2wUZeyko7U78nPUnYoSeuNeAfA5uAix5hTspfkl6smm3yCBObrex+kFvzeIcJg=="], + "text-case": ["text-case@1.2.11", "", { "dependencies": { "text-camel-case": "^1.2.11", "text-capital-case": "^1.2.11", "text-constant-case": "^1.2.11", "text-dot-case": "^1.2.11", "text-header-case": "^1.2.11", "text-is-lower-case": "^1.2.11", "text-is-upper-case": "^1.2.11", "text-kebab-case": "^1.2.11", "text-lower-case": "^1.2.11", "text-lower-case-first": "^1.2.11", "text-no-case": "^1.2.11", "text-param-case": "^1.2.11", "text-pascal-case": "^1.2.11", "text-path-case": "^1.2.11", "text-sentence-case": "^1.2.11", "text-snake-case": "^1.2.11", "text-swap-case": "^1.2.11", "text-title-case": "^1.2.11", "text-upper-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-LbdWNQeuXWbfav+pxBxvaefkziffMYeSA53BHp52cgJa9rjiC0dkjum9AKrH8iQWoQJ4InGPSGexLeerGFaZ1Q=="], - "text-constant-case": ["text-constant-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case": "1.2.10" } }, "sha512-/OfU798O2wrwKN9kQf71WhJeAlklGnbby0Tupp+Ez9NXymW+6oF9LWDRTkN+OreTmHucdvp4WQd6O5Rah5zj8A=="], + "text-constant-case": ["text-constant-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case": "^1.2.11" } }, "sha512-XnTBILsa7UpMWncUCchqIybZlg15FUcrlyNaWIJ8ybPy54qcoN513EXFswueyizuAgyJFXPCwwSFbSji6kw/Uw=="], "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], - "text-dot-case": ["text-dot-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-vf4xguy5y6e39RlDZeWZFMDf2mNkR23VTSVb9e68dUSpfJscG9/1YWWpW3n8TinzQxBZlsn5sT5olL33MvvQXw=="], + "text-dot-case": ["text-dot-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-7SLKiT45KZO0qad0+p+GvC0+F+6pZ851HJcTcBJiSF88HsK/e1qErlGLtVBT6hkTHIaAj48WfSyQr4lZRv1xJQ=="], - "text-header-case": ["text-header-case@1.2.10", "", { "dependencies": { "text-capital-case": "1.2.10" } }, "sha512-sVb1NY9bwxtu+Z7CVyWbr+I0AkWtF0kEHL/Zz5V2u/WdkjK5tKBwl5nXf0NGy9da4ZUYTBb+TmQpOIqihzvFMQ=="], + "text-header-case": ["text-header-case@1.2.11", "", { "dependencies": { "text-capital-case": "^1.2.11" } }, "sha512-7OBHd2g7X+aH6rXMC3cANFh6yvhXjXkyumw2NaRwJRIk343pP2e1SQCTCfowPDmmi8wkZVqz1fdWNq5LwvcBOQ=="], - "text-is-lower-case": ["text-is-lower-case@1.2.10", "", {}, "sha512-dMTeTgrdWWfYf3fKxvjMkDPuXWv96cWbd1Uym6Zjv9H855S1uHxjkFsGbTYJ2tEK0NvAylRySTQlI6axlcMc4w=="], + "text-is-lower-case": ["text-is-lower-case@1.2.11", "", {}, "sha512-dBqPAkNmX7eTM7ZbS3D/UBCQ5i9EXt5tujF2wIGGbZ1+aN8bY7Qda4mDpxgd6Hbzf/z10uQWRNzupl99wFQ8CQ=="], - "text-is-upper-case": ["text-is-upper-case@1.2.10", "", {}, "sha512-PGD/cXoXECGAY1HVZxDdmpJUW2ZUAKQ6DTamDfCHC9fc/z4epOz0pB/ThBnjJA3fz+d2ApkMjAfZDjuZFcodzg=="], + "text-is-upper-case": ["text-is-upper-case@1.2.11", "", {}, "sha512-MZeUIYEYfKZ2FSeg0vnHCH4mHXLgGzes+iz2K+4BYnhnkEa2svKA1nNjQAqTUiVNHOPqPCuzmUr1LsyQZ73uyA=="], - "text-kebab-case": ["text-kebab-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-3XZJAApx5JQpUO7eXo7GQ2TyRcGw3OVbqxz6QJb2h+N8PbLLbz3zJVeXdGrhTkoUIbkSZ6PmHx6LRDaHXTdMcA=="], + "text-kebab-case": ["text-kebab-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-RIg9iN6VwH+JrX9dFdm1nd1efPGR9LjNc0CiQz496sQETeKGkDEzxES/ZzxbkerrAL2DFEMGdLXckzDz1OEDBQ=="], - "text-lower-case": ["text-lower-case@1.2.10", "", {}, "sha512-c9j5pIAN3ObAp1+4R7970e1bgtahTRF/5ZQdX2aJBuBngYTYZZIck0NwFXUKk5BnYpLGsre5KFHvpqvf4IYKgg=="], + "text-lower-case": ["text-lower-case@1.2.11", "", {}, "sha512-txTy6y0y8M23Lhf0mk8WcvXTqlf4OQ3AGnDsRB6o3uMNfIa0CJDol2s1PdKNa63rt5B2277zkZCCn6Xeq//big=="], - "text-lower-case-first": ["text-lower-case-first@1.2.10", "", {}, "sha512-Oro84jZPDLD9alfdZWmtFHYTvCaaSz2o4thPtjMsK4GAkTyVg9juYXWj0y0YFyjLYGH69muWsBe4/MR5S7iolw=="], + "text-lower-case-first": ["text-lower-case-first@1.2.11", "", {}, "sha512-QR483XLyuyIpq8tKu1ds3Q1jfsgfaa/p9rtoQKHe6Rv5ah9ic/SUzTGN0MQ7UIS9APADd8SUPn5TTh1Z2/ACyg=="], - "text-no-case": ["text-no-case@1.2.10", "", { "dependencies": { "text-lower-case": "1.2.10" } }, "sha512-4/m79pzQrywrwEG5lCULY1lQvFY+EKjhH9xSMT6caPK5plqzm9Y7rXyv+UXPd3s9qH6QODZnvsAYWW3M0JgxRA=="], + "text-no-case": ["text-no-case@1.2.11", "", { "dependencies": { "text-lower-case": "^1.2.11" } }, "sha512-wazS7FEq0Ct3aJzeE8MEMcSs0eW4+/X/fwdotv/rG66bLS+g1T0pa0gUsbBGjjLFs191AIXVIry+bYE0uaaBBQ=="], - "text-param-case": ["text-param-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-hkavcLsRRzZcGryPAshct1AwIOMj/FexYjMaLpGZCYYBn1lcZEeyMzJZPSckzkOYpq35LYSQr3xZto9XU5OAsw=="], + "text-param-case": ["text-param-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-3EMMAMLSz/mJXOnATNnrS+dZAvghpq09VhOVYDOkUnbm5zlYc6iU5AZOKVDpiAVVllQ9P1h5IKVZzsEYrdIRGw=="], - "text-pascal-case": ["text-pascal-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10" } }, "sha512-/kynZD8vTYOmm/RECjIDaz3qYEUZc/N/bnC79XuAFxwXjdNVjj/jGovKJLRzqsYK/39N22XpGcVmGg7yIrbk6w=="], + "text-pascal-case": ["text-pascal-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11" } }, "sha512-BNhQ1O/g/Q4dH5gPyLIJLDLDknl2dipBwV629ScsiZCKJaCLGXYhTXp23rp9Htg3O5OSSsiU3mqDKq+pBmwTSw=="], - "text-path-case": ["text-path-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-vbKdRCaVEeOaW6sm24QP9NbH7TS9S4ZQ3u19H8eylDox7m2HtFwYIBjAPv+v3z4I/+VjrMy9LB54lNP1uEqRHw=="], + "text-path-case": ["text-path-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-FsJU4BmMdtLtmnBK/XRJPqTwLF8yFiTEClHjxlQjSAG5Xt9R4p6D1WNaM1CI2dG5Lr4rsFM4jiVC620m0AsRbw=="], - "text-sentence-case": ["text-sentence-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-NO4MRlbfxFhl9QgQLuCL4xHmvE7PUWHVPWsZxQ5nzRtDjXOUllWvtsvl8CP5tBEvBmzg0kwfflxfhRtr5vBQGg=="], + "text-sentence-case": ["text-sentence-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-ApiVsvdLy+Wb8x7mZRVuoy8VO12jJ22G2djVM3ZZbUhXVIkqGgHxmiXRwdhRPoWGojK9n53m7jviJgBVNdRn+g=="], - "text-snake-case": ["text-snake-case@1.2.10", "", { "dependencies": { "text-dot-case": "1.2.10" } }, "sha512-6ttMZ+B9jkHKun908HYr4xSvEtlbfJJ4MvpQ06JEKRGhwjMI0x8t2Wywp+MEzN6142O6E/zKhra18KyBL6cvXA=="], + "text-snake-case": ["text-snake-case@1.2.11", "", { "dependencies": { "text-dot-case": "^1.2.11" } }, "sha512-NOEQvjyyVABB41SS8dUx423Y6hWS+Z4TrAAJg1xzCkOD3q9y0JtdJjCvCA1FWI8oDu+HiIOp/N446uDM8j54XQ=="], - "text-swap-case": ["text-swap-case@1.2.10", "", {}, "sha512-vO3jwInIk0N77oEFakYZ2Hn/llTmRwf2c3RvkX/LfvmLWVp+3QcIc6bwUEtbqGQ5Xh2okjFhYrfkHZstVc3N4Q=="], + "text-swap-case": ["text-swap-case@1.2.11", "", {}, "sha512-PBmC5xvZdDZ4suikydpeXH0s4JV2XHelMj9/OEXEbA3oLpdV2A+B4BspVDWVw7C2Gi5eCareqk/7EE8I1/WwgQ=="], "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - "text-title-case": ["text-title-case@1.2.10", "", { "dependencies": { "text-no-case": "1.2.10", "text-upper-case-first": "1.2.10" } }, "sha512-bqA+WWexUMWu9A3fdNar+3GXXW+c5xOvMyuK5hOx/w0AlqhyQptyCrMFjGB8Fd9dxbryBNmJ+5rWtC1OBDxlaA=="], + "text-title-case": ["text-title-case@1.2.11", "", { "dependencies": { "text-no-case": "^1.2.11", "text-upper-case-first": "^1.2.11" } }, "sha512-V1GZy0XlqdkYUQm0tqm1jqtYlXJqFVMreBCTUReOaz8d/JbozTSpZrcakIeV8+1bN7LsvfhPFhA5zREiax6YIA=="], - "text-upper-case": ["text-upper-case@1.2.10", "", {}, "sha512-L1AtZ8R+jtSMTq0Ffma9R4Rzbrc3iuYW89BmWFH41AwnDfRmEBlBOllm1ZivRLQ/6pEu2p+3XKBHx9fsMl2CWg=="], + "text-upper-case": ["text-upper-case@1.2.11", "", {}, "sha512-BfTL7yB1YIRlVGNdZUvno013hOq2cRs07fDR2ApppOXRDuKrEmsLDEY82xXlDzQHELp0jexqkI+NeyPIl6MtMw=="], - "text-upper-case-first": ["text-upper-case-first@1.2.10", "", {}, "sha512-VXs7j7BbpKwvolDh5fwpYRmMrUHGkxbY8E90fhBzKUoKfadvWmPT/jFieoZ4UPLzr208pXvQEFbb2zO9Qzs9Fg=="], + "text-upper-case-first": ["text-upper-case-first@1.2.11", "", {}, "sha512-vgfbwKo8TEJbRsapR9LWWvIJRnv8u9aXVa6cyYOAQQmurCx54Cnt59x5fKdiq+hFaBJ51AbzCgMpbP3p65/pHQ=="], "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + "thread-stream": ["thread-stream@3.2.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw=="], "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], @@ -5792,7 +5790,7 @@ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tinygradient": ["tinygradient@1.1.5", "", { "dependencies": { "@types/tinycolor2": "^1.4.0", "tinycolor2": "^1.0.0" } }, "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw=="], @@ -5864,13 +5862,13 @@ "tsutils": ["tsutils@3.21.0", "", { "dependencies": { "tslib": "^1.8.1" }, "peerDependencies": { "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA=="], - "tsx": ["tsx@4.22.3", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg=="], + "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], "tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo": ["turbo@2.9.14", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.14", "@turbo/darwin-arm64": "2.9.14", "@turbo/linux-64": "2.9.14", "@turbo/linux-arm64": "2.9.14", "@turbo/windows-64": "2.9.14", "@turbo/windows-arm64": "2.9.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw=="], + "turbo": ["turbo@2.9.16", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.16", "@turbo/darwin-arm64": "2.9.16", "@turbo/linux-64": "2.9.16", "@turbo/linux-arm64": "2.9.16", "@turbo/windows-64": "2.9.16", "@turbo/windows-arm64": "2.9.16" }, "bin": { "turbo": "bin/turbo" } }, "sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], @@ -5880,7 +5878,7 @@ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + "type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -5890,13 +5888,13 @@ "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], - "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="], "typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "typescript-eslint": ["typescript-eslint@8.59.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.4", "@typescript-eslint/parser": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/utils": "8.59.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ=="], + "typescript-eslint": ["typescript-eslint@8.60.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.1", "@typescript-eslint/parser": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/utils": "8.60.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA=="], "typescript-event-target": ["typescript-event-target@1.1.2", "", {}, "sha512-TvkrTUpv7gCPlcnSoEwUVUBwsdheKm+HF5u2tPAKubkIGMfovdSizCTaZRY/NhR8+Ijy8iZZUapbVQAsNrkFrw=="], @@ -5918,9 +5916,9 @@ "undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="], - "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], + "undici": ["undici@7.27.2", "", {}, "sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -5952,7 +5950,7 @@ "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -5982,7 +5980,7 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "use-stick-to-bottom": ["use-stick-to-bottom@1.1.4", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2w/lydkrwhWMv1vCaEhYbzMDhgbwIodHpAHPV0/xKJErRkbjDEUe1EWmvr6Fwb+qhiERjc1EWgAEZaSaF69CpA=="], + "use-stick-to-bottom": ["use-stick-to-bottom@1.1.6", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-z3Up8jYQGTkUCsGBnwg6/wj70KgXoW5Kz1AAc1j8MtQuYMBo6ZsdhrIXoegxa7gaMMilgQYyTohTrt3p94jHog=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], @@ -6042,15 +6040,15 @@ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - "web-vitals": ["web-vitals@5.2.0", "", {}, "sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA=="], + "web-vitals": ["web-vitals@5.3.0", "", {}, "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g=="], "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.1", "", {}, "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "webpack": ["webpack@5.106.2", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.1", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA=="], + "webpack": ["webpack@5.107.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.22.0", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.5.0", "watchpack": "^2.5.1", "webpack-sources": "^3.5.0" }, "peerDependencies": { "webpack-cli": "*" }, "optionalPeers": ["webpack-cli"], "bin": { "webpack": "bin/webpack.js" } }, "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ=="], - "webpack-sources": ["webpack-sources@3.4.1", "", {}, "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A=="], + "webpack-sources": ["webpack-sources@3.5.0", "", {}, "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -6072,7 +6070,7 @@ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], "widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], @@ -6094,7 +6092,7 @@ "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], - "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], @@ -6158,31 +6156,25 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "zod-v4": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "zod-validation-error": ["zod-validation-error@1.5.0", "", { "peerDependencies": { "zod": "^3.18.0" } }, "sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw=="], - "zustand": ["zustand@5.0.13", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ=="], + "zustand": ["zustand@5.0.14", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], "@ai-sdk/provider-utils-v5/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/provider-utils-v5/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@ai-sdk/provider-utils-v5/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], - "@ai-sdk/provider-utils-v6/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@ai-sdk/provider-utils-v6/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], - "@ai-sdk/ui-utils-v5/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], + "@antfu/install-pkg/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], - "@ai-sdk/ui-utils-v5/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], + "@antfu/ni/ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], - "@antfu/install-pkg/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], - - "@antfu/ni/ansis": ["ansis@4.3.0", "", {}, "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg=="], - - "@antfu/ni/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "@antfu/ni/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], "@anthropic-ai/claude-agent-sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], @@ -6196,7 +6188,7 @@ "@artilleryio/int-core/socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], - "@artilleryio/int-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "@artilleryio/int-core/ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="], "@asyncapi/parser/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], @@ -6204,7 +6196,7 @@ "@autumn/auth/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], - "@autumn/leaf/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@autumn/leaf/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@autumn/leaf/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -6218,9 +6210,7 @@ "@autumn/server/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="], - "@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], - - "@autumn/server/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/server/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], @@ -6234,21 +6224,25 @@ "@autumn/shared/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@autumn/vite/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + "@autumn/vite/@types/node": ["@types/node@22.19.20", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw=="], "@autumn/vite/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/vite/@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "@autumn/vite/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "@autumn/website/eslint": ["eslint@10.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ=="], + "@autumn/website/eslint": ["eslint@10.4.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw=="], + + "@autumn/website/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "@autumn/website/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "@autumn/website/shiki": ["shiki@4.1.0", "", { "dependencies": { "@shikijs/core": "4.1.0", "@shikijs/engine-javascript": "4.1.0", "@shikijs/engine-oniguruma": "4.1.0", "@shikijs/langs": "4.1.0", "@shikijs/themes": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q=="], + "@autumn/website/shiki": ["shiki@4.2.0", "", { "dependencies": { "@shikijs/core": "4.2.0", "@shikijs/engine-javascript": "4.2.0", "@shikijs/engine-oniguruma": "4.2.0", "@shikijs/langs": "4.2.0", "@shikijs/themes": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ=="], "@autumn/website/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], @@ -6378,6 +6372,8 @@ "@better-auth/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@better-auth/dash/@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="], + "@better-auth/drizzle-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="], "@better-auth/kysely-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="], @@ -6396,7 +6392,7 @@ "@better-auth/prisma-adapter/@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="], - "@datadog/datadog-api-client/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@datadog/datadog-api-client/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], @@ -6432,6 +6428,8 @@ "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], + "@infisical/sdk/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.600.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.600.0", "@aws-sdk/client-sso": "3.598.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/credential-provider-cognito-identity": "3.600.0", "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", "@aws-sdk/credential-provider-ini": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/credential-provider-process": "3.598.0", "@aws-sdk/credential-provider-sso": "3.598.0", "@aws-sdk/credential-provider-web-identity": "3.598.0", "@aws-sdk/types": "3.598.0", "@smithy/credential-provider-imds": "^3.1.1", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-cC9uqmX0rgx1efiJGqeR+i0EXr8RQ5SAzH7M45WNBZpYiLEe6reWgIYJY9hmOxuaoMdWSi8kekuN3IjTIORRjw=="], "@infisical/sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -6448,25 +6446,23 @@ "@langchain/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@langchain/langgraph/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "@langchain/langgraph/uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], - "@langchain/langgraph-checkpoint/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "@langchain/langgraph-checkpoint/uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], "@langchain/langgraph-sdk/p-queue": ["p-queue@9.3.0", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang=="], "@langchain/langgraph-sdk/p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], - "@langchain/langgraph-sdk/uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + "@langchain/langgraph-sdk/uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], "@mastra/braintrust/braintrust": ["braintrust@2.2.2", "", { "dependencies": { "@ai-sdk/provider": "^1.1.3", "@next/env": "^14.2.3", "@types/nunjucks": "^3.2.6", "@vercel/functions": "^1.0.2", "ajv": "^8.17.1", "argparse": "^2.0.1", "boxen": "^8.0.1", "chalk": "^4.1.2", "cli-progress": "^3.12.0", "cli-table3": "^0.6.5", "cors": "^2.8.5", "dotenv": "^16.4.5", "esbuild": "^0.27.0", "eventsource-parser": "^1.1.2", "express": "^4.21.2", "graceful-fs": "^4.2.11", "http-errors": "^2.0.0", "minimatch": "^9.0.3", "mustache": "^4.2.0", "nunjucks": "^3.2.4", "pluralize": "^8.0.0", "simple-git": "^3.21.0", "source-map": "^0.7.4", "termi-link": "^1.0.1", "uuid": "^9.0.1", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "zod": "^3.25.34 || ^4.0" }, "bin": { "braintrust": "dist/cli.js" } }, "sha512-g8TPnfZb7X8ziJG3w2iYRBMiIbSTV6YW79rjhDyDAeVCwa4hq52ns4JzQeQTPRWusm7vE3gXAEgIxuBc9q18uQ=="], "@mastra/core/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], - "@mastra/core/hono": ["hono@4.12.22", "", {}, "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw=="], - "@mastra/core/p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="], - "@mendable/firecrawl-js/axios": ["axios@1.15.2", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A=="], + "@mendable/firecrawl-js/axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], "@mermaid-js/parser/@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], @@ -6480,6 +6476,8 @@ "@mintlify/cli/inquirer": ["inquirer@12.3.0", "", { "dependencies": { "@inquirer/core": "^10.1.2", "@inquirer/prompts": "^7.2.1", "@inquirer/type": "^3.0.2", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", "run-async": "^3.0.0", "rxjs": "^7.8.1" }, "peerDependencies": { "@types/node": ">=18" } }, "sha512-3NixUXq+hM8ezj2wc7wC37b32/rHq1MwNZDYdvx+d6jokOD+r+i8Q4Pkylh9tISYP114A128LCX8RKhopC5RfQ=="], + "@mintlify/cli/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@mintlify/cli/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "@mintlify/cli/posthog-node": ["posthog-node@5.17.2", "", { "dependencies": { "@posthog/core": "1.7.1" } }, "sha512-lz3YJOr0Nmiz0yHASaINEDHqoV+0bC3eD8aZAG+Ky292dAnVYul+ga/dMX8KCBXg8hHfKdxw0SztYD5j6dgUqQ=="], @@ -6500,7 +6498,7 @@ "@mintlify/common/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="], - "@mintlify/common/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "@mintlify/common/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "@mintlify/common/mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="], @@ -6520,18 +6518,20 @@ "@mintlify/common/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], - "@mintlify/common/unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], - "@mintlify/link-rot/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], "@mintlify/mdx/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@mintlify/models/axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], + "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + "@mintlify/prebuild/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@mintlify/prebuild/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "@mintlify/prebuild/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], @@ -6548,16 +6548,22 @@ "@mintlify/previewing/ink": ["ink@6.3.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-2CbJAa7XeziZYe6pDS5RVLirRY28iSGMQuEV8jRU5NQsONQNfcR/BZHHc9vkMg2lGYTHTM2pskxC1YmY28p6bQ=="], + "@mintlify/previewing/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@mintlify/previewing/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], "@mintlify/previewing/socket.io": ["socket.io@4.8.0", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA=="], + "@mintlify/previewing/tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "@mintlify/previewing/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], "@mintlify/previewing/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], "@mintlify/scraping/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="], + "@mintlify/scraping/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@mintlify/scraping/mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], "@mintlify/scraping/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], @@ -6570,11 +6576,13 @@ "@mintlify/scraping/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], + "@mintlify/validation/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@mintlify/validation/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], "@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], - "@modelcontextprotocol/sdk/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@modelcontextprotocol/sdk/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -6772,7 +6780,7 @@ "@posthog/ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.78.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w=="], - "@posthog/ai/openai": ["openai@6.38.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-AoMplt2UalrpgUDMh3L09QWjNRlgJPipclQvA6sYAaeF6nHNBMgmikAZGmcYLn8on4d9sQY9Q8bOLfrBS7Lc8g=="], + "@posthog/ai/openai": ["openai@6.42.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg=="], "@posthog/ai/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], @@ -6780,103 +6788,21 @@ "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="], - "@radix-ui/react-accordion/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-aspect-ratio/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-checkbox/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-context-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-form/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-hover-card/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-menubar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-navigation-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-one-time-password-field/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-password-toggle-field/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-radio-group/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-scroll-area/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-slider/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-switch/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-toast/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-toggle/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-toggle-group/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-toolbar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@react-grab/cli/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - "@react-grab/cli/ora": ["ora@9.4.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ=="], - "@react-grab/cli/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "@react-grab/cli/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], "@reduxjs/toolkit/immer": ["immer@11.1.8", "", {}, "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA=="], - "@sentry-internal/browser-utils/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry-internal/browser-utils/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], - "@sentry-internal/feedback/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry-internal/feedback/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], - "@sentry-internal/replay/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry-internal/replay/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], - "@sentry-internal/replay-canvas/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry-internal/replay-canvas/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], - "@sentry/browser/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry/browser/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], "@sentry/bundler-plugin-core/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -6914,17 +6840,17 @@ "@sentry/opentelemetry/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - "@sentry/react/@sentry/core": ["@sentry/core@10.53.1", "", {}, "sha512-XG4ezlkyuAPjBC5+9kXC94rXXuqYTw9NRhfaDHssbTFaGnqBR8vQX2UUgZfY7ucbeelRDGfBu1sywoU+mB04uA=="], + "@sentry/react/@sentry/core": ["@sentry/core@10.56.0", "", {}, "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ=="], "@sentry/vite-plugin/unplugin": ["unplugin@1.0.1", "", { "dependencies": { "acorn": "^8.8.1", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.5.0" } }, "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA=="], - "@shikijs/primitive/@shikijs/types": ["@shikijs/types@4.1.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA=="], + "@shikijs/primitive/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], - "@slack/logger/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@slack/logger/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@slack/socket-mode/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@slack/socket-mode/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@slack/web-api/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@slack/web-api/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@slack/web-api/@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], @@ -7044,11 +6970,11 @@ "@tailwindcss/node/tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], @@ -7062,7 +6988,7 @@ "@tailwindcss/vite/tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], - "@tinybirdco/sdk/@clack/prompts": ["@clack/prompts@1.4.0", "", { "dependencies": { "@clack/core": "1.3.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA=="], + "@tinybirdco/sdk/@clack/prompts": ["@clack/prompts@1.5.1", "", { "dependencies": { "@clack/core": "1.4.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw=="], "@tinybirdco/sdk/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -7082,7 +7008,7 @@ "@trigger.dev/core/@s2-dev/streamstore": ["@s2-dev/streamstore@0.22.5", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1", "debug": "^4.4.3" } }, "sha512-GqdOKIbIoIxT+40fnKzHbrsHB6gBqKdECmFe7D3Ojk4FoN1Hu0LhFzZv6ZmVMjoHHU+55debS1xSWjZwQmbIyQ=="], - "@trigger.dev/core/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "@trigger.dev/core/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], "@trigger.dev/core/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], @@ -7092,49 +7018,49 @@ "@trigger.dev/core/socket.io": ["socket.io@4.7.4", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw=="], + "@trigger.dev/schema-to-json/effect": ["effect@3.21.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-RqwU7WnJ6CqYhyjpOVJA5vh1Sgkn6eVECO6mnD0EjlbWcC2M3LJaPglXXr13Rdo/Y+B+wTEPzGRYFNL2xKxNeQ=="], + "@trigger.dev/sdk/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@types/body-parser/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/body-parser/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/buffer-from/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/buffer-from/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/cacheable-request/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/cacheable-request/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/chai-http/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/chai-http/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/connect/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/connect/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/cors/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/cors/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/es-aggregate-error/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/es-aggregate-error/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/express-serve-static-core/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/express-serve-static-core/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/keyv/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/keyv/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/mysql/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/mysql/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/node-fetch/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/node-fetch/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/pg/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/pg/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/react-dom/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@types/responselike/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/responselike/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/send/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/send/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/serve-static/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/serve-static/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/set-cookie-parser/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/set-cookie-parser/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/superagent/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/superagent/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/tedious/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/tedious/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/ws/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@types/ws/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], - - "@types/yauzl/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@types/yauzl/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -7148,6 +7074,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "agent-install/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "aggregate-error/clean-stack": ["clean-stack@4.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg=="], "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -7192,7 +7120,7 @@ "artillery-plugin-publish-metrics/prom-client": ["prom-client@14.2.0", "", { "dependencies": { "tdigest": "^0.1.1" } }, "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA=="], - "atmn/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], + "atmn/@tanstack/react-query": ["@tanstack/react-query@5.101.0", "", { "dependencies": { "@tanstack/query-core": "5.101.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg=="], "atmn/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], @@ -7204,11 +7132,11 @@ "atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "autoevals/openai": ["openai@6.38.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-AoMplt2UalrpgUDMh3L09QWjNRlgJPipclQvA6sYAaeF6nHNBMgmikAZGmcYLn8on4d9sQY9Q8bOLfrBS7Lc8g=="], + "autoevals/openai": ["openai@6.42.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg=="], - "autumn-js/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + "autumn-js/@types/node": ["@types/node@22.19.20", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw=="], - "autumn-js/next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="], + "autumn-js/next": ["next@15.5.19", "", { "dependencies": { "@next/env": "15.5.19", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.19", "@next/swc-darwin-x64": "15.5.19", "@next/swc-linux-arm64-gnu": "15.5.19", "@next/swc-linux-arm64-musl": "15.5.19", "@next/swc-linux-x64-gnu": "15.5.19", "@next/swc-linux-x64-musl": "15.5.19", "@next/swc-win32-arm64-msvc": "15.5.19", "@next/swc-win32-x64-msvc": "15.5.19", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-xNOW6tYshGX1/Oi3F8uuk4gpDeWsSUE/1Z0G5uUMekIxaQ0xc03UXd9II0VQHYMWviMeA0OHpJFAKsHf8bTYVg=="], "autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], @@ -7240,8 +7168,6 @@ "better-call/set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], - "better-call/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -7260,7 +7186,11 @@ "braintrust/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "bun-types/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "bullmq/ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="], + + "bullmq/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + + "bun-types/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -7276,9 +7206,7 @@ "camelcase-keys/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], - "checkout/@tanstack/react-query": ["@tanstack/react-query@5.100.11", "", { "dependencies": { "@tanstack/query-core": "5.100.11" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg=="], - - "checkout/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "checkout/@tanstack/react-query": ["@tanstack/react-query@5.101.0", "", { "dependencies": { "@tanstack/query-core": "5.101.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg=="], "checkout/decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], @@ -7338,14 +7266,16 @@ "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "engine.io/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "engine.io/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "engine.io/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "engine.io/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], "engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], + "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], @@ -7420,7 +7350,7 @@ "eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@2.1.0", "", {}, "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="], - "eventsource/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + "eventsource/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], "execa/figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], @@ -7514,7 +7444,7 @@ "jake/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], - "jest-worker/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "jest-worker/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], @@ -7564,7 +7494,7 @@ "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], - "msw/@inquirer/confirm": ["@inquirer/confirm@6.0.13", "", { "dependencies": { "@inquirer/core": "^11.1.10", "@inquirer/type": "^4.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw=="], + "msw/@inquirer/confirm": ["@inquirer/confirm@6.1.1", "", { "dependencies": { "@inquirer/core": "^11.2.1", "@inquirer/type": "^4.0.7" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ=="], "msw/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], @@ -7650,19 +7580,11 @@ "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - "posthog-js/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.208.0", "@opentelemetry/otlp-transformer": "0.208.0", "@opentelemetry/sdk-logs": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg=="], - - "posthog-js/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], - - "posthog-js/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA=="], - "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "protobufjs/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "protobufjs/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], @@ -7678,21 +7600,21 @@ "puppeteer/@puppeteer/browsers": ["@puppeteer/browsers@2.3.0", "", { "dependencies": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA=="], - "puppeteer/cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + "puppeteer/cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], "puppeteer/devtools-protocol": ["devtools-protocol@0.0.1312386", "", {}, "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA=="], "puppeteer/puppeteer-core": ["puppeteer-core@22.14.0", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "chromium-bidi": "0.6.2", "debug": "^4.3.5", "devtools-protocol": "0.0.1312386", "ws": "^8.18.0" } }, "sha512-rl4tOY5LcA3e374GAlsGGHc05HL3eGNf5rZ+uxkl6id9zVZKcwcp1Z+Nd6byb6WPiPeecT/dwz8f/iUm+AZQSw=="], - "radix-ui/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "react-day-picker/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], - "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "react-devtools-core/shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="], + + "react-devtools-core/ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="], "react-dom/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], @@ -7704,7 +7626,7 @@ "react-email/log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="], - "react-email/next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="], + "react-email/next": ["next@15.5.19", "", { "dependencies": { "@next/env": "15.5.19", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.19", "@next/swc-darwin-x64": "15.5.19", "@next/swc-linux-arm64-gnu": "15.5.19", "@next/swc-linux-arm64-musl": "15.5.19", "@next/swc-linux-x64-gnu": "15.5.19", "@next/swc-linux-x64-musl": "15.5.19", "@next/swc-win32-arm64-msvc": "15.5.19", "@next/swc-win32-x64-msvc": "15.5.19", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-xNOW6tYshGX1/Oi3F8uuk4gpDeWsSUE/1Z0G5uUMekIxaQ0xc03UXd9II0VQHYMWviMeA0OHpJFAKsHf8bTYVg=="], "react-email/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -7732,8 +7654,6 @@ "rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "run-jxa/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], @@ -7744,9 +7664,7 @@ "sdk-test/@biomejs/biome": ["@biomejs/biome@2.2.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.0", "@biomejs/cli-darwin-x64": "2.2.0", "@biomejs/cli-linux-arm64": "2.2.0", "@biomejs/cli-linux-arm64-musl": "2.2.0", "@biomejs/cli-linux-x64": "2.2.0", "@biomejs/cli-linux-x64-musl": "2.2.0", "@biomejs/cli-win32-arm64": "2.2.0", "@biomejs/cli-win32-x64": "2.2.0" }, "bin": { "biome": "bin/biome" } }, "sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw=="], - "sdk-test/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="], - - "sdk-test/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "sdk-test/@types/node": ["@types/node@20.19.42", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg=="], "sdk-test/next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], @@ -7762,7 +7680,7 @@ "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], - "shadcn/cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + "shadcn/cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], "shadcn/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -7776,7 +7694,7 @@ "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], - "socket.io-adapter/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "socket.io-adapter/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], "socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], @@ -7966,7 +7884,7 @@ "xo/run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "xo/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + "xo/semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], "xo/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], @@ -7988,17 +7906,13 @@ "zod-from-json-schema/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@ai-sdk/ui-utils-v5/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - - "@ai-sdk/ui-utils-v5/@ai-sdk/provider-utils/secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], - "@artilleryio/int-core/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "@artilleryio/int-core/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], "@artilleryio/int-core/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - "@artilleryio/int-core/socket.io-client/engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="], + "@artilleryio/int-core/socket.io-client/engine.io-client": ["engine.io-client@6.6.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg=="], "@autumn/auth/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -8036,7 +7950,7 @@ "@autumn/website/eslint/@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], - "@autumn/website/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], + "@autumn/website/eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], "@autumn/website/eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], @@ -8050,17 +7964,17 @@ "@autumn/website/eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "@autumn/website/shiki/@shikijs/core": ["@shikijs/core@4.1.0", "", { "dependencies": { "@shikijs/primitive": "4.1.0", "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ=="], + "@autumn/website/shiki/@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="], - "@autumn/website/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ=="], + "@autumn/website/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="], - "@autumn/website/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg=="], + "@autumn/website/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g=="], - "@autumn/website/shiki/@shikijs/langs": ["@shikijs/langs@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg=="], + "@autumn/website/shiki/@shikijs/langs": ["@shikijs/langs@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ=="], - "@autumn/website/shiki/@shikijs/themes": ["@shikijs/themes@4.1.0", "", { "dependencies": { "@shikijs/types": "4.1.0" } }, "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw=="], + "@autumn/website/shiki/@shikijs/themes": ["@shikijs/themes@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0" } }, "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w=="], - "@autumn/website/shiki/@shikijs/types": ["@shikijs/types@4.1.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA=="], + "@autumn/website/shiki/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], "@aws-sdk/client-sso-oidc/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@3.1.2", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA=="], @@ -8112,15 +8026,15 @@ "@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "@google/genai/p-retry/@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], - "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "@infisical/sdk/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.600.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.600.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-8dYsnDLiD0rjujRiZZl0E57heUkHqMSFZHBi0YMs57SM8ODPxK3tahwDYZtS7bqanvFKZwGy+o9jIcij7jBOlA=="], @@ -8198,10 +8112,6 @@ "@mintlify/common/hast-util-to-html/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], - "@mintlify/common/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], - - "@mintlify/common/mdast-util-mdx-jsx/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], - "@mintlify/common/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "@mintlify/common/remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], @@ -8224,8 +8134,6 @@ "@mintlify/common/tailwindcss/sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - "@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - "@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@mintlify/link-rot/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], @@ -8568,7 +8476,7 @@ "@sentry/node/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], - "@sentry/node/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "@sentry/node/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@sentry/vite-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.5.0", "", {}, "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="], @@ -8598,7 +8506,7 @@ "@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="], - "@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "@tailwindcss/postcss/@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -8628,7 +8536,7 @@ "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA=="], - "@tinybirdco/sdk/@clack/prompts/@clack/core": ["@clack/core@1.3.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA=="], + "@tinybirdco/sdk/@clack/prompts/@clack/core": ["@clack/core@1.4.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw=="], "@tinybirdco/sdk/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -8774,7 +8682,7 @@ "artillery/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "atmn/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.100.11", "", {}, "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw=="], + "atmn/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.101.0", "", {}, "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow=="], "atmn/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SYrqVOlapDxDG7FzHBIJbfgaix+mXPkYzYGqwpz/TAhoPA7sgbfAoGLaqi3ut9N88C/OYNhEX4tjz/0PC9i1nw=="], @@ -8794,23 +8702,23 @@ "autumn-js/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "autumn-js/next/@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="], + "autumn-js/next/@next/env": ["@next/env@15.5.19", "", {}, "sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw=="], - "autumn-js/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ=="], + "autumn-js/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jx9wWlTKueHKPvVOndyr7WuaevWCkuYqsQ8gC0TMPKAVWG3MhcdMrjfo9tvIZNXd0QOUYXXvAcZ325y8Uq7uzg=="], - "autumn-js/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og=="], + "autumn-js/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-291KFcsIQ3OenRdiUDFOR6W3wezzH4auENXm1gbm1Bjd4ANMMRgxPrWTUztQN43BnVoVuMnHCrLeECIMwgFKbA=="], - "autumn-js/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw=="], + "autumn-js/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-WeH+nelQyyMeE2f8FxBRZNrGipya5zHZV2vjzfCOAYyiI6am+NbnWAAldOBFQBB2w0DjJcsvrKqoFT2b7+5YoA=="], - "autumn-js/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw=="], + "autumn-js/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-5xTOE0lDlDCSSfp+BAif7j17VRRCjWp//ZPZy6NI0QpdrhxtQnsZguSx0xAAZ0c9XZLrLLwCe/XVe5YPrRilKw=="], - "autumn-js/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A=="], + "autumn-js/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.19", "", { "os": "linux", "cpu": "x64" }, "sha512-LTxRmMgqqMv05Had879W00Fm53quiJd3Zuz8h1JSNJ3nGSlbZ/7Tjs1tKyScgN3Au3t3MyPsjPlq60fMmSHLsg=="], - "autumn-js/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA=="], + "autumn-js/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.19", "", { "os": "linux", "cpu": "x64" }, "sha512-eoNQSpA5PQfB9wBO4RA47MTDXWz1fizy9Y3Z6e4DetYIF3dvjuu8sj7aIGn/bFCU6lnFzTK34NtCaffP4NsQ7Q=="], - "autumn-js/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA=="], + "autumn-js/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-6UNt2dFuCHOe446sm/Kp69nUe8/wIhnh9bm6Xcqw4qEWCOppLMOvhTBVgvM7invVUNr4SPpP6NOQsACtn2IN9Q=="], - "autumn-js/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.18", "", { "os": "win32", "cpu": "x64" }, "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg=="], + "autumn-js/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.19", "", { "os": "win32", "cpu": "x64" }, "sha512-PhmojAHyqMne56HBLGu9dhDnHPuFmEjrXSQMM/nW0J6j849lk3ESrVtqNJcCk8CKOV7brpTTbaYAjwKPzKM69w=="], "autumn-js/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], @@ -8852,11 +8760,13 @@ "braintrust/express/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], + "bullmq/ioredis/@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="], + "bun-types/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "checkout/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.100.11", "", {}, "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw=="], + "checkout/@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.101.0", "", {}, "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow=="], "cli-progress/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -8890,6 +8800,8 @@ "engine.io/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "eslint-config-next/eslint-plugin-react-hooks/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "eslint-config-next/eslint-plugin-react-hooks/zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], @@ -8912,15 +8824,15 @@ "eslint-plugin-es/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@1.3.0", "", {}, "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="], - "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "eslint-plugin-import/tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], - "eslint-plugin-n/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "eslint-plugin-n/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], - "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], @@ -8928,7 +8840,7 @@ "eslint/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "eslint/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "eslint/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "execa/figures/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], @@ -8974,7 +8886,7 @@ "favicons/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], - "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -9022,7 +8934,7 @@ "log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], - "matcher-collection/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "matcher-collection/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "meow/read-pkg-up/find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], @@ -9038,13 +8950,13 @@ "mocha/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "mocha/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "mocha/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - "msw/@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.1.10", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", "@inquirer/type": "^4.0.5", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-a4Q5BXHQAHa9eO202sTaFCHFYVB3x5fauDuThEAdZ9gfn76pSxiKU7wWcEH0N1O0XmQvNfQNU6QXpiRxmYQx+A=="], + "msw/@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.2.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.7", "@inquirer/figures": "^2.0.7", "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA=="], - "msw/@inquirer/confirm/@inquirer/type": ["@inquirer/type@4.0.5", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q=="], + "msw/@inquirer/confirm/@inquirer/type": ["@inquirer/type@4.0.7", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g=="], - "msw/tough-cookie/tldts": ["tldts@7.0.30", "", { "dependencies": { "tldts-core": "^7.0.30" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw=="], + "msw/tough-cookie/tldts": ["tldts@7.4.2", "", { "dependencies": { "tldts-core": "^7.4.2" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw=="], "next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], @@ -9100,20 +9012,6 @@ "pkg-conf/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ=="], - - "posthog-js/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], - - "posthog-js/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - - "posthog-js/@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "posthog-js/@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - "prebuild-install/tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], "prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], @@ -9144,23 +9042,23 @@ "react-email/log-symbols/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "react-email/next/@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="], + "react-email/next/@next/env": ["@next/env@15.5.19", "", {}, "sha512-sWWluFvcv5v3Fxznmf2ZfjyoVQt/64oCnYqS90inQWGzMPK1VjvekPiz3OPHKmFT30EnHrjlbyaHLt3M0vWabw=="], - "react-email/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ=="], + "react-email/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jx9wWlTKueHKPvVOndyr7WuaevWCkuYqsQ8gC0TMPKAVWG3MhcdMrjfo9tvIZNXd0QOUYXXvAcZ325y8Uq7uzg=="], - "react-email/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og=="], + "react-email/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-291KFcsIQ3OenRdiUDFOR6W3wezzH4auENXm1gbm1Bjd4ANMMRgxPrWTUztQN43BnVoVuMnHCrLeECIMwgFKbA=="], - "react-email/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw=="], + "react-email/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-WeH+nelQyyMeE2f8FxBRZNrGipya5zHZV2vjzfCOAYyiI6am+NbnWAAldOBFQBB2w0DjJcsvrKqoFT2b7+5YoA=="], - "react-email/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw=="], + "react-email/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-5xTOE0lDlDCSSfp+BAif7j17VRRCjWp//ZPZy6NI0QpdrhxtQnsZguSx0xAAZ0c9XZLrLLwCe/XVe5YPrRilKw=="], - "react-email/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A=="], + "react-email/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.19", "", { "os": "linux", "cpu": "x64" }, "sha512-LTxRmMgqqMv05Had879W00Fm53quiJd3Zuz8h1JSNJ3nGSlbZ/7Tjs1tKyScgN3Au3t3MyPsjPlq60fMmSHLsg=="], - "react-email/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA=="], + "react-email/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.19", "", { "os": "linux", "cpu": "x64" }, "sha512-eoNQSpA5PQfB9wBO4RA47MTDXWz1fizy9Y3Z6e4DetYIF3dvjuu8sj7aIGn/bFCU6lnFzTK34NtCaffP4NsQ7Q=="], - "react-email/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA=="], + "react-email/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-6UNt2dFuCHOe446sm/Kp69nUe8/wIhnh9bm6Xcqw4qEWCOppLMOvhTBVgvM7invVUNr4SPpP6NOQsACtn2IN9Q=="], - "react-email/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.18", "", { "os": "win32", "cpu": "x64" }, "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg=="], + "react-email/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.19", "", { "os": "win32", "cpu": "x64" }, "sha512-PhmojAHyqMne56HBLGu9dhDnHPuFmEjrXSQMM/nW0J6j849lk3ESrVtqNJcCk8CKOV7brpTTbaYAjwKPzKM69w=="], "react-email/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], @@ -9370,7 +9268,7 @@ "@artilleryio/int-core/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - "@artilleryio/int-core/socket.io-client/engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "@artilleryio/int-core/socket.io-client/engine.io-client/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], "@artilleryio/int-core/socket.io-client/engine.io-client/xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], @@ -9400,8 +9298,12 @@ "@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="], + "@better-auth/cli/@better-auth/core/better-call/@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="], + "@better-auth/cli/@better-auth/core/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + "@better-auth/cli/better-auth/better-call/@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="], + "@better-auth/cli/better-auth/better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], "@dotenvx/dotenvx/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], @@ -9454,7 +9356,7 @@ "@mastra/braintrust/braintrust/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "@mastra/braintrust/braintrust/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "@mastra/braintrust/braintrust/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@mintlify/cli/ink/cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], @@ -9470,14 +9372,14 @@ "@mintlify/cli/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@mintlify/common/remark-gfm/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], - "@mintlify/common/sucrase/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "@mintlify/common/tailwindcss/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "@mintlify/common/tailwindcss/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "@mintlify/prebuild/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], + "@mintlify/previewing/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "@mintlify/previewing/got/cacheable-request/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -9570,7 +9472,7 @@ "@sentry/bundler-plugin-core/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -9604,11 +9506,11 @@ "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], - "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], - "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], @@ -9618,7 +9520,7 @@ "@trigger.dev/core/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], - "@trigger.dev/core/socket.io/engine.io/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], + "@trigger.dev/core/socket.io/engine.io/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], "@trigger.dev/core/socket.io/engine.io/cookie": ["cookie@0.4.2", "", {}, "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="], @@ -9724,6 +9626,8 @@ "eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "favicons/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "find-cache-dir/pkg-dir/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], @@ -9758,13 +9662,13 @@ "mocha/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "msw/@inquirer/confirm/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.5", "", {}, "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw=="], + "msw/@inquirer/confirm/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.7", "", {}, "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q=="], - "msw/@inquirer/confirm/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.5", "", {}, "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ=="], + "msw/@inquirer/confirm/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.7", "", {}, "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw=="], "msw/@inquirer/confirm/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], - "msw/tough-cookie/tldts/tldts-core": ["tldts-core@7.0.30", "", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], + "msw/tough-cookie/tldts/tldts-core": ["tldts-core@7.4.2", "", {}, "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA=="], "ngrok/got/cacheable-request/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], @@ -9788,18 +9692,6 @@ "pkg-conf/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - - "posthog-js/@opentelemetry/sdk-logs/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - - "posthog-js/@opentelemetry/sdk-logs/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - "prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "public-ip/got/cacheable-request/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -9842,7 +9734,7 @@ "xo/@eslint/eslintrc/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - "xo/@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "xo/@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "xo/eslint/@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], @@ -9856,7 +9748,7 @@ "xo/eslint/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - "xo/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "xo/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "xo/eslint/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -9882,7 +9774,7 @@ "@mintlify/cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@mintlify/common/sucrase/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@mintlify/common/sucrase/glob/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "@mintlify/previewing/ink/cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], @@ -9892,7 +9784,7 @@ "@prisma/config/c12/giget/nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - "@prisma/config/c12/giget/nypm/tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], + "@prisma/config/c12/giget/nypm/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], "@react-grab/cli/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -9924,7 +9816,7 @@ "atmn/eslint-plugin-react-hooks/eslint/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - "atmn/eslint-plugin-react-hooks/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "atmn/eslint-plugin-react-hooks/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "atmn/eslint-plugin-react-hooks/eslint/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -9948,10 +9840,6 @@ "pkg-conf/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], - "react-email/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 722aa3f95..117fb2802 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@types/node": "^24.9.1", + "@typescript/native-preview": "catalog:", "tsup": "^8.4.0", "typescript": "^5.8.3" } diff --git a/server/package.json b/server/package.json index d694e4511..15b23510e 100644 --- a/server/package.json +++ b/server/package.json @@ -159,8 +159,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", "@types/pg": "8.20.0", - "@types/react": "18.3.28", - "@types/react-dom": "18.3.7", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/ws": "^8.18.1", "artillery": "^2.0.30", "cross-env": "^7.0.3", From d8f8e2e6f1097c61ae830607bba1f6d3647eed39 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 11:28:04 +0100 Subject: [PATCH 24/41] updated setup integration tests --- server/tests/setup-integration-tests.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/tests/setup-integration-tests.ts b/server/tests/setup-integration-tests.ts index dca10aa10..b0d7e8a58 100644 --- a/server/tests/setup-integration-tests.ts +++ b/server/tests/setup-integration-tests.ts @@ -1,16 +1,15 @@ import { execSync } from "node:child_process"; import { loadLocalEnv } from "@/utils/envUtils"; -import { - createTestContext, - type TestContext, -} from "./utils/testInitUtils/createTestContext"; +import type { TestContext } from "./utils/testInitUtils/createTestContext"; const loadInfisicalSecrets = async () => { // `bun test:integration` wraps the run in `infisical run --env=dev`, which // already injects every secret into the parent process. Workers inherit // those, so re-running the infisical CLI per worker is redundant churn // (and a flake source). Skip when env is clearly already populated. - if (process.env.STRIPE_TEST_KEY || process.env.TESTS_ORG) return; + // CI never has the infisical CLI; this fetch is a local-dev convenience only. + if (process.env.CI || process.env.STRIPE_TEST_KEY || process.env.TESTS_ORG) + return; try { const secrets = execSync( @@ -58,6 +57,11 @@ loadLocalEnv({ force: true }); // "Default TestContext is not initialized" Proxy error from every test // scheduled on this worker. if (process.env.TESTS_ORG) { + // Dynamic import: createTestContext drags in the server init graph (db, + // redis, stripe), which unit-only lanes must never load or connect to. + const { createTestContext } = await import( + "./utils/testInitUtils/createTestContext" + ); globalThis.__autumnTestContext = await createTestContext(); console.log("--- Setup integration tests complete ---"); } From 850fca8cd2deda21f183d9eb76726499c0118ca1 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Thu, 11 Jun 2026 11:30:53 +0100 Subject: [PATCH 25/41] chore: make webhook failures more robust --- .../external/stripe/stripeWebhookRouter.ts | 4 +- .../stripeConnectSeederMiddleware.ts | 15 ++- .../webhooks/stripe-connect-seeder.test.ts | 121 ++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 server/tests/unit/webhooks/stripe-connect-seeder.test.ts diff --git a/server/src/external/stripe/stripeWebhookRouter.ts b/server/src/external/stripe/stripeWebhookRouter.ts index eea22bb3f..38d34d3a9 100644 --- a/server/src/external/stripe/stripeWebhookRouter.ts +++ b/server/src/external/stripe/stripeWebhookRouter.ts @@ -17,11 +17,11 @@ export const stripeWebhookRouter = new Hono(); stripeWebhookRouter.post( "/webhooks/stripe/:orgId/:env", stripeLegacySeederMiddleware, + stripeToAutumnCustomerMiddleware, stripeIdempotencyMiddleware, stripeWebhookEarlyAckMiddleware, stripeWebhookRefreshMiddleware, stripeSyncMiddleware, - stripeToAutumnCustomerMiddleware, stripeLoggerMiddleware, traceEnrichMiddleware, handleStripeWebhookEvent, @@ -31,11 +31,11 @@ stripeWebhookRouter.post( stripeWebhookRouter.post( "/webhooks/connect/:env", stripeConnectSeederMiddleware, + stripeToAutumnCustomerMiddleware, stripeIdempotencyMiddleware, stripeWebhookEarlyAckMiddleware, stripeWebhookRefreshMiddleware, stripeSyncMiddleware, - stripeToAutumnCustomerMiddleware, stripeLoggerMiddleware, traceEnrichMiddleware, handleStripeWebhookEvent, diff --git a/server/src/external/stripe/webhookMiddlewares/stripeConnectSeederMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeConnectSeederMiddleware.ts index a747c3326..94ccf332d 100644 --- a/server/src/external/stripe/webhookMiddlewares/stripeConnectSeederMiddleware.ts +++ b/server/src/external/stripe/webhookMiddlewares/stripeConnectSeederMiddleware.ts @@ -1,6 +1,7 @@ import { type AppEnv, AuthType, + ErrCode, type Feature, type Organization, } from "@autumn/shared"; @@ -11,6 +12,7 @@ import { initMasterStripe, } from "@/external/connect/initStripeCli.js"; import { OrgService } from "@/internal/orgs/OrgService.js"; +import RecaseError from "@/utils/errorUtils.js"; import { createStripeCli } from "../../connect/createStripeCli.js"; import type { StripeWebhookContext, @@ -100,7 +102,18 @@ export const stripeConnectSeederMiddleware = async ( }); org = data.org; features = data.features; - } catch { + } catch (error) { + // Only ack accounts genuinely not linked to an org; any other failure + // (e.g. DB outage) must 500 so Stripe retries instead of dropping the event. + const isOrgNotFound = + error instanceof RecaseError && error.code === ErrCode.OrgNotFound; + if (!isOrgNotFound) { + logger.error( + `Failed to resolve org for Stripe account ${accountId}, returning 500 for Stripe to retry: ${error}`, + ); + return c.json({ error: "Failed to resolve org for Stripe webhook" }, 500); + } + if (process.env.NODE_ENV !== "development") { logger.error( `Account ID ${accountId} not linked to any org, skipping Stripe webhook`, diff --git a/server/tests/unit/webhooks/stripe-connect-seeder.test.ts b/server/tests/unit/webhooks/stripe-connect-seeder.test.ts new file mode 100644 index 000000000..e5a22bd94 --- /dev/null +++ b/server/tests/unit/webhooks/stripe-connect-seeder.test.ts @@ -0,0 +1,121 @@ +import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"; +import { ErrCode } from "@autumn/shared"; +import { Hono } from "hono"; +import RecaseError from "@/utils/errorUtils.js"; + +const mockState = { + getByAccountId: undefined as (() => Promise) | undefined, +}; + +mock.module("@/internal/orgs/OrgService.js", () => ({ + OrgService: { + getByAccountId: async () => { + if (!mockState.getByAccountId) throw new Error("not configured"); + return mockState.getByAccountId(); + }, + }, +})); + +mock.module("@/external/connect/initStripeCli.js", () => ({ + initMasterStripe: () => ({}), + getStripeWebhookSecret: async () => "whsec_test", +})); + +mock.module("@/external/connect/createStripeCli.js", () => ({ + createStripeCli: () => ({}), +})); + +const { stripeConnectSeederMiddleware } = await import( + "@/external/stripe/webhookMiddlewares/stripeConnectSeederMiddleware.js" +); + +const originalSkipVerify = process.env.STRIPE_WEBHOOK_SKIP_VERIFY; + +type TestEnv = { Variables: { ctx: unknown } }; + +const createApp = () => { + const app = new Hono(); + + app.use("*", async (c, next) => { + c.set("ctx", { + db: {}, + logger: { error: () => {}, warn: () => {}, info: () => {} }, + }); + await next(); + }); + + let handlerRan = false; + app.post( + "/webhooks/connect/:env", + stripeConnectSeederMiddleware as never, + (c) => { + handlerRan = true; + return c.json({ processed: true }, 200); + }, + ); + + return { app, didHandlerRun: () => handlerRan }; +}; + +const postEvent = (app: Hono) => + app.request("/webhooks/connect/live", { + method: "POST", + body: JSON.stringify({ + id: "evt_test", + type: "customer.subscription.deleted", + account: "acct_test", + data: { object: {} }, + }), + }); + +describe("stripeConnectSeederMiddleware org resolution", () => { + beforeEach(() => { + process.env.STRIPE_WEBHOOK_SKIP_VERIFY = "true"; + mockState.getByAccountId = undefined; + }); + + afterAll(() => { + process.env.STRIPE_WEBHOOK_SKIP_VERIFY = originalSkipVerify; + }); + + test("returns 200 and skips processing when the account is genuinely unlinked", async () => { + mockState.getByAccountId = async () => { + throw new RecaseError({ + message: "Organization not found", + code: ErrCode.OrgNotFound, + statusCode: 404, + }); + }; + + const { app, didHandlerRun } = createApp(); + const response = await postEvent(app); + + expect(response.status).toBe(200); + expect(didHandlerRun()).toBe(false); + }); + + test("returns 500 so Stripe retries when org lookup fails for any other reason", async () => { + mockState.getByAccountId = async () => { + throw new Error("no more connections allowed (max_client_conn)"); + }; + + const { app, didHandlerRun } = createApp(); + const response = await postEvent(app); + + expect(response.status).toBe(500); + expect(didHandlerRun()).toBe(false); + }); + + test("runs the handler when the org resolves", async () => { + mockState.getByAccountId = async () => ({ + org: { id: "org_test", slug: "test-org", config: {} }, + features: [], + }); + + const { app, didHandlerRun } = createApp(); + const response = await postEvent(app); + + expect(response.status).toBe(200); + expect(didHandlerRun()).toBe(true); + }); +}); From 7fd681b562e4defb06c16bc08cfef7367479c36e Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 12:00:54 +0100 Subject: [PATCH 26/41] latest" --- bun.lock | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index a687953df..ea75d2b7a 100644 --- a/bun.lock +++ b/bun.lock @@ -5950,7 +5950,7 @@ "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -6518,6 +6518,8 @@ "@mintlify/common/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "@mintlify/common/unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + "@mintlify/link-rot/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], @@ -8134,6 +8136,8 @@ "@mintlify/common/tailwindcss/sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + "@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@mintlify/link-rot/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], From 0ed9bddde7e6a57e9ad71809d14d78d2fe99b1f6 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 11 Jun 2026 12:24:07 +0100 Subject: [PATCH 27/41] fail open check/track on hydration-gate rejections --- server/src/db/shed503OnTransientError.ts | 2 +- .../external/redis/utils/withRedisFailOpen.ts | 12 +- .../balances/check/runCheckWithRollout.ts | 2 + .../balances/track/runTrackWithRollout.ts | 2 + .../getFullSubject/getFullSubjectGate.ts | 5 + .../misc/rateLimiter/rateLimitFactory.ts | 21 ++ .../gate-reject-failopen.test.ts | 170 +++++++++++++++ .../gate-rejection-failopen.test.ts | 76 +++++++ ...ith-redis-fail-open-gate-rejection.test.ts | 205 ++++++++++++++++++ 9 files changed, 491 insertions(+), 4 deletions(-) create mode 100644 server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts create mode 100644 server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts create mode 100644 server/tests/unit/redis/with-redis-fail-open-gate-rejection.test.ts diff --git a/server/src/db/shed503OnTransientError.ts b/server/src/db/shed503OnTransientError.ts index 42c699d62..e2d7c4482 100644 --- a/server/src/db/shed503OnTransientError.ts +++ b/server/src/db/shed503OnTransientError.ts @@ -18,7 +18,7 @@ export const shed503OnTransientError = async ({ if (!(isTransientDbError({ error }) || isTransientRedisError({ error }))) { throw error; } - ctx.logger.warn(`[${source}] DB unavailable, shedding with 503`, { + ctx.logger.warn(`[${source}] transient DB error, shedding with 503`, { type: `${source}_fail_open`, error, }); diff --git a/server/src/external/redis/utils/withRedisFailOpen.ts b/server/src/external/redis/utils/withRedisFailOpen.ts index 05ed327b5..0b4fd6dde 100644 --- a/server/src/external/redis/utils/withRedisFailOpen.ts +++ b/server/src/external/redis/utils/withRedisFailOpen.ts @@ -3,16 +3,18 @@ import { shouldUseRedisV2 } from "@/external/redis/initUtils/redisV2Availability import { RedisUnavailableError } from "./errors.js"; import { isTransientRedisError } from "./isTransientRedisError.js"; -/** Runs `run`. If Redis is unavailable or a transient DB error occurs, - * calls `fallback`. Any other error propagates. */ +/** Runs `run`. If Redis is unavailable, a transient DB error occurs, or + * `alsoFailOpen` matches, calls `fallback`. Any other error propagates. */ export const withRedisFailOpen = async ({ source, run, fallback, + alsoFailOpen, }: { source: string; run: () => T | Promise; fallback: (error: unknown) => T | Promise; + alsoFailOpen?: (error: unknown) => boolean; }): Promise => { try { if (!shouldUseRedisV2()) { @@ -21,7 +23,11 @@ export const withRedisFailOpen = async ({ return await run(); } catch (error) { - if (isTransientRedisError({ error }) || isTransientDbError({ error })) { + if ( + isTransientRedisError({ error }) || + isTransientDbError({ error }) || + alsoFailOpen?.(error) + ) { return await fallback(error); } diff --git a/server/src/internal/balances/check/runCheckWithRollout.ts b/server/src/internal/balances/check/runCheckWithRollout.ts index 17cd689b0..d6d7d7302 100644 --- a/server/src/internal/balances/check/runCheckWithRollout.ts +++ b/server/src/internal/balances/check/runCheckWithRollout.ts @@ -3,6 +3,7 @@ import { withRedisFailOpen } from "@/external/redis/utils/withRedisFailOpen.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import type { CheckData } from "@/internal/api/check/checkTypes/CheckData.js"; import { getCheckFailOpenFallback } from "@/internal/api/check/checkUtils/getCheckFailOpenFallback.js"; +import { isFullSubjectGateRejection } from "@/internal/customers/repos/getFullSubject/getFullSubjectGate.js"; import { isFullSubjectRolloutEnabled } from "@/internal/misc/rollouts/fullSubjectRolloutUtils.js"; import type { CheckDataV2 } from "./checkTypes/CheckDataV2.js"; import { runCheckLegacyFlow } from "./runCheckLegacyFlow.js"; @@ -37,6 +38,7 @@ export const runCheckWithRollout = async ({ return withRedisFailOpen>({ source: "runCheckWithRollout", run: () => runCheckV2({ ctx, body, requiredBalance }), + alsoFailOpen: isFullSubjectGateRejection, fallback: (error) => ({ checkData: null, response: getCheckFailOpenFallback({ diff --git a/server/src/internal/balances/track/runTrackWithRollout.ts b/server/src/internal/balances/track/runTrackWithRollout.ts index 622590075..771904f41 100644 --- a/server/src/internal/balances/track/runTrackWithRollout.ts +++ b/server/src/internal/balances/track/runTrackWithRollout.ts @@ -1,6 +1,7 @@ import type { ApiVersion, TrackParams, TrackResponseV3 } from "@autumn/shared"; import { withRedisFailOpen } from "@/external/redis/utils/withRedisFailOpen.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { isFullSubjectGateRejection } from "@/internal/customers/repos/getFullSubject/getFullSubjectGate.js"; import { isFullSubjectRolloutEnabled } from "@/internal/misc/rollouts/fullSubjectRolloutUtils.js"; import type { FeatureDeduction } from "../utils/types/featureDeduction.js"; import { runTrackV2 } from "./runTrackV2.js"; @@ -38,6 +39,7 @@ export const runTrackWithRollout = async ({ featureDeductions, apiVersion, }), + alsoFailOpen: isFullSubjectGateRejection, fallback: async (error) => { const queuedResponse = await queueTrack({ ctx, body }); if (queuedResponse) return queuedResponse; diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts index 4df6c8cd9..3a7dba253 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts @@ -116,6 +116,11 @@ const rejectOverloaded = ({ }); }; +export const isFullSubjectGateRejection = (error: unknown): boolean => + error instanceof RecaseError && + error.code === "rate_limit_exceeded" && + error.statusCode === 429; + export const runWithFullSubjectGate = async ({ customerId, orgId, diff --git a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts index 5371fa09f..d61869a07 100644 --- a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts +++ b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts @@ -40,6 +40,26 @@ const warnRateLimitBypass = () => { ); }; +const CAP_EXCEEDED_WARNING_INTERVAL_MS = 10_000; +let lastCapExceededWarningAt = 0; + +const warnOrgCapExceeded = ({ + limitType, + orgSlug, +}: { + limitType: string; + orgSlug?: string; +}) => { + const now = Date.now(); + if (now - lastCapExceededWarningAt < CAP_EXCEEDED_WARNING_INTERVAL_MS) return; + + lastCapExceededWarningAt = now; + logger.warn( + `[rate-limit] org aggregate cap exceeded: ${orgSlug ?? "unknown"} (${limitType})`, + { type: "org_rate_cap_exceeded", limitType, org: orgSlug }, + ); +}; + export const rateLimitFactory = ({ type, config, @@ -69,6 +89,7 @@ export const rateLimitFactory = ({ ): Promise => { const honoContext = c as Context; const ctx = honoContext.get("ctx"); + warnOrgCapExceeded({ limitType: type, orgSlug: ctx?.org?.slug }); if (type === RateLimitType.CheckOrg && !isCheckFailOpenRoute(honoContext)) { return c.json( diff --git a/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts b/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts new file mode 100644 index 000000000..c2bb36c85 --- /dev/null +++ b/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts @@ -0,0 +1,170 @@ +/** + * Trips the real FullSubject gate (tiny limits + cold cache + concurrent + * calls) and verifies check/track fail open instead of surfacing 429s. + */ + +import { afterAll, expect, mock, test } from "bun:test"; +import chalk from "chalk"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; + +const queueCalls: Record[] = []; +mock.module("@/queue/queueUtils.js", () => ({ + addTaskToQueue: async (args: Record) => { + queueCalls.push(args); + }, +})); + +process.env.TRACK_SQS_QUEUE_URL ??= "https://sqs.test/gate-failopen"; + +const { ParsedCheckParamsSchema } = await import("@autumn/shared"); +const { TestFeature } = await import("@tests/setup/v2Features.js"); +const { items } = await import("@tests/utils/fixtures/items.js"); +const { products } = await import("@tests/utils/fixtures/products.js"); +const { initScenario, s } = await import( + "@tests/utils/testInitUtils/initScenario.js" +); +const { createTestContext } = await import( + "@tests/utils/testInitUtils/createTestContext.js" +); +const { runCheckWithRollout } = await import( + "@/internal/balances/check/runCheckWithRollout.js" +); +const { runTrackWithRollout } = await import( + "@/internal/balances/track/runTrackWithRollout.js" +); +const { invalidateCachedFullSubject } = await import( + "@/internal/customers/cache/fullSubject/actions/invalidate/invalidateFullSubject.js" +); +const { _setFullSubjectGateConfigForTesting } = await import( + "@/internal/misc/fullSubjectGateEdgeConfig/fullSubjectGateEdgeConfigStore.js" +); + +const CONCURRENCY = 8; + +// Held for the whole file so test.concurrent tests can't reset it mid-flight. +_setFullSubjectGateConfigForTesting({ + config: { + per_customer_limit: 1, + per_org_limit: 1, + max_wait_ms: 100, + per_customer_pending_max: 1, + per_org_pending_max: 1, + }, +}); + +afterAll(() => { + _setFullSubjectGateConfigForTesting({ config: {} }); +}); + +const buildContext = async ({ customerId }: { customerId: string }) => { + const ctx = (await createTestContext()) as unknown as AutumnContext; + ctx.rolloutSnapshot = { + rolloutId: "v2-cache", + enabled: true, + percent: 100, + previousPercent: 100, + changedAt: 0, + customerBucket: 0, + }; + ctx.customerId = customerId; + return ctx; +}; + +test.concurrent( + `${chalk.yellowBright("gate-failopen: concurrent checks on cold cache never 429")}`, + async () => { + const customerId = "gate-failopen-check"; + const freeProd = products.base({ + id: "free", + items: [items.monthlyMessages({ includedUsage: 1000 })], + }); + + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freeProd] }), + ], + actions: [s.attach({ productId: freeProd.id })], + }); + + const ctx = await buildContext({ customerId }); + const body = ParsedCheckParamsSchema.parse({ + customer_id: customerId, + feature_id: TestFeature.Messages, + }); + + await invalidateCachedFullSubject({ ctx, customerId, source: "test" }); + + const results = await Promise.allSettled( + Array.from({ length: CONCURRENCY }, () => + runCheckWithRollout({ ctx, body, requiredBalance: 1 }), + ), + ); + + const rejected = results.filter((result) => result.status === "rejected"); + expect(rejected).toEqual([]); + + const fulfilled = results.filter( + (result) => result.status === "fulfilled", + ) as PromiseFulfilledResult< + Awaited> + >[]; + + const failOpen = fulfilled.filter( + (result) => result.value.checkData === null, + ); + const served = fulfilled.filter( + (result) => result.value.checkData !== null, + ); + + expect(failOpen.length).toBeGreaterThan(0); + expect(served.length).toBeGreaterThan(0); + for (const result of failOpen) { + expect(result.value.response).toMatchObject({ allowed: true }); + } + }, +); + +test.concurrent( + `${chalk.yellowBright("gate-failopen: concurrent tracks on cold cache queue instead of 429")}`, + async () => { + const customerId = "gate-failopen-track"; + const freeProd = products.base({ + id: "free", + items: [items.monthlyMessages({ includedUsage: 1000 })], + }); + + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freeProd] }), + ], + actions: [s.attach({ productId: freeProd.id })], + }); + + const ctx = await buildContext({ customerId }); + const feature = ctx.features.find( + (candidate: { id: string }) => candidate.id === TestFeature.Messages, + ); + if (!feature) throw new Error("Messages feature missing from test org"); + + await invalidateCachedFullSubject({ ctx, customerId, source: "test" }); + queueCalls.length = 0; + + const results = await Promise.allSettled( + Array.from({ length: CONCURRENCY }, () => + runTrackWithRollout({ + ctx, + body: { customer_id: customerId, feature_id: TestFeature.Messages }, + featureDeductions: [{ feature, deduction: 1 }], + }), + ), + ); + + const rejected = results.filter((result) => result.status === "rejected"); + expect(rejected).toEqual([]); + expect(queueCalls.length).toBeGreaterThan(0); + }, +); diff --git a/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts b/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts new file mode 100644 index 000000000..1d7ae0e98 --- /dev/null +++ b/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from "bun:test"; +import { AppEnv, RecaseError } from "@autumn/shared"; +import { + isFullSubjectGateRejection, + runWithFullSubjectGate, +} from "@/internal/customers/repos/getFullSubject/getFullSubjectGate.js"; +import { _setFullSubjectGateConfigForTesting } from "@/internal/misc/fullSubjectGateEdgeConfig/fullSubjectGateEdgeConfigStore.js"; + +describe("isFullSubjectGateRejection", () => { + test("matches a rejection thrown by the real gate", async () => { + _setFullSubjectGateConfigForTesting({ + config: { + per_customer_limit: 1, + per_org_limit: 1, + max_wait_ms: 100, + per_customer_pending_max: 1, + per_org_pending_max: 1, + }, + }); + + const slow = () => new Promise((resolve) => setTimeout(resolve, 80)); + const results = await Promise.allSettled( + Array.from({ length: 6 }, () => + runWithFullSubjectGate({ + customerId: "cus-predicate-real-gate", + orgId: "org-predicate-real-gate", + env: AppEnv.Live, + queryFn: slow, + }), + ), + ); + + const rejections = results.filter( + (result) => result.status === "rejected", + ) as PromiseRejectedResult[]; + expect(rejections.length).toBeGreaterThan(0); + for (const rejection of rejections) { + expect(isFullSubjectGateRejection(rejection.reason)).toBe(true); + } + + _setFullSubjectGateConfigForTesting({ config: {} }); + }); + test("matches the gate's rejection error", () => { + const rejection = new RecaseError({ + message: "Too many concurrent requests for this customer.", + code: "rate_limit_exceeded", + statusCode: 429, + data: { reason: "per_org_queue_full" }, + }); + expect(isFullSubjectGateRejection(rejection)).toBe(true); + }); + + test("does not match other RecaseErrors", () => { + const serviceUnavailable = new RecaseError({ + message: "Service is temporarily unavailable, please retry shortly.", + code: "service_unavailable", + statusCode: 503, + }); + expect(isFullSubjectGateRejection(serviceUnavailable)).toBe(false); + + const wrongStatus = new RecaseError({ + message: "rate limited", + code: "rate_limit_exceeded", + statusCode: 400, + }); + expect(isFullSubjectGateRejection(wrongStatus)).toBe(false); + }); + + test("does not match plain errors or non-errors", () => { + expect(isFullSubjectGateRejection(new Error("rate_limit_exceeded"))).toBe( + false, + ); + expect(isFullSubjectGateRejection(null)).toBe(false); + expect(isFullSubjectGateRejection("rate_limit_exceeded")).toBe(false); + }); +}); diff --git a/server/tests/unit/redis/with-redis-fail-open-gate-rejection.test.ts b/server/tests/unit/redis/with-redis-fail-open-gate-rejection.test.ts new file mode 100644 index 000000000..7387093f3 --- /dev/null +++ b/server/tests/unit/redis/with-redis-fail-open-gate-rejection.test.ts @@ -0,0 +1,205 @@ +import { beforeEach, describe, expect, mock, test } from "bun:test"; +import { ApiVersionClass, LATEST_VERSION, RecaseError } from "@autumn/shared"; + +mock.module("@/external/redis/initUtils/redisV2Availability.js", () => ({ + shouldUseRedisV2: () => true, +})); + +const gateRejection = () => + new RecaseError({ + message: "Too many concurrent requests for this customer.", + code: "rate_limit_exceeded", + statusCode: 429, + data: { reason: "per_customer_queue_full" }, + }); + +mock.module("@/internal/balances/check/runCheckV2.js", () => ({ + runCheckV2: async () => { + throw checkError; + }, +})); + +mock.module("@/internal/balances/track/v3/runTrackV3.js", () => ({ + runTrackV3: async () => { + throw trackError; + }, +})); + +const queueCalls: Record[] = []; +mock.module("@/queue/queueUtils.js", () => ({ + addTaskToQueue: async (args: Record) => { + queueCalls.push(args); + }, +})); + +let checkError: unknown = gateRejection(); +let trackError: unknown = gateRejection(); + +const { withRedisFailOpen } = await import( + "@/external/redis/utils/withRedisFailOpen.js" +); +const { isFullSubjectGateRejection } = await import( + "@/internal/customers/repos/getFullSubject/getFullSubjectGate.js" +); +const { runCheckWithRollout } = await import( + "@/internal/balances/check/runCheckWithRollout.js" +); +const { runTrackWithRollout } = await import( + "@/internal/balances/track/runTrackWithRollout.js" +); +const { ParsedCheckParamsSchema } = await import("@autumn/shared"); + +process.env.TRACK_SQS_QUEUE_URL = "https://sqs.test/queue"; + +const noopLogger = { + info: () => {}, + warn: () => {}, + error: () => {}, + debug: () => {}, +}; + +const makeContext = () => + ({ + id: "req_test_gate_failopen", + org: { id: "org_test", slug: "test-org" }, + env: "live", + apiVersion: new ApiVersionClass(LATEST_VERSION), + logger: noopLogger, + extraLogs: {}, + features: [], + rolloutSnapshot: { + rolloutId: "v2-cache", + enabled: true, + percent: 100, + previousPercent: 100, + changedAt: 0, + customerBucket: 0, + }, + // biome-ignore lint/suspicious/noExplicitAny: minimal test context + }) as any; + +const checkBody = ParsedCheckParamsSchema.parse({ + customer_id: "cus_gate_failopen", + feature_id: "messages", +}); + +describe("withRedisFailOpen alsoFailOpen", () => { + test("gate rejection falls open when alsoFailOpen matches", async () => { + const rejection = gateRejection(); + let receivedError: unknown; + const result = await withRedisFailOpen({ + source: "test", + run: () => { + throw rejection; + }, + alsoFailOpen: isFullSubjectGateRejection, + fallback: (error) => { + receivedError = error; + return "fallback"; + }, + }); + expect(result).toBe("fallback"); + expect(receivedError).toBe(rejection); + }); + + test("gate rejection still propagates without alsoFailOpen", async () => { + await expect( + withRedisFailOpen({ + source: "test", + run: () => { + throw gateRejection(); + }, + fallback: () => "fallback", + }), + ).rejects.toMatchObject({ code: "rate_limit_exceeded", statusCode: 429 }); + }); + + test("non-matching errors propagate even with alsoFailOpen", async () => { + await expect( + withRedisFailOpen({ + source: "test", + run: () => { + throw new Error("boom"); + }, + alsoFailOpen: isFullSubjectGateRejection, + fallback: () => "fallback", + }), + ).rejects.toThrow("boom"); + }); + + test("transient db errors still fall open without alsoFailOpen", async () => { + const result = await withRedisFailOpen({ + source: "test", + run: () => { + const error = new Error("too many connections") as Error & { + code: string; + }; + error.code = "53300"; + throw error; + }, + fallback: () => "fallback", + }); + expect(result).toBe("fallback"); + }); +}); + +describe("check flow on gate rejection", () => { + beforeEach(() => { + checkError = gateRejection(); + }); + + test("returns the fail-open allow response instead of throwing", async () => { + const result = await runCheckWithRollout({ + ctx: makeContext(), + body: checkBody, + requiredBalance: 1, + }); + expect(result.checkData).toBeNull(); + expect(result.response).toMatchObject({ allowed: true }); + }); + + test("non-gate errors still propagate", async () => { + checkError = new Error("genuine bug"); + await expect( + runCheckWithRollout({ + ctx: makeContext(), + body: checkBody, + requiredBalance: 1, + }), + ).rejects.toThrow("genuine bug"); + }); +}); + +describe("track flow on gate rejection", () => { + beforeEach(() => { + trackError = gateRejection(); + queueCalls.length = 0; + }); + + test("queues the event and returns the queued response", async () => { + const ctx = makeContext(); + const result = await runTrackWithRollout({ + ctx, + body: { customer_id: "cus_gate_failopen", feature_id: "messages" }, + featureDeductions: [], + }); + expect(result).toMatchObject({ + customer_id: "cus_gate_failopen", + balance: null, + }); + expect(queueCalls.length).toBe(1); + expect(queueCalls[0]?.messageDeduplicationId).toBe(ctx.id); + }); + + test("non-gate errors still propagate", async () => { + trackError = new Error("genuine bug"); + await expect( + runTrackWithRollout({ + ctx: makeContext(), + body: { customer_id: "cus_gate_failopen", feature_id: "messages" }, + featureDeductions: [], + }), + ).rejects.toThrow("genuine bug"); + expect(queueCalls.length).toBe(0); + }); +}); From 13c00c8ab993c835122ba562b0b63e67d8b223a1 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 12:31:30 +0100 Subject: [PATCH 28/41] fix: bun lock --- bun.lock | 42 +++--------------------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index ea75d2b7a..a45e5e4e6 100644 --- a/bun.lock +++ b/bun.lock @@ -2418,7 +2418,7 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.0", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "postcss": "^8.5.10", "tailwindcss": "4.3.0" } }, "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w=="], - "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], + "@tailwindcss/typography": ["@tailwindcss/typography@0.5.20", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" } }, "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw=="], "@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="], @@ -5950,7 +5950,7 @@ "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -6198,35 +6198,25 @@ "@autumn/leaf/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@autumn/leaf/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@autumn/logging/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/mcp/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/openapi/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], - "@autumn/scripts/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@autumn/server/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="], "@autumn/server/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], - "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], - "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="], - "@autumn/shared/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@autumn/vite/@types/node": ["@types/node@22.19.20", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw=="], - "@autumn/vite/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@autumn/vite/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], "@autumn/vite/@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], @@ -6518,8 +6508,6 @@ "@mintlify/common/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], - "@mintlify/common/unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], - "@mintlify/link-rot/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], @@ -7068,10 +7056,6 @@ "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "@useautumn/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "@useautumn/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -7130,8 +7114,6 @@ "atmn/eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@4.6.2", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ=="], - "atmn/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "autoevals/openai": ["openai@6.42.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg=="], @@ -7142,8 +7124,6 @@ "autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "autumn-js/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "ava/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -7928,20 +7908,6 @@ "@autumn/server/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SYrqVOlapDxDG7FzHBIJbfgaix+mXPkYzYGqwpz/TAhoPA7sgbfAoGLaqi3ut9N88C/OYNhEX4tjz/0PC9i1nw=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zIe31OYgBvkgTIQEwJtKim6SYyuVTkr+9fK/87hVwKN15X3Ikjeh0C0g2W/Vl4rXeMvy95wBGDN1jpW11DIvgg=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "arm" }, "sha512-02b45lpPmYf125PvcnK67WW93N55qwKmtInwfVefV997S17Ib3h6hlCW4e24BDhNsGRCSLhPA4Lu7ZvTq5pLkw=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-YbmCQXGYkDChGFG7hXJzIgmRjtU1kE5VK/+k322nGnbq4ePqSjS3dS0+ehPATmvfO1XjCDfh3ekED+AtmWk6aQ=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "x64" }, "sha512-e+TweaVJFaM96tV1UM1kRfk2y8QBkZtz7+0wcxrDGmyJz3IIRUlg1btocaBkhsmVtQPXMr37RutBBMgpl3vgUg=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260511.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-zgkoGiCpOrly5h8ghcuu6ZNSfrnRqtHoCq584Q92+s4D/j1MU3oKkGPvmkezp5Mj2v7ffR9AjU+lWRDkrfm6eA=="], - - "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260511.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SUm7iVYzKaflol+QwH0Ny5jZtco6PJduI+h/TEg0sgBJzVBa+9RN4I9+Xu9v+EJ1bci3XI7835IRdSP36lCgCw=="], - "@autumn/server/autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -8136,8 +8102,6 @@ "@mintlify/common/tailwindcss/sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - "@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - "@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@mintlify/link-rot/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], From 5fbd890fa39c4a8b20589a2e95b794ed508f3fe7 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 11 Jun 2026 12:49:41 +0100 Subject: [PATCH 29/41] pin gate-rejection predicate, key cap warn throttle per limit type --- .../getFullSubject/getFullSubjectGate.ts | 29 +++++++++++++++---- .../misc/rateLimiter/rateLimitFactory.ts | 7 +++-- .../gate-rejection-failopen.test.ts | 17 +++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts index 3a7dba253..fcd19cd89 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubjectGate.ts @@ -99,11 +99,21 @@ const attrs = ({ orgId, env }: { orgId: string; env: AppEnv }) => ({ env, }); +const GATE_REJECTION_REASONS = [ + "per_customer_queue_full", + "per_org_queue_full", + "wait_timeout", +] as const; +type GateRejectionReason = (typeof GATE_REJECTION_REASONS)[number]; +const gateRejectionReasonSet: ReadonlySet = new Set( + GATE_REJECTION_REASONS, +); + const rejectOverloaded = ({ reason, labels, }: { - reason: string; + reason: GateRejectionReason; labels: Record; }): never => { rejectedCounter.add(1, { ...labels, reason }); @@ -116,10 +126,19 @@ const rejectOverloaded = ({ }); }; -export const isFullSubjectGateRejection = (error: unknown): boolean => - error instanceof RecaseError && - error.code === "rate_limit_exceeded" && - error.statusCode === 429; +export const isFullSubjectGateRejection = (error: unknown): boolean => { + if (!(error instanceof RecaseError)) return false; + if (error.code !== "rate_limit_exceeded" || error.statusCode !== 429) + return false; + const data = error.data; + return ( + typeof data === "object" && + data !== null && + "reason" in data && + typeof data.reason === "string" && + gateRejectionReasonSet.has(data.reason) + ); +}; export const runWithFullSubjectGate = async ({ customerId, diff --git a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts index d61869a07..34feef182 100644 --- a/server/src/internal/misc/rateLimiter/rateLimitFactory.ts +++ b/server/src/internal/misc/rateLimiter/rateLimitFactory.ts @@ -41,7 +41,7 @@ const warnRateLimitBypass = () => { }; const CAP_EXCEEDED_WARNING_INTERVAL_MS = 10_000; -let lastCapExceededWarningAt = 0; +const lastCapWarnAtByType = new Map(); const warnOrgCapExceeded = ({ limitType, @@ -51,9 +51,10 @@ const warnOrgCapExceeded = ({ orgSlug?: string; }) => { const now = Date.now(); - if (now - lastCapExceededWarningAt < CAP_EXCEEDED_WARNING_INTERVAL_MS) return; + const lastWarnAt = lastCapWarnAtByType.get(limitType) ?? 0; + if (now - lastWarnAt < CAP_EXCEEDED_WARNING_INTERVAL_MS) return; - lastCapExceededWarningAt = now; + lastCapWarnAtByType.set(limitType, now); logger.warn( `[rate-limit] org aggregate cap exceeded: ${orgSlug ?? "unknown"} (${limitType})`, { type: "org_rate_cap_exceeded", limitType, org: orgSlug }, diff --git a/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts b/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts index 1d7ae0e98..5d972fc3d 100644 --- a/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts +++ b/server/tests/unit/full-subject-gate/gate-rejection-failopen.test.ts @@ -66,6 +66,23 @@ describe("isFullSubjectGateRejection", () => { expect(isFullSubjectGateRejection(wrongStatus)).toBe(false); }); + test("does not match rate-limit 429s that are not from the gate", () => { + const unknownReason = new RecaseError({ + message: "rate limited", + code: "rate_limit_exceeded", + statusCode: 429, + data: { reason: "some_other_limiter" }, + }); + expect(isFullSubjectGateRejection(unknownReason)).toBe(false); + + const noData = new RecaseError({ + message: "rate limited", + code: "rate_limit_exceeded", + statusCode: 429, + }); + expect(isFullSubjectGateRejection(noData)).toBe(false); + }); + test("does not match plain errors or non-errors", () => { expect(isFullSubjectGateRejection(new Error("rate_limit_exceeded"))).toBe( false, From 20f811267e1d9696f119ad39325ca821f2ee3845 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:03:42 +0100 Subject: [PATCH 30/41] fix: bun lock --- docker/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 70aa5575d..004edf7ce 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -41,11 +41,11 @@ COPY packages/stripe-sync/package.json packages/stripe-sync/ # install step doesn't fail before the real source is copied. RUN mkdir -p scripts && touch scripts/preload-env.ts -# Install only the workspaces the runtime services need (server hosts workers + -# cron), plus their transitive workspace deps. --frozen-lockfile guarantees no -# re-resolution; --filter skips the frontend-heavy workspaces. +# Install only runtime workspaces; --no-save keeps this layer from mutating bun.lock. +# Frozen filtered installs fail even immediately after Bun regenerates the lockfile. RUN --mount=type=cache,target=/root/.bun/install/cache \ - bun install --frozen-lockfile --ignore-scripts \ + bun install --ignore-scripts --no-save \ + --minimum-release-age 0 \ --filter @autumn/server \ --filter @autumn/leaf From 576586c61863ee719a70baccdc29abce2329ec49 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:03:53 +0100 Subject: [PATCH 31/41] regen bunlock --- bun.lock | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/bun.lock b/bun.lock index a45e5e4e6..0db9a1c7a 100644 --- a/bun.lock +++ b/bun.lock @@ -6198,22 +6198,32 @@ "@autumn/leaf/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@autumn/leaf/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@autumn/logging/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/mcp/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/openapi/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "@autumn/scripts/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@autumn/server/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="], "@autumn/server/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], + "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], "@autumn/server/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="], + "@autumn/shared/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@autumn/vite/@types/node": ["@types/node@22.19.20", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw=="], "@autumn/vite/@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="], @@ -7056,6 +7066,10 @@ "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "@useautumn/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "@useautumn/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -7114,6 +7128,8 @@ "atmn/eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@4.6.2", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ=="], + "atmn/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "autoevals/openai": ["openai@6.42.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg=="], @@ -7124,6 +7140,8 @@ "autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + "autumn-js/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "ava/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -7908,6 +7926,20 @@ "@autumn/server/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SYrqVOlapDxDG7FzHBIJbfgaix+mXPkYzYGqwpz/TAhoPA7sgbfAoGLaqi3ut9N88C/OYNhEX4tjz/0PC9i1nw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zIe31OYgBvkgTIQEwJtKim6SYyuVTkr+9fK/87hVwKN15X3Ikjeh0C0g2W/Vl4rXeMvy95wBGDN1jpW11DIvgg=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "arm" }, "sha512-02b45lpPmYf125PvcnK67WW93N55qwKmtInwfVefV997S17Ib3h6hlCW4e24BDhNsGRCSLhPA4Lu7ZvTq5pLkw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-YbmCQXGYkDChGFG7hXJzIgmRjtU1kE5VK/+k322nGnbq4ePqSjS3dS0+ehPATmvfO1XjCDfh3ekED+AtmWk6aQ=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260511.1", "", { "os": "linux", "cpu": "x64" }, "sha512-e+TweaVJFaM96tV1UM1kRfk2y8QBkZtz7+0wcxrDGmyJz3IIRUlg1btocaBkhsmVtQPXMr37RutBBMgpl3vgUg=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260511.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-zgkoGiCpOrly5h8ghcuu6ZNSfrnRqtHoCq584Q92+s4D/j1MU3oKkGPvmkezp5Mj2v7ffR9AjU+lWRDkrfm6eA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260511.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SUm7iVYzKaflol+QwH0Ny5jZtco6PJduI+h/TEg0sgBJzVBa+9RN4I9+Xu9v+EJ1bci3XI7835IRdSP36lCgCw=="], + "@autumn/server/autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], From 1f855bdbc54939a57cacb99a2f1a73305539db9b Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 11 Jun 2026 13:17:29 +0100 Subject: [PATCH 32/41] pin gate predicate, per-type warn throttle, fix integration test env --- .../gate-failopen/gate-reject-failopen.test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts b/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts index c2bb36c85..b832b78c9 100644 --- a/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts +++ b/server/tests/integration/balances/gate-failopen/gate-reject-failopen.test.ts @@ -38,6 +38,12 @@ const { invalidateCachedFullSubject } = await import( const { _setFullSubjectGateConfigForTesting } = await import( "@/internal/misc/fullSubjectGateEdgeConfig/fullSubjectGateEdgeConfigStore.js" ); +const { primeRedisV2Monitor } = await import( + "@/external/redis/initUtils/redisV2Availability.js" +); + +// In-process runs skip server boot, which is what normally primes availability. +await primeRedisV2Monitor(); const CONCURRENCY = 8; @@ -144,7 +150,12 @@ test.concurrent( actions: [s.attach({ productId: freeProd.id })], }); - const ctx = await buildContext({ customerId }); + // One ctx per call: concurrent prod tracks carry distinct request IDs, + // and the Redis dedup treats a reused ID as a duplicate request. + const contexts = await Promise.all( + Array.from({ length: CONCURRENCY }, () => buildContext({ customerId })), + ); + const [ctx] = contexts; const feature = ctx.features.find( (candidate: { id: string }) => candidate.id === TestFeature.Messages, ); @@ -154,9 +165,9 @@ test.concurrent( queueCalls.length = 0; const results = await Promise.allSettled( - Array.from({ length: CONCURRENCY }, () => + contexts.map((trackContext) => runTrackWithRollout({ - ctx, + ctx: trackContext, body: { customer_id: customerId, feature_id: TestFeature.Messages }, featureDeductions: [{ feature, deduction: 1 }], }), From dd9248165decf9de7ff3aab599524e70a728a99d Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:21:54 +0100 Subject: [PATCH 33/41] regen bunlock --- docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 004edf7ce..5d1f19ed4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -47,7 +47,9 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \ bun install --ignore-scripts --no-save \ --minimum-release-age 0 \ --filter @autumn/server \ - --filter @autumn/leaf + --filter @autumn/leaf \ + --filter autumn-js \ + --filter @useautumn/sdk FROM oven/bun:1.3.10 WORKDIR /app From 041b7cd462170d1835cbe613bd2db9604023b34d Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:24:11 +0100 Subject: [PATCH 34/41] do a full workspace install in dockerfile --- docker/Dockerfile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5d1f19ed4..9433c7e34 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -41,15 +41,11 @@ COPY packages/stripe-sync/package.json packages/stripe-sync/ # install step doesn't fail before the real source is copied. RUN mkdir -p scripts && touch scripts/preload-env.ts -# Install only runtime workspaces; --no-save keeps this layer from mutating bun.lock. -# Frozen filtered installs fail even immediately after Bun regenerates the lockfile. +# Install the full workspace because runtime source imports cross package boundaries. +# --no-save keeps this image layer from mutating bun.lock. RUN --mount=type=cache,target=/root/.bun/install/cache \ bun install --ignore-scripts --no-save \ - --minimum-release-age 0 \ - --filter @autumn/server \ - --filter @autumn/leaf \ - --filter autumn-js \ - --filter @useautumn/sdk + --minimum-release-age 0 FROM oven/bun:1.3.10 WORKDIR /app From 60e7e24a7b06c4632e8d2deeff2e004acc858ba5 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:32:14 +0100 Subject: [PATCH 35/41] =?UTF-8?q?fix(billing):=20=F0=9F=90=9B=20charge=20z?= =?UTF-8?q?ero-decimal=20currencies=20in=20major=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One-off and usage invoice paths converted amounts with a raw *100 instead of atmnToStripeAmount, so zero-decimal currencies (RWF, JPY, KRW, ...) were charged 100x. Also pass the org currency to standalone v2 invoices and to the v2 line-item converters, which silently defaulted to USD. Adds legacy + v2 regression tests in the core-billing group. Co-Authored-By: Claude Fable 5 --- .../priceToUsageInAdvance.ts | 6 +- .../src/external/stripe/stripePriceUtils.ts | 3 +- .../updateOneOffTieredItems.ts | 3 +- .../lineItemsToCreateInvoiceItemsParams.ts | 4 +- .../lineItemsToInvoiceAddLinesParams.ts | 4 +- ...temsToSubscriptionAddInvoiceItemsParams.ts | 5 +- .../utils/invoices/createInvoiceForBilling.ts | 20 ++-- .../addProductFlow/handleOneOffFunction.ts | 16 +-- .../createUsageInvoiceItems.ts | 6 +- .../createContUseInvoiceItems.ts | 6 +- .../invoiceItemUtils/invoiceItemUtils.ts | 15 ++- server/tests/_groups/core/coreAttach.ts | 1 + server/tests/_groups/core/coreLegacy.ts | 1 + .../attach-one-off-zero-decimal.test.ts | 100 +++++++++++++++++ .../legacy-new-oneoff-zero-decimal.test.ts | 101 ++++++++++++++++++ 15 files changed, 261 insertions(+), 30 deletions(-) create mode 100644 server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts create mode 100644 server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts diff --git a/server/src/external/stripe/priceToStripeItem/priceToUsageInAdvance.ts b/server/src/external/stripe/priceToStripeItem/priceToUsageInAdvance.ts index 2bba621c0..a4fbdb28b 100644 --- a/server/src/external/stripe/priceToStripeItem/priceToUsageInAdvance.ts +++ b/server/src/external/stripe/priceToStripeItem/priceToUsageInAdvance.ts @@ -1,4 +1,5 @@ import { + atmnToStripeAmount, type EntitlementWithFeature, type FeatureOptions, featureOptionUtils, @@ -58,7 +59,10 @@ export const priceToOneOffAndTiered = ({ product: config.stripe_product_id ? config.stripe_product_id : stripeProductId, - unit_amount: Number(amount.toFixed(2)) * 100, + unit_amount: atmnToStripeAmount({ + amount, + currency: orgToCurrency({ org }), + }), currency: orgToCurrency({ org }), }, diff --git a/server/src/external/stripe/stripePriceUtils.ts b/server/src/external/stripe/stripePriceUtils.ts index 6067b9ca0..34c9f2827 100644 --- a/server/src/external/stripe/stripePriceUtils.ts +++ b/server/src/external/stripe/stripePriceUtils.ts @@ -1,4 +1,5 @@ import { + atmnToStripeAmount, BillingInterval, type Customer, type Feature, @@ -95,7 +96,7 @@ export const getInvoiceItemForUsage = ({ price_data: { product: config.stripe_product_id!, - unit_amount: Math.max(Math.round(amount * 100), 0), + unit_amount: Math.max(atmnToStripeAmount({ amount, currency }), 0), currency, }, period: { diff --git a/server/src/internal/billing/v2/providers/stripe/utils/checkoutSessions/updateOneOffTieredItems.ts b/server/src/internal/billing/v2/providers/stripe/utils/checkoutSessions/updateOneOffTieredItems.ts index 2e5c0e2be..f027087fa 100644 --- a/server/src/internal/billing/v2/providers/stripe/utils/checkoutSessions/updateOneOffTieredItems.ts +++ b/server/src/internal/billing/v2/providers/stripe/utils/checkoutSessions/updateOneOffTieredItems.ts @@ -1,4 +1,5 @@ import { + atmnToStripeAmount, InternalError, type LineItemContext, type Organization, @@ -76,7 +77,7 @@ export const updateOneOffTieredItems = ({ product_data: { name: lineItem.description, }, - unit_amount: Math.round(lineItem.amount * 100), + unit_amount: atmnToStripeAmount({ amount: lineItem.amount, currency }), currency, }, quantity: 1, diff --git a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToCreateInvoiceItemsParams.ts b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToCreateInvoiceItemsParams.ts index 6465b1847..1ff7fcf6d 100644 --- a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToCreateInvoiceItemsParams.ts +++ b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToCreateInvoiceItemsParams.ts @@ -35,11 +35,11 @@ const toStripeCreateInvoiceItemParams = ({ amount: shouldUsePriceData ? undefined - : atmnToStripeAmount({ amount: lineAmount }), + : atmnToStripeAmount({ amount: lineAmount, currency }), price_data: shouldUsePriceData ? { - unit_amount: atmnToStripeAmount({ amount: lineAmount }), + unit_amount: atmnToStripeAmount({ amount: lineAmount, currency }), currency, product: stripeProductId, } diff --git a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToInvoiceAddLinesParams.ts b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToInvoiceAddLinesParams.ts index 6a4ab61b7..87d6e299e 100644 --- a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToInvoiceAddLinesParams.ts +++ b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToInvoiceAddLinesParams.ts @@ -28,10 +28,10 @@ const toStripeAddLineParams = ({ description, amount: shouldUsePriceData ? undefined - : atmnToStripeAmount({ amount: lineAmount }), + : atmnToStripeAmount({ amount: lineAmount, currency }), price_data: shouldUsePriceData ? { - unit_amount: atmnToStripeAmount({ amount: lineAmount }), + unit_amount: atmnToStripeAmount({ amount: lineAmount, currency }), currency, product: stripeProductId, } diff --git a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToSubscriptionAddInvoiceItemsParams.ts b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToSubscriptionAddInvoiceItemsParams.ts index 087c254f8..97e78f0d5 100644 --- a/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToSubscriptionAddInvoiceItemsParams.ts +++ b/server/src/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToSubscriptionAddInvoiceItemsParams.ts @@ -19,7 +19,10 @@ const toStripeSubscriptionAddInvoiceItem = ({ price_data: { currency: context.currency, product: stripeProductId, - unit_amount: atmnToStripeAmount({ amount: amountAfterDiscounts }), + unit_amount: atmnToStripeAmount({ + amount: amountAfterDiscounts, + currency: context.currency, + }), }, period: context.effectivePeriod ? { diff --git a/server/src/internal/billing/v2/providers/stripe/utils/invoices/createInvoiceForBilling.ts b/server/src/internal/billing/v2/providers/stripe/utils/invoices/createInvoiceForBilling.ts index 0e0066b1f..e23ec2eec 100644 --- a/server/src/internal/billing/v2/providers/stripe/utils/invoices/createInvoiceForBilling.ts +++ b/server/src/internal/billing/v2/providers/stripe/utils/invoices/createInvoiceForBilling.ts @@ -1,7 +1,8 @@ -import type { - BillingContext, - StripeDiscountWithCoupon, - StripeInvoiceAction, +import { + type BillingContext, + orgToCurrency, + type StripeDiscountWithCoupon, + type StripeInvoiceAction, } from "@autumn/shared"; import { type PayInvoiceResult, @@ -92,12 +93,17 @@ export const createInvoiceForBilling = async ({ }); const wantsAutoTax = shouldEnableStripeAutomaticTax({ ctx, billingContext }); + const stripeSubId = options.skipSubscriptionLink + ? undefined + : billingContext.stripeSubscription?.id; + const draftInvoice = await createStripeInvoice({ stripeCli, stripeCusId: billingContext.stripeCustomer?.id ?? "none", - stripeSubId: options.skipSubscriptionLink - ? undefined - : billingContext.stripeSubscription?.id, + stripeSubId, + // Subscription-linked invoices inherit currency from the subscription; + // standalone invoices default to the account currency, not the org's. + currency: stripeSubId ? undefined : orgToCurrency({ org: ctx.org }), collectionMethod, daysUntilDue: invoiceMode?.daysUntilDue, footer: invoiceMode?.footer, diff --git a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleOneOffFunction.ts b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleOneOffFunction.ts index f82a91e0c..81fc0ac7e 100644 --- a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleOneOffFunction.ts +++ b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleOneOffFunction.ts @@ -2,6 +2,7 @@ import { type AttachConfig, type AttachFunctionResponse, AttachFunctionResponseSchema, + atmnToStripeAmount, isFixedPrice, MetadataType, priceToInvoiceAmount, @@ -99,7 +100,10 @@ export const handleOneOffFunction = async ({ invoiceItemData = { description, price_data: { - unit_amount: new Decimal(amount).mul(100).round().toNumber(), + unit_amount: atmnToStripeAmount({ + amount, + currency: orgToCurrency({ org }), + }), currency: orgToCurrency({ org }), product: price.config?.stripe_product_id || product?.processor?.id, }, @@ -136,7 +140,7 @@ export const handleOneOffFunction = async ({ // Skip auto_tax in invoice mode: send_invoice has no // address-collection UI so Stripe Tax rejects. -const wantsAutoTax = + const wantsAutoTax = !!org.config.automatic_tax && !attachParams.invoiceOnly && customerHasUsableTaxLocationForStripeTax(attachParams.stripeCus); @@ -145,12 +149,8 @@ const wantsAutoTax = customer: customer.processor.id!, auto_advance: false, currency: orgToCurrency({ org }), - discounts: rewards - ? rewards.map((r) => ({ coupon: r.id })) - : undefined, - collection_method: attachParams.invoiceOnly - ? "send_invoice" - : undefined, + discounts: rewards ? rewards.map((r) => ({ coupon: r.id })) : undefined, + collection_method: attachParams.invoiceOnly ? "send_invoice" : undefined, days_until_due: attachParams.invoiceOnly ? 30 : undefined, ...(shouldMemo ? { description: invoiceMemo } : {}), ...(wantsAutoTax ? { automatic_tax: { enabled: true } } : {}), diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts index 3638043d3..de463b16e 100644 --- a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts +++ b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts @@ -1,4 +1,5 @@ import { + atmnToStripeAmount, type BillingInterval, BillingType, cusProductsToCusPrices, @@ -91,7 +92,10 @@ const getUsageInvoiceItems = async ({ description, price_data: { product: config.stripe_product_id!, - unit_amount: Math.round(amount * 100), + unit_amount: atmnToStripeAmount({ + amount, + currency: org.default_currency || "usd", + }), currency: org.default_currency || "usd", }, period: { diff --git a/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts b/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts index cb69efe6a..1b285f6bd 100644 --- a/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts +++ b/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts @@ -1,4 +1,5 @@ import { + atmnToStripeAmount, type BillingInterval, BillingType, cusProductToPrices, @@ -138,7 +139,10 @@ export const createAndFilterContUseItems = async ({ const { start, end } = subToPeriodStartEnd({ sub }); await stripeCli.invoiceItems.create({ customer: customer.processor?.id ?? undefined, - amount: Math.round(item.amount * 100), + amount: atmnToStripeAmount({ + amount: item.amount, + currency: org.default_currency || "usd", + }), description: item.description, currency: org.default_currency || "usd", subscription: sub.id, diff --git a/server/src/internal/invoices/invoiceItemUtils/invoiceItemUtils.ts b/server/src/internal/invoices/invoiceItemUtils/invoiceItemUtils.ts index 1711f7834..2c67c0ac5 100644 --- a/server/src/internal/invoices/invoiceItemUtils/invoiceItemUtils.ts +++ b/server/src/internal/invoices/invoiceItemUtils/invoiceItemUtils.ts @@ -1,5 +1,9 @@ -import type { Price, Product, UsagePriceConfig } from "@autumn/shared"; -import { Decimal } from "decimal.js"; +import { + atmnToStripeAmount, + type Price, + type Product, + type UsagePriceConfig, +} from "@autumn/shared"; import type Stripe from "stripe"; import type { AutumnContext } from "@/honoUtils/HonoEnv"; @@ -27,9 +31,10 @@ export const constructStripeInvoiceItem = ({ const { org } = ctx; const config = price.config as UsagePriceConfig; - const amountInCents = Math.floor( - new Decimal(amount).mul(100).round().toNumber(), - ); + const amountInCents = atmnToStripeAmount({ + amount, + currency: org.default_currency || "usd", + }); const priceData = amountInCents > 0 diff --git a/server/tests/_groups/core/coreAttach.ts b/server/tests/_groups/core/coreAttach.ts index 223e5ca48..bf3850e1c 100644 --- a/server/tests/_groups/core/coreAttach.ts +++ b/server/tests/_groups/core/coreAttach.ts @@ -6,6 +6,7 @@ export const coreAttach: TestGroup = { tier: "core", paths: [ "billing/attach/new-plan/attach-paid.test.ts", + "billing/attach/new-plan/attach-one-off-zero-decimal.test.ts", "billing/attach/new-plan/attach-free.test.ts", "billing/attach/new-plan/attach-addon.test.ts", "billing/attach/new-plan/attach-entities.test.ts", diff --git a/server/tests/_groups/core/coreLegacy.ts b/server/tests/_groups/core/coreLegacy.ts index 8aa2f3353..4cf069901 100644 --- a/server/tests/_groups/core/coreLegacy.ts +++ b/server/tests/_groups/core/coreLegacy.ts @@ -16,6 +16,7 @@ export const coreLegacy: TestGroup = { "legacy/attach/invoice/payment-failure/legacy-attach-payment-failed.test.ts", "legacy/attach/new/legacy-new-merged.test.ts", "legacy/attach/new/legacy-new-oneoff.test.ts", + "legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts", "legacy/attach/trial/legacy-trial.test.ts", "legacy/attach/update-quantity/legacy-update-quantity.test.ts", "legacy/attach/upgrade/legacy-upgrade.test.ts", diff --git a/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts b/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts new file mode 100644 index 000000000..6c0b9dae1 --- /dev/null +++ b/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts @@ -0,0 +1,100 @@ +/** + * Regression test for v2 billing.attach one-off purchases in a non-USD org: + * createInvoiceForBilling did not pass `currency` to stripeCli.invoices.create, + * so Stripe defaulted the invoice to the account currency (usd) and rejected + * the org-currency (rwf) price with "price only supports rwf, expected usd". + * + * Pre-fix: attach fails with a Stripe currency-mismatch error. + * Post-fix: invoice created in RWF with total 23,198 (23,188 prepaid + 10 base). + * + * Uses a dedicated sub-org because default_currency is org-wide state and + * group runs execute test files in parallel against the shared master org. + */ + +import { test } from "bun:test"; +import type { ApiCustomerV3 } from "@autumn/shared"; +import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { db } from "@/db/initDrizzle.js"; +import { + getConfiguredRegions, + getRegionalRedis, + waitForRedisReady, +} from "@/external/redis/initRedis.js"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js"; + +const setOrgCurrency = async ({ + orgId, + currency, +}: { + orgId: string; + currency: string; +}) => { + await OrgService.update({ + db, + orgId, + updates: { default_currency: currency }, + }); + // clearOrgCache silently skips Redis deletes until each regional client is ready + await Promise.all( + getConfiguredRegions().map((region) => + waitForRedisReady(getRegionalRedis(region), region), + ), + ); + await clearOrgCache({ db, orgId }); +}; + +test(`${chalk.yellowBright("v2 one-off rwf: billing.attach invoices in the org currency")}`, async () => { + const customerId = "v2-oneoff-rwf-zero-decimal"; + + // Sub-org first so the currency is RWF before any Stripe prices exist. + const { ctx } = await initScenario({ + setup: [s.platform.create({ setupDefaultFeatures: true })], + actions: [], + }); + + await setOrgCurrency({ orgId: ctx.org.id, currency: "rwf" }); + ctx.org.default_currency = "rwf"; + + const oneOff = products.oneOff({ + id: "v2-one-off-rwf", + items: [ + items.oneOffMessages({ + includedUsage: 0, + billingUnits: 1, + price: 23_188, + }), + ], + }); + + const { autumnV1 } = await initScenario({ + ctx, + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [oneOff] }), + ], + actions: [], + }); + + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: oneOff.id, + options: [{ feature_id: TestFeature.Messages, quantity: 1 }], + }); + + const customer = await autumnV1.customers.get(customerId); + + // 23,188 RWF prepaid item + 10 RWF product base price + await expectCustomerInvoiceCorrect({ + customer, + count: 1, + latestTotal: 23_198, + latestStatus: "paid", + }); +}, 120_000); diff --git a/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts b/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts new file mode 100644 index 000000000..aacaec051 --- /dev/null +++ b/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts @@ -0,0 +1,101 @@ +/** + * Regression test for zero-decimal currency (RWF) one-off purchases charging + * 100x: handleOneOffFunction built inline price_data with a raw `* 100` + * instead of atmnToStripeAmount, so RWF 23,188 was charged as RWF 2,318,800. + * + * Pre-fix: invoice total 2,318,810 RWF (prepaid item 100x'd; fixed base OK). + * Post-fix: invoice total 23,198 RWF (23,188 prepaid + 10 base). + * + * Uses a dedicated sub-org because default_currency is org-wide state and + * group runs execute test files in parallel against the shared master org. + */ + +import { test } from "bun:test"; +import type { ApiCustomerV3 } from "@autumn/shared"; +import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { db } from "@/db/initDrizzle.js"; +import { + getConfiguredRegions, + getRegionalRedis, + waitForRedisReady, +} from "@/external/redis/initRedis.js"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js"; + +const setOrgCurrency = async ({ + orgId, + currency, +}: { + orgId: string; + currency: string; +}) => { + await OrgService.update({ + db, + orgId, + updates: { default_currency: currency }, + }); + // clearOrgCache silently skips Redis deletes until each regional client is ready + await Promise.all( + getConfiguredRegions().map((region) => + waitForRedisReady(getRegionalRedis(region), region), + ), + ); + await clearOrgCache({ db, orgId }); +}; + +test(`${chalk.yellowBright("legacy one-off rwf: prepaid one-off charges major units, not x100")}`, async () => { + const customerId = "legacy-oneoff-rwf-zero-decimal"; + + // Sub-org first so the currency is RWF before any Stripe prices exist. + const { ctx } = await initScenario({ + setup: [ + s.platform.create({ setupDefaultFeatures: true }), + ], + actions: [], + }); + + await setOrgCurrency({ orgId: ctx.org.id, currency: "rwf" }); + ctx.org.default_currency = "rwf"; + + const oneOff = products.oneOff({ + id: "one-off-rwf", + items: [ + items.oneOffMessages({ + includedUsage: 0, + billingUnits: 1, + price: 23_188, + }), + ], + }); + + const { autumnV1 } = await initScenario({ + ctx, + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [oneOff] }), + ], + actions: [], + }); + + await autumnV1.attach({ + customer_id: customerId, + product_id: oneOff.id, + options: [{ feature_id: TestFeature.Messages, quantity: 1 }], + }); + + const customer = await autumnV1.customers.get(customerId); + + // 23,188 RWF prepaid item + 10 RWF product base price + await expectCustomerInvoiceCorrect({ + customer, + count: 1, + latestTotal: 23_198, + latestStatus: "paid", + }); +}, 120_000); From df79175faee44f6e924bd820fa024f8f9843b9f7 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:35:50 +0100 Subject: [PATCH 36/41] fix: no billing changes blanks out line items --- scripts/dw/constants.ts | 2 - scripts/dw/helpers/emulate.ts | 12 ++-- scripts/dw/helpers/env-files.ts | 56 ++++++++++++++----- scripts/dw/helpers/ports.ts | 34 +++++++++-- scripts/dw/helpers/start.ts | 22 +++++--- scripts/setup/start-emulate.sh | 16 +++++- .../v2/compute/finalize/finalizeLineItems.ts | 4 ++ .../update-subscription-preview.test.ts | 43 ++++++++++++++ 8 files changed, 151 insertions(+), 38 deletions(-) diff --git a/scripts/dw/constants.ts b/scripts/dw/constants.ts index 4c9d037fd..713709d83 100644 --- a/scripts/dw/constants.ts +++ b/scripts/dw/constants.ts @@ -16,8 +16,6 @@ export const NEON_TEMPLATE_BRANCH = "dw-template"; export const NEON_PARENT_BRANCH = "production"; export const EMULATE_PID_FILE = join(homedir(), ".autumn-emulate.pid"); -export const EMULATE_HEALTH_URL = - "https://google.emulate.localhost/.well-known/openid-configuration"; export const START_EMULATE_SH = join(SCRIPT_DIR, "../setup/start-emulate.sh"); export const ENV_LOCAL_TARGETS = [ diff --git a/scripts/dw/helpers/emulate.ts b/scripts/dw/helpers/emulate.ts index d28793a11..483432838 100644 --- a/scripts/dw/helpers/emulate.ts +++ b/scripts/dw/helpers/emulate.ts @@ -1,19 +1,17 @@ import { existsSync, readFileSync, rmSync } from "node:fs"; -import { sh, log } from "./shell.ts"; -import { - EMULATE_PID_FILE, - EMULATE_HEALTH_URL, - START_EMULATE_SH, -} from "../constants.ts"; +import { EMULATE_PID_FILE, START_EMULATE_SH } from "../constants.ts"; +import { portlessHttpsUrl } from "./ports.ts"; +import { log, sh } from "./shell.ts"; function emulateReachable(): boolean { + const healthUrl = `${portlessHttpsUrl("google.emulate.localhost")}/.well-known/openid-configuration`; const res = sh("curl", [ "-sf", "-o", "/dev/null", "--max-time", "1", - EMULATE_HEALTH_URL, + healthUrl, ]); return res.code === 0; } diff --git a/scripts/dw/helpers/env-files.ts b/scripts/dw/helpers/env-files.ts index e203b158a..96f99b8b9 100644 --- a/scripts/dw/helpers/env-files.ts +++ b/scripts/dw/helpers/env-files.ts @@ -1,15 +1,34 @@ -import { existsSync, readFileSync, renameSync, writeFileSync, rmSync } from "node:fs"; -import { dirname, join } from "node:path"; +import { + existsSync, + readFileSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; import { homedir } from "node:os"; +import { dirname, join } from "node:path"; +import { + ENV_LOCAL_DISABLED_SUFFIX, + ENV_LOCAL_TARGETS, + PROJECT_ROOT, +} from "../constants.ts"; +import type { RegistryEntry } from "../types.ts"; +import { + aliasesFor, + dragonflyPortFor, + elasticMqPortFor, + portlessHttpsUrl, +} from "./ports.ts"; import { log } from "./shell.ts"; import { forceSslVerifyFull } from "./url.ts"; -import { aliasesFor, dragonflyPortFor, elasticMqPortFor } from "./ports.ts"; -import { PROJECT_ROOT, ENV_LOCAL_TARGETS, ENV_LOCAL_DISABLED_SUFFIX } from "../constants.ts"; -import type { RegistryEntry } from "../types.ts"; // Simple KEY=VALUE parse (no quoting/multiline). Sufficient for .env.local // files we own end-to-end; preserves blank lines and comments untouched. -export function parseEnvFile(contents: string): { keys: string[]; values: Record; raw: string[] } { +export function parseEnvFile(contents: string): { + keys: string[]; + values: Record; + raw: string[]; +} { const raw = contents.split(/\r?\n/); const values: Record = {}; const keys: string[] = []; @@ -23,11 +42,14 @@ export function parseEnvFile(contents: string): { keys: string[]; values: Record return { keys, values, raw }; } -export function mergeEnvFile(existing: string | null, managed: Record): string { +export function mergeEnvFile( + existing: string | null, + managed: Record, +): string { if (!existing) { - return Object.entries(managed) + return `${Object.entries(managed) .map(([k, v]) => `${k}=${v}`) - .join("\n") + "\n"; + .join("\n")}\n`; } const parsed = parseEnvFile(existing); const managedKeys = new Set(Object.keys(managed)); @@ -49,7 +71,7 @@ export function mergeEnvFile(existing: string | null, managed: Record 0 && outLines[outLines.length - 1] === "") { outLines.pop(); } - return outLines.join("\n") + "\n"; + return `${outLines.join("\n")}\n`; } export function writeEnvLocalFiles(entry: RegistryEntry): void { @@ -68,7 +90,7 @@ export function writeEnvLocalFiles(entry: RegistryEntry): void { DATABASE_CRITICAL_URL: dbUrl, BETTER_AUTH_URL: aliases.apiUrl, CLIENT_URL: aliases.viteUrl, - EMULATE_GOOGLE_URL: "https://google.emulate.localhost", + EMULATE_GOOGLE_URL: portlessHttpsUrl("google.emulate.localhost"), AUTUMN_TEST_BASE_URL: `http://localhost:${serverPort}`, AUTUMN_TEST_VITE_URL: aliases.viteUrl, STRIPE_WEBHOOK_SKIP_VERIFY: "true", @@ -129,7 +151,11 @@ export function removeEnvLocalFiles(): void { } } -export function disableEnvLocalFiles(): { moved: number; missing: number; alreadyDisabled: number } { +export function disableEnvLocalFiles(): { + moved: number; + missing: number; + alreadyDisabled: number; +} { let moved = 0; let missing = 0; let alreadyDisabled = 0; @@ -150,7 +176,11 @@ export function disableEnvLocalFiles(): { moved: number; missing: number; alread return { moved, missing, alreadyDisabled }; } -export function enableEnvLocalFiles(): { moved: number; missing: number; alreadyEnabled: number } { +export function enableEnvLocalFiles(): { + moved: number; + missing: number; + alreadyEnabled: number; +} { let moved = 0; let missing = 0; let alreadyEnabled = 0; diff --git a/scripts/dw/helpers/ports.ts b/scripts/dw/helpers/ports.ts index 79da5d4b4..4da773386 100644 --- a/scripts/dw/helpers/ports.ts +++ b/scripts/dw/helpers/ports.ts @@ -1,5 +1,10 @@ -import { sh, log } from "./shell.ts"; +import { existsSync, readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; import type { WorktreeAliases } from "../types.ts"; +import { log, sh } from "./shell.ts"; + +const PORTLESS_PROXY_PORT_FILE = join(homedir(), ".portless", "proxy.port"); export function dragonflyPortFor(worktreeNum: number): number { return 6379 + (worktreeNum - 1) * 100; @@ -18,17 +23,38 @@ export function aliasesFor(worktreeNum: number): WorktreeAliases { const viteHost = `wt${worktreeNum}.localhost`; return { apiHost, - apiUrl: `https://${apiHost}`, + apiUrl: portlessHttpsUrl(apiHost), viteHost, - viteUrl: `https://${viteHost}`, + viteUrl: portlessHttpsUrl(viteHost), }; } +export function portlessHttpsUrl(host: string): string { + const port = currentPortlessProxyPort(); + const suffix = port && port !== 443 ? `:${port}` : ""; + return `https://${host}${suffix}`; +} + +export function currentPortlessProxyPort(): number | undefined { + const envPort = Number(process.env.PORTLESS_PORT); + if (Number.isInteger(envPort) && envPort > 0) return envPort; + if (!existsSync(PORTLESS_PROXY_PORT_FILE)) return undefined; + + const filePort = Number( + readFileSync(PORTLESS_PROXY_PORT_FILE, "utf-8").trim(), + ); + if (Number.isInteger(filePort) && filePort > 0) return filePort; + return undefined; +} + export function killOwnPorts(worktreeNum: number): void { const offset = (worktreeNum - 1) * 100; const ports = [8080 + offset, 3000 + offset, 3001 + offset]; if (process.platform === "win32") return; - const lsof = sh("lsof", ports.flatMap((p) => ["-ti", `:${p}`])); + const lsof = sh( + "lsof", + ports.flatMap((p) => ["-ti", `:${p}`]), + ); const pids = lsof.stdout.split("\n").filter(Boolean); for (const pid of pids) { try { diff --git a/scripts/dw/helpers/start.ts b/scripts/dw/helpers/start.ts index 82fa10533..a2f417701 100644 --- a/scripts/dw/helpers/start.ts +++ b/scripts/dw/helpers/start.ts @@ -1,13 +1,13 @@ import { existsSync } from "node:fs"; -import { join } from "node:path"; import { homedir } from "node:os"; -import { log, fatal } from "./shell.ts"; -import { registerPortlessAliases } from "./portless.ts"; -import { rewriteDbEnv } from "./url.ts"; -import { aliasesFor, killOwnPorts } from "./ports.ts"; -import { tmuxSessionName, spawnDevInTmux } from "./tmux.ts"; +import { join } from "node:path"; import { PROJECT_ROOT } from "../constants.ts"; import type { RegistryEntry } from "../types.ts"; +import { registerPortlessAliases } from "./portless.ts"; +import { portlessHttpsUrl } from "./ports.ts"; +import { fatal, log } from "./shell.ts"; +import { spawnDevInTmux, tmuxSessionName } from "./tmux.ts"; +import { rewriteDbEnv } from "./url.ts"; export function buildDevEnvAndArgs(entry: RegistryEntry): { env: Record; @@ -21,7 +21,7 @@ export function buildDevEnvAndArgs(entry: RegistryEntry): { if (!databaseUrl) fatal("agent worktree missing databaseUrl"); env = rewriteDbEnv(env, databaseUrl); if (!env.EMULATE_GOOGLE_URL) { - env.EMULATE_GOOGLE_URL = "https://google.emulate.localhost"; + env.EMULATE_GOOGLE_URL = portlessHttpsUrl("google.emulate.localhost"); } const portlessCa = join(homedir(), ".portless", "ca.pem"); if (existsSync(portlessCa) && !env.NODE_EXTRA_CA_CERTS) { @@ -44,14 +44,18 @@ export function buildDevEnvAndArgs(entry: RegistryEntry): { return { env, args }; } -export function startDev(entry: RegistryEntry, opts?: { allowTmux?: boolean }): never { +export function startDev( + entry: RegistryEntry, + opts?: { allowTmux?: boolean }, +): never { const { worktreeNum, branchName } = entry; const { env, args } = buildDevEnvAndArgs(entry); // Agent worktrees (N > 1) in a non-TTY invocation: wrap in detached tmux // so the calling agent doesn't block. Canonical (N=1) stays inline always. // Node/Bun sets isTTY to true when stdout is a TTY and undefined otherwise. - const useTmux = (opts?.allowTmux ?? true) && worktreeNum > 1 && !process.stdout.isTTY; + const useTmux = + (opts?.allowTmux ?? true) && worktreeNum > 1 && !process.stdout.isTTY; if (useTmux) { log( `starting dev in tmux (worktree=${worktreeNum}${branchName ? `, branch=${branchName}` : ""}, non-TTY)`, diff --git a/scripts/setup/start-emulate.sh b/scripts/setup/start-emulate.sh index 388b4deef..238cd1494 100644 --- a/scripts/setup/start-emulate.sh +++ b/scripts/setup/start-emulate.sh @@ -8,13 +8,23 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" SEED="$ROOT/emulate.config.yaml" LOG="$HOME/.autumn-emulate.log" PID_FILE="$HOME/.autumn-emulate.pid" +PORTLESS_PORT_FILE="$HOME/.portless/proxy.port" +EMULATE_URL="https://google.emulate.localhost" + +PORTLESS_PROXY_PORT="${PORTLESS_PORT:-}" +if [[ -z "$PORTLESS_PROXY_PORT" && -f "$PORTLESS_PORT_FILE" ]]; then + PORTLESS_PROXY_PORT="$(cat "$PORTLESS_PORT_FILE" 2>/dev/null || true)" +fi +if [[ -n "$PORTLESS_PROXY_PORT" && "$PORTLESS_PROXY_PORT" != "443" ]]; then + EMULATE_URL="${EMULATE_URL}:${PORTLESS_PROXY_PORT}" +fi reachable() { - curl -sf -o /dev/null --max-time 1 "https://google.emulate.localhost/.well-known/openid-configuration" + curl -sf -o /dev/null --max-time 1 "${EMULATE_URL}/.well-known/openid-configuration" } if reachable; then - echo "[emulate] already reachable at https://google.emulate.localhost" + echo "[emulate] already reachable at ${EMULATE_URL}" exit 0 fi @@ -54,7 +64,7 @@ disown # Block briefly until the emulator is actually serving so callers can race. for _ in $(seq 1 30); do if reachable; then - echo "[emulate] ready at https://google.emulate.localhost (pid $(cat "$PID_FILE"))" + echo "[emulate] ready at ${EMULATE_URL} (pid $(cat "$PID_FILE"))" exit 0 fi sleep 0.3 diff --git a/server/src/internal/billing/v2/compute/finalize/finalizeLineItems.ts b/server/src/internal/billing/v2/compute/finalize/finalizeLineItems.ts index 3884f2ca5..c2b931027 100644 --- a/server/src/internal/billing/v2/compute/finalize/finalizeLineItems.ts +++ b/server/src/internal/billing/v2/compute/finalize/finalizeLineItems.ts @@ -31,6 +31,10 @@ export const finalizeLineItems = ({ autumnBillingPlan: AutumnBillingPlan; customLineItems?: CustomLineItem[]; }): LineItem[] => { + if (billingContext.skipBillingChanges) { + return []; + } + if ( billingContext.requestedProrationBehavior === "none" && !billingContext.anchorResetRefund?.noPartialRefund diff --git a/server/tests/integration/billing/update-subscription/preview/update-subscription-preview.test.ts b/server/tests/integration/billing/update-subscription/preview/update-subscription-preview.test.ts index 97e7b17bd..78ef736d8 100644 --- a/server/tests/integration/billing/update-subscription/preview/update-subscription-preview.test.ts +++ b/server/tests/integration/billing/update-subscription/preview/update-subscription-preview.test.ts @@ -2,6 +2,7 @@ import { expect, test } from "bun:test"; import { type ApiCustomerV3, UpdateSubscriptionPreviewIntent, + type UpdateSubscriptionV1ParamsInput, } from "@autumn/shared"; import { expectProductActive, @@ -268,3 +269,45 @@ test.concurrent(`${chalk.yellowBright("update-subscription preview: uncancel wit outgoing: [{ planId: pro.id }], }); }); + +// Regression: no_billing_changes previews showed immediate charges despite skipping Stripe. +// Green: entitlement-only DB updates preview $0 due now and still show the plan change. +test.concurrent(`${chalk.yellowBright("update-subscription preview: no_billing_changes custom entitlement has no immediate due")}`, async () => { + const customerId = "update-sub-preview-no-billing-custom-ent"; + const pro = products.base({ + id: "pro-no-billing-preview", + items: [ + items.monthlyMessages({ includedUsage: 100 }), + items.monthlyPrice({ price: 20 }), + ], + }); + + const { autumnV2_2 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [pro] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + const preview = await autumnV2_2.subscriptions.previewUpdate({ + customer_id: customerId, + plan_id: pro.id, + no_billing_changes: true, + customize: { + price: itemsV2.monthlyPrice({ amount: 20 }), + items: [itemsV2.monthlyMessages({ included: 250 })], + }, + }); + + expect(preview.intent).toBe(UpdateSubscriptionPreviewIntent.UpdatePlan); + expect(preview.line_items).toEqual([]); + expect(preview.subtotal).toBe(0); + expect(preview.total).toBe(0); + expectPreviewChanges({ + preview, + incoming: [{ planId: pro.id, effectiveAt: null }], + outgoing: [{ planId: pro.id }], + }); +}); From af173861e21a7d972ab344c4927572f536f7f4a5 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:44:27 +0100 Subject: [PATCH 37/41] =?UTF-8?q?refactor(tests):=20=F0=9F=92=A1=20extract?= =?UTF-8?q?=20shared=20setOrgCurrency=20test=20util?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deduplicates the org-currency helper from both zero-decimal regression tests, per PR review. Co-Authored-By: Claude Fable 5 --- .../attach-one-off-zero-decimal.test.ts | 30 +--------------- .../legacy-new-oneoff-zero-decimal.test.ts | 34 ++----------------- .../utils/testInitUtils/setOrgCurrency.ts | 33 ++++++++++++++++++ 3 files changed, 36 insertions(+), 61 deletions(-) create mode 100644 server/tests/utils/testInitUtils/setOrgCurrency.ts diff --git a/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts b/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts index 6c0b9dae1..202a8e99f 100644 --- a/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts +++ b/server/tests/integration/billing/attach/new-plan/attach-one-off-zero-decimal.test.ts @@ -18,36 +18,8 @@ import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import { setOrgCurrency } from "@tests/utils/testInitUtils/setOrgCurrency.js"; import chalk from "chalk"; -import { db } from "@/db/initDrizzle.js"; -import { - getConfiguredRegions, - getRegionalRedis, - waitForRedisReady, -} from "@/external/redis/initRedis.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; -import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js"; - -const setOrgCurrency = async ({ - orgId, - currency, -}: { - orgId: string; - currency: string; -}) => { - await OrgService.update({ - db, - orgId, - updates: { default_currency: currency }, - }); - // clearOrgCache silently skips Redis deletes until each regional client is ready - await Promise.all( - getConfiguredRegions().map((region) => - waitForRedisReady(getRegionalRedis(region), region), - ), - ); - await clearOrgCache({ db, orgId }); -}; test(`${chalk.yellowBright("v2 one-off rwf: billing.attach invoices in the org currency")}`, async () => { const customerId = "v2-oneoff-rwf-zero-decimal"; diff --git a/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts b/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts index aacaec051..1e6243e2b 100644 --- a/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts +++ b/server/tests/integration/billing/legacy/attach/new/legacy-new-oneoff-zero-decimal.test.ts @@ -17,45 +17,15 @@ import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import { setOrgCurrency } from "@tests/utils/testInitUtils/setOrgCurrency.js"; import chalk from "chalk"; -import { db } from "@/db/initDrizzle.js"; -import { - getConfiguredRegions, - getRegionalRedis, - waitForRedisReady, -} from "@/external/redis/initRedis.js"; -import { OrgService } from "@/internal/orgs/OrgService.js"; -import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js"; - -const setOrgCurrency = async ({ - orgId, - currency, -}: { - orgId: string; - currency: string; -}) => { - await OrgService.update({ - db, - orgId, - updates: { default_currency: currency }, - }); - // clearOrgCache silently skips Redis deletes until each regional client is ready - await Promise.all( - getConfiguredRegions().map((region) => - waitForRedisReady(getRegionalRedis(region), region), - ), - ); - await clearOrgCache({ db, orgId }); -}; test(`${chalk.yellowBright("legacy one-off rwf: prepaid one-off charges major units, not x100")}`, async () => { const customerId = "legacy-oneoff-rwf-zero-decimal"; // Sub-org first so the currency is RWF before any Stripe prices exist. const { ctx } = await initScenario({ - setup: [ - s.platform.create({ setupDefaultFeatures: true }), - ], + setup: [s.platform.create({ setupDefaultFeatures: true })], actions: [], }); diff --git a/server/tests/utils/testInitUtils/setOrgCurrency.ts b/server/tests/utils/testInitUtils/setOrgCurrency.ts new file mode 100644 index 000000000..d06d5006f --- /dev/null +++ b/server/tests/utils/testInitUtils/setOrgCurrency.ts @@ -0,0 +1,33 @@ +import { db } from "@/db/initDrizzle.js"; +import { + getConfiguredRegions, + getRegionalRedis, + waitForRedisReady, +} from "@/external/redis/initRedis.js"; +import { OrgService } from "@/internal/orgs/OrgService.js"; +import { clearOrgCache } from "@/internal/orgs/orgUtils/clearOrgCache.js"; + +/** + * Sets an org's default_currency and clears its cached secret-key verification. + * Use a dedicated sub-org: currency is org-wide state and test files run in parallel. + */ +export const setOrgCurrency = async ({ + orgId, + currency, +}: { + orgId: string; + currency: string; +}) => { + await OrgService.update({ + db, + orgId, + updates: { default_currency: currency }, + }); + // clearOrgCache silently skips Redis deletes until each regional client is ready + await Promise.all( + getConfiguredRegions().map((region) => + waitForRedisReady(getRegionalRedis(region), region), + ), + ); + await clearOrgCache({ db, orgId }); +}; From 8d2d5b019fab5ae1d739f191d5959838e69d5d81 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 13:55:20 +0100 Subject: [PATCH 38/41] fix: mcp server url --- apps/leaf/src/lib/env.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/leaf/src/lib/env.ts b/apps/leaf/src/lib/env.ts index 53e94476b..8d422c99b 100644 --- a/apps/leaf/src/lib/env.ts +++ b/apps/leaf/src/lib/env.ts @@ -42,7 +42,10 @@ const envSchema = z return { ...values, MCP_SERVER_URL: - values.MCP_SERVER_URL ?? `http://localhost:${values.PORT}`, + values.MCP_SERVER_URL ?? + (process.env.NODE_ENV === "production" + ? "https://mcp.useautumn.com/mcp" + : `http://localhost:${values.PORT}`), BETTER_AUTH_URL: values.BETTER_AUTH_URL ?? (process.env.NODE_ENV === "production" From 02c6eea6518fea9f71e2929ed3f403c0cf72c002 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 14:33:49 +0100 Subject: [PATCH 39/41] chore: clean up db migrations --- apps/leaf/src/main.ts | 2 ++ shared/drizzle/0001_talented_thor.sql | 25 ------------------- shared/drizzle/0002_shocking_wong.sql | 1 - .../drizzle/0008_slippery_william_stryker.sql | 1 - 4 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 shared/drizzle/0001_talented_thor.sql delete mode 100644 shared/drizzle/0002_shocking_wong.sql delete mode 100644 shared/drizzle/0008_slippery_william_stryker.sql diff --git a/apps/leaf/src/main.ts b/apps/leaf/src/main.ts index fb00cd9e2..627c01925 100644 --- a/apps/leaf/src/main.ts +++ b/apps/leaf/src/main.ts @@ -18,6 +18,8 @@ app.use("*", async (c, next) => { app.get("/health", (c) => c.json({ ok: true })); + + app.route( "", createMcpRouter({ diff --git a/shared/drizzle/0001_talented_thor.sql b/shared/drizzle/0001_talented_thor.sql deleted file mode 100644 index 0fe098099..000000000 --- a/shared/drizzle/0001_talented_thor.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE TABLE "passkey" ( - "id" text PRIMARY KEY NOT NULL, - "name" text, - "public_key" text NOT NULL, - "user_id" text NOT NULL, - "credential_id" text NOT NULL, - "counter" integer NOT NULL, - "device_type" text NOT NULL, - "backed_up" boolean NOT NULL, - "transports" text, - "created_at" timestamp with time zone, - "aaguid" text, - CONSTRAINT "passkey_credential_id_unique" UNIQUE("credential_id") -); ---> statement-breakpoint -ALTER TABLE "passkey" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -ALTER TABLE "migration_runs" ADD COLUMN "target_limit" numeric;--> statement-breakpoint -ALTER TABLE "migrations" ADD COLUMN "archived" boolean DEFAULT false NOT NULL;--> statement-breakpoint -ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "passkey_userId_idx" ON "passkey" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "passkey_credentialId_idx" ON "passkey" USING btree ("credential_id");--> statement-breakpoint -CREATE INDEX "idx_customer_products_revenuecat_processor" ON "customer_products" USING btree ("internal_customer_id") WHERE ("customer_products"."processor" ->> 'type') = 'revenuecat';--> statement-breakpoint -CREATE INDEX "idx_customers_cursor" ON "customers" USING btree ("org_id","env","created_at" DESC,"id" DESC);--> statement-breakpoint -CREATE INDEX "idx_entities_cursor" ON "entities" USING btree ("org_id","env","created_at" DESC,"id" DESC);--> statement-breakpoint -CREATE INDEX "idx_entitlements_internal_reward_id_c_partial" ON "entitlements" USING btree ("internal_reward_id" COLLATE "C") WHERE "entitlements"."internal_reward_id" IS NOT NULL; \ No newline at end of file diff --git a/shared/drizzle/0002_shocking_wong.sql b/shared/drizzle/0002_shocking_wong.sql deleted file mode 100644 index be0692e60..000000000 --- a/shared/drizzle/0002_shocking_wong.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "features" ADD COLUMN "model_markups" jsonb DEFAULT null; \ No newline at end of file diff --git a/shared/drizzle/0008_slippery_william_stryker.sql b/shared/drizzle/0008_slippery_william_stryker.sql deleted file mode 100644 index 269375952..000000000 --- a/shared/drizzle/0008_slippery_william_stryker.sql +++ /dev/null @@ -1 +0,0 @@ --- ALTER TABLE "migrations" ADD COLUMN "archived" boolean DEFAULT false NOT NULL; \ No newline at end of file From 494dd93b0374413a1ddf3247a17d22d8a1c0f214 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 15:10:45 +0100 Subject: [PATCH 40/41] chore: clean up db migrations --- .../0014_repair_migrations_archived.sql | 1 + shared/drizzle/meta/0014_snapshot.json | 7802 +++++++++++++++++ shared/drizzle/meta/_journal.json | 7 + 3 files changed, 7810 insertions(+) create mode 100644 shared/drizzle/0014_repair_migrations_archived.sql create mode 100644 shared/drizzle/meta/0014_snapshot.json diff --git a/shared/drizzle/0014_repair_migrations_archived.sql b/shared/drizzle/0014_repair_migrations_archived.sql new file mode 100644 index 000000000..8a80df394 --- /dev/null +++ b/shared/drizzle/0014_repair_migrations_archived.sql @@ -0,0 +1 @@ +ALTER TABLE "migrations" ADD COLUMN IF NOT EXISTS "archived" boolean DEFAULT false NOT NULL; diff --git a/shared/drizzle/meta/0014_snapshot.json b/shared/drizzle/meta/0014_snapshot.json new file mode 100644 index 000000000..644bdd458 --- /dev/null +++ b/shared/drizzle/meta/0014_snapshot.json @@ -0,0 +1,7802 @@ +{ + "id": "6df1dc30-df93-47b2-978f-9dbbf81c6133", + "prevId": "fc2ee520-88f5-4204-9013-e54d6472effa", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "columnsFrom": [ + "internal_entity_id" + ], + "tableTo": "entities", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.agent_rules": { + "name": "agent_rules", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_rules": { + "name": "entity_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "credit_rules": { + "name": "credit_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_rules_org_id_fkey": { + "name": "agent_rules_org_id_fkey", + "tableFrom": "agent_rules", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "columns": [ + "hashed_key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_approvals": { + "name": "chat_approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_ts": { + "name": "message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_args": { + "name": "tool_args", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "preview": { + "name": "preview", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "decided_at": { + "name": "decided_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "decided_by_provider_user_id": { + "name": "decided_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chat_approvals_org_id_fkey": { + "name": "chat_approvals_org_id_fkey", + "tableFrom": "chat_approvals", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_installations": { + "name": "chat_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_access_token": { + "name": "bot_access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_env": { + "name": "default_env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_api_key_id": { + "name": "sandbox_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_api_key": { + "name": "sandbox_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key_id": { + "name": "live_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_api_key": { + "name": "live_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_by_provider_user_id": { + "name": "installed_by_provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_installations_org_id_fkey": { + "name": "chat_installations_org_id_fkey", + "tableFrom": "chat_installations", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_installations_org_provider_key": { + "name": "chat_installations_org_provider_key", + "columns": [ + "org_id", + "provider" + ], + "nullsNotDistinct": false + }, + "chat_installations_provider_workspace_key": { + "name": "chat_installations_provider_workspace_key", + "columns": [ + "provider", + "workspace_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_oauth_credentials": { + "name": "chat_oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_installation_id": { + "name": "chat_installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_consent_id": { + "name": "oauth_consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_oauth_credentials_installation_id_fkey": { + "name": "chat_oauth_credentials_installation_id_fkey", + "tableFrom": "chat_oauth_credentials", + "columnsFrom": [ + "chat_installation_id" + ], + "tableTo": "chat_installations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "chat_oauth_credentials_org_id_fkey": { + "name": "chat_oauth_credentials_org_id_fkey", + "tableFrom": "chat_oauth_credentials", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_oauth_credentials_installation_env_key": { + "name": "chat_oauth_credentials_installation_env_key", + "columns": [ + "chat_installation_id", + "env" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "leaf.cma_memory": { + "name": "cma_memory", + "schema": "leaf", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "memory_store_id": { + "name": "memory_store_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "cma_memory_org_id_env_pk": { + "name": "cma_memory_org_id_env_pk", + "columns": [ + "org_id", + "env" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "leaf.cma_sessions": { + "name": "cma_sessions", + "schema": "leaf", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_key": { + "name": "thread_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "braintrust_parent": { + "name": "braintrust_parent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "cma_sessions_org_id_env_thread_key_pk": { + "name": "cma_sessions_org_id_env_thread_key_pk", + "columns": [ + "org_id", + "env", + "thread_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "leaf.cma_vaults": { + "name": "cma_vaults", + "schema": "leaf", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_id": { + "name": "vault_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "cma_vaults_org_id_env_pk": { + "name": "cma_vaults_org_id_env_pk", + "columns": [ + "org_id", + "env" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "hash", + "concurrently": false + }, + "idx_customer_entitlements_internal_customer_id_btree": { + "name": "idx_customer_entitlements_internal_customer_id_btree", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_entitlements_internal_entity_id": { + "name": "idx_customer_entitlements_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "hash", + "concurrently": false + }, + "idx_customer_entitlements_on_next_reset_at": { + "name": "idx_customer_entitlements_on_next_reset_at", + "columns": [ + { + "expression": "next_reset_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "columnsFrom": [ + "internal_feature_id" + ], + "tableTo": "features", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "columnsFrom": [ + "internal_entity_id" + ], + "tableTo": "entities", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "columnsFrom": [ + "customer_product_id" + ], + "tableTo": "customer_products", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "tableTo": "entitlements", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_prices_internal_customer_id": { + "name": "idx_customer_prices_internal_customer_id", + "columns": [ + { + "expression": "\"internal_customer_id\" COLLATE \"C\"", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"customer_prices\".\"internal_customer_id\" IS NOT NULL", + "concurrently": true + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "columnsFrom": [ + "customer_product_id" + ], + "tableTo": "customer_products", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "columnsFrom": [ + "price_id" + ], + "tableTo": "prices", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "access_starts_at": { + "name": "access_starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "billing_cycle_anchor_resets_at": { + "name": "billing_cycle_anchor_resets_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_customer_product_id": { + "name": "previous_customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_trial_end": { + "name": "on_trial_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_products_on_internal_product_id": { + "name": "idx_customer_products_on_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_products_subscription_ids": { + "name": "idx_customer_products_subscription_ids", + "columns": [ + { + "expression": "subscription_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "idx_customer_products_scheduled_ids": { + "name": "idx_customer_products_scheduled_ids", + "columns": [ + { + "expression": "scheduled_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "idx_customer_products_stripe_checkout_session_id": { + "name": "idx_customer_products_stripe_checkout_session_id", + "columns": [ + { + "expression": "stripe_checkout_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customer_products_revenuecat_processor": { + "name": "idx_customer_products_revenuecat_processor", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "(\"customer_products\".\"processor\" ->> 'type') = 'revenuecat'", + "concurrently": false + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "columnsFrom": [ + "free_trial_id" + ], + "tableTo": "free_trials", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "columnsFrom": [ + "internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "columnsFrom": [ + "internal_entity_id" + ], + "tableTo": "entities", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false + }, + "idx_customers_processor_id": { + "name": "idx_customers_processor_id", + "columns": [ + { + "expression": "(\"processor\" ->> 'id')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_composite": { + "name": "idx_customers_composite", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_org_env_internal_id": { + "name": "idx_customers_org_env_internal_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_email_trgm": { + "name": "idx_customers_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"customers\".\"email\" IS NOT NULL", + "concurrently": false + }, + "idx_customers_name_trgm": { + "name": "idx_customers_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"customers\".\"name\" IS NOT NULL", + "concurrently": false + }, + "idx_customers_id_trgm": { + "name": "idx_customers_id_trgm", + "columns": [ + { + "expression": "\"id\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"customers\".\"id\" IS NOT NULL", + "concurrently": false + }, + "idx_customers_org_id_env_created_at": { + "name": "idx_customers_org_id_env_created_at", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_cursor": { + "name": "idx_customers_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_processors_revenuecat": { + "name": "idx_customers_processors_revenuecat", + "columns": [ + { + "expression": "(\"processors\" ->> 'revenuecat')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_customers_processors_vercel": { + "name": "idx_customers_processors_vercel", + "columns": [ + { + "expression": "(\"processors\" ->> 'vercel')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "columns": [ + "org_id", + "id", + "env" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "overage_allowed": { + "name": "overage_allowed", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entities_org_env_id": { + "name": "idx_entities_org_env_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entities_cursor": { + "name": "idx_entities_cursor", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entities_customer_created_at": { + "name": "idx_entities_customer_created_at", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "columnsFrom": [ + "internal_feature_id" + ], + "tableTo": "features", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "expiry_duration": { + "name": "expiry_duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_length": { + "name": "expiry_length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entitlements_internal_reward_id": { + "name": "idx_entitlements_internal_reward_id", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entitlements_reward_feature": { + "name": "idx_entitlements_reward_feature", + "columns": [ + { + "expression": "internal_reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_entitlements_internal_reward_id_c_partial": { + "name": "idx_entitlements_internal_reward_id_c_partial", + "columns": [ + { + "expression": "\"internal_reward_id\" COLLATE \"C\"", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"entitlements\".\"internal_reward_id\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "columnsFrom": [ + "internal_feature_id" + ], + "tableTo": "features", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "columnsFrom": [ + "internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "entitlements_internal_reward_id_fkey": { + "name": "entitlements_internal_reward_id_fkey", + "tableFrom": "entitlements", + "columnsFrom": [ + "internal_reward_id" + ], + "tableTo": "rewards", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "columns": [ + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deductions": { + "name": "deductions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"events\".\"set_usage\" = false", + "concurrently": false + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "model_markups": { + "name": "model_markups", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "columns": [ + "org_id", + "id", + "env" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "on_end": { + "name": "on_end", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "columnsFrom": [ + "internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "columnsFrom": [ + "inviter_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoice_line_items_customer_product_ids": { + "name": "idx_invoice_line_items_customer_product_ids", + "columns": [ + { + "expression": "customer_product_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "columnsFrom": [ + "invoice_id" + ], + "tableTo": "invoices", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "columns": [ + "stripe_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_templates": { + "name": "invoice_templates", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "footer": { + "name": "footer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "net_terms_days": { + "name": "net_terms_days", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_invoice_templates_org_id": { + "name": "idx_invoice_templates_org_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invoice_templates_org_id_fkey": { + "name": "invoice_templates_org_id_fkey", + "tableFrom": "invoice_templates", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_templates_id_unique": { + "name": "invoice_templates_id_unique", + "columns": [ + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor_type": { + "name": "processor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "refunded_amount": { + "name": "refunded_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_invoices_internal_entity_id": { + "name": "idx_invoices_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"invoices\".\"internal_entity_id\" IS NOT NULL", + "concurrently": true + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "columnsFrom": [ + "internal_entity_id" + ], + "tableTo": "entities", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "columns": [ + "stripe_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "columnsFrom": [ + "migration_job_id" + ], + "tableTo": "migration_jobs", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_item_runs": { + "name": "migration_item_runs", + "schema": "", + "columns": { + "migration_item_run_id": { + "name": "migration_item_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_run_id": { + "name": "migration_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "item_kind": { + "name": "item_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_item_runs_live_unique": { + "name": "migration_item_runs_live_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"migration_item_runs\".\"dry_run\" = false", + "concurrently": false + }, + "migration_item_runs_dry_run_unique": { + "name": "migration_item_runs_dry_run_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"migration_item_runs\".\"dry_run\" = true", + "concurrently": false + }, + "migration_item_runs_customer_recent_idx": { + "name": "migration_item_runs_customer_recent_idx", + "columns": [ + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"migration_item_runs\".\"item_kind\" = 'customer'", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "columnsFrom": [ + "from_internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "columnsFrom": [ + "to_internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "migration_internal_id": { + "name": "migration_internal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lazy_run": { + "name": "lazy_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_run_id": { + "name": "trigger_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "only_ids": { + "name": "only_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "target_limit": { + "name": "target_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migration_runs_active_per_migration_unique": { + "name": "migration_runs_active_per_migration_unique", + "columns": [ + { + "expression": "migration_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"migration_runs\".\"status\" IN ('queued', 'running')", + "concurrently": false + } + }, + "foreignKeys": { + "migration_runs_migration_internal_id_fkey": { + "name": "migration_runs_migration_internal_id_fkey", + "tableFrom": "migration_runs", + "columnsFrom": [ + "migration_internal_id" + ], + "tableTo": "migrations", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "migration_runs_org_id_fkey": { + "name": "migration_runs_org_id_fkey", + "tableFrom": "migration_runs", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migrations": { + "name": "migrations", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prepared_state": { + "name": "prepared_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "no_billing_changes": { + "name": "no_billing_changes", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "retry_failed": { + "name": "retry_failed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "migrations_org_env_id_unique": { + "name": "migrations_org_env_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "migrations_org_id_fkey": { + "name": "migrations_org_id_fkey", + "tableFrom": "migrations", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "refresh_id" + ], + "tableTo": "oauth_refresh_token", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_api_key_id": { + "name": "oauth_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redis_config": { + "name": "redis_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_organizations_name_trgm": { + "name": "idx_organizations_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"organizations\".\"name\" IS NOT NULL", + "concurrently": false + }, + "idx_organizations_slug_trgm": { + "name": "idx_organizations_slug_trgm", + "columns": [ + { + "expression": "\"slug\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"organizations\".\"slug\" IS NOT NULL", + "concurrently": false + }, + "idx_organizations_created_at_id": { + "name": "idx_organizations_created_at_id", + "columns": [ + { + "expression": "\"createdAt\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "nullsNotDistinct": false + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "columns": [ + "test_pkey" + ], + "nullsNotDistinct": false + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "columns": [ + "live_pkey" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "passkey_credentialId_idx": { + "name": "passkey_credentialId_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "passkey_credential_id_unique": { + "name": "passkey_credential_id_unique", + "columns": [ + "credential_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "columnsFrom": [ + "entitlement_id" + ], + "tableTo": "entitlements", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "columnsFrom": [ + "internal_product_id" + ], + "tableTo": "products", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "columns": [ + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_products_org_env_id_version": { + "name": "idx_products_org_env_id_version", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "columns": [ + "org_id", + "id", + "env", + "version" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_referral_codes_internal_customer_id": { + "name": "idx_referral_codes_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "columnsFrom": [ + "internal_reward_program_id" + ], + "tableTo": "reward_programs", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "columns": [ + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "columnsFrom": [ + "cus_ent_id" + ], + "tableTo": "customer_entitlements", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "columnsFrom": [ + "internal_reward_id" + ], + "tableTo": "rewards", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_internal_id": { + "name": "reward_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_code": { + "name": "promo_code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_reward_redemptions_referral_code_id": { + "name": "idx_reward_redemptions_referral_code_id", + "columns": [ + { + "expression": "referral_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_reward_redemptions_reward_internal_id": { + "name": "idx_reward_redemptions_reward_internal_id", + "columns": [ + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_reward_redemptions_customer_reward": { + "name": "idx_reward_redemptions_customer_reward", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reward_internal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "columnsFrom": [ + "internal_reward_program_id" + ], + "tableTo": "reward_programs", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "columnsFrom": [ + "referral_code_id" + ], + "tableTo": "referral_codes", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "columnsFrom": [ + "cus_ent_id" + ], + "tableTo": "customer_entitlements", + "columnsTo": [ + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.phases": { + "name": "phases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "phases_schedule_id_fkey": { + "name": "phases_schedule_id_fkey", + "tableFrom": "phases", + "columnsFrom": [ + "schedule_id" + ], + "tableTo": "schedules", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "phases_schedule_id_starts_at_key": { + "name": "phases_schedule_id_starts_at_key", + "columns": [ + "schedule_id", + "starts_at" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "schedules_customer_scope_unique": { + "name": "schedules_customer_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"schedules\".\"internal_entity_id\" IS NULL", + "concurrently": false + }, + "schedules_entity_scope_unique": { + "name": "schedules_entity_scope_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"schedules\".\"internal_entity_id\" IS NOT NULL", + "concurrently": false + }, + "idx_schedules_internal_customer_id": { + "name": "idx_schedules_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_schedules_internal_entity_id": { + "name": "idx_schedules_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "schedules_org_id_fkey": { + "name": "schedules_org_id_fkey", + "tableFrom": "schedules", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "schedules_internal_customer_id_fkey": { + "name": "schedules_internal_customer_id_fkey", + "tableFrom": "schedules", + "columnsFrom": [ + "internal_customer_id" + ], + "tableTo": "customers", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "schedules_internal_entity_id_fkey": { + "name": "schedules_internal_entity_id_fkey", + "tableFrom": "schedules", + "columnsFrom": [ + "internal_entity_id" + ], + "tableTo": "entities", + "columnsTo": [ + "internal_id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "columns": [ + "stripe_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_name_trgm": { + "name": "idx_user_name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"user\".\"name\" IS NOT NULL", + "concurrently": false + }, + "idx_user_email_trgm": { + "name": "idx_user_email_trgm", + "columns": [ + { + "expression": "\"email\" gin_trgm_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"user\".\"email\" IS NOT NULL", + "concurrently": false + }, + "idx_user_created_at_id": { + "name": "idx_user_created_at_id", + "columns": [ + { + "expression": "\"created_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "columnsFrom": [ + "created_by" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "vercel_resources_installation_name_unique_idx": { + "name": "vercel_resources_installation_name_unique_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status <> 'uninstalled'", + "concurrently": false + } + }, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "columnsFrom": [ + "org_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": { + "leaf": "leaf" + }, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json index c7a381440..122899b47 100644 --- a/shared/drizzle/meta/_journal.json +++ b/shared/drizzle/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1781165345168, "tag": "0013_third_darkhawk", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1781186995896, + "tag": "0014_repair_migrations_archived", + "breakpoints": true } ] } \ No newline at end of file From db7088b4dde69c25f73f9fb540b6ca40b62191e6 Mon Sep 17 00:00:00 2001 From: johnyeo Date: Thu, 11 Jun 2026 15:28:59 +0100 Subject: [PATCH 41/41] cleaned up usage windows --- ai | 2 +- scripts/migrations/migrate-functions.ts | 11 + .../rollUsageWindows/rollUsageWindows.lua | 80 ++ .../fullSubjectDeduction/contextUtilsV2.lua | 26 +- .../deductFromSubjectBalances.lua | 141 ++-- .../lock/unwindLockV2.lua | 3 + .../readSubjectBalances.lua | 26 +- .../runDeductionOnContextV2.lua | 75 +- .../usageWindowUtilsV2.lua | 237 ------ .../usageWindows/readUsageWindows.lua | 101 +++ .../usageWindowContextUtilsV2.lua | 301 ++++++++ server/src/_luaScriptsV2/luaScriptsV2.ts | 15 +- .../initUtils/createRedisAvailability.ts | 16 +- .../redis/initUtils/redisAvailability.ts | 2 +- .../external/redis/initUtils/redisTypes.ts | 1 + .../redis/initUtils/registerRedisCommands.ts | 6 + .../balances/check/getCheckResponseV2.ts | 1 + .../finalizeLock/runRedisFinalizeLockV2.ts | 16 +- .../track/v3/handleRedisTrackErrorV3.ts | 7 - .../balances/track/v3/runRedisTrackV3.ts | 73 +- .../updateBalance/v2/updateRemainingV2.ts | 10 +- .../updateBalance/v2/updateUsageV2.ts | 10 +- .../applyDeductionUpdateToFullSubject.ts | 1 - .../applyUsageWindowUpdatesToFullSubject.ts | 36 + .../deductionV2/executeRedisDeductionV2.ts | 51 +- .../deductionV2/prepareFeatureDeductionV2.ts | 16 +- .../balances/utils/sql/syncBalancesV2.sql | 123 +++- .../utils/sync/SyncBatchingManagerV3.ts | 36 +- .../balances/utils/sync/syncItemV4.ts | 37 +- .../balances/utils/types/deductionTypes.ts | 7 +- .../balances/utils/types/deductionUpdate.ts | 2 - .../utils/types/redisDeductionError.ts | 1 - .../utils/types/redisDeductionResult.ts | 9 + .../utils/types/usageWindowMutation.ts | 14 + .../balances/utils/types/usageWindowUpdate.ts | 18 + .../v2/setup/setupBillingCycleAnchor.ts | 18 +- .../lineItems/chargeRowToRefundLineItem.ts | 2 +- .../v2/utils/lineItems/getRefundLineItems.ts | 3 + .../lineItems/getRefundLineItemsForPrice.ts | 1 + .../invoiceCreditFromStoredLineItems.ts | 4 +- .../applyUsageWindowRollsToSubject.ts | 34 + .../computeUsageWindowRolls.ts | 87 +++ .../lazyResetSubjectUsageWindows.ts | 76 ++ .../rollUsageWindowsCache.ts | 60 ++ .../actions/update/updateCustomer.ts | 2 + .../actions/getCachedFullSubject.ts | 21 +- .../actions/getOrSetCachedFullSubject.ts | 7 +- .../invalidateSharedBalanceFields.ts | 31 +- .../partial/getCachedPartialFullSubject.ts | 26 +- .../actions/rehydrateWithLiveBalances.ts | 20 +- .../setCachedFullSubject.ts | 2 + .../setSharedFullSubjectBalances.ts | 30 +- .../balances/applyLiveUsageWindows.ts | 20 + .../balances/getCachedFeatureBalances.ts | 54 +- .../buildDeductFromSubjectBalancesKeys.ts | 14 +- .../config/fullSubjectCacheConfig.ts | 6 + .../fullSubject/fullSubjectCacheModel.ts | 27 +- .../cache/fullSubject/roundCacheBalance.ts | 18 +- .../apiCusUtils/getApiCustomerBase.ts | 7 +- .../getCusAutoTopupPurchaseLimits.ts | 2 +- .../cusResponseUtils/getCusProcessors.ts | 2 +- .../internal/customers/cusUtils/cusUtils.ts | 5 +- .../getApiBalance/getApiBalancesV2.ts | 8 +- .../getApiCustomerV2/getApiCustomerBaseV2.ts | 7 +- .../getApiSubscriptionV2.ts | 12 +- .../repos/getFullSubject/getFullSubject.ts | 3 + .../getFullSubject/getFullSubjectRowsQuery.ts | 10 +- .../subjectQueryRowToNormalized.ts | 12 +- .../customers/usageWindows/repos/index.ts | 5 + .../usageWindows/repos/rollUsageWindows.ts | 32 + .../internal/entities/actions/updateEntity.ts | 1 + .../getApiEntityV2/getApiEntityBaseV2.ts | 8 + server/tests/_groups/temp.ts | 110 +-- .../tests/_temp/cycle-differential-sweep.ts | 210 ++++++ .../track-customer-usage-limit.test.ts | 688 ----------------- .../entity-usage-window-check.test.ts | 207 ++++++ .../entity-usage-window-credits.test.ts | 161 ++++ .../entity-usage-window-enforcement.test.ts | 354 +++++++++ .../entity-usage-window-inheritance.test.ts | 342 +++++++++ .../entity-usage-window-persistence.test.ts | 212 ++++++ .../plan-changes/plan-change-anchor.test.ts | 254 +++++++ .../plan-change-replacement.test.ts | 194 +++++ .../plan-change-scheduled.test.ts | 170 +++++ .../plan-changes/plan-change-update.test.ts | 209 ++++++ .../plan-changes/plan-change-upgrade.test.ts | 313 ++++++++ .../usage-windows/usage-window-api.test.ts | 189 +++++ .../usage-windows/usage-window-check.test.ts | 188 +++++ .../usage-window-enforcement.test.ts | 697 ++++++++++++++++++ .../usage-windows/usage-window-lock.test.ts | 315 ++++++++ .../usage-window-multi-feature-caps.test.ts | 177 +++++ .../usage-window-own-feature.test.ts | 310 ++++++++ .../usage-window-persistence.test.ts | 416 +++++++++++ .../usage-windows/usage-window-reset.test.ts | 383 ++++++++++ .../usage-windows/usage-window-sync.test.ts | 392 ++++++++++ .../customerUsageLimitUtils.ts | 90 +++ .../entityUsageLimitUtils.ts | 87 +++ .../expireUsageWindowForReset.ts | 59 ++ .../usageWindowDbTestUtils.ts | 88 +++ .../utils/fullSubjectScenarioBuilders.ts | 1 + .../utils/expectUsageLimitCorrect.ts | 39 + .../get-cycle-end-eom-clamp.test.ts | 163 ++++ .../get-cycle-start-eom-clamp.test.ts | 104 +++ .../invoice-credit-matcher.spec.ts | 68 +- .../setSharedFullSubjectBalances.test.ts | 33 +- .../computeUsageWindowRolls.test.ts | 117 +++ .../fullSubjectToUsageWindowLimits.test.ts | 249 +++---- .../pickAnchorCustomerEntitlementId.test.ts | 13 + .../customerBillingControls.ts | 39 + .../billingControls/entityBillingControls.ts | 28 + shared/api/billingControls/index.ts | 2 + shared/api/billingControls/spendLimit.ts | 6 +- shared/api/billingControls/usageLimit.ts | 15 + shared/api/customers/baseApiCustomer.ts | 2 +- .../utils/convert/apiBalanceToAllowed.ts | 20 +- .../utils/apiSubjectToUsageLimitHeadroom.ts | 63 ++ shared/drizzle/0009_usage_windows.sql | 12 +- shared/drizzle/0010_usage_limits_control.sql | 2 + shared/drizzle/meta/_journal.json | 7 + shared/index.ts | 17 +- .../customerBillingControls.ts | 58 +- .../billingControls/entityBillingControls.ts | 5 + .../cusModels/billingControls/spendLimit.ts | 25 +- .../cusModels/billingControls/usageLimit.ts | 28 + shared/models/cusModels/cusModels.ts | 2 + shared/models/cusModels/cusTable.ts | 2 + .../cusModels/entityModels/entityModels.ts | 2 + .../cusModels/entityModels/entityTable.ts | 2 + .../cusModels/fullSubject/fullSubjectModel.ts | 7 + .../fullSubject/normalizedFullSubjectModel.ts | 9 +- .../cusEntModels/cusEntModels.ts | 5 - .../cusEntModels/usageWindowModels.ts | 14 +- .../cusEntModels/usageWindowTable.ts | 64 +- .../fullSubjectToApiSpendLimits.ts | 91 --- .../fullSubjectToApiUsageLimits.ts | 53 ++ .../fullSubjectToUsageWindowLimits.ts | 196 +---- shared/utils/fullSubjectUtils/index.ts | 2 +- .../mergeCustomerBillingControlsForCheck.ts | 27 +- .../normalizedToFullSubject.ts | 19 +- .../usageWindowMatchesLimit.ts | 16 + .../getUsageWindowDimension.ts | 26 + .../usageLimitToUsageWindowLimit.ts | 94 +++ .../findUsageWindow/findUsageWindowByLimit.ts | 15 + .../findUsageWindowLimitByWindow.ts | 13 + .../findUsageWindowAnchor.ts | 112 +++ .../pickAnchorCustomerEntitlementId.ts | 6 +- .../getCurrentUsageWindowUsage.ts | 30 + .../getUsageWindowAnchorTimestamp.ts | 16 + statement-breakpoint | 0 .../CustomerBillingControlsSection.tsx | 56 +- .../sheets/BillingUsageLimitSheet.tsx | 111 +-- .../components/sheets/RecordUsageSheet.tsx | 91 ++- .../sheets/billing-usage-limit-sheet.test.ts | 54 +- 152 files changed, 9203 insertions(+), 2051 deletions(-) create mode 100644 server/src/_luaScriptsV2/fullSubject/rollUsageWindows/rollUsageWindows.lua delete mode 100644 server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua create mode 100644 server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/readUsageWindows.lua create mode 100644 server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/usageWindowContextUtilsV2.lua create mode 100644 server/src/internal/balances/utils/deductionV2/applyUsageWindowUpdatesToFullSubject.ts create mode 100644 server/src/internal/balances/utils/types/usageWindowMutation.ts create mode 100644 server/src/internal/balances/utils/types/usageWindowUpdate.ts create mode 100644 server/src/internal/customers/actions/resetUsageWindows/applyUsageWindowRollsToSubject.ts create mode 100644 server/src/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.ts create mode 100644 server/src/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.ts create mode 100644 server/src/internal/customers/actions/resetUsageWindows/rollUsageWindowsCache.ts create mode 100644 server/src/internal/customers/cache/fullSubject/balances/applyLiveUsageWindows.ts create mode 100644 server/src/internal/customers/usageWindows/repos/index.ts create mode 100644 server/src/internal/customers/usageWindows/repos/rollUsageWindows.ts create mode 100644 server/tests/_temp/cycle-differential-sweep.ts delete mode 100644 server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts create mode 100644 server/tests/integration/balances/usage-windows/entities/entity-usage-window-check.test.ts create mode 100644 server/tests/integration/balances/usage-windows/entities/entity-usage-window-credits.test.ts create mode 100644 server/tests/integration/balances/usage-windows/entities/entity-usage-window-enforcement.test.ts create mode 100644 server/tests/integration/balances/usage-windows/entities/entity-usage-window-inheritance.test.ts create mode 100644 server/tests/integration/balances/usage-windows/entities/entity-usage-window-persistence.test.ts create mode 100644 server/tests/integration/balances/usage-windows/plan-changes/plan-change-anchor.test.ts create mode 100644 server/tests/integration/balances/usage-windows/plan-changes/plan-change-replacement.test.ts create mode 100644 server/tests/integration/balances/usage-windows/plan-changes/plan-change-scheduled.test.ts create mode 100644 server/tests/integration/balances/usage-windows/plan-changes/plan-change-update.test.ts create mode 100644 server/tests/integration/balances/usage-windows/plan-changes/plan-change-upgrade.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-api.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-check.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-enforcement.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-lock.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-multi-feature-caps.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-own-feature.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-persistence.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-reset.test.ts create mode 100644 server/tests/integration/balances/usage-windows/usage-window-sync.test.ts create mode 100644 server/tests/integration/balances/utils/usage-limit-utils/customerUsageLimitUtils.ts create mode 100644 server/tests/integration/balances/utils/usage-limit-utils/entityUsageLimitUtils.ts create mode 100644 server/tests/integration/balances/utils/usage-limit-utils/expireUsageWindowForReset.ts create mode 100644 server/tests/integration/balances/utils/usage-limit-utils/usageWindowDbTestUtils.ts create mode 100644 server/tests/integration/utils/expectUsageLimitCorrect.ts create mode 100644 server/tests/unit/billing/interval/get-cycle-end/get-cycle-end-eom-clamp.test.ts create mode 100644 server/tests/unit/billing/interval/get-cycle-start/get-cycle-start-eom-clamp.test.ts create mode 100644 server/tests/unit/usage-windows/computeUsageWindowRolls.test.ts create mode 100644 shared/api/billingControls/customerBillingControls.ts create mode 100644 shared/api/billingControls/usageLimit.ts create mode 100644 shared/api/customers/utils/apiSubjectToUsageLimitHeadroom.ts create mode 100644 shared/drizzle/0010_usage_limits_control.sql create mode 100644 shared/models/cusModels/billingControls/usageLimit.ts delete mode 100644 shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts create mode 100644 shared/utils/fullSubjectUtils/fullSubjectToApiUsageLimits.ts create mode 100644 shared/utils/usageWindowUtils/classifyUsageWindow/usageWindowMatchesLimit.ts create mode 100644 shared/utils/usageWindowUtils/convertUsageWindow/getUsageWindowDimension.ts create mode 100644 shared/utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.ts create mode 100644 shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowByLimit.ts create mode 100644 shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowLimitByWindow.ts create mode 100644 shared/utils/usageWindowUtils/findUsageWindowAnchor/findUsageWindowAnchor.ts rename shared/utils/usageWindowUtils/{ => findUsageWindowAnchor}/pickAnchorCustomerEntitlementId.ts (84%) create mode 100644 shared/utils/usageWindowUtils/getCurrentUsageWindowUsage.ts create mode 100644 shared/utils/usageWindowUtils/getUsageWindowAnchorTimestamp.ts create mode 100644 statement-breakpoint diff --git a/ai b/ai index 0e52f71fb..bca809a30 160000 --- a/ai +++ b/ai @@ -1 +1 @@ -Subproject commit 0e52f71fbd69e7a4a58b63863a8c4929bfd9ebf8 +Subproject commit bca809a3078696361300cc65dc201fc077f91915 diff --git a/scripts/migrations/migrate-functions.ts b/scripts/migrations/migrate-functions.ts index e4011c13b..302d6575b 100644 --- a/scripts/migrations/migrate-functions.ts +++ b/scripts/migrations/migrate-functions.ts @@ -3,6 +3,17 @@ import inquirer from "inquirer"; loadLocalEnv(); +// Dev worktrees (scripts/dw): overlay server/.env.local -- the same override +// `bun dw run` gets via Bun's automatic .env.local loading -- so functions +// land on the worktree's Neon branch instead of the canonical dev DB. Never +// applied for prod targets (migrate-functions:prod): infisical injects the +// prod DATABASE_URL before this script starts, and prod URLs carry the +// us-east-2 marker (same convention as assertNotProductionDb). +if (!process.env.DATABASE_URL?.includes("us-east-2")) { + process.env.ENV_FILE = ".env.local"; + loadLocalEnv({ force: true }); +} + export const migrateFunctions = async () => { // Dynamic import to ensure env is loaded first const { initializeDatabaseFunctions } = await import( diff --git a/server/src/_luaScriptsV2/fullSubject/rollUsageWindows/rollUsageWindows.lua b/server/src/_luaScriptsV2/fullSubject/rollUsageWindows/rollUsageWindows.lua new file mode 100644 index 000000000..d52a72ac3 --- /dev/null +++ b/server/src/_luaScriptsV2/fullSubject/rollUsageWindows/rollUsageWindows.lua @@ -0,0 +1,80 @@ +--[[ + Lua Script: Roll usage-window counters in a per-feature hash + + Atomically patches rows in the reserved '_usage_windows' field: the lazy + roll (post-getFullSubject) zeroes counts whose window closed and advances + bounds/anchor to the current derivation. Atomicity matters because a + concurrent deduction may be writing the same field. + + Fail-open: a missing/malformed field, or a row absent for a scope, is left + untouched (the write path creates rows; the roll only maintains them). + + KEYS[1] = balance hash key + ARGV[1] = JSON params: + { + now: number, + ttl_seconds: number, + rolls: [{ + internal_entity_id: string | null, -- scope selector + zero_usage: boolean, -- stored window closed: count dies + window_start_at: number, + window_end_at: number, + anchor_customer_entitlement_id: string | null, + }] + } + + Returns JSON: { rolled: number } +]] + +local params = cjson.decode(ARGV[1]) +local now = safe_number(params.now) +local ttl_seconds = safe_number(params.ttl_seconds) + +local USAGE_WINDOWS_FIELD = '_usage_windows' + +local raw = redis.call('HGET', KEYS[1], USAGE_WINDOWS_FIELD) +if is_nil(raw) then + return cjson.encode({ rolled = 0 }) +end + +local ok, windows = pcall(cjson.decode, raw) +if not ok or type(windows) ~= 'table' then + return cjson.encode({ rolled = 0 }) +end + +local rolled = 0 +for _, roll in ipairs(params.rolls or {}) do + local roll_entity = roll.internal_entity_id + for _, window in ipairs(windows) do + if type(window) == 'table' then + local window_entity = window.internal_entity_id + local entities_match = + (is_nil(roll_entity) and is_nil(window_entity)) + or roll_entity == window_entity + if entities_match then + if roll.zero_usage then + window.usage = 0 + end + window.window_start_at = roll.window_start_at + window.window_end_at = roll.window_end_at + window.anchor_customer_entitlement_id = + roll.anchor_customer_entitlement_id + window.updated_at = now + rolled = rolled + 1 + end + end + end +end + +if rolled == 0 then + return cjson.encode({ rolled = 0 }) +end + +local encoded = #windows > 0 and cjson.encode(windows) or '[]' +redis.call('HSET', KEYS[1], USAGE_WINDOWS_FIELD, encoded) + +if ttl_seconds > 0 and redis.call('TTL', KEYS[1]) < 0 then + redis.call('EXPIRE', KEYS[1], ttl_seconds) +end + +return cjson.encode({ rolled = rolled }) diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua index 25b75b3e0..b36ce837c 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/contextUtilsV2.lua @@ -24,17 +24,24 @@ local function init_context(params) env = params.env, customer_id = params.customer_id, customer_entitlement_deductions = params.customer_entitlement_deductions, - anchor_entitlements = params.anchor_entitlements, balance_keys_by_feature_id = params.balance_keys_by_feature_id, }) local context = { customer_entitlements = {}, rollovers = {}, + -- Customer-scoped windowed-cap counters, loaded below alongside the other + -- subject state: { [feature_id] = { balance_key, windows, dirty } }. + usage_windows = read_usage_windows({ + usage_window_limits = params.usage_window_limits, + balance_keys_by_feature_id = params.balance_keys_by_feature_id, + now = params.usage_window_now, + }), org_id = params.org_id, env = params.env, customer_id = params.customer_id, mutation_logs = {}, + usage_window_mutations = {}, pending_writes = {}, pending_write_ids = {}, missing_customer_entitlement_ids = @@ -97,23 +104,6 @@ local function init_context(params) end end - -- Register usage-window anchor cus_ents that are not in the deduction set so - -- their counter can be read/mutated and persisted (HSET) on apply. - for customer_entitlement_id, balance_entry in pairs(read_result.balances_by_id) do - if balance_entry.anchor_only - and is_nil(context.customer_entitlements[customer_entitlement_id]) - then - context.customer_entitlements[customer_entitlement_id] = { - base_path = customer_entitlement_id, - balance_key = balance_entry.balance_key, - subject_balance = balance_entry.subject_balance, - customer_entitlement_id = customer_entitlement_id, - feature_id = balance_entry.feature_id, - is_anchor_only = true, - } - end - end - return context end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua index e36a1c362..5943f64c4 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/deductFromSubjectBalances.lua @@ -48,11 +48,30 @@ idempotency_ttl_ms: number | null } + Usage windows (customer-scoped windowed caps): + CONFIG IN: params.usage_window_limits[] -- the resolved caps (limit, + bounds, dimension) from fullSubjectToUsageWindowLimits. + COUNTERS OUT: usage_windows_by_feature_id -- the post-deduction COUNTER + ROWS (DbUsageWindow: usage amounts, mirrors the + usage_windows table), NOT the config. + Counters live in the capped feature's balance hash under the reserved + '_usage_windows' field (so each capped feature's hash key must be in + KEYS[], via usageWindowFeatureIds in the TS key builder); they are loaded + into context.usage_windows by init_context and follow the same in-memory + mutate -> flush lifecycle as entitlement balances. Enforcement is woven + into the deduction passes like spend limits: each ent's deductible amount + is gated by window headroom (with credit conversions), and a window-capped + leftover flows through the standard overage_behaviour handling ('cap' + applies the partial deduction, 'reject' returns INSUFFICIENT_BALANCE). A + missing field loads as an empty counter set (fail open). + Returns JSON: { updates: { [cus_ent_id]: { balance, additional_balance, adjustment, entities, deducted, additional_deducted } }, rollover_updates: { [rollover_id]: { balance, usage, entities } }, modified_customer_entitlement_ids: string[], + usage_windows_by_feature_id: { [feature_id]: DbUsageWindow[] } | null, + usage_window_mutations: { usage_window_id, feature_id, internal_entity_id, window_start_at, usage_delta }[], remaining: number, error: string | null, feature_id: string | null @@ -113,29 +132,9 @@ local unwind_value = params.unwind_value local lock_receipt_key = lock_receipt_key_from_keys local usage_window_limits = params.usage_window_limits local usage_window_now = params.usage_window_now +local usage_window_ttl_seconds = params.usage_window_ttl_seconds local is_consumption = params.is_consumption --- Distinct usage-window anchor cus_ents to force-load into context (they own --- the counters and may not be in the deduction set). -local anchor_entitlements = {} -if not is_nil(usage_window_limits) then - local seen_anchor_ids = {} - for _, usage_window_limit in ipairs(usage_window_limits) do - local anchor_id = usage_window_limit.anchor_customer_entitlement_id - local anchor_feature_id = usage_window_limit.anchor_feature_id - if not is_nil(anchor_id) - and not is_nil(anchor_feature_id) - and not seen_anchor_ids[anchor_id] - then - seen_anchor_ids[anchor_id] = true - table.insert(anchor_entitlements, { - customer_entitlement_id = anchor_id, - feature_id = anchor_feature_id, - }) - end - end -end - if not is_nil(idempotency_key) then if redis.call('EXISTS', idempotency_key) == 1 then return cjson.encode({ @@ -163,12 +162,30 @@ if #customer_entitlement_deductions == 0 then }) end +-- Usage windows are enforced for positive consumption INCLUDING locks (a +-- lock reserves headroom and counts at lock time), never for refunds, +-- target_balance, or granted-balance edits. Unwinds don't enforce but DO +-- load counters so the freed amount can be decremented back. Computed before +-- init_context so non-participating calls skip the counter reads entirely. +local has_usage_window_limits = not is_nil(usage_window_limits) + and #usage_window_limits > 0 +-- A zero unwind_value (finalize at-or-above the lock) is no unwind at all: +-- the extra delta must still be enforced and counted. +local has_unwind = not is_nil(unwind_value) and safe_number(unwind_value) > 0 +local enforce_usage_windows = is_consumption + and not has_unwind + and has_usage_window_limits +local unwind_usage_windows = has_unwind and has_usage_window_limits + local context = init_context({ org_id = org_id, env = env, customer_id = customer_id, customer_entitlement_deductions = customer_entitlement_deductions, - anchor_entitlements = anchor_entitlements, + usage_window_limits = (enforce_usage_windows or unwind_usage_windows) + and usage_window_limits + or nil, + usage_window_now = usage_window_now, balance_keys_by_feature_id = params.balance_keys_by_feature_id, debug = params.debug, }) @@ -210,6 +227,14 @@ if not is_nil(unwind_value) and safe_number(unwind_value) > 0 then -- Track which entitlements the unwind touched so the caller can sync them. unwind_modified_cus_ent_ids = unwind_result.modified_customer_entitlement_ids or {} + if unwind_usage_windows then + decrement_usage_windows_for_unwind({ + context = context, + iterations = unwind_result.iterations, + now = usage_window_now, + }) + end + -- Fold any skipped unwind (missing entitlements/rollovers) into amount_to_deduct -- so the forward pass compensates against current live entitlements. local skipped = unwind_result.remaining_signed_unwind_value or 0 @@ -220,37 +245,6 @@ end local logger = context.logger --- Usage windows are enforced only for positive consumption, never for refunds, --- target_balance, granted-balance edits, locks, or unwinds. -local enforce_usage_windows = is_consumption - and is_nil(unwind_value) - and (is_nil(lock) or not lock.enabled) - and not is_nil(usage_window_limits) - and #usage_window_limits > 0 - -if enforce_usage_windows then - local clamp_result = clamp_amount_to_usage_windows({ - context = context, - usage_window_limits = usage_window_limits, - amount_to_deduct = amount_to_deduct, - }) - - if not is_nil(clamp_result.exceeded_feature_id) then - return cjson.encode({ - error = 'USAGE_LIMIT_EXCEEDED', - feature_id = clamp_result.exceeded_feature_id, - remaining = safe_number(amount_to_deduct), - updates = {}, - rollover_updates = {}, - modified_customer_entitlement_ids = new_empty_array(), - mutation_logs = new_empty_array(), - logs = context.logs, - }) - end - - amount_to_deduct = clamp_result.amount_to_deduct -end - logger.log("=== LUA DEDUCTION START ===") logger.log("=== PARAMS ===") logger.log(" amount_to_deduct: %s", tostring(amount_to_deduct or "nil")) @@ -298,7 +292,15 @@ local mutation_logs = context.mutation_logs if type(mutation_logs) ~= 'table' or #mutation_logs == 0 then mutation_logs = cjson.decode('[]') end --- Throw error and don't apply updates if we're in reject mode and there's still remaining amount +local usage_window_mutations = context.usage_window_mutations +if type(usage_window_mutations) ~= 'table' or #usage_window_mutations == 0 then + usage_window_mutations = cjson.decode('[]') +end +-- Throw error and don't apply updates if we're in reject mode and there's +-- still remaining amount. Usage-window shortfalls flow through here like any +-- other: the deduction passes already gated every ent by window headroom, so +-- a window-capped leftover clamps under 'cap' and rejects as +-- INSUFFICIENT_BALANCE under 'reject'. if remaining_amount > 0 and overage_behaviour == 'reject' then return cjson.encode({ error = 'INSUFFICIENT_BALANCE', @@ -312,33 +314,9 @@ if remaining_amount > 0 and overage_behaviour == 'reject' then end if enforce_usage_windows then - local exceeded_feature_id = check_usage_window_limits({ - context = context, - usage_window_limits = usage_window_limits, - updates = updates, - amount_to_deduct = amount_to_deduct, - remaining_amount = remaining_amount, - }) - - if not is_nil(exceeded_feature_id) then - return cjson.encode({ - error = 'USAGE_LIMIT_EXCEEDED', - feature_id = exceeded_feature_id, - remaining = remaining_amount, - updates = {}, - rollover_updates = {}, - modified_customer_entitlement_ids = new_empty_array(), - mutation_logs = mutation_logs, - logs = context.logs, - }) - end - increment_usage_window_counters({ context = context, usage_window_limits = usage_window_limits, - updates = updates, - amount_to_deduct = amount_to_deduct, - remaining_amount = remaining_amount, now = usage_window_now, }) end @@ -397,6 +375,10 @@ update_aggregated_balances({ mutation_logs = mutation_logs, }) +if enforce_usage_windows or unwind_usage_windows then + apply_usage_window_writes(context, usage_window_ttl_seconds) +end + if not is_nil(idempotency_key) and not is_nil(idempotency_ttl_ms) then redis.call('SET', idempotency_key, '1', 'PX', idempotency_ttl_ms) end @@ -408,6 +390,9 @@ return cjson.encode({ rollover_updates = rollover_updates, modified_customer_entitlement_ids = modified_customer_entitlement_ids, mutation_logs = mutation_logs, + usage_windows_by_feature_id = + usage_windows_to_result(context) or cjson.null, + usage_window_mutations = usage_window_mutations, remaining = remaining_amount, error = cjson.null, logs = context.logs diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/lock/unwindLockV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/lock/unwindLockV2.lua index b8775ec37..aa7188abc 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/lock/unwindLockV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/lock/unwindLockV2.lua @@ -442,5 +442,8 @@ local function unwind_lock_on_context(params) modified_customer_entitlement_ids = modified_ids.modified_customer_entitlement_ids, modified_rollover_ids = modified_ids.modified_rollover_ids, mutation_logs = context.mutation_logs, + -- Per-item applied amounts (tracked units + credit_cost), so callers can + -- mirror the unwind onto usage-window counters. + iterations = unwind_items_result.iterations, } end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua index b118c39b5..acbad7d88 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/readSubjectBalances.lua @@ -19,9 +19,6 @@ local function read_subject_balances(params) local missing_customer_entitlement_ids = {} local entries_by_balance_key = {} local seen_ids_by_balance_key = {} - -- Anchor-only cus_ents (usage-window owners not in the deduction set) must - -- not abort the deduction when absent; a missing anchor fails closed later. - local anchor_only_ids = {} local balance_keys_by_feature_id = safe_table(params.balance_keys_by_feature_id) local function queue_balance_read(customer_entitlement_id, feature_id) @@ -54,7 +51,6 @@ local function read_subject_balances(params) return true end - local deduction_ids = {} for _, ent_obj in ipairs(params.customer_entitlement_deductions or {}) do local customer_entitlement_id = ent_obj.customer_entitlement_id if customer_entitlement_id then @@ -62,17 +58,6 @@ local function read_subject_balances(params) if not queued then table.insert(missing_customer_entitlement_ids, customer_entitlement_id) end - deduction_ids[customer_entitlement_id] = true - end - end - - -- A deduction target must never be marked anchor_only, or a cache miss on it is - -- swallowed instead of surfacing as missing + triggering the Postgres fallback. - for _, anchor in ipairs(params.anchor_entitlements or {}) do - local customer_entitlement_id = anchor.customer_entitlement_id - if customer_entitlement_id and not deduction_ids[customer_entitlement_id] then - anchor_only_ids[customer_entitlement_id] = true - queue_balance_read(customer_entitlement_id, anchor.feature_id) end end @@ -89,19 +74,16 @@ local function read_subject_balances(params) local subject_balance = decode_subject_balance(raw_value) if subject_balance == nil then - if not anchor_only_ids[customer_entitlement_id] then - table.insert( - missing_customer_entitlement_ids, - customer_entitlement_id - ) - end + table.insert( + missing_customer_entitlement_ids, + customer_entitlement_id + ) else balances_by_id[customer_entitlement_id] = { balance_key = balance_key, customer_entitlement_id = customer_entitlement_id, feature_id = entry.feature_id, subject_balance = subject_balance, - anchor_only = anchor_only_ids[customer_entitlement_id] or nil, } end end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua index 5e9901b33..1680edbd5 100644 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/runDeductionOnContextV2.lua @@ -85,18 +85,43 @@ local function process_deduction_pass(params) usage_allowed = usage_allowed or overage_behavior_is_allow local should_process = not skip_if_not_usage_allowed or usage_allowed + local skip_reason = "usage_allowed=false" if not context.customer_entitlements[ent_id] then should_process = false + skip_reason = "not in context" + end + + -- Usage-window gate, mirroring the spend-limit overage gate above: cap + -- this ent's deductible amount by the remaining window headroom (metered + -- limits cap every ent in tracked units; balance limits cap ents of the + -- capped feature, converted via THIS ent's credit_cost). A fully blocked + -- ent is skipped rather than breaking the loop -- a balance-dim cap only + -- binds its own feature's pools, so other ents may be unconstrained. + local ent_amount = remaining_amount + if should_process and remaining_amount > 0 then + local available_from_usage_windows = get_available_from_usage_windows({ + context = context, + ent_feature_id = ent_feature_id, + credit_cost = credit_cost, + }) + if not is_nil(available_from_usage_windows) + and available_from_usage_windows < ent_amount then + ent_amount = available_from_usage_windows + end + if ent_amount == 0 then + should_process = false + skip_reason = "usage window headroom exhausted" + end end if not should_process then - logger.log("%s skipping %s - usage_allowed=false or not in context", pass_name, ent_id) + logger.log("%s skipping %s - %s", pass_name, ent_id, skip_reason) else local deducted = deduct_from_main_balance({ context = context, ent_id = ent_id, target_entity_id = target_entity_id, - amount = remaining_amount, + amount = ent_amount, credit_cost = credit_cost, pass_number = pass_number, available_overage = available_overage, @@ -107,7 +132,17 @@ local function process_deduction_pass(params) log_prefix = pass_name, }) - remaining_amount = remaining_amount - (deducted / credit_cost) + local deducted_units = deducted / credit_cost + remaining_amount = remaining_amount - deducted_units + + -- Settle the gate: record what this ent actually drained against every + -- applicable window limit so the next ent sees the reduced headroom. + consume_usage_window_headroom({ + context = context, + ent_feature_id = ent_feature_id, + credit_cost = credit_cost, + units = deducted_units, + }) if deducted ~= 0 then if not updates[ent_id] then @@ -146,6 +181,26 @@ local function process_rollover_deduction(params) return 0 end + -- Metered window limits count tracked units regardless of funding source, + -- so they gate the rollover phase too. Balance limits do not (ent_feature_id + -- = nil): rollover drains stay outside credit-pool caps, matching how spend + -- limits ignore them. + local rollover_amount = remaining_amount + local available_from_usage_windows = get_available_from_usage_windows({ + context = context, + ent_feature_id = nil, + credit_cost = 1, + }) + if not is_nil(available_from_usage_windows) + and available_from_usage_windows < rollover_amount then + rollover_amount = available_from_usage_windows + end + + if rollover_amount <= 0 then + logger.log("Rollover deduction skipped - usage window headroom exhausted") + return 0 + end + local first_ent = customer_entitlement_deductions[1] local has_entity_scope = false if first_ent then @@ -155,11 +210,18 @@ local function process_rollover_deduction(params) local rollover_deducted = deduct_from_rollovers({ context = context, rollovers = rollovers, - amount = remaining_amount, + amount = rollover_amount, target_entity_id = target_entity_id, has_entity_scope = has_entity_scope, }) + consume_usage_window_headroom({ + context = context, + ent_feature_id = nil, + credit_cost = 1, + units = rollover_deducted, + }) + logger.log("Rollover deduction: deducted=%s, remaining=%s", rollover_deducted, remaining_amount - rollover_deducted) return rollover_deducted @@ -274,11 +336,6 @@ local function run_deduction_on_context(params) update.adjustment = ent_data.adjustment or 0 update.additional_balance = 0 - - if ent_data.subject_balance - and type(ent_data.subject_balance.usage_windows) == 'table' then - update.usage_windows = ent_data.subject_balance.usage_windows - end end end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua deleted file mode 100644 index 5cded80e7..000000000 --- a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindowUtilsV2.lua +++ /dev/null @@ -1,237 +0,0 @@ --- ============================================================================ --- USAGE WINDOW UTILITIES (V2) --- Hard windowed usage-limit enforcement, evaluated at the orchestration layer --- against ACTUAL consumed amounts (post-deduction, pre-write). --- --- Counters live inline on the anchor cus_ent's subject_balance.usage_windows as --- a lean ARRAY of rows mirroring the usage_windows table (DbUsageWindow): --- { id, customer_entitlement_id, feature_id, internal_feature_id, --- window_start_at, window_end_at, usage, updated_at } --- A window is identified by (customer_entitlement_id, feature_id, --- window_start_at) -- the table's unique key. The current window is --- found-or-created; a rolled window has a different window_start_at, so its --- counter starts fresh at 0 and old windows are pruned. --- ============================================================================ - --- Tolerance for float drift (credit-ratio conversions leave sub-nano noise). -local USAGE_WINDOW_EPSILON = 1e-9 - -local function get_anchor_usage_windows(context, anchor_customer_entitlement_id) - if is_nil(anchor_customer_entitlement_id) then - return nil - end - - local ent_data = context.customer_entitlements[anchor_customer_entitlement_id] - if not ent_data or not ent_data.subject_balance then - return nil - end - - -- Reset a non-array blob to []: a legacy keyed-map blob (pre-array deploy) whose - -- string keys ipairs would skip and table.insert would corrupt into a - -- sync-breaking JSON object. The current window restarts at 0 (one-time cost). - local windows = ent_data.subject_balance.usage_windows - if type(windows) ~= 'table' - or (next(windows) ~= nil and windows[1] == nil) then - windows = new_empty_array() - ent_data.subject_balance.usage_windows = windows - end - - return windows -end - --- The array is one anchor cus_ent's rows, so customer_entitlement_id is implied; --- a window is the row matching (feature_id, window_start_at). -local function find_usage_window(windows, feature_id, window_start_at) - for _, window in ipairs(windows) do - if window.feature_id == feature_id - and safe_number(window.window_start_at) == window_start_at then - return window - end - end - return nil -end - --- Stable id matching the table's unique key, so the async sync upserts the same --- row each window rather than inserting duplicates. -local function build_usage_window_id(limit) - return limit.anchor_customer_entitlement_id - .. ':' .. limit.feature_id - .. ':' .. string.format('%.0f', limit.window_start_at) -end - --- Actually-consumed amount for a limit, in its native unit. metered_feature --- counts feature units (the tracked total); balance counts credits drained from --- the anchor pool (its `deducted`, which is in credits). -local function usage_window_consumed(params) - local limit = params.limit - local updates = params.updates - - if limit.dimension_type == 'balance' then - local anchor_update = updates[limit.anchor_customer_entitlement_id] - return anchor_update and safe_number(anchor_update.deducted) or 0 - end - - return safe_number(params.amount_to_deduct) - safe_number(params.remaining_amount) -end - --- Clamps metered-feature usage caps before deduction so over-cap tracks apply --- only the remaining headroom. Balance caps are credit-denominated, so unit --- clamping would need credit conversion; they stay on the post-deduction check. -local function clamp_amount_to_usage_windows(params) - local context = params.context - local limits = params.usage_window_limits or {} - local clamped_amount = safe_number(params.amount_to_deduct) - - for _, limit in ipairs(limits) do - local windows = get_anchor_usage_windows( - context, - limit.anchor_customer_entitlement_id - ) - if is_nil(windows) then - return { - amount_to_deduct = clamped_amount, - exceeded_feature_id = limit.feature_id, - } - end - - if limit.dimension_type ~= 'balance' then - local existing = find_usage_window( - windows, - limit.feature_id, - limit.window_start_at - ) - local current_usage = existing and safe_number(existing.usage) or 0 - local headroom = safe_number(limit.limit) - current_usage - if headroom < 0 then - headroom = 0 - end - - if clamped_amount > headroom then - clamped_amount = headroom - end - end - end - - return { - amount_to_deduct = clamped_amount, - exceeded_feature_id = nil, - } -end - --- Returns the feature_id of the first limit that would be exceeded (so the --- caller can hard-reject), or nil if every limit has room. Null/missing anchor --- fails closed: a cap that cannot resolve an owner must not silently allow. -local function check_usage_window_limits(params) - local context = params.context - local limits = params.usage_window_limits or {} - - for _, limit in ipairs(limits) do - local windows = get_anchor_usage_windows( - context, - limit.anchor_customer_entitlement_id - ) - if is_nil(windows) then - return limit.feature_id - end - - local consumed = usage_window_consumed({ - limit = limit, - updates = params.updates, - amount_to_deduct = params.amount_to_deduct, - remaining_amount = params.remaining_amount, - }) - - if consumed > USAGE_WINDOW_EPSILON then - local existing = find_usage_window( - windows, - limit.feature_id, - limit.window_start_at - ) - local current_usage = existing and safe_number(existing.usage) or 0 - if current_usage + consumed - > safe_number(limit.limit) + USAGE_WINDOW_EPSILON then - return limit.feature_id - end - end - end - - return nil -end - --- Applies the consumed amount to each anchor counter (find-or-create the current --- window row), prunes closed windows, and marks the anchor dirty so --- apply_pending_writes persists it. -local function increment_usage_window_counters(params) - local context = params.context - local limits = params.usage_window_limits or {} - local now = params.now - - for _, limit in ipairs(limits) do - local ent_data = - context.customer_entitlements[limit.anchor_customer_entitlement_id] - local windows = get_anchor_usage_windows( - context, - limit.anchor_customer_entitlement_id - ) - if not is_nil(windows) then - -- Rebuild (rather than nil-out) so the array stays hole-free and cjson - -- re-encodes it as [] not {}; prune every pass so it never grows for - -- sporadically-active features. - local kept = new_empty_array() - local pruned = false - for _, window in ipairs(windows) do - if type(window) == 'table' - and safe_number(window.window_end_at) < now then - pruned = true - else - table.insert(kept, window) - end - end - if pruned then - ent_data.subject_balance.usage_windows = kept - windows = kept - end - - local consumed = usage_window_consumed({ - limit = limit, - updates = params.updates, - amount_to_deduct = params.amount_to_deduct, - remaining_amount = params.remaining_amount, - }) - - if consumed > USAGE_WINDOW_EPSILON then - local existing = find_usage_window( - windows, - limit.feature_id, - limit.window_start_at - ) - if is_nil(existing) then - existing = { - id = build_usage_window_id(limit), - customer_entitlement_id = limit.anchor_customer_entitlement_id, - feature_id = limit.feature_id, - internal_feature_id = limit.internal_feature_id, - window_start_at = limit.window_start_at, - window_end_at = limit.window_end_at, - usage = 0, - updated_at = now, - } - table.insert(windows, existing) - end - - existing.usage = safe_number(existing.usage) + consumed - existing.updated_at = now - - mark_customer_entitlement_for_update( - context, - limit.anchor_customer_entitlement_id - ) - elseif pruned then - mark_customer_entitlement_for_update( - context, - limit.anchor_customer_entitlement_id - ) - end - end - end -end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/readUsageWindows.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/readUsageWindows.lua new file mode 100644 index 000000000..dd09f2ae8 --- /dev/null +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/readUsageWindows.lua @@ -0,0 +1,101 @@ +-- ============================================================================ +-- READ USAGE WINDOWS +-- Loads customer-scoped usage-window counter state into the deduction context +-- (sibling of read_subject_balances). Counters live in the capped feature's +-- balance hash under the reserved '_usage_windows' field, as a lean ARRAY of +-- rows mirroring the usage_windows table (DbUsageWindow): +-- { id, internal_customer_id, internal_entity_id, feature_id, +-- internal_feature_id, anchor_customer_entitlement_id, +-- window_start_at, window_end_at, usage, updated_at } +-- +-- Each loaded entry also carries the deduction-time runtime state the per-ent +-- gate consumes: the resolved limit, its dimension, the remaining `headroom` +-- (limit - current window usage, decremented as the deduction passes drain +-- it), and `consumed` (this operation's total, in the limit's native unit). +-- +-- FAIL OPEN: a missing/undecodable field (or an undeclared balance key) loads +-- as an empty counter set -- the window simply restarts. Stale-cache guards +-- may return in a future iteration. +-- ============================================================================ + +local USAGE_WINDOWS_FIELD = '_usage_windows' + +-- ONE mutable counter row per scope: a row matches its limit on +-- internal_entity_id alone. Bounds are payload, not identity. +local function find_usage_window(windows, limit) + local limit_entity = limit.internal_entity_id + for _, window in ipairs(windows) do + if type(window) == 'table' then + local window_entity = window.internal_entity_id + local entities_match = + (is_nil(limit_entity) and is_nil(window_entity)) + or limit_entity == window_entity + if entities_match then + return window + end + end + end + return nil +end + +-- Returns { [feature_id] = { balance_key, windows, dirty, limit, +-- dimension_type, headroom, consumed } }, one entry per distinct capped +-- feature in usage_window_limits. +local function read_usage_windows(params) + local limits = params.usage_window_limits or {} + local balance_keys_by_feature_id = + safe_table(params.balance_keys_by_feature_id) + local usage_windows = {} + + for _, limit in ipairs(limits) do + local feature_id = limit.feature_id + if usage_windows[feature_id] == nil then + local balance_key = balance_keys_by_feature_id[feature_id] + local windows = nil + + if not is_nil(balance_key) then + local raw_value = redis.call('HGET', balance_key, USAGE_WINDOWS_FIELD) + windows = safe_decode(raw_value) + end + + if type(windows) ~= 'table' then + windows = new_empty_array() + end + + -- cjson decodes an empty JSON object ({}) to the same empty table as []; + -- a non-empty map-like blob should be impossible for this field, but + -- reset it defensively rather than letting ipairs skip rows silently. + if next(windows) ~= nil and windows[1] == nil then + windows = new_empty_array() + end + + local existing = find_usage_window(windows, limit) + -- A count is valid only within its exact stamped window: derive 0 when + -- it expired OR its bounds no longer match the current derivation (the + -- lazy roll persists the zero; this read must not trust it blindly). + local current_usage = 0 + if not is_nil(existing) + and safe_number(existing.window_end_at) > safe_number(params.now) + and safe_number(existing.window_start_at) == limit.window_start_at + then + current_usage = safe_number(existing.usage) + end + local headroom = safe_number(limit.limit) - current_usage + if headroom < 0 then + headroom = 0 + end + + usage_windows[feature_id] = { + balance_key = not is_nil(balance_key) and balance_key or nil, + windows = windows, + dirty = false, + limit = limit, + dimension_type = limit.dimension_type, + headroom = headroom, + consumed = 0, + } + end + end + + return usage_windows +end diff --git a/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/usageWindowContextUtilsV2.lua b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/usageWindowContextUtilsV2.lua new file mode 100644 index 000000000..4fb76381c --- /dev/null +++ b/server/src/_luaScriptsV2/fullSubjectDeduction/usageWindows/usageWindowContextUtilsV2.lua @@ -0,0 +1,301 @@ +-- ============================================================================ +-- USAGE WINDOW CONTEXT UTILITIES (V2) +-- Hard windowed usage-limit enforcement against context.usage_windows (loaded +-- by init_context via read_usage_windows), integrated into the deduction +-- passes the same way spend limits are: +-- per-ent gate (get_available_from_usage_windows, in the deduction loop) +-- -> consume headroom as each ent drains (consume_usage_window_headroom) +-- -> update_in_memory_usage_window (mark dirty) -> apply_usage_window_writes. +-- A window-capped leftover is handled by the standard overage_behaviour path +-- ('cap' applies the partial deduction, 'reject' returns INSUFFICIENT_BALANCE) +-- -- no window-specific error. +-- +-- CONFIG IN: usage_window_limits[] -- resolved caps (limit, bounds, +-- dimension) from fullSubjectToUsageWindowLimits. +-- COUNTERS OUT: context.usage_windows[feature_id].windows -- DbUsageWindow +-- rows (usage amounts), NOT the config. +-- +-- Units: the deduction loop works in TRACKED-FEATURE UNITS; each ent's +-- credit_cost converts them to that ent's balance units. A metered_feature +-- limit counts tracked units (applies to every ent in the deduction set); a +-- balance limit counts credits drained from ents OF the capped feature, so +-- headroom converts via credit_cost at the gate. +-- ============================================================================ + +-- Tolerance for float drift (credit-ratio conversions leave sub-nano noise). +local USAGE_WINDOW_EPSILON = 1e-9 + +-- Max tracked units deductible from ONE ent given every applicable window +-- limit, or nil when unbounded. Windows never store a conversion -- headroom +-- lives in the limit's own unit (tracked units for metered dims, credits for +-- balance dims) and is converted HERE, per call, with the calling ent's +-- credit_cost: the same balance-dim headroom yields different unit allowances +-- for ents with different credit ratios, and only ents OF the capped feature +-- are bound by it at all. Metered dims need no conversion (the deduction loop +-- is denominated in tracked units, whatever pool funds them). +-- +-- Pass ent_feature_id = nil for the rollover phase: metered limits still +-- apply (rollover drains consume tracked units), balance limits do not +-- (parity with spend limits, whose overage math also ignores rollover +-- drains). +local function get_available_from_usage_windows(params) + local context = params.context + local ent_feature_id = params.ent_feature_id + local credit_cost = params.credit_cost or 1 + local allowed = nil + + for feature_id, feature_windows in pairs(context.usage_windows or {}) do + local headroom = feature_windows.headroom + if headroom <= USAGE_WINDOW_EPSILON then + headroom = 0 + end + + local units = nil + if feature_windows.dimension_type ~= 'balance' then + units = headroom + elseif not is_nil(ent_feature_id) and feature_id == ent_feature_id then + units = headroom / credit_cost + end + + if units ~= nil and (allowed == nil or units < allowed) then + allowed = units + end + end + + return allowed +end + +-- Records `units` tracked units drained from an ent against every applicable +-- limit: metered limits consume units 1:1, balance limits consume +-- units * credit_cost (credits). Decrements live headroom so the next ent's +-- gate sees it, and accumulates `consumed` for the counter increment. +local function consume_usage_window_headroom(params) + local context = params.context + local ent_feature_id = params.ent_feature_id + local credit_cost = params.credit_cost or 1 + local units = params.units or 0 + + if units <= 0 then + return + end + + for feature_id, feature_windows in pairs(context.usage_windows or {}) do + local consumed = nil + if feature_windows.dimension_type ~= 'balance' then + consumed = units + elseif not is_nil(ent_feature_id) and feature_id == ent_feature_id then + consumed = units * credit_cost + end + + if consumed ~= nil and consumed > 0 then + feature_windows.headroom = feature_windows.headroom - consumed + if feature_windows.headroom < 0 then + feature_windows.headroom = 0 + end + feature_windows.consumed = feature_windows.consumed + consumed + end + end +end + +-- Sibling of append_mutation_log: records which window row moved and by how +-- much (usage_delta in the limit's native unit). Kept as its own stream so +-- mutation_logs stays entitlement/rollover-shaped. +local function append_usage_window_mutation(params) + local context = params.context + table.insert(context.usage_window_mutations, { + usage_window_id = params.usage_window_id or cjson.null, + feature_id = params.feature_id, + internal_entity_id = params.internal_entity_id or cjson.null, + window_start_at = params.window_start_at, + usage_delta = params.usage_delta or 0, + }) +end + +-- In-memory mutation for one limit (sibling of +-- update_in_memory_customer_entitlement_mutation). ONE mutable row per scope: +-- zero the count if its stored window closed (defensive guard -- the lazy +-- roll action owns the roll), stamp the current bounds/anchor, add consumed. +local function update_in_memory_usage_window(params) + local context = params.context + local limit = params.limit + local now = params.now + + local feature_windows = context.usage_windows[limit.feature_id] + if feature_windows == nil then + return + end + + if feature_windows.consumed > USAGE_WINDOW_EPSILON then + local existing = find_usage_window(feature_windows.windows, limit) + if is_nil(existing) then + -- The TS-minted candidate id is used ONLY at creation; under concurrency + -- the second request finds the first one's row and its id is discarded. + existing = { + id = limit.new_window_id, + internal_customer_id = limit.internal_customer_id, + internal_entity_id = limit.internal_entity_id, + feature_id = limit.feature_id, + internal_feature_id = limit.internal_feature_id, + usage = 0, + } + table.insert(feature_windows.windows, existing) + elseif safe_number(existing.window_end_at) <= now + or safe_number(existing.window_start_at) ~= limit.window_start_at + then + -- A count never survives its stamped window: zero on expiry AND on any + -- bounds re-derivation mismatch (plan change). + existing.usage = 0 + end + + existing.window_start_at = limit.window_start_at + existing.window_end_at = limit.window_end_at + existing.anchor_customer_entitlement_id = + limit.anchor_customer_entitlement_id + existing.usage = safe_number(existing.usage) + feature_windows.consumed + existing.updated_at = now + feature_windows.dirty = true + + append_usage_window_mutation({ + context = context, + usage_window_id = existing.id, + feature_id = limit.feature_id, + internal_entity_id = limit.internal_entity_id, + window_start_at = limit.window_start_at, + usage_delta = feature_windows.consumed, + }) + end +end + +-- Applies each limit's in-flight consumed amount to its counter row. +-- Mirrors a lock UNWIND onto the counters: each applied unwind iteration +-- frees window headroom (metered dims by tracked units, balance dims by the +-- credits restored to that feature's entitlements). Clamped at 0; only the +-- limit's CURRENT window is decremented (a roll between lock and unwind +-- forfeits the old window's count, which is the conservative outcome). +local function decrement_usage_windows_for_unwind(params) + local context = params.context + local iterations = safe_table(params.iterations) + local now = params.now + + if is_nil(context.usage_windows) or #iterations == 0 then + return + end + + local total_units = 0 + local credits_by_feature_id = {} + + for _, iteration in ipairs(iterations) do + local units = safe_number(iteration.unwind_iteration_value) + total_units = total_units + units + + local item = iteration.item or {} + local ent_feature_id = nil + local ent = context.customer_entitlements[item.customer_entitlement_id] + if ent then + ent_feature_id = ent.feature_id + elseif item.rollover_id and context.rollovers[item.rollover_id] then + local rollover_ent = context.customer_entitlements[ + context.rollovers[item.rollover_id].cus_ent_id + ] + if rollover_ent then + ent_feature_id = rollover_ent.feature_id + end + end + + if ent_feature_id then + local credits = units * safe_number(item.credit_cost or 1) + credits_by_feature_id[ent_feature_id] = + (credits_by_feature_id[ent_feature_id] or 0) + credits + end + end + + for feature_id, feature_windows in pairs(context.usage_windows) do + local amount = 0 + if feature_windows.dimension_type == 'balance' then + amount = credits_by_feature_id[feature_id] or 0 + else + amount = total_units + end + + if amount > 0 then + local existing = find_usage_window( + feature_windows.windows, + feature_windows.limit + ) + if not is_nil(existing) then + local current = safe_number(existing.usage) + local next_usage = current - amount + if next_usage < 0 then + next_usage = 0 + end + + if next_usage ~= current then + existing.usage = next_usage + existing.updated_at = now + feature_windows.dirty = true + + append_usage_window_mutation({ + context = context, + usage_window_id = existing.id, + feature_id = feature_windows.limit.feature_id, + internal_entity_id = feature_windows.limit.internal_entity_id, + window_start_at = feature_windows.limit.window_start_at, + usage_delta = next_usage - current, + }) + end + end + end + end +end + +local function increment_usage_window_counters(params) + local context = params.context + local limits = params.usage_window_limits or {} + + for _, limit in ipairs(limits) do + update_in_memory_usage_window({ + context = context, + limit = limit, + now = params.now, + }) + end +end + +-- Persists dirty counter arrays back to their '_usage_windows' fields +-- (sibling of apply_pending_writes; direct HSET like updateAggregatedBalances +-- since the cusEnt pending-write path is keyed by entitlement blobs). +-- The EXPIRE guard is load-bearing under fail-open: a write to a hash that +-- did not exist (capped feature with no entitlements and no rebuild yet) +-- must not create an immortal key. +local function apply_usage_window_writes(context, ttl_seconds) + local ttl = tonumber(ttl_seconds) + + for _, feature_windows in pairs(context.usage_windows or {}) do + if feature_windows.dirty and not is_nil(feature_windows.balance_key) then + redis.call( + 'HSET', + feature_windows.balance_key, + USAGE_WINDOWS_FIELD, + cjson.encode(feature_windows.windows) + ) + if ttl and ttl > 0 + and redis.call('TTL', feature_windows.balance_key) < 0 then + redis.call('EXPIRE', feature_windows.balance_key, ttl) + end + end + end +end + +-- Result payload: { [feature_id] = windows[] } for every loaded capped +-- feature, so the TS caller can refresh the in-flight subject and hand the +-- post-deduction counters to syncItemV4 (no Redis re-read). +local function usage_windows_to_result(context) + local result = nil + for feature_id, feature_windows in pairs(context.usage_windows or {}) do + if result == nil then + result = {} + end + result[feature_id] = feature_windows.windows + end + return result +end diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index 93d91c7b3..fedf8f45c 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -44,12 +44,14 @@ import READ_SUBJECT_BALANCES from "./fullSubjectDeduction/readSubjectBalances.lu import RUN_DEDUCTION_ON_CONTEXT_V2 from "./fullSubjectDeduction/runDeductionOnContextV2.lua"; import SPEND_LIMIT_UTILS_V2 from "./fullSubjectDeduction/spendLimitUtilsV2.lua"; import UPDATE_AGGREGATED_BALANCES from "./fullSubjectDeduction/updateAggregatedBalances.lua"; -import USAGE_WINDOW_UTILS_V2 from "./fullSubjectDeduction/usageWindowUtilsV2.lua"; +import READ_USAGE_WINDOWS from "./fullSubjectDeduction/usageWindows/readUsageWindows.lua"; +import USAGE_WINDOW_CONTEXT_UTILS_V2 from "./fullSubjectDeduction/usageWindows/usageWindowContextUtilsV2.lua"; // ============================================================================ // UPDATE SUBJECT BALANCES HELPERS (V2 cache — per-feature hash updates) // ============================================================================ +import ROLL_USAGE_WINDOWS_MAIN from "./fullSubject/rollUsageWindows/rollUsageWindows.lua"; import APPLY_FIELD_UPDATES from "./fullSubject/updateSubjectBalances/applyFieldUpdates.lua"; import UPDATE_CONTEXT_UTILS from "./fullSubject/updateSubjectBalances/updateContextUtils.lua"; import UPDATE_SUBJECT_BALANCES_MAIN from "./fullSubject/updateSubjectBalances/updateSubjectBalances.lua"; @@ -201,12 +203,13 @@ export const UPDATE_CUSTOMER_PRODUCT_SCRIPT = */ export const DEDUCT_FROM_SUBJECT_BALANCES_SCRIPT = `${LUA_UTILS} ${READ_SUBJECT_BALANCES} +${READ_USAGE_WINDOWS} ${CONTEXT_UTILS_V2} ${GET_TOTAL_BALANCE} ${DEDUCT_FROM_ROLLOVERS_V2} ${DEDUCT_FROM_MAIN_BALANCE_V2} ${SPEND_LIMIT_UTILS_V2} -${USAGE_WINDOW_UTILS_V2} +${USAGE_WINDOW_CONTEXT_UTILS_V2} ${RUN_DEDUCTION_ON_CONTEXT_V2} ${MUTATION_ITEM_UTILS} ${LOCK_RECEIPT_UTILS_V2} @@ -240,3 +243,11 @@ ${UPDATE_CONTEXT_UTILS} ${APPLY_FIELD_UPDATES} ${UPDATE_AGGREGATED_BALANCES} ${UPDATE_SUBJECT_BALANCES_MAIN}`; + +/** + * Lua script for atomically rolling usage-window counters in a per-feature + * balance hash's '_usage_windows' field (zero expired counts, advance + * bounds/anchor). Called once per feature via pipeline by the lazy roll. + */ +export const ROLL_USAGE_WINDOWS_SCRIPT = `${LUA_UTILS} +${ROLL_USAGE_WINDOWS_MAIN}`; diff --git a/server/src/external/redis/initUtils/createRedisAvailability.ts b/server/src/external/redis/initUtils/createRedisAvailability.ts index 98a86f506..c6834f765 100644 --- a/server/src/external/redis/initUtils/createRedisAvailability.ts +++ b/server/src/external/redis/initUtils/createRedisAvailability.ts @@ -114,16 +114,12 @@ export const createRedisAvailability = ({ } const shouldReconnectReadyClient = - failedWhileReady && - consecutiveFailures + 1 >= REDIS_FAILURES_TO_DEGRADE; + failedWhileReady && consecutiveFailures + 1 >= REDIS_FAILURES_TO_DEGRADE; if (shouldReconnectReadyClient) { await reconnectRedis(); } else if (redis.status !== "ready") { - if ( - redis.status === "connecting" || - redis.status === "reconnecting" - ) { + if (redis.status === "connecting" || redis.status === "reconnecting") { reconnectStartedAt ??= Date.now(); if (Date.now() - reconnectStartedAt < REDIS_STALE_RECONNECT_MS) { return false; @@ -149,10 +145,7 @@ export const createRedisAvailability = ({ return { prime: async () => { if (!hasConfig) return; - if ( - redis.status === "connecting" || - redis.status === "reconnecting" - ) { + if (redis.status === "connecting" || redis.status === "reconnecting") { await waitForRedisReady(redis, logPrefix).catch(() => undefined); } const available = await probeRedisAvailability(); @@ -174,8 +167,7 @@ export const createRedisAvailability = ({ clearInterval(redisMonitorInterval); redisMonitorInterval = null; }, - shouldUseRedis: () => - hasConfig && redisAvailabilityState === "healthy", + shouldUseRedis: () => hasConfig && redisAvailabilityState === "healthy", getRedisAvailability: (): RedisAvailabilitySnapshot => ({ configured: hasConfig, state: redisAvailabilityState, diff --git a/server/src/external/redis/initUtils/redisAvailability.ts b/server/src/external/redis/initUtils/redisAvailability.ts index d584b4068..8d79eb9bf 100644 --- a/server/src/external/redis/initUtils/redisAvailability.ts +++ b/server/src/external/redis/initUtils/redisAvailability.ts @@ -1,8 +1,8 @@ -import { redis } from "./redisClientRegistry.js"; import { createRedisAvailability, type RedisAvailabilitySnapshot, } from "./createRedisAvailability.js"; +import { redis } from "./redisClientRegistry.js"; import { hasRedisConfig } from "./redisConfig.js"; const redisAvailability = createRedisAvailability({ diff --git a/server/src/external/redis/initUtils/redisTypes.ts b/server/src/external/redis/initUtils/redisTypes.ts index 69c6b6963..724c8b158 100644 --- a/server/src/external/redis/initUtils/redisTypes.ts +++ b/server/src/external/redis/initUtils/redisTypes.ts @@ -92,6 +92,7 @@ declare module "ioredis" { balanceKey: string, paramsJson: string, ): Promise; + rollUsageWindows(balanceKey: string, paramsJson: string): Promise; deleteFullCustomerCache( cacheKey: string, testGuardKey: string, diff --git a/server/src/external/redis/initUtils/registerRedisCommands.ts b/server/src/external/redis/initUtils/registerRedisCommands.ts index d894a75f3..41131a746 100644 --- a/server/src/external/redis/initUtils/registerRedisCommands.ts +++ b/server/src/external/redis/initUtils/registerRedisCommands.ts @@ -22,6 +22,7 @@ import { DEDUCT_FROM_SUBJECT_BALANCES_SCRIPT, DELETE_FULL_CUSTOMER_CACHE_SCRIPT, RESET_CUSTOMER_ENTITLEMENTS_SCRIPT, + ROLL_USAGE_WINDOWS_SCRIPT, SET_CACHED_FULL_SUBJECT_SCRIPT, SET_FULL_CUSTOMER_CACHE_SCRIPT, UPDATE_CACHED_INVOICE_V2_SCRIPT, @@ -127,6 +128,11 @@ export const registerRedisCommands = ({ lua: prepareScript(UPDATE_SUBJECT_BALANCES_SCRIPT), }); + redisInstance.defineCommand("rollUsageWindows", { + numberOfKeys: 1, + lua: prepareScript(ROLL_USAGE_WINDOWS_SCRIPT), + }); + redisInstance.defineCommand("deleteFullCustomerCache", { numberOfKeys: 4, lua: DELETE_FULL_CUSTOMER_CACHE_SCRIPT, diff --git a/server/src/internal/balances/check/getCheckResponseV2.ts b/server/src/internal/balances/check/getCheckResponseV2.ts index 1172b811a..eb086be48 100644 --- a/server/src/internal/balances/check/getCheckResponseV2.ts +++ b/server/src/internal/balances/check/getCheckResponseV2.ts @@ -55,6 +55,7 @@ export const getCheckResponseV2 = async ({ apiSubject: evaluationApiSubject, feature: featureToUse, requiredBalance, + originalFeature, }).allowed : false; diff --git a/server/src/internal/balances/finalizeLock/runRedisFinalizeLockV2.ts b/server/src/internal/balances/finalizeLock/runRedisFinalizeLockV2.ts index 07cdaf54f..7f84b3f13 100644 --- a/server/src/internal/balances/finalizeLock/runRedisFinalizeLockV2.ts +++ b/server/src/internal/balances/finalizeLock/runRedisFinalizeLockV2.ts @@ -41,13 +41,20 @@ export const runRedisFinalizeLockV2 = async ({ throw error; } - const { updates, rolloverUpdates, modifiedCusEntIdsByFeatureId } = - redisResult; + const { + updates, + rolloverUpdates, + modifiedCusEntIdsByFeatureId, + usageWindowUpdates, + } = redisResult; const modifiedCusEntIds = deductionUpdatesToModifiedIds({ updates }); const rolloverIds = Object.keys(rolloverUpdates); - if (modifiedCusEntIds.length > 0 || rolloverIds.length > 0) { - + if ( + modifiedCusEntIds.length > 0 || + rolloverIds.length > 0 || + usageWindowUpdates.length > 0 + ) { globalSyncBatchingManagerV3.addSyncItem({ customerId: receipt.customer_id, orgId: ctx.org.id, @@ -57,6 +64,7 @@ export const runRedisFinalizeLockV2 = async ({ region: currentRegion, entityId: receipt.entity_id ?? undefined, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }); } diff --git a/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts b/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts index 62b79af28..8277bae00 100644 --- a/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts +++ b/server/src/internal/balances/track/v3/handleRedisTrackErrorV3.ts @@ -5,7 +5,6 @@ import { RecaseError, type TrackParams, type TrackResponseV3, - UsageLimitExceededError, } from "@autumn/shared"; import { RedisUnavailableError } from "@/external/redis/utils/errors.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; @@ -40,12 +39,6 @@ export const handleRedisTrackErrorV3 = async ({ }); } - if (error.code === RedisDeductionErrorCode.UsageLimitExceeded) { - throw new UsageLimitExceededError({ - featureId: error.featureId ?? body.feature_id, - }); - } - if (error.code === RedisDeductionErrorCode.LockAlreadyExists) { throw new RecaseError({ message: "A lock with this ID already exists", diff --git a/server/src/internal/balances/track/v3/runRedisTrackV3.ts b/server/src/internal/balances/track/v3/runRedisTrackV3.ts index 577ea1150..234fe1898 100644 --- a/server/src/internal/balances/track/v3/runRedisTrackV3.ts +++ b/server/src/internal/balances/track/v3/runRedisTrackV3.ts @@ -1,26 +1,26 @@ import type { - FullSubject, - TrackDeduction, - TrackParams, - TrackResponseV3, + FullSubject, + TrackDeduction, + TrackParams, + TrackResponseV3, } from "@autumn/shared"; import { tryCatch } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { globalEventBatchingManager } from "@/internal/balances/events/EventBatchingManager.js"; +import { + buildEventInfo, + initEvent, +} from "@/internal/balances/events/initEvent.js"; import { resolveInternalProductIdForEvent } from "@/internal/balances/events/resolveInternalProductIdForEvent.js"; import { - buildEventInfo, - initEvent, -} from "@/internal/balances/events/initEvent.js"; -import { - deductionToTrackResponseV2, - executeRedisDeductionV2, - projectMutationLogsToTrackDeductionsV2, + deductionToTrackResponseV2, + executeRedisDeductionV2, + projectMutationLogsToTrackDeductionsV2, } from "@/internal/balances/utils/deductionV2/index.js"; import { globalSyncBatchingManagerV3 } from "@/internal/balances/utils/sync/SyncBatchingManagerV3.js"; -import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import type { RolloverUpdate } from "../../utils/types/rolloverUpdate.js"; +import type { UsageWindowUpdate } from "../../utils/types/usageWindowUpdate.js"; import { handleRedisTrackErrorV3 } from "./handleRedisTrackErrorV3.js"; const queueSyncItem = ({ @@ -29,18 +29,25 @@ const queueSyncItem = ({ fullSubject, rolloverUpdates, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }: { ctx: AutumnContext; body: TrackParams; fullSubject: FullSubject; rolloverUpdates: Record; modifiedCusEntIdsByFeatureId: Record; + usageWindowUpdates?: UsageWindowUpdate[]; }): void => { const cusEntIds = Object.values(modifiedCusEntIdsByFeatureId).flat(); const rolloverIds = Object.keys(rolloverUpdates); - if (cusEntIds.length === 0 && rolloverIds.length === 0) return; - + if ( + cusEntIds.length === 0 && + rolloverIds.length === 0 && + (usageWindowUpdates?.length ?? 0) === 0 + ) { + return; + } globalSyncBatchingManagerV3.addSyncItem({ customerId: body.customer_id, @@ -50,6 +57,7 @@ const queueSyncItem = ({ rolloverIds, entityId: fullSubject.entityId, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }); }; @@ -129,36 +137,17 @@ export const runRedisTrackV3 = async ({ rolloverUpdates, modifiedCusEntIdsByFeatureId, mutationLogs, + usageWindowUpdates, } = result; - // Write the cap counter through to PG now; a later mutation rebuilds the cache from it. - const hasUsageCap = (fullSubject.customer.spend_limits ?? []).some( - (limit) => limit.usage_limit != null, - ); - if (hasUsageCap) { - await tryCatch( - syncItemV4({ - ctx, - payload: { - customerId: body.customer_id, - orgId: ctx.org.id, - env: ctx.env, - timestamp: Date.now(), - entityId: updatedFullSubject.entityId, - rolloverIds: Object.keys(rolloverUpdates), - modifiedCusEntIdsByFeatureId, - }, - }), - ); - } else { - queueSyncItem({ - ctx, - body, - fullSubject: updatedFullSubject, - rolloverUpdates, - modifiedCusEntIdsByFeatureId, - }); - } + queueSyncItem({ + ctx, + body, + fullSubject: updatedFullSubject, + rolloverUpdates, + modifiedCusEntIdsByFeatureId, + usageWindowUpdates, + }); const deductions = projectMutationLogsToTrackDeductionsV2({ fullSubject: updatedFullSubject, diff --git a/server/src/internal/balances/updateBalance/v2/updateRemainingV2.ts b/server/src/internal/balances/updateBalance/v2/updateRemainingV2.ts index 2d9318428..1123eba5b 100644 --- a/server/src/internal/balances/updateBalance/v2/updateRemainingV2.ts +++ b/server/src/internal/balances/updateBalance/v2/updateRemainingV2.ts @@ -67,11 +67,16 @@ export const updateRemainingV2 = async ({ }); } - const { rolloverUpdates, modifiedCusEntIdsByFeatureId } = result; + const { rolloverUpdates, modifiedCusEntIdsByFeatureId, usageWindowUpdates } = + result; const cusEntIds = Object.values(modifiedCusEntIdsByFeatureId).flat(); const rolloverIds = Object.keys(rolloverUpdates); - if (cusEntIds.length > 0 || rolloverIds.length > 0) { + if ( + cusEntIds.length > 0 || + rolloverIds.length > 0 || + usageWindowUpdates.length > 0 + ) { await syncItemV4({ ctx, payload: { @@ -82,6 +87,7 @@ export const updateRemainingV2 = async ({ rolloverIds, entityId: fullSubject.entityId, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }, }); } diff --git a/server/src/internal/balances/updateBalance/v2/updateUsageV2.ts b/server/src/internal/balances/updateBalance/v2/updateUsageV2.ts index aa0a11e56..cc189a066 100644 --- a/server/src/internal/balances/updateBalance/v2/updateUsageV2.ts +++ b/server/src/internal/balances/updateBalance/v2/updateUsageV2.ts @@ -111,11 +111,16 @@ export const updateUsageV2 = async ({ }); } - const { rolloverUpdates, modifiedCusEntIdsByFeatureId } = result; + const { rolloverUpdates, modifiedCusEntIdsByFeatureId, usageWindowUpdates } = + result; const cusEntIds = Object.values(modifiedCusEntIdsByFeatureId).flat(); const rolloverIds = Object.keys(rolloverUpdates); - if (cusEntIds.length > 0 || rolloverIds.length > 0) { + if ( + cusEntIds.length > 0 || + rolloverIds.length > 0 || + usageWindowUpdates.length > 0 + ) { await syncItemV4({ ctx, payload: { @@ -126,6 +131,7 @@ export const updateUsageV2 = async ({ rolloverIds, entityId: fullSubject.entityId, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }, }); } diff --git a/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts b/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts index 26de4db00..4a4fe11a7 100644 --- a/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts +++ b/server/src/internal/balances/utils/deductionV2/applyDeductionUpdateToFullSubject.ts @@ -45,7 +45,6 @@ const applyUpdate = ({ additional_balance: update.additional_balance, adjustment: update.adjustment, entities: update.entities, - usage_windows: update.usage_windows ?? customerEntitlement.usage_windows, replaceables: getUpdatedReplaceables({ replaceables: customerEntitlement.replaceables, update, diff --git a/server/src/internal/balances/utils/deductionV2/applyUsageWindowUpdatesToFullSubject.ts b/server/src/internal/balances/utils/deductionV2/applyUsageWindowUpdatesToFullSubject.ts new file mode 100644 index 000000000..c33679a6e --- /dev/null +++ b/server/src/internal/balances/utils/deductionV2/applyUsageWindowUpdatesToFullSubject.ts @@ -0,0 +1,36 @@ +import type { FullSubject, UsageWindow } from "@autumn/shared"; + +/** + * Refresh the in-flight subject's customer-scoped usage-window counters from + * the deduction result, so usage_limit_used (webhooks, API responses built + * from this subject) reflects the deduction. Sibling of + * applyDeductionUpdateToFullSubject / applyRolloverUpdatesToFullSubject. + * + * The Lua result carries ALL scopes; the subject keeps its own scope only + * (entity subjects hold just their entity's rows). + */ +export const applyUsageWindowUpdatesToFullSubject = ({ + fullSubject, + usageWindowsByFeatureId, +}: { + fullSubject: FullSubject; + usageWindowsByFeatureId: Record | null | undefined; +}): void => { + if (!usageWindowsByFeatureId) return; + + const updatedFeatureIds = new Set(Object.keys(usageWindowsByFeatureId)); + const updatedWindows = Object.values(usageWindowsByFeatureId) + .flat() + .filter((usageWindow) => + fullSubject.internalEntityId + ? usageWindow.internal_entity_id === fullSubject.internalEntityId + : true, + ); + + fullSubject.usage_windows = [ + ...(fullSubject.usage_windows ?? []).filter( + (usageWindow) => !updatedFeatureIds.has(usageWindow.feature_id), + ), + ...updatedWindows, + ]; +}; diff --git a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts index c0c67a96c..d9a3169c9 100644 --- a/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/executeRedisDeductionV2.ts @@ -17,6 +17,7 @@ import { createAllocatedInvoice } from "@/internal/balances/utils/allocatedInvoi import { saveLockReceiptV2 } from "@/internal/balances/utils/lockV2/saveLockReceiptV2.js"; import { buildDeductFromSubjectBalancesKeys } from "@/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.js"; import { buildFullSubjectKey } from "@/internal/customers/cache/fullSubject/builders/buildFullSubjectKey.js"; +import { FULL_SUBJECT_CACHE_TTL_SECONDS } from "@/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.js"; import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import type { DeductionUpdate } from "../types/deductionUpdate.js"; @@ -28,8 +29,11 @@ import { } from "../types/redisDeductionError.js"; import type { LuaDeductionResult } from "../types/redisDeductionResult.js"; import type { RolloverUpdate } from "../types/rolloverUpdate.js"; +import type { UsageWindowMutation } from "../types/usageWindowMutation.js"; +import type { UsageWindowUpdate } from "../types/usageWindowUpdate.js"; import { applyDeductionUpdateToFullSubject } from "./applyDeductionUpdateToFullSubject.js"; import { applyRolloverUpdatesToFullSubject } from "./applyRolloverUpdatesToFullSubject.js"; +import { applyUsageWindowUpdatesToFullSubject } from "./applyUsageWindowUpdatesToFullSubject.js"; import { logDeductionUpdatesV2 } from "./logDeductionUpdatesV2.js"; import { mutationLogsToFeaturesV2 } from "./mutationLogsToFeaturesV2.js"; import { normalizeDeductionSyncStateV2 } from "./normalizeDeductionSyncStateV2.js"; @@ -60,6 +64,8 @@ export const executeRedisDeductionV2 = async ({ rolloverUpdates: Record; mutationLogs: MutationLogItem[]; modifiedCusEntIdsByFeatureId: Record; + usageWindowUpdates: UsageWindowUpdate[]; + usageWindowMutations: UsageWindowMutation[]; }> => { const { org, env } = ctx; const oldFullSubject = structuredClone(fullSubject); @@ -95,7 +101,11 @@ export const executeRedisDeductionV2 = async ({ let allUpdates: Record = {}; let allRolloverUpdates: Record = {}; let allMutationLogs: MutationLogItem[] = []; + let allUsageWindowMutations: UsageWindowMutation[] = []; const allModifiedCusEntIdsByFeatureId: Record = {}; + // Keyed by feature id: each Lua result carries the COMPLETE post-deduction + // counter array per capped feature, so last write wins across deductions. + const allUsageWindowUpdates: Record = {}; const customerId = fullSubject.customerId; const routingKey = buildFullSubjectKey({ @@ -123,6 +133,7 @@ export const executeRedisDeductionV2 = async ({ spendLimitByFeatureId, usageBasedCusEntIdsByFeatureId, usageWindowLimits, + usageWindowFeatureIds, rollovers, customerEntitlements, unlimitedFeatureIds, @@ -178,16 +189,6 @@ export const executeRedisDeductionV2 = async ({ }).redisKey : null; - // Anchor features own usage-window counters and may not be in the - // deduction set, so their balance hash keys must be declared too. - const anchorFeatureIds = [ - ...new Set( - (usageWindowLimits ?? []) - .map((limit) => limit.anchor_feature_id) - .filter((featureId): featureId is string => featureId !== null), - ), - ]; - const { keys, balanceKeyIndexByFeatureId } = buildDeductFromSubjectBalancesKeys({ orgId: org.id, @@ -198,7 +199,7 @@ export const executeRedisDeductionV2 = async ({ idempotencyKey: idempotencyRedisKey, customerEntitlementDeductions, fallbackFeatureId: feature.id, - anchorFeatureIds, + usageWindowFeatureIds, }); // Usage windows are enforced/incremented only for real positive @@ -220,6 +221,7 @@ export const executeRedisDeductionV2 = async ({ usageBasedCusEntIdsByFeatureId ?? null, usage_window_limits: usageWindowLimits ?? null, usage_window_now: usageWindowNow, + usage_window_ttl_seconds: FULL_SUBJECT_CACHE_TTL_SECONDS, is_consumption: isConsumption, amount_to_deduct: toDeduct ?? null, target_balance: targetBalance ?? null, @@ -280,6 +282,11 @@ export const executeRedisDeductionV2 = async ({ const mutationLogs = Array.isArray(resultJson.mutation_logs) ? resultJson.mutation_logs : []; + const usageWindowMutations = Array.isArray( + resultJson.usage_window_mutations, + ) + ? resultJson.usage_window_mutations + : []; const modifiedCustomerEntitlementIds = Array.isArray( resultJson.modified_customer_entitlement_ids, ) @@ -296,6 +303,21 @@ export const executeRedisDeductionV2 = async ({ allUpdates = { ...allUpdates, ...updates }; allRolloverUpdates = { ...allRolloverUpdates, ...rollover_updates }; allMutationLogs = [...allMutationLogs, ...mutationLogs]; + allUsageWindowMutations = [ + ...allUsageWindowMutations, + ...usageWindowMutations, + ]; + // Typed handoff for the PG mirror; empty arrays kept (prune-to-empty + // must still full-replace). + for (const [featureId, usageWindows] of Object.entries( + resultJson.usage_windows_by_feature_id ?? {}, + )) { + allUsageWindowUpdates[featureId] = { + internal_customer_id: fullSubject.internalCustomerId, + feature_id: featureId, + usage_windows: usageWindows, + }; + } const syncState = normalizeDeductionSyncStateV2({ customerEntitlements, @@ -320,6 +342,11 @@ export const executeRedisDeductionV2 = async ({ rolloverUpdates: rollover_updates, }); + applyUsageWindowUpdatesToFullSubject({ + fullSubject, + usageWindowsByFeatureId: resultJson.usage_windows_by_feature_id, + }); + for (const customerEntitlementId of Object.keys(updates)) { const update = updates[customerEntitlementId]; const customerEntitlement = customerEntitlements.find( @@ -394,5 +421,7 @@ export const executeRedisDeductionV2 = async ({ rolloverUpdates: allRolloverUpdates, mutationLogs: allMutationLogs, modifiedCusEntIdsByFeatureId: allModifiedCusEntIdsByFeatureId, + usageWindowUpdates: Object.values(allUsageWindowUpdates), + usageWindowMutations: allUsageWindowMutations, }; }; diff --git a/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts b/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts index 5760e07d5..952344f2c 100644 --- a/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts +++ b/server/src/internal/balances/utils/deductionV2/prepareFeatureDeductionV2.ts @@ -21,6 +21,7 @@ import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { buildLockReceiptKey } from "@/internal/balances/utils/lock/buildLockReceiptKey.js"; import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; import { getCreditCost } from "@/internal/features/creditSystemUtils.js"; +import { generateId } from "@/utils/genUtils.js"; import type { CustomerEntitlementDeduction, DeductionOptions, @@ -132,19 +133,16 @@ export const prepareFeatureDeductionV2 = ({ inStatuses: orgToInStatuses({ org }), }); + // Counters are customer-scoped: a null anchor only means calendar-aligned + // bounds with no provenance, not an unenforceable cap. for (const windowLimit of usageWindowLimits) { + windowLimit.new_window_id = generateId("uw"); if (windowLimit.anchor_customer_entitlement_id === null) { ctx.logger.warn( - `usage window for feature ${windowLimit.feature_id} has no eligible anchor entitlement; failing closed (rejecting). Likely a misconfigured cap with no in-status, non-entity-scoped owning entitlement.`, + `usage window for feature ${windowLimit.feature_id} has no anchor entitlement; using calendar-aligned bounds with no provenance.`, ); } } - if (fullSubject.entity?.spend_limits?.some((s) => s.usage_limit != null)) { - ctx.logger.warn( - `entity-scoped usage windows are not enforced in v1; ignored for entity ${fullSubject.entity.id}`, - ); - } - // set_usage carries no window provenance, so it would silently bypass the hard // cap; reject it when the feature has an enforced usage window. if (notNullish(targetBalance) && usageWindowLimits.length > 0) { @@ -260,6 +258,10 @@ export const prepareFeatureDeductionV2 = ({ : undefined, usageWindowLimits: usageWindowLimits.length > 0 ? usageWindowLimits : undefined, + usageWindowFeatureIds: + usageWindowLimits.length > 0 + ? [...new Set(usageWindowLimits.map((limit) => limit.feature_id))] + : undefined, rollovers: sortedRollovers.map((rollover) => ({ id: rollover.id, credit_cost: rollover.credit_cost, diff --git a/server/src/internal/balances/utils/sql/syncBalancesV2.sql b/server/src/internal/balances/utils/sql/syncBalancesV2.sql index 7553aae52..f918b4140 100644 --- a/server/src/internal/balances/utils/sql/syncBalancesV2.sql +++ b/server/src/internal/balances/utils/sql/syncBalancesV2.sql @@ -6,7 +6,6 @@ -- - balance: number -- - adjustment: number -- - entities: jsonb (the full entities object) --- - usage_windows: jsonb array of DbUsageWindow rows, mirrored to the usage_windows table (null = skip) -- - next_reset_at: bigint/number (unix timestamp, for conflict detection) -- - entity_count: number (for conflict detection) -- - cache_version: number (if defined, skip write if DB cache_version differs) @@ -15,6 +14,12 @@ -- - balance: number -- - usage: number -- - entities: jsonb (the full entities object) +-- usage_window_updates: array of objects with: +-- - internal_customer_id: string +-- - feature_id: string +-- - usage_windows: jsonb array of DbUsageWindow rows (the COMPLETE set for +-- that customer+feature; Redis is authoritative and prunes closed +-- windows, so rows are full-replaced per customer+feature) -- -- Returns JSONB with: -- updates: object mapping customer_entitlement_id -> { balance, adjustment, entities } @@ -33,16 +38,21 @@ AS $$ DECLARE customer_entitlement_updates jsonb := params->'customer_entitlement_updates'; rollover_updates_param jsonb := params->'rollover_updates'; - + usage_window_updates_param jsonb := params->'usage_window_updates'; + ent_obj jsonb; ent_id text; ent_balance numeric; ent_adjustment numeric; ent_entities jsonb; - ent_usage_windows jsonb; ent_next_reset_at bigint; ent_entity_count int; ent_cache_version int; + + uw_obj jsonb; + uw_internal_customer_id text; + uw_feature_id text; + uw_windows jsonb; db_next_reset_at bigint; db_entity_count int; @@ -101,7 +111,6 @@ BEGIN ent_balance := (ent_obj->>'balance')::numeric; ent_adjustment := (ent_obj->>'adjustment')::numeric; ent_entities := ent_obj->'entities'; - ent_usage_windows := ent_obj->'usage_windows'; ent_next_reset_at := (ent_obj->>'next_reset_at')::bigint; ent_entity_count := COALESCE((ent_obj->>'entity_count')::int, 0); ent_cache_version := COALESCE((ent_obj->>'cache_version')::int, 0); @@ -145,48 +154,13 @@ BEGIN WHERE ce.id = ent_id; IF FOUND THEN - -- Mirror the windowed-usage counters into the usage_windows table (Redis is - -- authoritative and already prunes closed windows): full-replace the cus_ent's - -- rows. Clear on ANY present blob -- an emptied window array re-encodes as {} - -- (lua-cjson encodes an empty table as an object), so guarding the DELETE on - -- 'array' would leave stale closed rows. Only a real array has rows to INSERT; - -- a null/object blob simply clears, so the shared sync never reaches - -- jsonb_array_elements on a non-array. null = balance-only sync (untouched). - -- The internal_feature_id filters keep a stray null/orphan row from aborting - -- the whole batch on the NOT NULL + FK column. - IF ent_usage_windows IS NOT NULL AND ent_usage_windows != 'null'::jsonb THEN - DELETE FROM usage_windows WHERE customer_entitlement_id = ent_id; - IF jsonb_typeof(ent_usage_windows) = 'array' THEN - INSERT INTO usage_windows ( - id, customer_entitlement_id, feature_id, internal_feature_id, - window_start_at, window_end_at, usage, updated_at - ) - SELECT - w->>'id', - ent_id, - w->>'feature_id', - w->>'internal_feature_id', - (w->>'window_start_at')::numeric, - (w->>'window_end_at')::numeric, - (w->>'usage')::numeric, - (w->>'updated_at')::numeric - FROM jsonb_array_elements(ent_usage_windows) AS w - WHERE w->>'internal_feature_id' IS NOT NULL - AND EXISTS ( - SELECT 1 FROM features f - WHERE f.internal_id = w->>'internal_feature_id' - ); - END IF; - END IF; - updates_json := jsonb_set( updates_json, ARRAY[ent_id], jsonb_build_object( 'balance', ent_balance, 'adjustment', ent_adjustment, - 'entities', ent_entities, - 'usage_windows', ent_usage_windows + 'entities', ent_entities ) ); END IF; @@ -227,6 +201,75 @@ BEGIN END LOOP; END IF; + -- ============================================================================ + -- STEP 4: Mirror usage-window counters (race-safe upsert) + -- ============================================================================ + -- ONE mutable row per (customer, feature, entity scope); bounds roll in + -- place. Upsert on the scope key (never on id) so concurrent creates can't + -- abort, with an updated_at guard so older snapshots never clobber newer. + IF usage_window_updates_param IS NOT NULL THEN + FOR uw_obj IN SELECT * FROM jsonb_array_elements(usage_window_updates_param) + LOOP + uw_internal_customer_id := uw_obj->>'internal_customer_id'; + uw_feature_id := uw_obj->>'feature_id'; + uw_windows := uw_obj->'usage_windows'; + + IF uw_internal_customer_id IS NOT NULL + AND uw_feature_id IS NOT NULL + AND uw_windows IS NOT NULL + AND uw_windows != 'null'::jsonb THEN + IF jsonb_typeof(uw_windows) != 'array' THEN + uw_windows := '[]'::jsonb; + END IF; + + INSERT INTO usage_windows ( + id, internal_customer_id, internal_entity_id, feature_id, + internal_feature_id, anchor_customer_entitlement_id, + window_start_at, window_end_at, usage, updated_at + ) + SELECT + w->>'id', + uw_internal_customer_id, + w->>'internal_entity_id', + uw_feature_id, + w->>'internal_feature_id', + CASE + WHEN w->>'anchor_customer_entitlement_id' IS NOT NULL + AND EXISTS ( + SELECT 1 FROM customer_entitlements ce + WHERE ce.id = w->>'anchor_customer_entitlement_id' + ) + THEN w->>'anchor_customer_entitlement_id' + ELSE NULL + END, + (w->>'window_start_at')::numeric, + (w->>'window_end_at')::numeric, + (w->>'usage')::numeric, + (w->>'updated_at')::numeric + FROM jsonb_array_elements(uw_windows) AS w + WHERE w->>'id' IS NOT NULL + AND w->>'internal_feature_id' IS NOT NULL + AND EXISTS ( + SELECT 1 FROM features f + WHERE f.internal_id = w->>'internal_feature_id' + ) + ON CONFLICT ( + internal_customer_id, internal_feature_id, + COALESCE(internal_entity_id, '') + ) + DO UPDATE SET + usage = EXCLUDED.usage, + updated_at = EXCLUDED.updated_at, + window_start_at = EXCLUDED.window_start_at, + window_end_at = EXCLUDED.window_end_at, + feature_id = EXCLUDED.feature_id, + anchor_customer_entitlement_id = + EXCLUDED.anchor_customer_entitlement_id + WHERE EXCLUDED.updated_at >= usage_windows.updated_at; + END IF; + END LOOP; + END IF; + RETURN jsonb_build_object( 'updates', updates_json, 'rollover_updates', rollover_updates_json diff --git a/server/src/internal/balances/utils/sync/SyncBatchingManagerV3.ts b/server/src/internal/balances/utils/sync/SyncBatchingManagerV3.ts index 61245b703..120bbcb12 100644 --- a/server/src/internal/balances/utils/sync/SyncBatchingManagerV3.ts +++ b/server/src/internal/balances/utils/sync/SyncBatchingManagerV3.ts @@ -3,6 +3,7 @@ import { logger } from "@/external/logtail/logtailUtils.js"; import { currentRegion } from "@/external/redis/initRedis.js"; import { JobName } from "@/queue/JobName.js"; import { addTaskToQueue } from "@/queue/queueUtils.js"; +import type { UsageWindowUpdate } from "../types/usageWindowUpdate.js"; interface CustomerBatchContext { customerId: string; @@ -14,6 +15,10 @@ interface CustomerBatchContext { rolloverIds: Set; entityId?: string; modifiedCusEntIdsByFeatureId: Record; + // Counter SNAPSHOTS keyed by capped feature: each deduction returns the + // complete post-deduction array, so merging across batched items is + // last-write-wins (unlike cusEnt/rollover ids, which accumulate). + usageWindowUpdatesByFeatureId: Record; } interface CustomerBatch { @@ -33,6 +38,7 @@ export type QueueSyncV4Payload = { rolloverIds: string[]; entityId?: string; modifiedCusEntIdsByFeatureId: Record; + usageWindowUpdates?: UsageWindowUpdate[]; }; messageGroupId?: string; messageDeduplicationId: string; @@ -78,6 +84,7 @@ export class SyncBatchingManagerV3 { region, entityId, modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }: { customerId: string; orgId: string; @@ -87,6 +94,7 @@ export class SyncBatchingManagerV3 { region?: string; entityId?: string; modifiedCusEntIdsByFeatureId: Record; + usageWindowUpdates?: UsageWindowUpdate[]; }): void { const batchKey = this.buildBatchKey({ orgId, env, customerId }); let batch = this.customerBatches.get(batchKey); @@ -112,6 +120,12 @@ export class SyncBatchingManagerV3 { batch.context.modifiedCusEntIdsByFeatureId[featureId].push(...ids); } + for (const usageWindowUpdate of usageWindowUpdates ?? []) { + batch.context.usageWindowUpdatesByFeatureId[ + usageWindowUpdate.feature_id + ] = usageWindowUpdate; + } + const totalSize = batch.context.cusEntIds.size + batch.context.rolloverIds.size; if (totalSize >= this.MAX_BATCH_SIZE) { @@ -177,6 +191,7 @@ export class SyncBatchingManagerV3 { cusEntIds: new Set(), rolloverIds: new Set(), modifiedCusEntIdsByFeatureId: {}, + usageWindowUpdatesByFeatureId: {}, }, timer: null, }; @@ -231,7 +246,13 @@ export class SyncBatchingManagerV3 { this.customerBatches.delete(batchKey); const { context } = batch; - if (context.cusEntIds.size === 0 && context.rolloverIds.size === 0) return; + if ( + context.cusEntIds.size === 0 && + context.rolloverIds.size === 0 && + Object.keys(context.usageWindowUpdatesByFeatureId).length === 0 + ) { + return; + } await this.queueSyncJob({ context }); } @@ -247,10 +268,12 @@ export class SyncBatchingManagerV3 { context, cusEntIds, rolloverIds, + usageWindowUpdates, }: { context: CustomerBatchContext; cusEntIds: string[]; rolloverIds: string[]; + usageWindowUpdates: UsageWindowUpdate[]; }): string { const dedupBucket = Math.floor(Date.now() / this.DEDUP_BUCKET_MS); const dedupKey = JSON.stringify({ @@ -260,6 +283,10 @@ export class SyncBatchingManagerV3 { customerId: context.customerId, cusEntIds, rolloverIds, + // Snapshots ride the payload (cusEnt balances are re-read at consume + // time, counters are not), so a newer snapshot must never be dropped + // as a duplicate of an older one within the bucket. + usageWindowUpdates, dedupBucket, }); @@ -273,10 +300,14 @@ export class SyncBatchingManagerV3 { }): Promise { const cusEntIds = Array.from(context.cusEntIds).sort(); const rolloverIds = Array.from(context.rolloverIds).sort(); + const usageWindowUpdates = Object.values( + context.usageWindowUpdatesByFeatureId, + ); const messageDeduplicationId = this.buildDeduplicationId({ context, cusEntIds, rolloverIds, + usageWindowUpdates, }); try { @@ -292,13 +323,14 @@ export class SyncBatchingManagerV3 { rolloverIds, entityId: context.entityId, modifiedCusEntIdsByFeatureId: context.modifiedCusEntIdsByFeatureId, + usageWindowUpdates, }, // messageGroupId: `sync-v4:${context.orgId}:${context.env}:${context.customerId}`, messageDeduplicationId, }); logger.debug( - `[SyncV4] Queued sync for ${context.customerId}, ${cusEntIds.length} entitlements, ${rolloverIds.length} rollovers`, + `[SyncV4] Queued sync for ${context.customerId}, ${cusEntIds.length} entitlements, ${rolloverIds.length} rollovers, ${usageWindowUpdates.length} usage windows`, ); } catch (error) { logger.error( diff --git a/server/src/internal/balances/utils/sync/syncItemV4.ts b/server/src/internal/balances/utils/sync/syncItemV4.ts index 3450a85f5..8f883ef16 100644 --- a/server/src/internal/balances/utils/sync/syncItemV4.ts +++ b/server/src/internal/balances/utils/sync/syncItemV4.ts @@ -4,13 +4,13 @@ import { type EntityRolloverBalance, type SubjectBalance, tryCatch, - type UsageWindow, } from "@autumn/shared"; import { sql } from "drizzle-orm"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { getCachedFeatureBalance } from "@/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.js"; import { deleteCachedFullCustomer } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.js"; import { globalRefreshEntityAggregateBatchingManager } from "../refreshEntityAggregate/RefreshEntityAggregateBatchingManager"; +import type { UsageWindowUpdate } from "../types/usageWindowUpdate.js"; import { logSyncItem } from "./logs/logSyncItem"; const SYNC_CONFLICT_CODES = { @@ -69,6 +69,10 @@ interface SyncItemV4 { timestamp: number; rolloverIds?: string[]; modifiedCusEntIdsByFeatureId: Record; + /** Post-deduction counter snapshots handed straight from the Lua result + * (no Redis re-read); mirrored to the customer-scoped usage_windows table + * via full-replace per (customer, feature). */ + usageWindowUpdates?: UsageWindowUpdate[]; } export interface SyncEntry { @@ -77,7 +81,6 @@ export interface SyncEntry { balance: number; adjustment: number; entities: Record | null; - usage_windows: UsageWindow[] | null; next_reset_at: number | null; entity_count: number; cache_version: number | null; @@ -100,7 +103,6 @@ const subjectBalanceToSyncEntry = ({ balance: subjectBalance.balance ?? 0, adjustment: subjectBalance.adjustment ?? 0, entities: subjectBalance.entities ?? null, - usage_windows: subjectBalance.usage_windows ?? null, next_reset_at: subjectBalance.next_reset_at ?? null, entity_count: subjectBalance.entities ? Object.keys(subjectBalance.entities).length @@ -116,12 +118,17 @@ export const syncItemV4 = async ({ ctx: AutumnContext; payload: SyncItemV4; }): Promise => { - const { customerId, entityId, rolloverIds, modifiedCusEntIdsByFeatureId } = - payload; + const { + customerId, + entityId, + rolloverIds, + modifiedCusEntIdsByFeatureId, + usageWindowUpdates, + } = payload; const { db } = ctx; // Read targeted balance hashes - const allSubjectBalances: SubjectBalance[] = []; + let allSubjectBalances: SubjectBalance[] = []; for (const [featureId, customerEntitlementIds] of Object.entries( modifiedCusEntIdsByFeatureId, )) { @@ -142,7 +149,11 @@ export const syncItemV4 = async ({ feature: featureId, }, }); - return; + // A miss (e.g. an invalidation racing the batch) drops the BALANCE + // sync wholesale, but usage-window snapshots ride in the payload and + // need no cache read -- they must still land. + allSubjectBalances = []; + break; } allSubjectBalances.push(...outcome.value.balances); @@ -172,7 +183,16 @@ export const syncItemV4 = async ({ } } - if (entries.length === 0 && rolloverEntries.length === 0) { + // Customer-scoped usage-window counters arrive pre-built from the deduction + // result (same atomic Lua execution that incremented them) -- no Redis + // re-read here. Full-replaced per (customer, feature) by the SQL function. + const usageWindowEntries: UsageWindowUpdate[] = usageWindowUpdates ?? []; + + if ( + entries.length === 0 && + rolloverEntries.length === 0 && + usageWindowEntries.length === 0 + ) { logSyncItem({ ctx, result: { kind: "skipped", reason: "no_entries" } }); return; } @@ -182,6 +202,7 @@ export const syncItemV4 = async ({ sql`SELECT * FROM sync_balances_v2(${JSON.stringify({ customer_entitlement_updates: entries, rollover_updates: rolloverEntries, + usage_window_updates: usageWindowEntries, })}::jsonb)`, ), ); diff --git a/server/src/internal/balances/utils/types/deductionTypes.ts b/server/src/internal/balances/utils/types/deductionTypes.ts index 800e25ae2..9d431b8e6 100644 --- a/server/src/internal/balances/utils/types/deductionTypes.ts +++ b/server/src/internal/balances/utils/types/deductionTypes.ts @@ -43,9 +43,12 @@ export type PreparedFeatureDeduction = { customerEntitlementDeductions: CustomerEntitlementDeduction[]; spendLimitByFeatureId?: Record; usageBasedCusEntIdsByFeatureId?: Record; - // Resolved windowed usage-limit caps (PR2: passed to Lua but not yet - // enforced; enforcement lands with the deduction-script changes). + // Resolved windowed usage-limit caps, enforced inside the deduction script. usageWindowLimits?: UsageWindowLimit[]; + // Distinct capped feature ids: their balance hashes carry the + // `_usage_windows` counter field, so their keys must be declared in KEYS[] + // even when no deduction entry references them. + usageWindowFeatureIds?: string[]; // rolloverIds: string[]; rollovers: RolloverDeduction[]; unlimitedFeatureIds: string[]; diff --git a/server/src/internal/balances/utils/types/deductionUpdate.ts b/server/src/internal/balances/utils/types/deductionUpdate.ts index a1a80aa43..d06d00dee 100644 --- a/server/src/internal/balances/utils/types/deductionUpdate.ts +++ b/server/src/internal/balances/utils/types/deductionUpdate.ts @@ -2,7 +2,6 @@ import type { EntityBalance, InsertReplaceable, Replaceable, - UsageWindow, } from "@autumn/shared"; export interface DeductionUpdate { @@ -15,7 +14,6 @@ export interface DeductionUpdate { additional_deducted?: number; newReplaceables?: InsertReplaceable[]; deletedReplaceables?: Replaceable[]; - usage_windows?: UsageWindow[] | null; } export type DeductionUpdates = Record; diff --git a/server/src/internal/balances/utils/types/redisDeductionError.ts b/server/src/internal/balances/utils/types/redisDeductionError.ts index 83449fbbb..a802673ab 100644 --- a/server/src/internal/balances/utils/types/redisDeductionError.ts +++ b/server/src/internal/balances/utils/types/redisDeductionError.ts @@ -9,7 +9,6 @@ export enum RedisDeductionErrorCode { SkipCache = "SKIP_CACHE", LockAlreadyExists = "LOCK_ALREADY_EXISTS", DuplicateIdempotencyKey = "DUPLICATE_IDEMPOTENCY_KEY", - UsageLimitExceeded = "USAGE_LIMIT_EXCEEDED", } /** Errors that should trigger a fallback to Postgres */ diff --git a/server/src/internal/balances/utils/types/redisDeductionResult.ts b/server/src/internal/balances/utils/types/redisDeductionResult.ts index 45f595329..5a9e4def8 100644 --- a/server/src/internal/balances/utils/types/redisDeductionResult.ts +++ b/server/src/internal/balances/utils/types/redisDeductionResult.ts @@ -1,12 +1,21 @@ +import type { UsageWindow } from "@autumn/shared"; import type { DeductionUpdate } from "./deductionUpdate.js"; import type { MutationLogItem } from "./mutationLogItem.js"; import type { RolloverUpdate } from "./rolloverUpdate.js"; +import type { UsageWindowMutation } from "./usageWindowMutation.js"; export interface LuaDeductionResult { updates: Record; rollover_updates: Record; modified_customer_entitlement_ids: string[]; mutation_logs: MutationLogItem[]; + /** Post-deduction COUNTER ROWS per capped feature (usage amounts; mirrors + * the usage_windows table) -- not the limits config, which goes IN via + * usage_window_limits. Null when no usage windows were enforced. */ + usage_windows_by_feature_id?: Record | null; + /** Per-window deltas applied by this deduction (sibling stream of + * mutation_logs). */ + usage_window_mutations?: UsageWindowMutation[]; remaining: number; error?: string; feature_id?: string; diff --git a/server/src/internal/balances/utils/types/usageWindowMutation.ts b/server/src/internal/balances/utils/types/usageWindowMutation.ts new file mode 100644 index 000000000..96c1ba40c --- /dev/null +++ b/server/src/internal/balances/utils/types/usageWindowMutation.ts @@ -0,0 +1,14 @@ +/** + * One usage-window counter mutation from a deduction (sibling of + * MutationLogItem, kept as its own stream): which window row moved and by how + * much. The row is identified by its stored id plus the logical key + * (feature + window + entity scope); `usage_delta` is in the limit's native + * unit (tracked units for metered dims, credits for balance dims). + */ +export interface UsageWindowMutation { + usage_window_id: string | null; + feature_id: string; + internal_entity_id: string | null; + window_start_at: number; + usage_delta: number; +} diff --git a/server/src/internal/balances/utils/types/usageWindowUpdate.ts b/server/src/internal/balances/utils/types/usageWindowUpdate.ts new file mode 100644 index 000000000..bc1934fc3 --- /dev/null +++ b/server/src/internal/balances/utils/types/usageWindowUpdate.ts @@ -0,0 +1,18 @@ +import type { UsageWindow } from "@autumn/shared"; + +/** + * Post-deduction usage-window counter state for one capped feature, handed + * down from the Lua result through the deduction flow to syncItemV4 (sibling + * of DeductionUpdate / RolloverUpdate). + * + * Deliberately a SNAPSHOT, not a MutationLog-style delta: the deduction + * script is atomic and the Postgres sync full-replaces rows per (customer, + * feature), so the complete `usage_windows` array IS the update. An empty + * array is meaningful (all windows pruned/closed) and still full-replaces. + * Matches the `usage_window_updates` jsonb param of sync_balances_v2 1:1. + */ +export interface UsageWindowUpdate { + internal_customer_id: string; + feature_id: string; + usage_windows: UsageWindow[]; +} diff --git a/server/src/internal/billing/v2/setup/setupBillingCycleAnchor.ts b/server/src/internal/billing/v2/setup/setupBillingCycleAnchor.ts index d8aaed637..50593020c 100644 --- a/server/src/internal/billing/v2/setup/setupBillingCycleAnchor.ts +++ b/server/src/internal/billing/v2/setup/setupBillingCycleAnchor.ts @@ -78,5 +78,21 @@ export const setupBillingCycleAnchor = ({ // Billing cycle anchor = trial ends at if exists if (newIsTrialing) return trialContext?.trialEndsAt ?? "now"; - return secondsToMs(stripeSubscription?.billing_cycle_anchor) ?? "now"; + const stripeAnchorMs = secondsToMs(stripeSubscription?.billing_cycle_anchor); + + // Stripe stores the anchor in SECONDS (rounded either way from the ms + // instant it was created). When it's the same instant the current product + // started, prefer the ms-precision starts_at so cycles recomputed across + // updates/upgrades don't drift sub-second (which would churn + // next_reset_at and spuriously move cycle-keyed state like usage windows). + const startsAtMs = customerProduct?.starts_at; + if ( + stripeAnchorMs != null && + startsAtMs != null && + Math.abs(startsAtMs - stripeAnchorMs) < 1000 + ) { + return startsAtMs; + } + + return stripeAnchorMs ?? "now"; }; diff --git a/server/src/internal/billing/v2/utils/lineItems/chargeRowToRefundLineItem.ts b/server/src/internal/billing/v2/utils/lineItems/chargeRowToRefundLineItem.ts index bfcbcd21d..f2a5f245f 100644 --- a/server/src/internal/billing/v2/utils/lineItems/chargeRowToRefundLineItem.ts +++ b/server/src/internal/billing/v2/utils/lineItems/chargeRowToRefundLineItem.ts @@ -85,7 +85,7 @@ export const chargeRowToRefundLineItem = ({ context, stripePriceId: chargeRow.stripe_price_id ?? undefined, stripeProductId: chargeRow.stripe_product_id ?? undefined, - chargeImmediately: true, + chargeImmediately: chargeRow.invoice_id === null ? false : true, prorated: true, discounts: (chargeRow.discounts as InvoiceLineItemDiscount[] | null)?.map((d) => ({ diff --git a/server/src/internal/billing/v2/utils/lineItems/getRefundLineItems.ts b/server/src/internal/billing/v2/utils/lineItems/getRefundLineItems.ts index b5941b2b6..a89bb0847 100644 --- a/server/src/internal/billing/v2/utils/lineItems/getRefundLineItems.ts +++ b/server/src/internal/billing/v2/utils/lineItems/getRefundLineItems.ts @@ -9,12 +9,14 @@ export const getRefundLineItems = ({ billingContext, priceFilters, billingCycleAnchorMsOverride, + includeCatalogFallback = true, }: { ctx: AutumnContext; customerProduct: FullCusProduct; billingContext: BillingContext; priceFilters?: { excludeOneOffPrices?: boolean }; billingCycleAnchorMsOverride?: BillingContext["billingCycleAnchorMs"]; + includeCatalogFallback?: boolean; }): LineItem[] => { const { lineItems: matchedCredits, @@ -27,6 +29,7 @@ export const getRefundLineItems = ({ }); if (allPricesResolved) return matchedCredits; + if (!includeCatalogFallback) return matchedCredits; const catalogCredits = customerProductToLineItems({ ctx, diff --git a/server/src/internal/billing/v2/utils/lineItems/getRefundLineItemsForPrice.ts b/server/src/internal/billing/v2/utils/lineItems/getRefundLineItemsForPrice.ts index 2afa57bf8..25281349b 100644 --- a/server/src/internal/billing/v2/utils/lineItems/getRefundLineItemsForPrice.ts +++ b/server/src/internal/billing/v2/utils/lineItems/getRefundLineItemsForPrice.ts @@ -19,6 +19,7 @@ export const getRefundLineItemsForPrice = ({ ctx, customerProduct, billingContext, + includeCatalogFallback: false, }); const matchedRefundsForPrice = matchedRefundLineItems.filter( diff --git a/server/src/internal/billing/v2/utils/lineItems/invoiceCreditFromStoredLineItems.ts b/server/src/internal/billing/v2/utils/lineItems/invoiceCreditFromStoredLineItems.ts index 423284811..11562139e 100644 --- a/server/src/internal/billing/v2/utils/lineItems/invoiceCreditFromStoredLineItems.ts +++ b/server/src/internal/billing/v2/utils/lineItems/invoiceCreditFromStoredLineItems.ts @@ -57,7 +57,7 @@ export const invoiceCreditFromStoredLineItems = ({ row.customer_product_ids.length > 0 && row.effective_period_start != null && row.effective_period_end != null && - row.effective_period_start < now && + row.effective_period_start <= now && row.effective_period_end > now, ); @@ -76,7 +76,7 @@ export const invoiceCreditFromStoredLineItems = ({ r.customer_product_ids.includes(customerProduct.id) && r.effective_period_end != null && r.effective_period_start != null && - r.effective_period_start < now && + r.effective_period_start <= now && r.effective_period_end > now, ); diff --git a/server/src/internal/customers/actions/resetUsageWindows/applyUsageWindowRollsToSubject.ts b/server/src/internal/customers/actions/resetUsageWindows/applyUsageWindowRollsToSubject.ts new file mode 100644 index 000000000..f7a95e887 --- /dev/null +++ b/server/src/internal/customers/actions/resetUsageWindows/applyUsageWindowRollsToSubject.ts @@ -0,0 +1,34 @@ +import type { FullSubject, NormalizedFullSubject } from "@autumn/shared"; +import type { UsageWindowRoll } from "./computeUsageWindowRolls.js"; + +/** Mirrors persisted rolls onto the in-flight subject (and its normalized + * twin), so this request's response already shows the rolled state. */ +export const applyUsageWindowRollsToSubject = ({ + fullSubject, + normalized, + rolls, + now, +}: { + fullSubject: FullSubject; + normalized?: NormalizedFullSubject; + rolls: UsageWindowRoll[]; + now: number; +}): void => { + const rollsById = new Map(rolls.map((roll) => [roll.id, roll])); + + const apply = (windows: FullSubject["usage_windows"]) => { + for (const usageWindow of windows ?? []) { + const roll = rollsById.get(usageWindow.id); + if (!roll) continue; + if (roll.zero_usage) usageWindow.usage = 0; + usageWindow.window_start_at = roll.window_start_at; + usageWindow.window_end_at = roll.window_end_at; + usageWindow.anchor_customer_entitlement_id = + roll.anchor_customer_entitlement_id; + usageWindow.updated_at = now; + } + }; + + apply(fullSubject.usage_windows); + if (normalized) apply(normalized.usage_windows); +}; diff --git a/server/src/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.ts b/server/src/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.ts new file mode 100644 index 000000000..c4ce49c3b --- /dev/null +++ b/server/src/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.ts @@ -0,0 +1,87 @@ +import { + findUsageWindowLimitByWindow, + type UsageWindow, + type UsageWindowLimit, +} from "@autumn/shared"; + +export type UsageWindowRoll = { + id: string; + feature_id: string; + internal_entity_id: string | null; + /** True when the stored window closed: the count must zero. A roll never + * writes a count otherwise, so it can't clobber a concurrent deduction. */ + zero_usage: boolean; + window_start_at: number; + window_end_at: number; + anchor_customer_entitlement_id: string | null; +}; + +/** + * Decides, per counter row, whether it needs rolling. A count is only valid + * within the exact window stamped on it; the anchor is provenance, so an + * anchor-only re-point keeps the count: + * + * expired | window moved | anchor moved | result + * --------+--------------+--------------+--------------------------------- + * no | no | no | no roll (the common case) + * no | no | yes | re-point anchor, count kept + * no | yes | any | re-bound, count zeroed (plan change) + * yes | any | any | re-bound, count zeroed (period over) + * yes | (no limit) | -- | bounds kept, count zeroed (entity rows, v1) + * + * "Window moved" compares the row's bounds against its limit's CURRENT + * derivation (anchor ent's cycle). Entity-scoped rows have no resolvable + * limit in v1, so their bounds can't re-derive -- but an expired count must + * still zero. + */ +export const computeUsageWindowRolls = ({ + usageWindows, + limits, + now, +}: { + usageWindows: UsageWindow[]; + limits: UsageWindowLimit[]; + now: number; +}): UsageWindowRoll[] => { + const rolls: UsageWindowRoll[] = []; + + for (const usageWindow of usageWindows) { + const expired = Number(usageWindow.window_end_at) <= now; + + const limit = findUsageWindowLimitByWindow({ limits, usageWindow }); + + const target = limit + ? { + window_start_at: limit.window_start_at, + window_end_at: limit.window_end_at, + anchor_customer_entitlement_id: limit.anchor_customer_entitlement_id, + } + : { + window_start_at: Number(usageWindow.window_start_at), + window_end_at: Number(usageWindow.window_end_at), + anchor_customer_entitlement_id: + usageWindow.anchor_customer_entitlement_id ?? null, + }; + + const windowMoved = + Number(usageWindow.window_start_at) !== target.window_start_at || + Number(usageWindow.window_end_at) !== target.window_end_at; + const anchorMoved = + (usageWindow.anchor_customer_entitlement_id ?? null) !== + target.anchor_customer_entitlement_id; + + if (!expired && !windowMoved && !anchorMoved) continue; + + rolls.push({ + id: usageWindow.id, + feature_id: usageWindow.feature_id, + internal_entity_id: usageWindow.internal_entity_id ?? null, + // A count never survives its stamped window; an anchor-only + // re-point (e.g. an ent recreated with the same cycle) keeps it. + zero_usage: expired || windowMoved, + ...target, + }); + } + + return rolls; +}; diff --git a/server/src/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.ts b/server/src/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.ts new file mode 100644 index 000000000..7c2e51005 --- /dev/null +++ b/server/src/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.ts @@ -0,0 +1,76 @@ +import { + type FullSubject, + fullSubjectToUsageWindowLimits, + type NormalizedFullSubject, + orgToInStatuses, +} from "@autumn/shared"; +import * as Sentry from "@sentry/bun"; +import { getDbHealth, PgHealth } from "@/db/pgHealthMonitor.js"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { usageWindowRepo } from "@/internal/customers/usageWindows/repos/index.js"; +import { applyUsageWindowRollsToSubject } from "./applyUsageWindowRollsToSubject.js"; +import { computeUsageWindowRolls } from "./computeUsageWindowRolls.js"; +import { rollUsageWindowsCache } from "./rollUsageWindowsCache.js"; + +/** + * Lazily ROLLS the subject's usage-window counters on every subject read: + * zero counts whose stored window closed, and re-align bounds/anchor to the + * current derivation (this is where a plan change lands in the DB). The + * decision table lives in computeUsageWindowRolls. + * + * Best-effort, like lazyResetSubjectEntitlements: reads and the deduction + * script both derive a closed count as 0 and stamp fresh bounds on write, so + * a failed roll only delays persistence. Rolls are idempotent (same target + * state), so concurrent reads converge. Returns true if any rows rolled. + */ +export const lazyResetSubjectUsageWindows = async ({ + ctx, + fullSubject, + normalized, +}: { + ctx: AutumnContext; + fullSubject: FullSubject; + normalized?: NormalizedFullSubject; +}): Promise => { + if (getDbHealth() === PgHealth.Degraded) return false; + + const now = Date.now(); + const usageWindows = fullSubject.usage_windows ?? []; + if (usageWindows.length === 0) return false; + + try { + const limits = fullSubjectToUsageWindowLimits({ + fullSubject, + featureIds: [ + ...new Set(usageWindows.map((usageWindow) => usageWindow.feature_id)), + ], + features: ctx.features, + now, + inStatuses: orgToInStatuses({ org: ctx.org }), + }); + + const rolls = computeUsageWindowRolls({ usageWindows, limits, now }); + if (rolls.length === 0) return false; + + ctx.logger.info( + `[lazyResetSubjectUsageWindows] customer: ${fullSubject.customerId}, rolling: ${rolls.length}`, + ); + + await usageWindowRepo.rollWindows({ db: ctx.db, rolls, now }); + await rollUsageWindowsCache({ + ctx, + customerId: fullSubject.customerId, + rolls, + now, + }); + applyUsageWindowRollsToSubject({ fullSubject, normalized, rolls, now }); + + return true; + } catch (error) { + ctx.logger.error( + `[lazyResetSubjectUsageWindows] customer: ${fullSubject.customerId}, failed: ${error}`, + ); + Sentry.captureException(error); + return false; + } +}; diff --git a/server/src/internal/customers/actions/resetUsageWindows/rollUsageWindowsCache.ts b/server/src/internal/customers/actions/resetUsageWindows/rollUsageWindowsCache.ts new file mode 100644 index 000000000..cdc2922c8 --- /dev/null +++ b/server/src/internal/customers/actions/resetUsageWindows/rollUsageWindowsCache.ts @@ -0,0 +1,60 @@ +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; +import { FULL_SUBJECT_CACHE_TTL_SECONDS } from "@/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.js"; +import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; +import type { UsageWindowRoll } from "./computeUsageWindowRolls.js"; + +/** + * Atomically patches rolled counters into each affected feature's + * '_usage_windows' field (one rollUsageWindows Lua call per feature, + * pipelined). Fire-and-forget -- reads and the deduction script both derive + * a closed window as 0, so a missed patch only delays the persisted roll. + */ +export const rollUsageWindowsCache = async ({ + ctx, + customerId, + rolls, + now, +}: { + ctx: AutumnContext; + customerId: string; + rolls: UsageWindowRoll[]; + now: number; +}): Promise => { + if (rolls.length === 0) return; + + try { + const { org, env, redisV2 } = ctx; + + const rollsByFeatureId: Record = {}; + for (const roll of rolls) { + const featureRolls = rollsByFeatureId[roll.feature_id] ?? []; + featureRolls.push(roll); + rollsByFeatureId[roll.feature_id] = featureRolls; + } + + const pipeline = redisV2.pipeline(); + for (const [featureId, featureRolls] of Object.entries(rollsByFeatureId)) { + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: org.id, + env, + customerId, + featureId, + }); + pipeline.rollUsageWindows( + balanceKey, + JSON.stringify({ + now, + ttl_seconds: FULL_SUBJECT_CACHE_TTL_SECONDS, + rolls: featureRolls, + }), + ); + } + + await tryRedisWrite(() => pipeline.exec(), redisV2); + } catch (error) { + ctx.logger.error( + `[rollUsageWindowsCache] customer=${customerId}, failed: ${error}`, + ); + } +}; diff --git a/server/src/internal/customers/actions/update/updateCustomer.ts b/server/src/internal/customers/actions/update/updateCustomer.ts index 89c2f304e..9ddc47034 100644 --- a/server/src/internal/customers/actions/update/updateCustomer.ts +++ b/server/src/internal/customers/actions/update/updateCustomer.ts @@ -139,6 +139,8 @@ export const updateCustomer = async ({ billingControlUpdates.auto_topups = billing_controls.auto_topups; if (billing_controls.spend_limits !== undefined) billingControlUpdates.spend_limits = billing_controls.spend_limits; + if (billing_controls.usage_limits !== undefined) + billingControlUpdates.usage_limits = billing_controls.usage_limits; if (billing_controls.usage_alerts !== undefined) billingControlUpdates.usage_alerts = billing_controls.usage_alerts; if (billing_controls.overage_allowed !== undefined) diff --git a/server/src/internal/customers/cache/fullSubject/actions/getCachedFullSubject.ts b/server/src/internal/customers/cache/fullSubject/actions/getCachedFullSubject.ts index b9c2740b1..6bace36b0 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/getCachedFullSubject.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/getCachedFullSubject.ts @@ -7,10 +7,12 @@ import { isRedisMigrationCacheStale } from "@/external/redis/customerRedisRoutin import { runRedisOp } from "@/external/redis/utils/runRedisOp.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { lazyResetSubjectEntitlements } from "@/internal/customers/actions/resetCustomerEntitlementsV2/lazyResetSubjectEntitlements.js"; +import { lazyResetSubjectUsageWindows } from "@/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.js"; import { checkPendingMigrationsForCustomer } from "@/internal/migrations/v2/lazy/checkPendingMigrationsForCustomer.js"; import { getFullSubjectRolloutSnapshot } from "@/internal/misc/rollouts/fullSubjectRolloutUtils.js"; import { isSnapshotCacheStale } from "@/internal/misc/rollouts/rolloutUtils.js"; import { applyLiveAggregatedBalances } from "../balances/applyLiveAggregatedBalances.js"; +import { applyLiveUsageWindows } from "../balances/applyLiveUsageWindows.js"; import { getCachedFeatureBalancesBatch } from "../balances/getCachedFeatureBalances.js"; import { buildFullSubjectKey } from "../builders/buildFullSubjectKey.js"; import { buildFullSubjectViewEpochKey } from "../builders/buildFullSubjectViewEpochKey.js"; @@ -195,12 +197,19 @@ export const getCachedFullSubject = async ({ } const isCustomerSubject = !entityId; + // Capped features may have no entitlements, so they aren't guaranteed to be + // in meteredFeatures; union them in so their `_usage_windows` field is read. + const usageWindowFeatureIds = new Set(cached.usageWindowFeatureIds ?? []); + const batchFeatureIds = [ + ...new Set([...cached.meteredFeatures, ...usageWindowFeatureIds]), + ]; const balancesOutcome = await getCachedFeatureBalancesBatch({ ctx, customerId, - featureIds: cached.meteredFeatures, + featureIds: batchFeatureIds, customerEntitlementIdsByFeatureId: cached.customerEntitlementIdsByFeatureId, includeAggregated: isCustomerSubject, + usageWindowFeatureIds, }); if (balancesOutcome.kind === "missing") { @@ -220,9 +229,9 @@ export const getCachedFullSubject = async ({ } const balances = balancesOutcome.value; - if (balances.length !== cached.meteredFeatures.length) { + if (balances.length !== batchFeatureIds.length) { logger.warn( - `[getCachedFullSubject] Incomplete cache for ${customerId}${entityId ? `:${entityId}` : ""}: expected ${cached.meteredFeatures.length} balance keys, got ${balances.length}. Rebuilding from DB, source: ${source}`, + `[getCachedFullSubject] Incomplete cache for ${customerId}${entityId ? `:${entityId}` : ""}: expected ${batchFeatureIds.length} balance keys, got ${balances.length}. Rebuilding from DB, source: ${source}`, ); await invalidateCachedFullSubjectExact({ ctx, @@ -249,8 +258,14 @@ export const getCachedFullSubject = async ({ }); } + applyLiveUsageWindows({ + normalized, + featureBalances: balances, + }); + const fullSubject = normalizedToFullSubject({ normalized }); await lazyResetSubjectEntitlements({ ctx, fullSubject }); + await lazyResetSubjectUsageWindows({ ctx, fullSubject, normalized }); await checkPendingMigrationsForCustomer({ ctx, fullCustomer: fullSubjectToFullCustomer({ fullSubject }), diff --git a/server/src/internal/customers/cache/fullSubject/actions/getOrSetCachedFullSubject.ts b/server/src/internal/customers/cache/fullSubject/actions/getOrSetCachedFullSubject.ts index aee65fc5e..8e5547147 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/getOrSetCachedFullSubject.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/getOrSetCachedFullSubject.ts @@ -1,7 +1,7 @@ import { - CustomerNotFoundError, - EntityNotFoundError, - type FullSubject, + CustomerNotFoundError, + EntityNotFoundError, + type FullSubject, } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { getFullSubjectNormalized } from "@/internal/customers/repos/getFullSubject/index.js"; @@ -27,7 +27,6 @@ export const getOrSetCachedFullSubject = async ({ let fetchedSubjectViewEpoch = 0; - if (useRedis) { // The pipeline inside getCachedFullSubject already fetches + refreshes // the epoch, so we reuse it on miss instead of a second round trip. diff --git a/server/src/internal/customers/cache/fullSubject/actions/invalidate/invalidateSharedBalanceFields.ts b/server/src/internal/customers/cache/fullSubject/actions/invalidate/invalidateSharedBalanceFields.ts index e2e9a8934..aa69c7f31 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/invalidate/invalidateSharedBalanceFields.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/invalidate/invalidateSharedBalanceFields.ts @@ -3,7 +3,10 @@ import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { tryRedisRead, tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; import { buildFullSubjectKey } from "../../builders/buildFullSubjectKey.js"; import { buildSharedFullSubjectBalanceKey } from "../../builders/buildSharedFullSubjectBalanceKey.js"; -import { AGGREGATED_BALANCE_FIELD } from "../../config/fullSubjectCacheConfig.js"; +import { + AGGREGATED_BALANCE_FIELD, + USAGE_WINDOWS_FIELD, +} from "../../config/fullSubjectCacheConfig.js"; import type { CachedFullSubject } from "../../fullSubjectCacheModel.js"; /** @@ -61,19 +64,37 @@ async function deleteFieldsFromManifest({ const { customerEntitlementIdsByFeatureId } = manifest; if (!customerEntitlementIdsByFeatureId) return; + // Capped features may have no entitlements, so their hashes only appear in + // usageWindowFeatureIds; union both so `_usage_windows` is cleared too. + // Safe to delete counters: capped tracks write through to PG synchronously, + // and the rebuild re-seeds the field from PG. + // Raw blob, no sanitize walker: cjson re-encodes empty arrays as {}, so + // array fields must be Array.isArray-guarded before spreading. + const usageWindowFeatureIds = Array.isArray(manifest.usageWindowFeatureIds) + ? manifest.usageWindowFeatureIds + : []; + const featureIds = new Set([ + ...Object.keys(customerEntitlementIdsByFeatureId), + ...usageWindowFeatureIds, + ]); + const pipeline = redisV2.pipeline(); let fieldCount = 0; - for (const [featureId, cusEntIds] of Object.entries( - customerEntitlementIdsByFeatureId, - )) { + for (const featureId of featureIds) { + const rawCusEntIds = customerEntitlementIdsByFeatureId[featureId]; + const cusEntIds = Array.isArray(rawCusEntIds) ? rawCusEntIds : []; const balanceKey = buildSharedFullSubjectBalanceKey({ orgId: org.id, env, customerId, featureId, }); - const fieldsToDelete = [...cusEntIds, AGGREGATED_BALANCE_FIELD]; + const fieldsToDelete = [ + ...cusEntIds, + AGGREGATED_BALANCE_FIELD, + USAGE_WINDOWS_FIELD, + ]; pipeline.hdel(balanceKey, ...fieldsToDelete); fieldCount += fieldsToDelete.length; } diff --git a/server/src/internal/customers/cache/fullSubject/actions/partial/getCachedPartialFullSubject.ts b/server/src/internal/customers/cache/fullSubject/actions/partial/getCachedPartialFullSubject.ts index 1d9446e29..eb399c58a 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/partial/getCachedPartialFullSubject.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/partial/getCachedPartialFullSubject.ts @@ -4,9 +4,11 @@ import { isRedisMigrationCacheStale } from "@/external/redis/customerRedisRoutin import { runRedisOp } from "@/external/redis/utils/runRedisOp.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { lazyResetSubjectEntitlements } from "@/internal/customers/actions/resetCustomerEntitlementsV2/lazyResetSubjectEntitlements.js"; +import { lazyResetSubjectUsageWindows } from "@/internal/customers/actions/resetUsageWindows/lazyResetSubjectUsageWindows.js"; import { getFullSubjectRolloutSnapshot } from "@/internal/misc/rollouts/fullSubjectRolloutUtils.js"; import { isSnapshotCacheStale } from "@/internal/misc/rollouts/rolloutUtils.js"; import { applyLiveAggregatedBalances } from "../../balances/applyLiveAggregatedBalances.js"; +import { applyLiveUsageWindows } from "../../balances/applyLiveUsageWindows.js"; import { getCachedFeatureBalancesBatch } from "../../balances/getCachedFeatureBalances.js"; import { buildFullSubjectKey } from "../../builders/buildFullSubjectKey.js"; import { buildFullSubjectViewEpochKey } from "../../builders/buildFullSubjectViewEpochKey.js"; @@ -212,9 +214,22 @@ export const getCachedPartialFullSubject = async ({ }; } - const meteredFeatureIdsToFetch = featureIds.filter((featureId) => - cached.meteredFeatures.includes(featureId), + // Capped features carry the '_usage_windows' counter field and may have no + // entitlements at all, so they must be part of the batch even when absent + // from meteredFeatures. + const usageWindowFeatureIds = new Set( + (cached.usageWindowFeatureIds ?? []).filter((featureId) => + featureIds.includes(featureId), + ), ); + const meteredFeatureIdsToFetch = [ + ...new Set([ + ...featureIds.filter((featureId) => + cached.meteredFeatures.includes(featureId), + ), + ...usageWindowFeatureIds, + ]), + ]; const isCustomerSubject = !entityId; const featureBalancesOutcome = await getCachedFeatureBalancesBatch({ @@ -223,6 +238,7 @@ export const getCachedPartialFullSubject = async ({ featureIds: meteredFeatureIdsToFetch, customerEntitlementIdsByFeatureId: cached.customerEntitlementIdsByFeatureId, includeAggregated: isCustomerSubject, + usageWindowFeatureIds, }); const invalidateIncomplete = () => @@ -287,8 +303,14 @@ export const getCachedPartialFullSubject = async ({ }); } + applyLiveUsageWindows({ + normalized, + featureBalances, + }); + const fullSubject = normalizedToFullSubject({ normalized }); await lazyResetSubjectEntitlements({ ctx, fullSubject, normalized }); + await lazyResetSubjectUsageWindows({ ctx, fullSubject, normalized }); return fullSubject; }, invalidate: () => diff --git a/server/src/internal/customers/cache/fullSubject/actions/rehydrateWithLiveBalances.ts b/server/src/internal/customers/cache/fullSubject/actions/rehydrateWithLiveBalances.ts index 3ed08c1d7..44d0141e7 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/rehydrateWithLiveBalances.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/rehydrateWithLiveBalances.ts @@ -2,6 +2,7 @@ import type { NormalizedFullSubject } from "@autumn/shared"; import { type FullSubject, normalizedToFullSubject } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { applyLiveAggregatedBalances } from "../balances/applyLiveAggregatedBalances.js"; +import { applyLiveUsageWindows } from "../balances/applyLiveUsageWindows.js"; import { getCachedFeatureBalancesBatch } from "../balances/getCachedFeatureBalances.js"; /** @@ -31,7 +32,18 @@ export const rehydrateWithLiveBalances = async ({ list.push(ce.id); customerEntitlementIdsByFeatureId[ce.feature_id] = list; } - const featureIds = Object.keys(customerEntitlementIdsByFeatureId); + const usageWindowFeatureIds = new Set( + [ + ...(normalized.customer.usage_limits ?? []), + ...(normalized.entity?.usage_limits ?? []), + ].map((usageLimit) => usageLimit.feature_id), + ); + const featureIds = [ + ...new Set([ + ...Object.keys(customerEntitlementIdsByFeatureId), + ...usageWindowFeatureIds, + ]), + ]; const isCustomerSubject = !entityId; const outcome = await getCachedFeatureBalancesBatch({ @@ -40,6 +52,7 @@ export const rehydrateWithLiveBalances = async ({ featureIds, customerEntitlementIdsByFeatureId, includeAggregated: isCustomerSubject, + usageWindowFeatureIds, }); if (outcome.kind !== "ok") return undefined; @@ -53,5 +66,10 @@ export const rehydrateWithLiveBalances = async ({ }); } + applyLiveUsageWindows({ + normalized, + featureBalances: outcome.value, + }); + return normalizedToFullSubject({ normalized }); }; diff --git a/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setCachedFullSubject.ts b/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setCachedFullSubject.ts index 530ffcc95..1fc9bcca6 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setCachedFullSubject.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setCachedFullSubject.ts @@ -49,6 +49,8 @@ export const setCachedFullSubject = async ({ customerEntitlements: normalized.customer_entitlements, aggregatedCustomerEntitlements: normalized.entity_aggregations?.aggregated_customer_entitlements ?? [], + usageWindows: normalized.usage_windows ?? [], + usageWindowFeatureIds: cached.usageWindowFeatureIds, }); const keys: string[] = [subjectKey, epochKey]; diff --git a/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setSharedFullSubjectBalances.ts b/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setSharedFullSubjectBalances.ts index c1774ce41..11d0571de 100644 --- a/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setSharedFullSubjectBalances.ts +++ b/server/src/internal/customers/cache/fullSubject/actions/setCachedFullSubject/setSharedFullSubjectBalances.ts @@ -1,10 +1,14 @@ import type { AggregatedFeatureBalance, NormalizedFullSubject, + UsageWindow, } from "@autumn/shared"; import { featureBalancesToHashFields } from "../../balances/featureBalancesToHashFields.js"; import { buildSharedFullSubjectBalanceKey } from "../../builders/buildSharedFullSubjectBalanceKey.js"; -import { AGGREGATED_BALANCE_FIELD } from "../../config/fullSubjectCacheConfig.js"; +import { + AGGREGATED_BALANCE_FIELD, + USAGE_WINDOWS_FIELD, +} from "../../config/fullSubjectCacheConfig.js"; export type SharedBalanceWrite = { balanceKey: string; @@ -17,12 +21,16 @@ export const buildSharedBalanceWrites = ({ customerId, customerEntitlements, aggregatedCustomerEntitlements, + usageWindows = [], + usageWindowFeatureIds = [], }: { orgId: string; env: string; customerId: string; customerEntitlements: NormalizedFullSubject["customer_entitlements"]; aggregatedCustomerEntitlements: AggregatedFeatureBalance[]; + usageWindows?: UsageWindow[]; + usageWindowFeatureIds?: string[]; }): SharedBalanceWrite[] => { const balancesByFeatureId = new Map(); @@ -38,9 +46,24 @@ export const buildSharedBalanceWrites = ({ aggregatedByFeatureId.set(aggregated.feature_id, aggregated); } + // Capped features get a `_usage_windows` field even with no rows and no + // entitlements: a present-but-empty field means "fresh counter", a missing + // field means "stale cache" and the deduction script fails closed on it. + const usageWindowsByFeatureId = new Map(); + for (const featureId of usageWindowFeatureIds) { + usageWindowsByFeatureId.set(featureId, []); + } + for (const usageWindow of usageWindows) { + const existingWindows = usageWindowsByFeatureId.get(usageWindow.feature_id); + // Rows for features whose cap is no longer armed are not re-cached. + if (!existingWindows) continue; + existingWindows.push(usageWindow); + } + const allFeatureIds = new Set([ ...balancesByFeatureId.keys(), ...aggregatedByFeatureId.keys(), + ...usageWindowsByFeatureId.keys(), ]); return Array.from(allFeatureIds).map((featureId) => { @@ -52,6 +75,11 @@ export const buildSharedBalanceWrites = ({ fields[AGGREGATED_BALANCE_FIELD] = JSON.stringify(aggregated); } + const featureUsageWindows = usageWindowsByFeatureId.get(featureId); + if (featureUsageWindows) { + fields[USAGE_WINDOWS_FIELD] = JSON.stringify(featureUsageWindows); + } + return { balanceKey: buildSharedFullSubjectBalanceKey({ orgId, diff --git a/server/src/internal/customers/cache/fullSubject/balances/applyLiveUsageWindows.ts b/server/src/internal/customers/cache/fullSubject/balances/applyLiveUsageWindows.ts new file mode 100644 index 000000000..03cf75971 --- /dev/null +++ b/server/src/internal/customers/cache/fullSubject/balances/applyLiveUsageWindows.ts @@ -0,0 +1,20 @@ +import type { NormalizedFullSubject } from "@autumn/shared"; +import type { FeatureBalanceResult } from "./getCachedFeatureBalances.js"; + +/** + * Fill `normalized.usage_windows` from the live `_usage_windows` hash fields + * returned by the batch balance read. The cached subject view never carries + * counter rows (they'd be instantly stale), so this is the only hydration + * source on the cache-hit path. + */ +export const applyLiveUsageWindows = ({ + normalized, + featureBalances, +}: { + normalized: NormalizedFullSubject; + featureBalances: FeatureBalanceResult[]; +}): void => { + normalized.usage_windows = featureBalances.flatMap( + (featureBalance) => featureBalance.usageWindows ?? [], + ); +}; diff --git a/server/src/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.ts b/server/src/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.ts index 19b92fff3..a75b27736 100644 --- a/server/src/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.ts +++ b/server/src/internal/customers/cache/fullSubject/balances/getCachedFeatureBalances.ts @@ -1,8 +1,15 @@ -import type { AggregatedFeatureBalance, SubjectBalance } from "@autumn/shared"; +import type { + AggregatedFeatureBalance, + SubjectBalance, + UsageWindow, +} from "@autumn/shared"; import { runRedisOp } from "@/external/redis/utils/runRedisOp.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { buildSharedFullSubjectBalanceKey } from "../builders/buildSharedFullSubjectBalanceKey.js"; -import { AGGREGATED_BALANCE_FIELD } from "../config/fullSubjectCacheConfig.js"; +import { + AGGREGATED_BALANCE_FIELD, + USAGE_WINDOWS_FIELD, +} from "../config/fullSubjectCacheConfig.js"; import { roundSubjectBalance } from "../roundCacheBalance.js"; import { sanitizeCachedAggregatedFeatureBalance, @@ -13,6 +20,24 @@ export type FeatureBalanceResult = { featureId: string; balances: SubjectBalance[]; aggregated?: AggregatedFeatureBalance; + /** Customer-scoped windowed-cap counters for this feature; only present for + * features in the requested usageWindowFeatureIds set. */ + usageWindows?: UsageWindow[]; +}; + +// Fail open: a missing/unparseable `_usage_windows` field reads as an empty +// counter set (the window restarts). cjson also encodes an empty Lua table as +// `{}`, so a non-array blob is an empty set, not corruption. +const parseUsageWindowsField = ( + usageWindowsJson: string | null, +): UsageWindow[] => { + if (!usageWindowsJson) return []; + try { + const parsed = JSON.parse(usageWindowsJson); + return Array.isArray(parsed) ? (parsed as UsageWindow[]) : []; + } catch { + return []; + } }; export type FeatureBalanceOutcome = @@ -118,12 +143,16 @@ export const getCachedFeatureBalancesBatch = async ({ featureIds, customerEntitlementIdsByFeatureId, includeAggregated = false, + usageWindowFeatureIds, }: { ctx: AutumnContext; customerId: string; featureIds: string[]; customerEntitlementIdsByFeatureId: Record; includeAggregated?: boolean; + /** Features with an armed windowed cap: their `_usage_windows` field is + * read too. A missing field fails open (reads as an empty counter set). */ + usageWindowFeatureIds?: Set; }): Promise => { if (featureIds.length === 0) return { kind: "ok", value: [] }; @@ -132,9 +161,11 @@ export const getCachedFeatureBalancesBatch = async ({ for (const featureId of featureIds) { const customerEntitlementIds = customerEntitlementIdsByFeatureId[featureId] ?? []; - const fields = includeAggregated - ? [...customerEntitlementIds, AGGREGATED_BALANCE_FIELD] - : customerEntitlementIds; + const fields = [...customerEntitlementIds]; + if (includeAggregated) fields.push(AGGREGATED_BALANCE_FIELD); + if (usageWindowFeatureIds?.has(featureId)) { + fields.push(USAGE_WINDOWS_FIELD); + } pipeline.hmget( buildSharedFullSubjectBalanceKey({ orgId: org.id, @@ -167,7 +198,12 @@ export const getCachedFeatureBalancesBatch = async ({ }; let aggregated: AggregatedFeatureBalance | undefined; - let ceValues: (string | null)[]; + let usageWindows: UsageWindow[] | undefined; + + // Pop reserved fields in reverse push order: [_aggregated?, _usage_windows?]. + if (usageWindowFeatureIds?.has(featureIds[i])) { + usageWindows = parseUsageWindowsField(allValues.pop() ?? null); + } if (includeAggregated) { const aggregatedJson = allValues.pop() ?? null; @@ -181,11 +217,10 @@ export const getCachedFeatureBalancesBatch = async ({ // Malformed _aggregated is non-fatal; fall back to subject string value } } - ceValues = allValues; - } else { - ceValues = allValues; } + const ceValues = allValues; + if (ceValues.length !== customerEntitlementIds.length) return { kind: "missing", @@ -221,6 +256,7 @@ export const getCachedFeatureBalancesBatch = async ({ featureId: featureIds[i], balances, aggregated, + usageWindows, }); } diff --git a/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts b/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts index b08776582..9fd90e07e 100644 --- a/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts +++ b/server/src/internal/customers/cache/fullSubject/builders/buildDeductFromSubjectBalancesKeys.ts @@ -21,7 +21,7 @@ export const buildDeductFromSubjectBalancesKeys = ({ idempotencyKey, customerEntitlementDeductions, fallbackFeatureId, - anchorFeatureIds = [], + usageWindowFeatureIds = [], }: { orgId: string; env: AppEnv; @@ -31,10 +31,10 @@ export const buildDeductFromSubjectBalancesKeys = ({ idempotencyKey?: string | null; customerEntitlementDeductions: { feature_id?: string }[]; fallbackFeatureId: string; - // Features owning a usage-window anchor counter. Their balance hash keys must - // be declared in KEYS[] so Lua can load the anchor even when it is not in the - // deduction set. - anchorFeatureIds?: string[]; + // Capped features: their balance hashes carry the `_usage_windows` counter + // field, and a capped feature may have no entitlements (so no deduction + // entry references its hash). Declare those keys in KEYS[] too. + usageWindowFeatureIds?: string[]; }) => { const balanceKeysByFeatureId: Record = {}; const addFeatureKey = (featureId: string) => { @@ -49,8 +49,8 @@ export const buildDeductFromSubjectBalancesKeys = ({ for (const deductionEntry of customerEntitlementDeductions) { addFeatureKey(deductionEntry.feature_id ?? fallbackFeatureId); } - for (const anchorFeatureId of anchorFeatureIds) { - addFeatureKey(anchorFeatureId); + for (const usageWindowFeatureId of usageWindowFeatureIds) { + addFeatureKey(usageWindowFeatureId); } const balanceFeatureIds = Object.keys(balanceKeysByFeatureId); diff --git a/server/src/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.ts b/server/src/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.ts index 20024f0b7..cf4ef8fe8 100644 --- a/server/src/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.ts +++ b/server/src/internal/customers/cache/fullSubject/config/fullSubjectCacheConfig.ts @@ -3,3 +3,9 @@ import { seconds } from "@autumn/shared"; export const FULL_SUBJECT_CACHE_TTL_SECONDS = seconds.days(3); export const FULL_SUBJECT_EPOCH_TTL_SECONDS = seconds.days(5); export const AGGREGATED_BALANCE_FIELD = "_aggregated"; +// Customer-scoped usage-window counters for the capped feature, stored as a +// reserved field in that feature's balance hash (JSON array of rows). The +// rebuild writes it (even []) for armed caps; readers fail OPEN on a missing +// field (the window restarts), so it is a warm-read optimization, not a +// correctness contract. +export const USAGE_WINDOWS_FIELD = "_usage_windows"; diff --git a/server/src/internal/customers/cache/fullSubject/fullSubjectCacheModel.ts b/server/src/internal/customers/cache/fullSubject/fullSubjectCacheModel.ts index 772616c04..f1782b19d 100644 --- a/server/src/internal/customers/cache/fullSubject/fullSubjectCacheModel.ts +++ b/server/src/internal/customers/cache/fullSubject/fullSubjectCacheModel.ts @@ -16,14 +16,23 @@ import { } from "@autumn/shared"; import { z } from "zod/v4"; +// `usage_windows` is omitted alongside balances: counters live in the +// per-feature balance hashes (`_usage_windows` field) and would be instantly +// stale if serialized into the subject view. export type CachedFullSubject = Omit< NormalizedFullSubject, - "customer_entitlements" + "customer_entitlements" | "usage_windows" > & { _schemaVersion: number; _cachedAt: number; meteredFeatures: string[]; customerEntitlementIdsByFeatureId: Record; + /** Features with an armed windowed cap (customer + entity usage_limits); + * may include features with no entitlements, so it cannot be derived from + * customerEntitlementIdsByFeatureId. Drives `_usage_windows` reads, + * writes, and invalidation. Optional: cache entries written before usage + * windows existed don't carry it (treat as []). */ + usageWindowFeatureIds?: string[]; subjectViewEpoch: number; }; @@ -72,6 +81,9 @@ export const CachedFullSubjectSchema = z.object({ _cachedAt: z.number(), meteredFeatures: z.array(z.string()), customerEntitlementIdsByFeatureId: z.record(z.string(), z.array(z.string())), + // Optional (not defaulted): pre-usage-windows cache entries don't carry it, + // and the hole-filling walker must not invent it. + usageWindowFeatureIds: z.array(z.string()).optional(), subjectViewEpoch: z.number(), }); @@ -105,6 +117,15 @@ export const normalizedToCachedFullSubject = ({ const meteredFeatures = [...meteredFeatureSet]; + const usageWindowFeatureIds = [ + ...new Set( + [ + ...(normalized.customer.usage_limits ?? []), + ...(normalized.entity?.usage_limits ?? []), + ].map((usageLimit) => usageLimit.feature_id), + ), + ]; + return { subjectType: normalized.subjectType, customerId: normalized.customerId, @@ -128,6 +149,7 @@ export const normalizedToCachedFullSubject = ({ _cachedAt: Date.now(), meteredFeatures, customerEntitlementIdsByFeatureId, + usageWindowFeatureIds, subjectViewEpoch, }; }; @@ -159,5 +181,8 @@ export const cachedFullSubjectToNormalized = ({ invoices: cached.invoices, entity_aggregations: cached.entity_aggregations, migration_item_runs: cached.migration_item_runs ?? [], + // Live data: filled from the balance hashes' `_usage_windows` fields by + // the caller, never from the cached subject view. + usage_windows: [], }; }; diff --git a/server/src/internal/customers/cache/fullSubject/roundCacheBalance.ts b/server/src/internal/customers/cache/fullSubject/roundCacheBalance.ts index 5ba806455..bb4099dea 100644 --- a/server/src/internal/customers/cache/fullSubject/roundCacheBalance.ts +++ b/server/src/internal/customers/cache/fullSubject/roundCacheBalance.ts @@ -5,9 +5,7 @@ import { Decimal } from "decimal.js"; * Round a number to avoid floating-point precision issues from Lua 5.1 double arithmetic. * Uses Decimal.js toDecimalPlaces(10) — enough precision while eliminating float drift. */ -export const roundCacheBalance = ( - value: number | null | undefined, -): number => { +export const roundCacheBalance = (value: number | null | undefined): number => { if (value === null || value === undefined) return 0; return new Decimal(value).toDecimalPlaces(10).toNumber(); }; @@ -23,11 +21,19 @@ export const roundSubjectBalance = ({ }): SubjectBalance => { subjectBalance.balance = roundCacheBalance(subjectBalance.balance); - if (subjectBalance.adjustment !== null && subjectBalance.adjustment !== undefined) + if ( + subjectBalance.adjustment !== null && + subjectBalance.adjustment !== undefined + ) subjectBalance.adjustment = roundCacheBalance(subjectBalance.adjustment); - if (subjectBalance.additional_balance !== null && subjectBalance.additional_balance !== undefined) - subjectBalance.additional_balance = roundCacheBalance(subjectBalance.additional_balance); + if ( + subjectBalance.additional_balance !== null && + subjectBalance.additional_balance !== undefined + ) + subjectBalance.additional_balance = roundCacheBalance( + subjectBalance.additional_balance, + ); if (subjectBalance.entities && typeof subjectBalance.entities === "object") { for (const entityId of Object.keys(subjectBalance.entities)) { diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts index f57dcb695..a82edf393 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts @@ -6,7 +6,7 @@ import { type CustomerLegacyData, type FullCustomer, fullCustomerToFullSubject, - fullSubjectToApiSpendLimits, + fullSubjectToApiUsageLimits, orgToInStatuses, scopeExpandForCtx, } from "@autumn/shared"; @@ -48,7 +48,7 @@ export const getApiCustomerBase = async ({ ctx: subscriptionsScopedCtx, fullCus, }); - const spendLimits = fullSubjectToApiSpendLimits({ + const usageLimits = fullSubjectToApiUsageLimits({ fullSubject: fullCustomerToFullSubject({ fullCustomer: fullCus }), features: ctx.features, inStatuses: orgToInStatuses({ org: ctx.org }), @@ -76,7 +76,8 @@ export const getApiCustomerBase = async ({ send_email_receipts: fullCus.send_email_receipts ?? false, billing_controls: { auto_topups: fullCus.auto_topups ?? undefined, - spend_limits: spendLimits, + spend_limits: fullCus.spend_limits ?? undefined, + usage_limits: usageLimits, usage_alerts: fullCus.usage_alerts ?? undefined, overage_allowed: fullCus.overage_allowed ?? undefined, }, diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusAutoTopupPurchaseLimits.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusAutoTopupPurchaseLimits.ts index 3c250ab76..951b42ce5 100644 --- a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusAutoTopupPurchaseLimits.ts +++ b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusAutoTopupPurchaseLimits.ts @@ -4,8 +4,8 @@ import { CustomerExpand, } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; -import { autoTopupLimitRepo } from "@/internal/balances/autoTopUp/repos"; import { normalizeWindowCounter } from "@/internal/balances/autoTopUp/helpers/limits/autoTopupLimitWindowUtils.js"; +import { autoTopupLimitRepo } from "@/internal/balances/autoTopUp/repos"; /** * When `expand=billing_controls.auto_topups.purchase_limit` is requested, diff --git a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusProcessors.ts b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusProcessors.ts index 88b2537e4..3149137dc 100644 --- a/server/src/internal/customers/cusUtils/cusResponseUtils/getCusProcessors.ts +++ b/server/src/internal/customers/cusUtils/cusResponseUtils/getCusProcessors.ts @@ -2,8 +2,8 @@ import { type ApiCusProcessors, type Customer, customerProductHasActiveStatus, - filterCustomerProductsByProcessorType, type FullCusProduct, + filterCustomerProductsByProcessorType, ProcessorType, } from "@autumn/shared"; diff --git a/server/src/internal/customers/cusUtils/cusUtils.ts b/server/src/internal/customers/cusUtils/cusUtils.ts index 4bbc4d444..35bfd889c 100644 --- a/server/src/internal/customers/cusUtils/cusUtils.ts +++ b/server/src/internal/customers/cusUtils/cusUtils.ts @@ -42,9 +42,8 @@ export const updateCustomerDetails = async ({ } if (!fullCustomer.email && customerData?.email) { if ( - z - .email({ pattern: z.regexes.unicodeEmail }) - .safeParse(customerData.email).error + z.email({ pattern: z.regexes.unicodeEmail }).safeParse(customerData.email) + .error ) { logger.info(`Invalid email ${customerData.email}, skipping update`); } else { diff --git a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiBalance/getApiBalancesV2.ts b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiBalance/getApiBalancesV2.ts index 8e54e0db7..bab4f83b3 100644 --- a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiBalance/getApiBalancesV2.ts +++ b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiBalance/getApiBalancesV2.ts @@ -4,10 +4,10 @@ import { type ApiFlagV0, type Feature, FeatureType, - findFeatureByInternalId, type FullAggregatedFeatureBalance, type FullCusEntWithFullCusProduct, type FullSubject, + findFeatureByInternalId, fullSubjectToCustomerEntitlements, orgToInStatuses, scopeExpandForCtx, @@ -50,8 +50,10 @@ const getFeatureInputs = ({ string, FullAggregatedFeatureBalance > = {}; - const aggregatedSubjectFlagByFeatureId: Record = - {}; + const aggregatedSubjectFlagByFeatureId: Record< + string, + AggregatedSubjectFlag + > = {}; if (fullSubject.subjectType === "customer") { for (const aggregatedFeatureBalance of fullSubject.aggregated_customer_entitlements ?? diff --git a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts index ea38770a7..84f84ef6f 100644 --- a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts +++ b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiCustomerBaseV2.ts @@ -4,7 +4,7 @@ import { CustomerExpand, type CustomerLegacyData, type FullSubject, - fullSubjectToApiSpendLimits, + fullSubjectToApiUsageLimits, orgToInStatuses, scopeExpandForCtx, } from "@autumn/shared"; @@ -48,7 +48,7 @@ export const getApiCustomerBaseV2 = async ({ }); const customer = fullSubject.customer; - const spendLimits = fullSubjectToApiSpendLimits({ + const usageLimits = fullSubjectToApiUsageLimits({ fullSubject, features: ctx.features, inStatuses: orgToInStatuses({ org: ctx.org }), @@ -73,7 +73,8 @@ export const getApiCustomerBaseV2 = async ({ send_email_receipts: customer.send_email_receipts ?? false, billing_controls: { auto_topups: customer.auto_topups ?? undefined, - spend_limits: spendLimits, + spend_limits: customer.spend_limits ?? undefined, + usage_limits: usageLimits, usage_alerts: customer.usage_alerts ?? undefined, overage_allowed: customer.overage_allowed ?? undefined, }, diff --git a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiSubscription/getApiSubscriptionV2.ts b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiSubscription/getApiSubscriptionV2.ts index fa2faa9e4..13a94fe45 100644 --- a/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiSubscription/getApiSubscriptionV2.ts +++ b/server/src/internal/customers/cusUtils/getApiCustomerV2/getApiSubscription/getApiSubscriptionV2.ts @@ -129,12 +129,12 @@ export const getApiSubscriptionV2 = async ({ trial_ends_at: isCustomerProductTrialing(customerProduct) ? (customerProduct.trial_ends_at ?? null) : null, - started_at: customerProduct.starts_at, - quantity: customerProduct.quantity, - current_period_start: subscriptionPeriod.current_period_start, - current_period_end: subscriptionPeriod.current_period_end, - scope: customerProduct.internal_entity_id ? "entity" : "customer", - } satisfies ApiSubscriptionV1), + started_at: customerProduct.starts_at, + quantity: customerProduct.quantity, + current_period_start: subscriptionPeriod.current_period_start, + current_period_end: subscriptionPeriod.current_period_end, + scope: customerProduct.internal_entity_id ? "entity" : "customer", + } satisfies ApiSubscriptionV1), legacyData: { subscription_id: subId || undefined, options: customerProduct.options, diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubject.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubject.ts index 5c084343c..e628324b9 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubject.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubject.ts @@ -9,6 +9,7 @@ import { import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { checkPendingMigrationsForCustomer } from "@/internal/migrations/v2/lazy/checkPendingMigrationsForCustomer.js"; import { lazyResetSubjectEntitlements } from "../../actions/resetCustomerEntitlementsV2/lazyResetSubjectEntitlements.js"; +import { lazyResetSubjectUsageWindows } from "../../actions/resetUsageWindows/lazyResetSubjectUsageWindows.js"; import { RELEVANT_STATUSES } from "../../cusProducts/CusProductService.js"; import { runWithFullSubjectGate } from "./getFullSubjectGate.js"; import { getFullSubjectQuery } from "./getFullSubjectQuery.js"; @@ -59,6 +60,7 @@ export async function getFullSubject({ allowMissingEntity, }); await lazyResetSubjectEntitlements({ ctx, fullSubject }); + await lazyResetSubjectUsageWindows({ ctx, fullSubject }); await checkPendingMigrationsForCustomer({ ctx, fullCustomer: fullSubjectToFullCustomer({ fullSubject }), @@ -113,6 +115,7 @@ export async function getFullSubjectNormalized({ const fullSubject = normalizedToFullSubject({ normalized }); await lazyResetSubjectEntitlements({ ctx, fullSubject, normalized }); + await lazyResetSubjectUsageWindows({ ctx, fullSubject, normalized }); await checkPendingMigrationsForCustomer({ ctx, fullCustomer: fullSubjectToFullCustomer({ fullSubject }), diff --git a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts index dd6534bdf..6bdbe85a2 100644 --- a/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts +++ b/server/src/internal/customers/repos/getFullSubject/getFullSubjectRowsQuery.ts @@ -204,7 +204,9 @@ export const getFullSubjectRowsQuery = ({ cus_usage_windows AS ( SELECT uw.* FROM usage_windows uw - WHERE uw.customer_entitlement_id IN (SELECT id FROM all_cus_ent_ids) + WHERE uw.internal_customer_id IN ( + SELECT internal_customer_id FROM subject_records + ) ), cus_replaceables AS ( @@ -406,11 +408,7 @@ export const getFullSubjectRowsQuery = ({ ORDER BY uw.window_start_at ASC, uw.id ASC ) FROM cus_usage_windows uw - WHERE uw.customer_entitlement_id IN ( - SELECT ace.id - FROM all_cus_ent_ids ace - WHERE ace.subject_key = sr.subject_key - ) + WHERE uw.internal_customer_id = sr.internal_customer_id ), '[]'::json ) AS usage_windows, diff --git a/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts b/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts index 2e17b3262..9e916eda3 100644 --- a/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts +++ b/server/src/internal/customers/repos/getFullSubject/subjectQueryRowToNormalized.ts @@ -2,8 +2,8 @@ import type { FeatureOptions } from "@autumn/shared"; import { type AggregatedFeatureBalance, type AggregatedSubjectFlag, - type Customer, CusProductStatus, + type Customer, type DbCustomerEntitlement, type DbCustomerPrice, type DbFreeTrial, @@ -73,14 +73,6 @@ export const subjectQueryRowToNormalized = ({ replaceablesByCusEntId.set(replaceable.cus_ent_id, existing); } - const usageWindowsByCusEntId = new Map(); - for (const usageWindow of row.usage_windows) { - const existing = - usageWindowsByCusEntId.get(usageWindow.customer_entitlement_id) ?? []; - existing.push(usageWindow); - usageWindowsByCusEntId.set(usageWindow.customer_entitlement_id, existing); - } - const customerProductsById = new Map( row.customer_products.map( (customerProduct) => [customerProduct.id, customerProduct] as const, @@ -217,7 +209,6 @@ export const subjectQueryRowToNormalized = ({ entitlement: catalogEntitlement as EntitlementWithFeature, replaceables: replaceablesByCusEntId.get(customerEntitlement.id) ?? [], rollovers: rolloversByCusEntId.get(customerEntitlement.id) ?? [], - usage_windows: usageWindowsByCusEntId.get(customerEntitlement.id) ?? [], customerPrice: resolveCustomerPrice({ customerEntitlement, entitlement: catalogEntitlement as EntitlementWithFeature, @@ -294,6 +285,7 @@ export const subjectQueryRowToNormalized = ({ customer_products: row.customer_products, customer_entitlements: meteredCustomerEntitlements, customer_prices: row.customer_prices, + usage_windows: (row.usage_windows ?? []) as DbUsageWindow[], flags, products: row.products as DbProduct[], entitlements: row.entitlements as EntitlementWithFeature[], diff --git a/server/src/internal/customers/usageWindows/repos/index.ts b/server/src/internal/customers/usageWindows/repos/index.ts new file mode 100644 index 000000000..8a87812e0 --- /dev/null +++ b/server/src/internal/customers/usageWindows/repos/index.ts @@ -0,0 +1,5 @@ +import { rollUsageWindows } from "./rollUsageWindows"; + +export const usageWindowRepo = { + rollWindows: rollUsageWindows, +}; diff --git a/server/src/internal/customers/usageWindows/repos/rollUsageWindows.ts b/server/src/internal/customers/usageWindows/repos/rollUsageWindows.ts new file mode 100644 index 000000000..8ea909994 --- /dev/null +++ b/server/src/internal/customers/usageWindows/repos/rollUsageWindows.ts @@ -0,0 +1,32 @@ +import { usageWindows } from "@autumn/shared"; +import { eq, sql } from "drizzle-orm"; +import type { DrizzleCli } from "@/db/initDrizzle"; +import type { UsageWindowRoll } from "@/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.js"; + +/** + * Rolls counter rows in place: advances bounds/anchor to the current + * derivation; zeroes the count only when its stored window closed (a + * bounds-only re-alignment, e.g. after a plan change, keeps the count). + */ +export const rollUsageWindows = async ({ + db, + rolls, + now, +}: { + db: DrizzleCli; + rolls: UsageWindowRoll[]; + now: number; +}): Promise => { + for (const roll of rolls) { + await db + .update(usageWindows) + .set({ + usage: roll.zero_usage ? 0 : sql`${usageWindows.usage}`, + window_start_at: roll.window_start_at, + window_end_at: roll.window_end_at, + anchor_customer_entitlement_id: roll.anchor_customer_entitlement_id, + updated_at: now, + }) + .where(eq(usageWindows.id, roll.id)); + } +}; diff --git a/server/src/internal/entities/actions/updateEntity.ts b/server/src/internal/entities/actions/updateEntity.ts index 08b476df8..c4209054c 100644 --- a/server/src/internal/entities/actions/updateEntity.ts +++ b/server/src/internal/entities/actions/updateEntity.ts @@ -43,6 +43,7 @@ export const updateEntity = async ({ const filteredUpdates = Object.fromEntries( Object.entries({ spend_limits: billing_controls?.spend_limits, + usage_limits: billing_controls?.usage_limits, usage_alerts: billing_controls?.usage_alerts, overage_allowed: billing_controls?.overage_allowed, }).filter(([, value]) => value !== undefined), diff --git a/server/src/internal/entities/entityUtils/getApiEntityV2/getApiEntityBaseV2.ts b/server/src/internal/entities/entityUtils/getApiEntityV2/getApiEntityBaseV2.ts index bb8b62338..d4c50c16c 100644 --- a/server/src/internal/entities/entityUtils/getApiEntityV2/getApiEntityBaseV2.ts +++ b/server/src/internal/entities/entityUtils/getApiEntityV2/getApiEntityBaseV2.ts @@ -3,7 +3,9 @@ import { ApiEntityV2Schema, type EntityLegacyData, type FullSubject, + fullSubjectToApiUsageLimits, InternalError, + orgToInStatuses, scopeExpandForCtx, } from "@autumn/shared"; import type { RequestContext } from "@/honoUtils/HonoEnv.js"; @@ -59,6 +61,12 @@ export const getApiEntityBaseV2 = async ({ flags, billing_controls: { spend_limits: entity.spend_limits ?? undefined, + usage_limits: fullSubjectToApiUsageLimits({ + fullSubject, + features: ctx.features, + inStatuses: orgToInStatuses({ org: ctx.org }), + source: "entity", + }), usage_alerts: entity.usage_alerts ?? undefined, overage_allowed: entity.overage_allowed ?? undefined, }, diff --git a/server/tests/_groups/temp.ts b/server/tests/_groups/temp.ts index 59a08b93d..8c8f1b4ba 100644 --- a/server/tests/_groups/temp.ts +++ b/server/tests/_groups/temp.ts @@ -1,100 +1,30 @@ import type { TestGroup } from "./types"; const activeTempPaths = [ - "integration/billing/attach/free-trial/trial-basic.test.ts", - "integration/billing/attach/free-trial/trial-conversion.test.ts", - "integration/billing/attach/free-trial/trial-downgrade.test.ts", - "integration/billing/attach/free-trial/trial-entity-upgrade.test.ts", - "integration/billing/attach/free-trial/trial-merge.test.ts", -]; - -export const tempBacklogPhases = [ - [ - "unit/billing/setup-billing-cycle-anchor.spec.ts", - "unit/billing/stripe-backdate-start-date-utils.spec.ts", - "unit/billing/stripe/discounts/apply-stripe-discounts-to-line-items.spec.ts", - "integration/billing/attach/params/start-date/starts-at-backdate.test.ts", - "integration/billing/attach/params/start-date/starts-at-backdate-invoice.test.ts", - ], - [ - "integration/billing/attach/params/start-date/starts-at-backdate-new-billing-subscription.test.ts", - "integration/billing/attach/params/start-date/starts-at-backdate-scheduled-replacement.test.ts", - "integration/billing/attach/params/start-date/starts-at-validation.test.ts", - "integration/billing/attach/params/start-date/starts-at-scheduling.test.ts", - "integration/billing/attach/params/start-date/starts-at-enable-plan-immediately.test.ts", - ], - [ - "integration/billing/attach/new-plan/attach-paid.test.ts", - "integration/billing/attach/new-plan/attach-addon.test.ts", - "integration/billing/attach/new-plan/attach-entities.test.ts", - "integration/billing/attach/new-plan/new-prepaid.test.ts", - "integration/billing/attach/new-plan/prepaid", - ], - [ - "integration/billing/attach/free-trial", - "integration/billing/attach/free-trial/override", - "integration/billing/attach/params/plan-schedule", - "integration/billing/attach/params/billing-cycle-anchor", - "integration/billing/attach/params/custom-plan/custom-plan-entity.test.ts", - ], - [ - "integration/billing/attach/discounts", - "integration/billing/attach/immediate-switch", - "integration/billing/attach/scheduled-switch", - "integration/billing/attach/checkout/stripe-checkout/stripe-checkout-entities.test.ts", - "integration/billing/attach/checkout/stripe-checkout/stripe-checkout-multi-interval.test.ts", - ], - [ - "integration/billing/attach/checkout/stripe-checkout/prepaid/stripe-checkout-prepaid-entities.test.ts", - "integration/billing/attach/invoice/attach-invoice-finalized-immediate.test.ts", - "integration/billing/attach/invoice/attach-invoice-draft-immediate.test.ts", - "integration/billing/attach/invoice-line-items/backdate-line-items.test.ts", - "integration/billing/attach/invoice-line-items/line-item-discounts.test.ts", - ], - [ - "integration/billing/multi-attach/basic", - "integration/billing/multi-attach/customize", - "integration/billing/multi-attach/multi-attach-paid-features.test.ts", - "integration/billing/multi-attach/multi-attach-multi-interval.test.ts", - "integration/billing/multi-attach/multi-attach-invoice-line-items.test.ts", - ], - [ - "integration/billing/multi-attach/scheduled-switch", - "integration/billing/create-schedule/backdate/create-schedule-backdate.test.ts", - "integration/billing/create-schedule/create-schedule-annual-proration.test.ts", - "integration/billing/create-schedule/phases/create-schedule-phases.test.ts", - "integration/billing/create-schedule/phases/create-schedule-phases-checkout.test.ts", - ], - [ - "integration/billing/create-schedule/phases/create-schedule-phases-replacements.test.ts", - "integration/billing/create-schedule/phases/create-schedule-phases-schedules.test.ts", - "integration/billing/create-schedule/phases/create-schedule-phases-validation.test.ts", - "integration/billing/create-schedule/params/create-schedule-enable-plan-immediately.test.ts", - "integration/billing/create-schedule/params/create-schedule-customize.test.ts", - ], - [ - "integration/billing/create-schedule/params/create-schedule-subscription-id.test.ts", - "integration/billing/create-schedule/one-off-prepaid-preserve/preserve-on-schedule.test.ts", - "integration/billing/update-subscription/billing-behavior/next-cycle-only.test.ts", - "integration/billing/update-subscription/billing-behavior/next-cycle-only-cancel.test.ts", - "integration/billing/update-subscription/discounts/proration-discount.test.ts", - ], - [ - "integration/billing/update-subscription/discounts/discount-applies-to.test.ts", - "integration/billing/update-subscription/discounts/multiple-discounts.test.ts", - "integration/billing/update-subscription/free-trial", - "integration/billing/update-subscription/params/billing-cycle-anchor/update-sub-anchor-reset-with-changes.test.ts", - "integration/billing/update-subscription/params/billing-cycle-anchor/update-sub-anchor-reset-no-partial-refund.test.ts", - ], - [ - "integration/billing/stripe-webhooks/invoice-created/invoice-created-multi-interval.test.ts", - ], + "integration/balances/usage-windows/usage-window-enforcement.test.ts", + "integration/balances/usage-windows/usage-window-own-feature.test.ts", + "integration/balances/usage-windows/usage-window-persistence.test.ts", + "integration/balances/usage-windows/usage-window-reset.test.ts", + "integration/balances/usage-windows/plan-changes/plan-change-upgrade.test.ts", + "integration/balances/usage-windows/plan-changes/plan-change-anchor.test.ts", + "integration/balances/usage-windows/plan-changes/plan-change-replacement.test.ts", + "integration/balances/usage-windows/plan-changes/plan-change-scheduled.test.ts", + "integration/balances/usage-windows/plan-changes/plan-change-update.test.ts", + "integration/balances/usage-windows/usage-window-sync.test.ts", + "integration/balances/usage-windows/usage-window-api.test.ts", + "integration/balances/usage-windows/usage-window-check.test.ts", + "integration/balances/usage-windows/usage-window-lock.test.ts", + "unit/full-subject-cache/setSharedFullSubjectBalances.test.ts", + "unit/usage-windows/buildUsageWindowKey.test.ts", + "unit/usage-windows/computeUsageWindowRolls.test.ts", + "unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts", + "unit/usage-windows/getUsageWindowBounds.test.ts", + "unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts", ]; export const temp: TestGroup = { name: "temp", - description: - "active temp slice for starts_at and next-cycle preview regressions", + description: "usage-windows PR tests (uw-1-storage review)", tier: "domain", paths: activeTempPaths, maxConcurrency: 2, diff --git a/server/tests/_temp/cycle-differential-sweep.ts b/server/tests/_temp/cycle-differential-sweep.ts new file mode 100644 index 000000000..45be113ee --- /dev/null +++ b/server/tests/_temp/cycle-differential-sweep.ts @@ -0,0 +1,210 @@ +/** + * Differential sweep: OLD (origin/main) getCycleStart/getCycleEnd vs NEW + * (bracket walk), both checked against an independent reference = the unique + * lattice boundary pair bracketing `now` (unique because add(anchor, k) is + * strictly increasing in k for every interval type). + * + * Verdict criteria: + * - newDeviations MUST be 0 (new always matches the ground truth) + * - every divergence between old and new MUST be an input where old != ref + * (i.e. the fix only changes outputs that were provably wrong) + */ + +import { UTCDate } from "@date-fns/utc"; +import { + addDays, + addHours, + addMonths, + addWeeks, + addYears, + differenceInDays, + differenceInHours, + differenceInMonths, + differenceInWeeks, + differenceInYears, +} from "date-fns"; +import { + BillingInterval, + EntInterval, + getCycleEnd, + getCycleStart, +} from "@autumn/shared"; + +type Fns = { + add: (d: Date, n: number) => Date; + diff: (l: Date, e: Date) => number; +}; + +const FNS: Record = { + hour: { add: addHours, diff: differenceInHours }, + day: { add: addDays, diff: differenceInDays }, + week: { add: addWeeks, diff: differenceInWeeks }, + month: { add: addMonths, diff: differenceInMonths }, + quarter: { + add: (d, n) => addMonths(d, n * 3), + diff: (l, e) => Math.floor(differenceInMonths(l, e) / 3), + }, + semi_annual: { + add: (d, n) => addMonths(d, n * 6), + diff: (l, e) => Math.floor(differenceInMonths(l, e) / 6), + }, + year: { add: addYears, diff: differenceInYears }, +}; + +const INTERVAL_ENUM: Record = { + hour: EntInterval.Hour, + day: EntInterval.Day, + week: BillingInterval.Week, + month: BillingInterval.Month, + quarter: BillingInterval.Quarter, + semi_annual: BillingInterval.SemiAnnual, + year: BillingInterval.Year, +}; + +// --- OLD implementations, verbatim logic from origin/main --- +const oldStart = (fns: Fns, anchor: number, c: number, now: number) => { + const a = new UTCDate(anchor); + const k = Math.floor(fns.diff(new UTCDate(now), a) / c); + const cycleStart = fns.add(a, k * c); + if (cycleStart.getTime() > now) return fns.add(a, (k - 1) * c).getTime(); + return cycleStart.getTime(); +}; + +const oldEnd = (fns: Fns, anchor: number, c: number, now: number) => { + const a = new UTCDate(anchor); + const k = Math.floor(fns.diff(new UTCDate(now), a) / c); + const candidate = fns.add(a, k * c); + if (candidate.getTime() > now) return candidate.getTime(); + return fns.add(a, (k + 1) * c).getTime(); +}; + +// --- Independent reference: walk to the unique bracketing k --- +const ref = (fns: Fns, anchor: number, c: number, now: number) => { + const a = new UTCDate(anchor); + let k = Math.floor(fns.diff(new UTCDate(now), a) / c); + let guard = 0; + while (fns.add(a, (k + 1) * c).getTime() <= now) { + k++; + if (++guard > 10_000) throw new Error("ref walk diverged (up)"); + } + while (fns.add(a, k * c).getTime() > now) { + k--; + if (++guard > 10_000) throw new Error("ref walk diverged (down)"); + } + const start = fns.add(a, k * c).getTime(); + const end = fns.add(a, (k + 1) * c).getTime(); + if (!(start <= now && now < end)) throw new Error("ref bracket violated"); + return { start, end }; +}; + +const fmt = (ms: number) => + new UTCDate(ms).toISOString().replace("T", " ").slice(0, 16); + +type Config = { + name: string; + interval: string; + c: number; + anchors: number[]; + sweepStart: number; + sweepEnd: number; + stepMs: number; +}; + +const d = ( + y: number, + mo: number, + day: number, + h = 10, + mi = 0, +): number => new UTCDate(y, mo - 1, day, h, mi, 0).getTime(); + +const HOUR = 3_600_000; +const jan2025Days = (days: number[]) => days.map((dd) => d(2025, 1, dd)); +const ALL_JAN_DAYS = jan2025Days( + Array.from({ length: 31 }, (_, index) => index + 1), +); +const EOM_ANCHORS = jan2025Days([2, 15, 28, 29, 30, 31]); + +const configs: Config[] = [ + { name: "month c=1 (all 31 anchor days, incl. future-anchor region)", interval: "month", c: 1, anchors: ALL_JAN_DAYS, sweepStart: d(2024, 7, 1), sweepEnd: d(2026, 7, 1), stepMs: 6 * HOUR }, + { name: "month c=2", interval: "month", c: 2, anchors: EOM_ANCHORS, sweepStart: d(2024, 7, 1), sweepEnd: d(2026, 7, 1), stepMs: 6 * HOUR }, + { name: "month c=3", interval: "month", c: 3, anchors: EOM_ANCHORS, sweepStart: d(2024, 7, 1), sweepEnd: d(2026, 7, 1), stepMs: 6 * HOUR }, + { name: "quarter c=1", interval: "quarter", c: 1, anchors: EOM_ANCHORS, sweepStart: d(2024, 7, 1), sweepEnd: d(2026, 7, 1), stepMs: 6 * HOUR }, + { name: "semi_annual c=1 (incl. Aug 31 anchor -> Feb 28 clamp)", interval: "semi_annual", c: 1, anchors: [...EOM_ANCHORS, d(2024, 8, 31)], sweepStart: d(2024, 1, 1), sweepEnd: d(2027, 1, 1), stepMs: 12 * HOUR }, + { name: "year c=1 (incl. Feb 29 leap anchor)", interval: "year", c: 1, anchors: [d(2024, 2, 29, 12), d(2023, 2, 28, 12), d(2024, 12, 31), d(2025, 1, 1)], sweepStart: d(2023, 6, 1), sweepEnd: d(2027, 6, 1), stepMs: 12 * HOUR }, + { name: "week c=1", interval: "week", c: 1, anchors: [d(2025, 1, 7, 13, 37)], sweepStart: d(2024, 11, 1), sweepEnd: d(2025, 11, 1), stepMs: 3 * HOUR }, + { name: "day c=1", interval: "day", c: 1, anchors: [d(2025, 1, 7, 13, 37)], sweepStart: d(2024, 12, 1), sweepEnd: d(2025, 4, 1), stepMs: 1 * HOUR }, + { name: "hour c=1", interval: "hour", c: 1, anchors: [d(2025, 1, 7, 13, 37)], sweepStart: d(2025, 1, 1), sweepEnd: d(2025, 1, 14), stepMs: 7 * 60_000 }, +]; + +let totalCombos = 0; +let totalOldWrong = 0; +let totalNewWrong = 0; +let totalChangedWhileOldCorrect = 0; + +for (const cfg of configs) { + const fns = FNS[cfg.interval]; + const intervalEnum = INTERVAL_ENUM[cfg.interval]; + let combos = 0; + let oldWrong = 0; + let newWrong = 0; + let changedWhileOldCorrect = 0; + const samples: string[] = []; + + const checkOne = (anchor: number, now: number) => { + combos++; + const r = ref(fns, anchor, cfg.c, now); + const os = oldStart(fns, anchor, cfg.c, now); + const oe = oldEnd(fns, anchor, cfg.c, now); + const ns = getCycleStart({ anchor, interval: intervalEnum, intervalCount: cfg.c, now }); + const ne = getCycleEnd({ anchor, interval: intervalEnum, intervalCount: cfg.c, now }); + + const oldOk = os === r.start && oe === r.end; + const newOk = ns === r.start && ne === r.end; + if (!newOk) { + newWrong++; + samples.push(`NEW WRONG anchor=${fmt(anchor)} now=${fmt(now)} new=[${fmt(ns)},${fmt(ne)}) ref=[${fmt(r.start)},${fmt(r.end)})`); + } + if (!oldOk) { + oldWrong++; + if (samples.length < 4) { + samples.push(`old wrong: anchor=${fmt(anchor)} now=${fmt(now)} old=[${fmt(os)},${fmt(oe)}) ref=[${fmt(r.start)},${fmt(r.end)})`); + } + } + if (oldOk && (ns !== os || ne !== oe)) { + changedWhileOldCorrect++; + samples.push(`REGRESSION anchor=${fmt(anchor)} now=${fmt(now)} old=[${fmt(os)},${fmt(oe)}) new=[${fmt(ns)},${fmt(ne)})`); + } + }; + + for (const anchor of cfg.anchors) { + for (let now = cfg.sweepStart; now < cfg.sweepEnd; now += cfg.stepMs) { + checkOne(anchor, now); + } + // exact boundary instants ±1ms for the first 24 boundaries after sweepStart + let k = 0; + for (;;) { + const b = fns.add(new UTCDate(anchor), k * cfg.c).getTime(); + if (b > cfg.sweepEnd || k > 24) break; + if (b >= cfg.sweepStart) { + checkOne(anchor, b - 1); + checkOne(anchor, b); + checkOne(anchor, b + 1); + } + k++; + } + } + + totalCombos += combos; + totalOldWrong += oldWrong; + totalNewWrong += newWrong; + totalChangedWhileOldCorrect += changedWhileOldCorrect; + console.log(`${cfg.name.padEnd(58)} combos=${String(combos).padStart(7)} old-wrong=${String(oldWrong).padStart(5)} new-wrong=${newWrong} changed-while-old-correct=${changedWhileOldCorrect}`); + for (const s of samples.slice(0, 3)) console.log(` ${s}`); +} + +console.log("\n=== TOTALS ==="); +console.log(`combos tested: ${totalCombos}`); +console.log(`old deviates from truth: ${totalOldWrong}`); +console.log(`new deviates from truth: ${totalNewWrong} <- must be 0`); +console.log(`new changed a correct old: ${totalChangedWhileOldCorrect} <- must be 0`); diff --git a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts b/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts deleted file mode 100644 index 237444ec0..000000000 --- a/server/tests/integration/balances/track/usage-limit/track-customer-usage-limit.test.ts +++ /dev/null @@ -1,688 +0,0 @@ -import { expect, test } from "bun:test"; -import { - type ApiCustomerV5, - type CustomerBillingControls, - EntInterval, -} from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import { items } from "@tests/utils/fixtures/items.js"; -import { products } from "@tests/utils/fixtures/products.js"; -import { timeout } from "@tests/utils/genUtils.js"; -import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js"; -import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; - -// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped -const queryRows = (result: unknown): any[] => - // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped - Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); - -type AutumnV2_1Client = Awaited>["autumnV2_1"]; - -// Arms a windowed usage cap via spend_limits[].usage_limit (overage off); -// `interval` sets the explicit window override. -const setCustomerUsageLimit = async ({ - autumn, - customerId, - featureId, - limit, - interval = EntInterval.Month, -}: { - autumn: AutumnV2_1Client; - customerId: string; - featureId: string; - limit: number; - interval?: EntInterval; -}) => { - const billingControls: CustomerBillingControls = { - spend_limits: [ - { - feature_id: featureId, - enabled: false, - usage_limit: limit, - usage_limit_interval: interval, - }, - ], - }; - - await timeout(2000); - await autumn.customers.update(customerId, { - billing_controls: billingControls, - }); - await timeout(3000); -}; - -// Credit system: 100 credits, 1 action1 = 0.2 credits (see v2Features.ts). -// A cap of 5 action1 units consumes only 1 credit, so the cap must clamp the -// 6th unit while ~99 credits remain, proving it's a second, independent -// dimension, not a balance check. -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit1: per-feature cap clamps the over-cap unit while credits remain")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-usage-limit", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-usage-limit-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Action1, - limit: 5, - }); - - // Consume exactly up to the cap: 5 action1 units = 1 credit deducted. Assert - // the synchronous track response; a re-read races the async write-through. - const consumed = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }); - expect(consumed.balances?.[TestFeature.Credits]).toMatchObject({ - feature_id: TestFeature.Credits, - granted: 100, - remaining: 99, - usage: 1, - }); - - // The 6th unit is over the cap, so it clamps to 0: the track succeeds but - // applies nothing, leaving credits unchanged. - const overCap = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - expect(overCap.balances?.[TestFeature.Credits]).toMatchObject({ - granted: 100, - remaining: 99, - usage: 1, - }); - }, -); - -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit2: credit-pool sub-interval cap (1 credit/day) blocks while monthly credits remain")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-credit-day-cap", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-credit-day-cap-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - // 1 action1 = 0.2 credits, so 5 action1 = exactly 1 credit (the daily cap). - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Credits, - limit: 1, - interval: EntInterval.Day, - }); - - const consumed = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }); - expect(consumed.balances?.[TestFeature.Credits]).toMatchObject({ - feature_id: TestFeature.Credits, - granted: 100, - remaining: 99, - usage: 1, - }); - - let blocked = false; - let blockedCode: string | undefined; - try { - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - } catch (error) { - blocked = true; - blockedCode = (error as { code?: string }).code; - } - - expect(blocked).toBe(true); - expect(blockedCode).toBe("usage_limit_exceeded"); - }, -); - -// set_usage must be rejected when the feature has an enforced usage window; -// otherwise it bypasses the hard cap (it carries no window provenance). -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit3: set_usage is rejected when the feature has a usage window")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-setusage-guard", - items: [items.monthlyMessages({ includedUsage: 100 })], - }); - - const customerId = `track-customer-setusage-guard-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Messages, - limit: 5, - }); - - let blockedCode: string | undefined; - try { - await autumnV2_1.balances.update({ - customer_id: customerId, - feature_id: TestFeature.Messages, - current_balance: 50, - }); - } catch (error) { - blockedCode = (error as { code?: string }).code; - } - - expect(blockedCode).toBe("set_usage_not_allowed_with_usage_limit"); - }, -); - -// A single spend_limit entry carrying BOTH an overage_limit and a windowed usage -// cap must still clamp on the window (the two caps are independent). -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit4: a spend_limit with both overage_limit and a usage window clamps the window")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-compound-cap", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-compound-cap-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - const billingControls: CustomerBillingControls = { - spend_limits: [ - { - feature_id: TestFeature.Action1, - enabled: true, - overage_limit: 20, - usage_limit: 5, - usage_limit_interval: EntInterval.Month, - }, - ], - }; - await timeout(2000); - await autumnV2_1.customers.update(customerId, { - billing_controls: billingControls, - }); - await timeout(3000); - - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }); - - // The window cap clamps the over-cap unit to 0 (the overage path is separate), - // so the track succeeds and credits are unchanged. - const overCap = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - expect(overCap.balances?.[TestFeature.Credits]).toMatchObject({ - remaining: 99, - usage: 1, - }); - }, -); - -// Two concurrent tracks on the SAME customer's SAME window must serialize (Redis -// runs each deduction Lua atomically): combined value exceeds the cap, so the -// second track clamps and the counter reflects exactly the capped usage. -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit6: concurrent tracks on one window serialize, total clamped to the cap")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-concurrent-cap", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-concurrent-cap-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - // Cap action1 at 5/month; two concurrent tracks of 5 each => combined 10 > 5. - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Action1, - limit: 5, - }); - - const results = await Promise.allSettled([ - autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }), - autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }), - ]); - - // Both succeed now (clamp, not reject), but the window clamps the combined - // applied usage to the cap: one applies 5, the other clamps to 0. - expect(results.every((result) => result.status === "fulfilled")).toBe(true); - - await timeout(2000); - const final = await autumnV2_1.customers.get(customerId); - expect(final.balances?.[TestFeature.Credits]).toMatchObject({ - feature_id: TestFeature.Credits, - remaining: 99, - usage: 1, - }); - }, -); - -// Write-through: the Redis counter must reach the usage_windows table via the -// shared sync (the other tests assert only the synchronous Redis response). -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit-sync: window counter writes through to the usage_windows table")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-sync", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-uw-sync-1-${Date.now()}`; - const { autumnV2_1, ctx } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Credits, - limit: 5, - interval: EntInterval.Day, - }); - - // 5 action1 = 1 credit; under the 5-credit/day cap. The counter lives on the - // credits cus-ent (balance dimension). - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 5, - }); - - const creditsEnt = queryRows( - await ctx.db.execute(sql` - SELECT id, internal_feature_id FROM customer_entitlements - WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} - LIMIT 1 - `), - )[0]; - expect(creditsEnt?.id).toBeTruthy(); - - // Drive the async write-through synchronously, then assert the mirrored row. - await syncItemV4({ - ctx, - payload: { - customerId, - orgId: ctx.org.id, - env: ctx.env, - timestamp: Date.now(), - modifiedCusEntIdsByFeatureId: { - [TestFeature.Credits]: [creditsEnt.id], - }, - }, - }); - - const windowRows = queryRows( - await ctx.db.execute(sql` - SELECT feature_id, internal_feature_id, usage - FROM usage_windows WHERE customer_entitlement_id = ${creditsEnt.id} - `), - ); - expect(windowRows).toHaveLength(1); - expect(windowRows[0].feature_id).toBe(TestFeature.Credits); - expect(windowRows[0].internal_feature_id).toBe( - creditsEnt.internal_feature_id, - ); - expect(Number(windowRows[0].usage)).toBeCloseTo(1, 5); - }, -); - -// Deploy-migration safety: a leftover pre-array keyed-map blob must be reset to a -// clean array, never iterated-then-corrupted into a JSON object that wedges sync. -test.concurrent( - `${chalk.yellowBright("track-customer-usage-limit-legacy: a pre-array keyed-map blob is reset, not corrupted into an object")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-legacy", - items: [items.monthlyCredits({ includedUsage: 100 })], - }); - - const customerId = `track-customer-uw-legacy-1-${Date.now()}`; - const { autumnV2_1, ctx } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Credits, - limit: 5, - interval: EntInterval.Day, - }); - - // One track creates a proper array blob on the credits cus-ent. - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - - const creditsEnt = queryRows( - await ctx.db.execute(sql` - SELECT id FROM customer_entitlements - WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} - LIMIT 1 - `), - )[0]; - expect(creditsEnt?.id).toBeTruthy(); - - const balanceKey = buildSharedFullSubjectBalanceKey({ - orgId: ctx.org.id, - env: ctx.env, - customerId, - featureId: TestFeature.Credits, - }); - - // Overwrite usage_windows with a LEGACY keyed-map shape (the pre-array format - // ipairs would skip and table.insert would corrupt into a JSON object). - const blobJson = await ctx.redisV2.hget(balanceKey, creditsEnt.id); - expect(blobJson).toBeTruthy(); - const blob = JSON.parse(blobJson as string); - blob.usage_windows = { - "customer:balance:credits:day:legacy": { - key: "customer:balance:credits:day:legacy", - usage_amount: 0.2, - window_start_at: 1_700_000_000_000, - window_end_at: 9_999_999_999_999, - dimension_type: "balance", - interval: "day", - }, - }; - await ctx.redisV2.hset(balanceKey, creditsEnt.id, JSON.stringify(blob)); - - // The next track must RESET the map blob to a clean array, not corrupt it. - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Action1, - value: 1, - }); - - const after = JSON.parse( - (await ctx.redisV2.hget(balanceKey, creditsEnt.id)) as string, - ); - // The poison case is a JSON OBJECT (string keys); it must be a clean array. - expect(Array.isArray(after.usage_windows)).toBe(true); - expect(after.usage_windows).toHaveLength(1); - expect(after.usage_windows[0].feature_id).toBe(TestFeature.Credits); - expect(typeof after.usage_windows[0].id).toBe("string"); - }, -); - -// No manual sync flush: the counter must survive the mutation's cache invalidation on -// its own, else the cap silently resets and hands out fresh headroom. -test( - `${chalk.yellowBright("track-customer-usage-limit-lowercap: lowering the cap below current usage keeps the counter (clamps, no reset)")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-lowercap", - items: [items.monthlyMessages({ includedUsage: 1000 })], - }); - - const customerId = `track-customer-uw-lowercap-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Messages, - limit: 10, - }); - - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 8, - }); - - await autumnV2_1.customers.update(customerId, { - billing_controls: { - spend_limits: [ - { - feature_id: TestFeature.Messages, - enabled: false, - usage_limit: 3, - usage_limit_interval: EntInterval.Month, - }, - ], - }, - }); - - const clamped = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - expect(clamped.balance).toMatchObject({ - remaining: 992, - usage: 8, - }); - }, -); - -// Bug 1: a second balance grant (balances.create) is a cache-invalidating mutation; -// the cap counter must survive it. It used to reset to 0, opening fresh headroom. -test( - `${chalk.yellowBright("track-customer-usage-limit-regrant: the cap counter survives a re-grant (clamps)")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-regrant", - items: [items.monthlyMessages({ includedUsage: 100 })], - }); - - const customerId = `track-customer-uw-regrant-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Messages, - limit: 5, - }); - - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 5, - }); - - const clampedBefore = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - expect(clampedBefore.balance).toMatchObject({ usage: 5 }); - - // Re-grant a second balance for the same feature while at the cap. - await autumnV2_1.post("/balances.create", { - customer_id: customerId, - feature_id: TestFeature.Messages, - included_grant: 100, - reset: { interval: EntInterval.Month }, - }); - - const clampedAfter = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 1, - }); - expect(clampedAfter.balance).toMatchObject({ usage: 5 }); - }, -); - -// Q1 clamp: an over-cap track applies what fits (the remaining headroom) instead of -// rejecting the whole track. cap 5, track 10 from 0 -> applies 5 (not 10, not a 400). -test( - `${chalk.yellowBright("track-customer-usage-limit-clamp: over-cap track applies what fits (clamp, not reject)")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-clamp", - items: [items.monthlyMessages({ includedUsage: 100 })], - }); - - const customerId = `track-customer-uw-clamp-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Messages, - limit: 5, - }); - - // Track 10 against a cap of 5 (from 0): clamps to 5, returns 200, not a reject. - const clamped = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 10, - }); - expect(clamped.value).toBe(10); - expect(clamped.balance).toMatchObject({ remaining: 95, usage: 5 }); - - // At the cap: a further track applies 0 (fully clamped), still 200. - const atCap = await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 3, - }); - expect(atCap.balance).toMatchObject({ remaining: 95, usage: 5 }); - }, -); - -// Q2: the spend_limit in the customer response exposes the current window usage. -test( - `${chalk.yellowBright("track-customer-usage-limit-counter: spend_limit exposes the current window usage")}`, - async () => { - const customerProduct = products.base({ - id: "track-customer-uw-counter", - items: [items.monthlyMessages({ includedUsage: 100 })], - }); - - const customerId = `track-customer-uw-counter-1-${Date.now()}`; - const { autumnV2_1 } = await initScenario({ - customerId, - setup: [ - s.customer({ paymentMethod: "success", testClock: false }), - s.products({ list: [customerProduct] }), - ], - actions: [s.billing.attach({ productId: customerProduct.id })], - }); - - await setCustomerUsageLimit({ - autumn: autumnV2_1, - customerId, - featureId: TestFeature.Messages, - limit: 5, - }); - - await autumnV2_1.track({ - customer_id: customerId, - feature_id: TestFeature.Messages, - value: 3, - }); - - const customer = (await autumnV2_1.get( - `/customers/${customerId}`, - )) as ApiCustomerV5; - const limit = customer.billing_controls?.spend_limits?.find( - (entry) => entry.feature_id === TestFeature.Messages, - ); - expect(limit?.usage_limit_used).toBe(3); - }, -); diff --git a/server/tests/integration/balances/usage-windows/entities/entity-usage-window-check.test.ts b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-check.test.ts new file mode 100644 index 000000000..dcb09a9da --- /dev/null +++ b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-check.test.ts @@ -0,0 +1,207 @@ +import { expect, test } from "bun:test"; +import { ApiVersion } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { setCustomerUsageLimit } from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + expectEntityUsageLimit, + setEntityUsageLimit, +} from "../../utils/usage-limit-utils/entityUsageLimitUtils.js"; + +/** + * TDD tests for CHECK against entity-level usage limits. + * + * Contract under test: + * - an entity's own cap gates that entity's checks: required_balance within + * the entity window's headroom -> allowed true; beyond -> allowed false, + * even with ample balance; pure checks never consume window headroom + * - a sibling entity with no cap anywhere is unconstrained + * - carve-out checks: an entity with its own cap checks against IT, while a + * capless entity checks against the customer's aggregate window + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("ent-uw-check1: entity's own cap gates that entity's checks only")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-check-own-cap", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-check-1"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 3, + }); + + // ── Headroom is 2: within allowed, beyond rejected despite 97 balance ── + const within = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + required_balance: 2, + }); + expect(within.allowed).toBe(true); + + const beyond = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + required_balance: 3, + }); + expect(beyond.allowed).toBe(false); + + // ── Pure checks never consume: window usage still 3 ── + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // ── The capless sibling entity is unconstrained (full balance) ── + const sibling = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + required_balance: 50, + }); + expect(sibling.allowed).toBe(true); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-check2: carved-out entity checks its own cap, capless entity checks the aggregate")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-check-carveout", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-check-2"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[1].id, + featureId: TestFeature.Messages, + limit: 2, + }); + + await autumnV2_3.customers.get(customerId); // initialize cache. + for (const entity of entities) { + await autumnV2_3.entities.get(customerId, entity.id); // initialize cache. + } + + // ── e1 (own cap 2): check 3 rejected even though the aggregate has 5 ── + const carvedBeyond = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + required_balance: 3, + }); + expect(carvedBeyond.allowed).toBe(false); + + const carvedWithin = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + required_balance: 2, + }); + expect(carvedWithin.allowed).toBe(true); + + // ── e0 (capless) checks the aggregate: 5 fits, 6 does not ── + const aggregateWithin = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + required_balance: 5, + }); + expect(aggregateWithin.allowed).toBe(true); + + const aggregateBeyond = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + required_balance: 6, + }); + expect(aggregateBeyond.allowed).toBe(false); + + // ── e0's tracks fill the aggregate; e1's own cap is untouched by them ── + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 5, + }); + + const aggregateFull = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + required_balance: 1, + }); + expect(aggregateFull.allowed).toBe(false); + + const carvedStillOpen = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + required_balance: 2, + }); + expect(carvedStillOpen.allowed).toBe(true); + }, +); diff --git a/server/tests/integration/balances/usage-windows/entities/entity-usage-window-credits.test.ts b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-credits.test.ts new file mode 100644 index 000000000..40af8537d --- /dev/null +++ b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-credits.test.ts @@ -0,0 +1,161 @@ +import { expect, test } from "bun:test"; +import { ApiVersion, ErrCode } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { expectEntityFeatureBalance } from "../../utils/spend-limit-utils/entitySpendLimitUtils.js"; +import { + expectEntityUsageLimit, + setEntityUsageLimit, +} from "../../utils/usage-limit-utils/entityUsageLimitUtils.js"; + +/** + * TDD test for an ENTITY-LEVEL usage limit on action1 when the entity is + * funded by prepaid + consumable CREDITS (metered cap on a credit-system + * member feature; 1 action1 unit = 0.2 credits). + * + * Contract under test: + * - check on action1 (entity subject) is gated by the entity cap's remaining + * headroom in ACTION1 UNITS, while hundreds of credits remain + * - track clamps at the cap: only the allowed units drain credits + * (prepaid + consumable cusEnts, breakdown 2) + * - over-cap track with reject -> InsufficientBalance + * - entities.get reports the cap's window usage + * - a direct credits check is NOT gated by the action1 cap + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("ent-uw-credits1: entity cap on action1 over prepaid + consumable credits")}`, + async () => { + const prepaidQuantity = 300; + const consumableIncluded = 200; + // granted = prepaid quantity + consumable included usage, per entity. + const grantedCredits = prepaidQuantity + consumableIncluded; + const action1CreditCost = 0.2; + + const perEntityProduct = products.base({ + id: "ent-uw-credits-prepaid-consumable", + items: [ + items.prepaid({ + featureId: TestFeature.Credits, + includedUsage: 100, + billingUnits: 100, + price: 8.5, + entityFeatureId: TestFeature.Users, + }), + items.consumable({ + featureId: TestFeature.Credits, + includedUsage: consumableIncluded, + price: 0.5, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-credits-1"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [ + s.billing.attach({ + productId: perEntityProduct.id, + options: [ + { feature_id: TestFeature.Credits, quantity: prepaidQuantity }, + ], + }), + ], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Action1, + limit: 5, + }); + + // ── 3 of 5 units used (= 0.6 credits) ── + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Action1, + value: 3, + }); + + // ── Check converts cap headroom in action1 units: 2 left ── + const within = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Action1, + required_balance: 2, + }); + expect(within.allowed).toBe(true); + + const beyond = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Action1, + required_balance: 3, + }); + expect(beyond.allowed).toBe(false); + + // ── Over-cap track clamps: 4 requested, 2 applied (5 total = 1 credit) ── + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Action1, + value: 4, + }); + + await expectEntityFeatureBalance({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Credits, + granted: grantedCredits, + remaining: grantedCredits - 5 * action1CreditCost, + usage: 5 * action1CreditCost, + breakdownLength: 2, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + + // ── Cap exhausted: reject fires while ~499 credits remain ── + await expectAutumnError({ + errCode: ErrCode.InsufficientBalance, + func: async () => + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Action1, + value: 1, + overage_behavior: "reject", + }), + }); + + // ── A direct credits check is not gated by the action1 cap ── + const creditsCheck = await autumnV2_3.check({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Credits, + required_balance: 100, + }); + expect(creditsCheck.allowed).toBe(true); + }, +); diff --git a/server/tests/integration/balances/usage-windows/entities/entity-usage-window-enforcement.test.ts b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-enforcement.test.ts new file mode 100644 index 000000000..b05e6950e --- /dev/null +++ b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-enforcement.test.ts @@ -0,0 +1,354 @@ +import { expect, test } from "bun:test"; +import { ApiVersion, ErrCode } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerFeatureBalance, + expectEntityFeatureBalance, +} from "../../utils/spend-limit-utils/entitySpendLimitUtils.js"; +import { + expectEntityUsageLimit, + setEntityUsageLimit, +} from "../../utils/usage-limit-utils/entityUsageLimitUtils.js"; +import { fetchUsageWindowRows } from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +/** + * TDD tests for ENTITY-LEVEL usage windows (spend-limit mirror semantics: + * exactly ONE cap per feature per subject — the entity's own usage_limits + * entry wins; without one the customer's entry applies at customer scope). + * + * Contract under test (enforcement half): + * - entities.update(..., { billing_controls: { usage_limits } }) arms a cap + * - entity tracks against an ENTITY-SCOPED window: counts only that entity's + * usage, clamps over-cap tracks to what fits ("apply what fits") + * - overage_behavior "reject" over the cap -> InsufficientBalance + * - windows are isolated between entities; customer balance still aggregates + * - entity cap works on customer-scoped features too (no entity_feature_id): + * only that entity's tracks count; customer-level tracks are uncapped + * - side effect: usage_windows PG row per entity with internal_entity_id set + * + * Pre-impl red: EntityBillingControls has no usage_limits, so the update is + * rejected/dropped and no cap is ever enforced. + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("ent-uw-enforce1: entity's own cap clamps that entity's tracks")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-enforce-own-cap", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-enforce-1"; + const { entities, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + + // ── Cap reached exactly: 3 then 4 applies only the remaining 2 ── + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 3, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 4, + }); + + await expectEntityFeatureBalance({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + granted: 100, + remaining: 95, + usage: 5, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // ── Over the cap: a further track applies nothing ── + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectEntityFeatureBalance({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + granted: 100, + remaining: 95, + usage: 5, + }); + + // ── Side effect: ONE window row, entity-scoped (no customer row) ── + const rows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rows).toHaveLength(1); + expect(rows[0].internal_entity_id).not.toBeNull(); + expect(Number(rows[0].usage)).toBe(5); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-enforce2: two entities with different caps stay isolated while customer balance aggregates")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-enforce-isolated", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-enforce-2"; + const { entities, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[1].id, + featureId: TestFeature.Messages, + limit: 10, + }); + + // e0: 7 -> clamps to 5. e1: 7 then 5 -> clamps to 10 total. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 7, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 7, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 5, + }); + + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[1].id, + featureId: TestFeature.Messages, + usage: 10, + limit: 10, + }); + + // Balances aggregate at the customer even though windows are isolated. + await expectCustomerFeatureBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + granted: 200, + remaining: 185, + usage: 15, + }); + + // ── Side effect: one row per entity, distinct scopes ── + const rows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rows).toHaveLength(2); + const entityIds = rows.map( + (row: { internal_entity_id: string | null }) => row.internal_entity_id, + ); + expect(entityIds[0]).not.toBeNull(); + expect(entityIds[1]).not.toBeNull(); + expect(entityIds[0]).not.toBe(entityIds[1]); + expect( + rows + .map((row: { usage: string | number }) => Number(row.usage)) + .sort((a: number, b: number) => a - b), + ).toEqual([5, 10]); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-enforce3: over-cap entity track with reject returns InsufficientBalance")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-enforce-reject", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-enforce-3"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 5, + }); + + await expectAutumnError({ + errCode: ErrCode.InsufficientBalance, + func: async () => + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 1, + overage_behavior: "reject", + }), + }); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-enforce4: entity cap on a customer-scoped feature counts only that entity's tracks")}`, + async () => { + const customerProduct = products.base({ + id: "ent-uw-enforce-cus-feature", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "ent-uw-enforce-4"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await autumnV2_3.customers.get(customerId); // initialize cache. + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 3, + }); + + // Entity track over its cap: applies 3 of 5. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 3, + limit: 3, + }); + + // Customer-level track is NOT bound by the entity's cap. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 10, + }); + await expectCustomerFeatureBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + granted: 100, + remaining: 87, + usage: 13, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/entities/entity-usage-window-inheritance.test.ts b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-inheritance.test.ts new file mode 100644 index 000000000..7e5667941 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-inheritance.test.ts @@ -0,0 +1,342 @@ +import { expect, test } from "bun:test"; +import { ApiVersion, ErrCode } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + expectEntityUsageLimit, + setEntityUsageLimit, +} from "../../utils/usage-limit-utils/entityUsageLimitUtils.js"; +import { fetchUsageWindowRows } from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +/** + * TDD tests for usage-limit INHERITANCE (spend-limit mirror: per feature the + * entity's own usage_limits entry wins; without one the customer's entry + * "fills the gap" and applies at CUSTOMER scope — one shared aggregate window + * across the customer and every entity without its own cap). + * + * Contract under test (inheritance half): + * - no entity entry -> entity tracks count into the shared customer window + * (inherit1 pins this aggregate behavior) + * - entity with its own entry is CARVED OUT: its tracks consume only its + * entity window, never the customer's aggregate window (inherit2) + * - arming an entity cap mid-window moves that entity to a fresh entity + * window; the customer window keeps its count (inherit3) + * + * Pre-impl red: inherit2/inherit3 fail because entity usage_limits don't + * exist, so every entity track still lands in the customer window. + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("ent-uw-inherit1: entities without their own cap share the customer's aggregate window")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-inherit-aggregate", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-inherit-1"; + const { entities, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.customers.get(customerId); // initialize cache. + for (const entity of entities) { + await autumnV2_3.entities.get(customerId, entity.id); // initialize cache. + } + + // e0: 3, e1: 1 -> aggregate 4. e1's next 3 applies only the remaining 1. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 3, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 1, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 3, + }); + + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + await expectAutumnError({ + errCode: ErrCode.InsufficientBalance, + func: async () => + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 1, + overage_behavior: "reject", + }), + }); + + // ── Side effect: ONE shared customer-scope row, no entity rows ── + await timeout(4000); + const rows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rows).toHaveLength(1); + expect(rows[0].internal_entity_id).toBeNull(); + expect(Number(rows[0].usage)).toBe(5); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-inherit2: an entity with its own cap is carved out of the customer's aggregate window")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-inherit-carveout", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-inherit-2"; + const { entities, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[1].id, + featureId: TestFeature.Messages, + limit: 2, + }); + + await autumnV2_3.customers.get(customerId); // initialize cache. + for (const entity of entities) { + await autumnV2_3.entities.get(customerId, entity.id); // initialize cache. + } + + // e1 (own cap 2) tracks 3 -> applies 2, into its OWN window. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 3, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[1].id, + featureId: TestFeature.Messages, + usage: 2, + limit: 2, + }); + + // e0 (no own cap) tracks 5 -> the FULL 5 fits in the customer window, + // proving e1's tracks never touched it (else only 3 would fit). + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // Both windows now full: e0 rejects on the customer window, e1 on its own. + await expectAutumnError({ + errCode: ErrCode.InsufficientBalance, + func: async () => + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 1, + overage_behavior: "reject", + }), + }); + await expectAutumnError({ + errCode: ErrCode.InsufficientBalance, + func: async () => + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 1, + overage_behavior: "reject", + }), + }); + + // ── Side effect: one customer-scope row (5) + one entity row (2) ── + await timeout(4000); + const rows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rows).toHaveLength(2); + const customerRow = rows.find( + (row: { internal_entity_id: string | null }) => + row.internal_entity_id === null, + ); + const entityRow = rows.find( + (row: { internal_entity_id: string | null }) => + row.internal_entity_id !== null, + ); + expect(customerRow).toBeDefined(); + expect(entityRow).toBeDefined(); + expect(Number(customerRow.usage)).toBe(5); + expect(Number(entityRow.usage)).toBe(2); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-inherit3: arming an entity cap mid-window moves the entity to a fresh window")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-inherit-midwindow", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-inherit-3"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.customers.get(customerId); // initialize cache. + for (const entity of entities) { + await autumnV2_3.entities.get(customerId, entity.id); // initialize cache. + } + + // e0 inherits: 4 land in the customer window. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 4, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 4, + limit: 5, + }); + + // Carve e0 out mid-window: its next 5 fit its FRESH entity window + // (had it still tracked the customer window, only 1 would fit). + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // Customer window kept its 4: e1 (still inheriting) fits exactly 1 more. + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 3, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/entities/entity-usage-window-persistence.test.ts b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-persistence.test.ts new file mode 100644 index 000000000..2dddc2a93 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/entities/entity-usage-window-persistence.test.ts @@ -0,0 +1,212 @@ +import { expect, test } from "bun:test"; +import { type ApiEntityV2, ApiVersion, ResetInterval } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectEntityUsageLimit, + setEntityUsageLimit, +} from "../../utils/usage-limit-utils/entityUsageLimitUtils.js"; +import { fetchUsageWindowRows } from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +/** + * TDD tests for entity usage-limit PERSISTENCE + API exposure. + * + * Contract under test: + * - the entity window counter syncs to Postgres (internal_entity_id set) and + * a skip_cache read serves the synced count (persist1) + * - entities.get exposes billing_controls.usage_limits: the entity's OWN + * entries, each decorated with the current window's `usage` (persist2) + * - entities.update rejects duplicate feature_id usage_limits entries + * (persist3) + * + * Pre-impl red: entity usage_limits don't exist on the schema, so the arm is + * dropped, nothing syncs, nothing is exposed, and dupes aren't validated. + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("ent-uw-persist1: entity window counter syncs to Postgres and survives skip_cache reads")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-persist-sync", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-persist-1"; + const { entities, ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 3, + }); + await timeout(4000); + + // ── PG row: entity-scoped, counted ── + const rows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rows).toHaveLength(1); + expect(rows[0].internal_entity_id).not.toBeNull(); + expect(Number(rows[0].usage)).toBe(3); + expect(Number(rows[0].window_end_at)).toBeGreaterThan(Date.now()); + + // ── skip_cache read serves the synced counter ── + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + skipCache: true, + }); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-persist2: entities.get exposes the entity's usage_limits with current window usage")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-persist-expose", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-persist-2"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await setEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Before any usage: entry echoed with usage 0. + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 2, + }); + await timeout(3000); + + await expectEntityUsageLimit({ + autumn: autumnV2_3, + customerId, + entityId: entities[0].id, + featureId: TestFeature.Messages, + usage: 2, + limit: 5, + }); + + // The OTHER entity has no entries: nothing echoed for it. + const otherEntity = await autumnV2_3.entities.get( + customerId, + entities[1].id, + ); + expect( + otherEntity.billing_controls?.usage_limits ?? undefined, + ).toBeUndefined(); + }, +); + +test.concurrent( + `${chalk.yellowBright("ent-uw-persist3: duplicate feature_id entries in entity usage_limits are rejected")}`, + async () => { + const perEntityProduct = products.base({ + id: "ent-uw-persist-dupe", + items: [ + items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }), + ], + }); + + const customerId = "ent-uw-persist-3"; + const { entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [perEntityProduct] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: perEntityProduct.id })], + }); + + await expectAutumnError({ + func: async () => + await autumnV2_3.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_limits: [ + { + feature_id: TestFeature.Messages, + limit: 5, + interval: ResetInterval.Month, + }, + { + feature_id: TestFeature.Messages, + limit: 9, + interval: ResetInterval.Month, + }, + ], + }, + }), + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/plan-changes/plan-change-anchor.test.ts b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-anchor.test.ts new file mode 100644 index 000000000..079fd7618 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-anchor.test.ts @@ -0,0 +1,254 @@ +/** + * TDD tests for usage-window ANCHOR selection across plan changes. + * + * Contract under test: + * - top-up-only customers: the window anchors to the loose top-up ent and + * aligns to the UTC calendar (no cycle exists) + * - subscribing later transfers the anchor to the plan ent: the window + * re-keys to the plan cycle (window_end == plan ent next_reset_at) and + * the moved window ZEROES the counter + * - when both exist up front, the plan-backed ent outranks the OLDER + * loose top-up ent + */ + +import { expect, test } from "bun:test"; +import { ApiVersion, EntInterval, getUsageWindowBounds } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + fetchActivePlanCusEnt, + fetchLooseCusEnt, + fetchUsageWindowRows, +} from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: top-up-only anchors to the loose ent, calendar bounds ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-anchor1: a cap with only a top-up grant anchors to it with calendar bounds")}`, + async () => { + const customerId = "uw-anchor-topup-1"; + const { ctx } = await initScenario({ + customerId, + setup: [s.customer({ testClock: false }), s.products({ list: [] })], + actions: [], + }); + + await autumnV2_3.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Credits, + included_grant: 100, + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 2, + limit: 5, + }); + + // PG: anchored to the loose top-up ent; lifetime grants have no cycle, so + // the window is UTC-calendar aligned. + await timeout(4000); + const topUpEnt = await fetchLooseCusEnt({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + expect(topUpEnt?.id).toBeTruthy(); + + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + expect(windowRows).toHaveLength(1); + expect(windowRows[0].anchor_customer_entitlement_id).toBe(topUpEnt.id); + + const calendar = getUsageWindowBounds({ + interval: EntInterval.Month, + now: Date.now(), + }); + expect(Number(windowRows[0].window_start_at)).toBe(calendar.windowStartAt); + expect(Number(windowRows[0].window_end_at)).toBe(calendar.windowEndAt); + }, +); + +// ── Contract: subscribing transfers the anchor to the plan cycle ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-anchor2: subscribing re-anchors the window to the plan ent and restarts the counter")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-anchor-subscribe-1"; + const { ctx, autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro] }), + ], + actions: [], + }); + + await autumnV2_3.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Credits, + included_grant: 100, + }); + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + + // Flush the counter to Postgres before the cache-invalidating change + // (the rebuild re-seeds counters from PG). + await timeout(4000); + + // Subscribe: the plan ent now outranks the top-up, the window re-keys to + // the plan cycle, and the moved window zeroes the counter. + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: pro.id, + redirect_mode: "if_required", + }); + + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 0, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 3, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 3, + limit: 5, + }); + + await timeout(4000); + const planEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + expect(planEnt?.next_reset_at).toBeTruthy(); + + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + const currentRow = windowRows.find( + (row) => Number(row.window_end_at) === Number(planEnt.next_reset_at), + ); + expect(currentRow).toBeDefined(); + expect(currentRow.anchor_customer_entitlement_id).toBe(planEnt.id); + expect(Number(currentRow.window_end_at)).toBe( + Number(planEnt.next_reset_at), + ); + }, +); + +// ── Contract: plan-backed ent outranks an OLDER top-up ent ────────── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-anchor3: the plan ent wins the anchor over an older top-up ent")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-anchor-rank-1"; + const { ctx, autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro] }), + ], + actions: [], + }); + + // Top-up FIRST (older created_at), plan second. + await autumnV2_3.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Credits, + included_grant: 50, + }); + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: pro.id, + redirect_mode: "if_required", + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + + await timeout(4000); + const planEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Credits, + }); + expect(windowRows).toHaveLength(1); + expect(windowRows[0].anchor_customer_entitlement_id).toBe(planEnt.id); + expect(Number(windowRows[0].window_end_at)).toBe( + Number(planEnt.next_reset_at), + ); + }, +); diff --git a/server/tests/integration/balances/usage-windows/plan-changes/plan-change-replacement.test.ts b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-replacement.test.ts new file mode 100644 index 000000000..142b02df4 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-replacement.test.ts @@ -0,0 +1,194 @@ +/** + * TDD tests for usage windows across plan REPLACEMENT and expiry. + * + * Contract under test (windows follow the anchor ent's reset cycle): + * - replacing a free plan re-anchors the window to the new ent (new + * bounds, window_end == its next_reset_at) and the moved window ZEROES + * the counter (moved from persistence3) + * - cancelling to NOTHING re-aligns the cap to the UTC calendar, zeroing + * the counter with the moved window + */ + +import { expect, test } from "bun:test"; +import { ApiVersion, EntInterval, getUsageWindowBounds } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + fetchActivePlanCusEnt, + fetchUsageWindowRows, +} from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: replacement restarts the cap with the new cycle ─────── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-replacement1: free-plan replacement restarts the cap on the new ent's cycle")}`, + async () => { + const planA = products.base({ + id: "uw-replace-a", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + const planB = products.base({ + id: "uw-replace-b", + items: [items.monthlyMessages({ includedUsage: 200 })], + }); + + const customerId = "uw-replace-1"; + const { ctx, autumnV2_1 } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [planA, planB] }), + ], + actions: [s.billing.attach({ productId: planA.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Exhaust the cap on plan A. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await timeout(4000); + + // Replace plan A with plan B: plan A's ents (incl. the anchor) expire and + // plan B's ent starts a fresh cycle -- the window restarts with it. + await autumnV2_1.attach({ + customer_id: customerId, + product_id: planB.id, + }); + await timeout(2000); + + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + + // Fresh headroom on the new cycle. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + granted: 200, + remaining: 199, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 1, + limit: 5, + }); + + await timeout(4000); + const planBEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const currentRow = windowRows.find( + (row) => Number(row.window_end_at) === Number(planBEnt.next_reset_at), + ); + expect(currentRow).toBeDefined(); + expect(currentRow.anchor_customer_entitlement_id).toBe(planBEnt.id); + expect(Number(currentRow.window_end_at)).toBe( + Number(planBEnt.next_reset_at), + ); + }, +); + +// ── Contract: cancel-to-nothing re-aligns the cap to the calendar ─── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-replacement2: cancelling the plan re-aligns the cap to calendar bounds, counter zeroed")}`, + async () => { + const freePlan = products.base({ + id: "uw-replace-cancel", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-replace-cancel-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2, + }); + + // Flush the counter to Postgres before the cache-invalidating change + // (the rebuild re-seeds counters from PG). + await timeout(4000); + + // Cancel the plan immediately: no ents remain for the feature. + await autumnV2_3.subscriptions.update({ + customer_id: customerId, + plan_id: freePlan.id, + cancel_action: "cancel_immediately", + }); + + // The cap entry survives on billing_controls with no anchor: the window + // re-aligns to the UTC calendar, zeroing the counter with the move. + const customer = await autumnV2_3.customers.get(customerId); + // biome-ignore lint/suspicious/noExplicitAny: response inspected loosely + const limit = (customer as any).billing_controls?.usage_limits?.find( + // biome-ignore lint/suspicious/noExplicitAny: response inspected loosely + (entry: any) => entry.feature_id === TestFeature.Messages, + ); + expect(limit).toBeDefined(); + expect(limit.limit).toBe(5); + expect(limit.usage ?? 0).toBe(0); + + // Sanity: the calendar window the cap now lives on is derivable. + const calendar = getUsageWindowBounds({ + interval: EntInterval.Month, + now: Date.now(), + }); + expect(calendar.windowEndAt).toBeGreaterThan(Date.now()); + }, +); diff --git a/server/tests/integration/balances/usage-windows/plan-changes/plan-change-scheduled.test.ts b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-scheduled.test.ts new file mode 100644 index 000000000..c02f64e8a --- /dev/null +++ b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-scheduled.test.ts @@ -0,0 +1,170 @@ +/** + * TDD test for usage windows across a SCHEDULED downgrade (premium -> pro at + * cycle end, triggered by advancing the test clock past the next invoice). + * + * Contract under test (WINDOW-IDENTITY rule): + * - while the downgrade is only scheduled, the cap keeps binding on the + * premium cycle (counter untouched by scheduling) + * - the scheduled switch preserves the billing cycle, so the pro-anchored + * bracket equals the premium one: anchor-only re-point, count KEPT. + * + * NOTE: in production the switch fires exactly when the old window closes by + * WALL clock, so the count zeroes there via natural expiry. Test clocks + * can't show that (windows live on server wall time); this test pins the + * re-anchor + carry half of the contract. + */ + +import { expect, test } from "bun:test"; +import { ApiVersion, EntInterval, getUsageWindowBounds } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { hoursToFinalizeInvoice } from "@tests/utils/constants.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { advanceTestClock } from "@tests/utils/stripeUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { addMonths } from "date-fns"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + fetchActivePlanCusEnt, + fetchUsageWindowRows, +} from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("uw-plan-change-scheduled1: premium -> pro at cycle end re-keys the window to the pro cycle")}`, + async () => { + const premium = products.premium({ + id: "premium", + items: [items.monthlyMessages({ includedUsage: 200 })], + }); + const pro = products.pro({ + id: "pro", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-sched-downgrade-1"; + const { + ctx, + autumnV1, + testClockId: maybeTestClockId, + advancedTo, + } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [premium, pro] }), + ], + actions: [s.billing.attach({ productId: premium.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Schedule the downgrade: premium keeps running (canceling), pro is + // scheduled. The cap is untouched by the scheduling itself. + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: pro.id, + redirect_mode: "if_required", + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // Flush the counter, then turn the cycle: premium ends, pro activates. + await timeout(4000); + const testClockId = maybeTestClockId as string; + expect(testClockId).toBeTruthy(); + await advanceTestClock({ + stripeCli: ctx.stripeCli, + testClockId, + advanceTo: addMonths(new Date(advancedTo ?? Date.now()), 1).getTime(), + waitForSeconds: 30, + }); + await advanceTestClock({ + stripeCli: ctx.stripeCli, + testClockId, + numberOfHours: hoursToFinalizeInvoice, + startingFrom: addMonths(new Date(advancedTo ?? Date.now()), 1), + waitForSeconds: 30, + }); + + // Cycle preserved across the switch: anchor re-points, count carried. + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // PG: the live row anchors to the pro ent; bounds derive from its + // next_reset_at (computed, since the test clock runs ahead of wall time). + await timeout(4000); + const proEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(proEnt?.next_reset_at).toBeTruthy(); + + const expectedBounds = getUsageWindowBounds({ + interval: EntInterval.Month, + now: Date.now(), + anchor: Number(proEnt.next_reset_at), + }); + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const currentRow = windowRows.find( + (row) => Number(row.window_end_at) === expectedBounds.windowEndAt, + ); + expect(currentRow).toBeDefined(); + expect(Number(currentRow.usage)).toBe(5); + // Bounds (above) prove pro-cycle alignment; the anchor id itself depends + // on which of the plan's ents the resolver tie-breaks to post-switch. + expect(currentRow.anchor_customer_entitlement_id).not.toBeNull(); + expect(Number(currentRow.window_start_at)).toBe( + expectedBounds.windowStartAt, + ); + expect(Number(currentRow.window_end_at)).toBe(expectedBounds.windowEndAt); + }, +); diff --git a/server/tests/integration/balances/usage-windows/plan-changes/plan-change-update.test.ts b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-update.test.ts new file mode 100644 index 000000000..c8f0ce812 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-update.test.ts @@ -0,0 +1,209 @@ +/** + * TDD tests for usage windows across SUBSCRIPTION UPDATES (customize items, + * non-patch PUT). + * + * Contract under test (anchor-only re-point): + * - a customize that keeps the feature's reset cadence PRESERVES the + * cycle: the recreated main ent's next_reset_at equals the pre-update + * value, the usage-window anchor follows a cycle-bearing ent, the window + * does not move, and the counter is NOT zeroed (only the anchor + * re-points to the new ent id) + * - this holds both for a base-price-only customize and for updating the + * capped item itself (includedUsage bump) + */ + +import { expect, test } from "bun:test"; +import { ApiVersion, ResetInterval } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + fetchActivePlanCusEnt, + fetchUsageWindowRows, +} from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: price-only customization never resets the counter ───── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-update1: a base-price-only customize preserves the cycle and the counter")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-update-price-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Flush the counter to Postgres before the cache-invalidating change + // (the rebuild re-seeds counters from PG). + await timeout(4000); + const entBefore = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(entBefore?.next_reset_at).toBeTruthy(); + + // Customize the base price only; the messages item is untouched. + await autumnV2_3.subscriptions.update({ + customer_id: customerId, + plan_id: pro.id, + customize: { + price: { amount: 50, interval: "month" }, + }, + }); + + // ── Contract: a price-only customize doesn't touch the ent at all ── + const entAfter = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(entAfter?.id).toBe(entBefore.id); + expect(Number(entAfter?.next_reset_at)).toBe( + Number(entBefore.next_reset_at), + ); + + // ── Contract: same window => anchor-only re-point, count KEPT ───── + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // ── Contract: the cap keeps binding (track 5 clamps to headroom 2) ─ + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // ── Contract: PG row stayed on the preserved cycle ──────────────── + await timeout(4000); + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(windowRows).toHaveLength(1); + expect(Number(windowRows[0].usage)).toBe(5); + expect(Number(windowRows[0].window_end_at)).toBe( + Number(entBefore.next_reset_at), + ); + }, +); + +// ── Contract: updating the capped item preserves the cycle + counter ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-update2: updating the capped item preserves the cycle and the counter")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-update-item-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Flush the counter to Postgres before the cache-invalidating change + // (the rebuild re-seeds counters from PG). + await timeout(4000); + + // Bump the capped item's included usage (100 -> 500): the cadence is + // unchanged, so the cycle is preserved and the counter survives. + await autumnV2_3.subscriptions.update({ + customer_id: customerId, + plan_id: pro.id, + customize: { + items: [ + { + feature_id: TestFeature.Messages, + included: 500, + reset: { interval: ResetInterval.Month }, + }, + ], + }, + }); + + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // Still binding on the enlarged balance: track 5 clamps to 2. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/plan-changes/plan-change-upgrade.test.ts b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-upgrade.test.ts new file mode 100644 index 000000000..43b31d3a1 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/plan-changes/plan-change-upgrade.test.ts @@ -0,0 +1,313 @@ +/** + * TDD tests for usage-window behavior on IMMEDIATE plan upgrades. + * + * Contract under test (WINDOW-IDENTITY rule): the count belongs to the + * window, not the plan. Upgrades here PRESERVE the billing cycle (same + * window bounds; the recomputed next_reset_at is precision-corrected by + * applyExistingNextResetAts), so an upgrade is an anchor-only re-point: + * - the counter SURVIVES the upgrade -- no fresh cap headroom mid-cycle + * - a counter AT the cap stays exhausted through the upgrade + * - multi-balance: the carried cap keeps binding; the top-up persists + * A cycle-RESTARTING change would move the window and zero (see the + * computeUsageWindowRolls unit table). + */ + +import { expect, test } from "bun:test"; +import { type ApiCustomerV5, ApiVersion } from "@autumn/shared"; +import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { + fetchActivePlanCusEnt, + fetchUsageWindowRows, +} from "../../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: upgrade resets the window, aligned to the new cycle ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-upgrade1: pro -> premium carries the counter; window_end == new ent next_reset_at")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + const premium = products.premium({ + id: "premium", + items: [items.monthlyMessages({ includedUsage: 200 })], + }); + + const customerId = "uw-upgrade-reset-1"; + const { ctx, autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro, premium] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // Immediate upgrade: pro is expired, premium's cycle starts now. + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: premium.id, + redirect_mode: "if_required", + }); + + // Cycle preserved: anchor-only re-point, the counter SURVIVES. + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // Only the remaining headroom (2) applies from a track of 5. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + granted: 200, + remaining: 198, + usage: 2, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // PG: the live counter row is anchored to the premium ent's cycle. + await timeout(4000); + const premiumEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(premiumEnt?.next_reset_at).toBeTruthy(); + + const windowRows = await fetchUsageWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const currentRow = windowRows.find( + (row) => Number(row.window_end_at) === Number(premiumEnt.next_reset_at), + ); + expect(currentRow).toBeDefined(); + expect(Number(currentRow.usage)).toBe(5); + expect(currentRow.anchor_customer_entitlement_id).toBe(premiumEnt.id); + }, +); + +// ── Contract: an exhausted cap yields fresh headroom post-upgrade ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-upgrade2: a counter AT the cap stays exhausted through the upgrade")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + const premium = products.premium({ + id: "premium", + items: [items.monthlyMessages({ includedUsage: 200 })], + }); + + const customerId = "uw-upgrade-atcap-1"; + const { autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro, premium] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Exhaust the cap; the next track fully clamps. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + }); + + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: premium.id, + redirect_mode: "if_required", + }); + + // Cycle preserved: the cap stays exhausted, a track fully clamps. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + granted: 200, + remaining: 200, + usage: 0, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); + +// ── Contract: multi-balance — cap resets, loose top-up untouched ── +test.concurrent( + `${chalk.yellowBright("uw-plan-change-upgrade3: cap on credits carries through upgrade while the top-up balance persists")}`, + async () => { + const pro = products.pro({ + id: "pro", + items: [items.monthlyCredits({ includedUsage: 3 })], + }); + const premium = products.premium({ + id: "premium", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-upgrade-multibal-1"; + const { autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success", testClock: false }), + s.products({ list: [pro, premium] }), + ], + actions: [s.billing.attach({ productId: pro.id })], + }); + + // Loose lifetime top-up alongside the plan credits. + await autumnV2_3.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Credits, + included_grant: 50, + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + }); + + // Track 5 credits: drains the 3 monthly then 2 from the top-up; the + // counter sums both (cap exhausted). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 5, + limit: 5, + }); + + await autumnV1.billing.attach({ + customer_id: customerId, + product_id: premium.id, + redirect_mode: "if_required", + }); + + // The counter carried (cap still exhausted); the top-up too. + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 5, + limit: 5, + }); + const postUpgrade = + await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: postUpgrade, + featureId: TestFeature.Credits, + // premium's fresh 100 + the top-up's 50 (2 already used pre-upgrade: + // the loose grant carries its usage across the plan change). + granted: 150, + remaining: 148, + usage: 2, + }); + + // No fresh headroom: a further track fully clamps. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 5, + limit: 5, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-api.test.ts b/server/tests/integration/balances/usage-windows/usage-window-api.test.ts new file mode 100644 index 000000000..ccc9f1287 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-api.test.ts @@ -0,0 +1,189 @@ +import { test } from "bun:test"; +import { + type ApiCustomerV5, + ApiVersion, + type CustomerBillingControls, + ErrCode, + ResetInterval, +} from "@autumn/shared"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { setCustomerUsageLimit } from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// Usage-window API surface: what the HTTP contract exposes and guards around +// windowed caps (not deduction outcomes). + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// The usage_limits entry in the customer response exposes the current window +// usage. +test.concurrent( + `${chalk.yellowBright("usage-window-api1: usage_limits exposes the current window usage")}`, + async () => { + const customerProduct = products.base({ + id: "uw-api-counter", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-api-counter-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + const customer = await autumnV2_3.customers.get(customerId); + expectUsageLimitCorrect({ + customer, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + interval: ResetInterval.Month, + }); + }, +); + +// set_usage must be rejected when the feature has an enforced usage window; +// otherwise it bypasses the hard cap (it carries no window provenance). +test.concurrent( + `${chalk.yellowBright("usage-window-api2: set_usage is rejected when the feature has a usage window")}`, + async () => { + const customerProduct = products.base({ + id: "uw-api-setusage", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-api-setusage-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await expectAutumnError({ + errCode: ErrCode.SetUsageNotAllowedWithUsageLimit, + func: async () => + await autumnV2_3.balances.update({ + customer_id: customerId, + feature_id: TestFeature.Messages, + current_balance: 50, + }), + }); + }, +); + +// usage_limits entries are strictly validated on write: limit, interval, and +// feature_id are all required, and one_off (never-resetting) windows are not +// supported. +test.concurrent( + `${chalk.yellowBright("usage-window-api3: usage_limits entries are validated (interval required, no one_off)")}`, + async () => { + const customerProduct = products.base({ + id: "uw-api-validate", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-api-validate-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + // Missing interval. + await expectAutumnError({ + func: async () => + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [{ feature_id: TestFeature.Messages, limit: 5 }], + } as unknown as CustomerBillingControls, + }), + }); + + // one_off window. + await expectAutumnError({ + func: async () => + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [ + { + feature_id: TestFeature.Messages, + limit: 5, + interval: "one_off", + }, + ], + } as unknown as CustomerBillingControls, + }), + }); + + // Missing limit. + await expectAutumnError({ + func: async () => + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [ + { feature_id: TestFeature.Messages, interval: "month" }, + ], + } as unknown as CustomerBillingControls, + }), + }); + + // Duplicate feature_id entries. + await expectAutumnError({ + func: async () => + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [ + { + feature_id: TestFeature.Messages, + limit: 5, + interval: "month", + }, + { + feature_id: TestFeature.Messages, + limit: 10, + interval: "day", + }, + ], + } as unknown as CustomerBillingControls, + }), + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-check.test.ts b/server/tests/integration/balances/usage-windows/usage-window-check.test.ts new file mode 100644 index 000000000..ec773f58f --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-check.test.ts @@ -0,0 +1,188 @@ +/** + * TDD tests for usage-limit awareness in check / lock / finalize. + * + * Contract under test: + * Pure check: + * - required_balance > window headroom (balance sufficient) -> allowed: false + * - required_balance <= headroom -> allowed: true; balance never deducted + * - metered cap on a credit-system member feature converts via credit_cost + * + * Lock / finalize contracts live in usage-window-lock.test.ts. + */ + +import { expect, test } from "bun:test"; +import { ApiVersion } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerBalance, + setCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: pure check is gated by window headroom ────────────── +test.concurrent( + `${chalk.yellowBright("usage-window-check1: pure check respects window headroom (metered cap)")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-pure", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-check-pure-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Headroom is 2: a check within it is allowed... + const within = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 2, + }); + expect(within.allowed).toBe(true); + + // ...and a check beyond it is rejected, despite 97 balance remaining. + const beyond = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 3, + }); + expect(beyond.allowed).toBe(false); + + // Pure checks never consume anything. + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 97, + usage: 3, + }); + }, +); + +// ── Contract: pure check converts metered caps via credit_cost ──── +test.concurrent( + `${chalk.yellowBright("usage-window-check2: pure check converts a metered cap on a credit-funded feature")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-convert", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-check-convert-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + // 3 of 5 action1 units used (= 0.6 credits). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 3, + }); + + const within = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 2, + }); + expect(within.allowed).toBe(true); + + // 3 more units exceed the 5-unit cap while ~99.4 credits remain. + const beyond = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 3, + }); + expect(beyond.allowed).toBe(false); + }, +); + +// ── Contract: a cap on another feature never gates this one ─────── +test.concurrent( + `${chalk.yellowBright("usage-window-check6: an exhausted action1 cap does not gate a credits check")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-scope", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-check-scope-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + // Exhaust the action1 cap. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + // Sanity: the cap binds its own feature... + const action1Check = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 1, + }); + expect(action1Check.allowed).toBe(false); + + // ...but a direct credits check sails through on the 99 remaining credits. + const creditsCheck = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Credits, + required_balance: 50, + }); + expect(creditsCheck.allowed).toBe(true); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-enforcement.test.ts b/server/tests/integration/balances/usage-windows/usage-window-enforcement.test.ts new file mode 100644 index 000000000..55e2cc018 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-enforcement.test.ts @@ -0,0 +1,697 @@ +import { expect, test } from "bun:test"; +import type { ApiCustomerV5 } from "@autumn/shared"; +import { + ApiVersion, + type CustomerBillingControls, + ResetInterval, +} from "@autumn/shared"; +import { expectCustomerEventsCorrect } from "@tests/integration/balances/utils/events/expectCustomerEventsCorrect.js"; +import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect.js"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// Usage-window ENFORCEMENT: what a track actually applies under a windowed +// cap (the deduction-script path). Covers both cap dimensions -- +// metered_feature (cap counts tracked units) and balance (cap counts credits +// drained) -- including credit conversion, multi-cusEnt deductions, the +// compound overage_limit case, and concurrency. + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// Credit system: 100 credits, 1 action1 = 0.2 credits (see v2Features.ts). +// A cap of 5 action1 units consumes only 1 credit, so the cap must clamp the +// 6th unit while ~99 credits remain, proving it's a second, independent +// dimension, not a balance check. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement1: metered cap clamps the over-cap unit while credits remain")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-metered", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-enforce-metered-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + // Consume exactly up to the cap: 5 action1 units = 1 credit deducted. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + + // The 6th unit is over the cap, so it clamps to 0: the track succeeds but + // applies nothing, leaving credits unchanged. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + }, +); + +// An over-cap track applies what fits (the remaining headroom) instead of +// rejecting the whole track. cap 5, track 10 from 0 -> applies 5 (not 10, not +// a 400). +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement2: over-cap track applies what fits (clamp, not reject)")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-clamp", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-enforce-clamp-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Track 10 against a cap of 5 (from 0): clamps to 5, returns 200, not a reject. + const clamped = await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 10, + }); + expect(clamped.value).toBe(10); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 95, + usage: 5, + }); + + // At the cap: a further track applies 0 (fully clamped), still 200. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 95, + usage: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); + +// Metered cap with TWO credits cusEnts (monthly + lifetime): the clamped +// deduction must drain the monthly bucket first, spill exactly 1 credit into +// lifetime, and stop there. 1 action1 = 0.2 credits, so the 10-unit cap is +// worth exactly 2 credits. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement3: metered cap clamps across monthly then lifetime credits cusEnts")}`, + async () => { + const monthlyCreditsItem = items.monthlyCredits({ includedUsage: 1 }); + const lifetimeCreditsItem = constructFeatureItem({ + featureId: TestFeature.Credits, + includedUsage: 10, + interval: null, + }); + const freePlan = products.base({ + id: "uw-enforce-metered-multi", + items: [monthlyCreditsItem, lifetimeCreditsItem], + }); + + const customerId = "uw-enforce-metered-multi-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 10, + }); + + // Track 15 action1 against a 10/month cap: clamps to 10 units = 2 credits. + // 1 credit drains the monthly cusEnt, 1 comes out of lifetime. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 15, + }); + + const afterClamp = + await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: afterClamp, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + breakdown: { + [ResetInterval.Month]: { + included_grant: 1, + remaining: 0, + usage: 1, + }, + [ResetInterval.OneOff]: { + included_grant: 10, + remaining: 9, + usage: 1, + }, + }, + }); + + // At the cap: a further track applies 0, so lifetime credits stay put. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + const atCap = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: atCap, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + breakdown: { + [ResetInterval.Month]: { remaining: 0, usage: 1 }, + [ResetInterval.OneOff]: { remaining: 9, usage: 1 }, + }, + }); + expectUsageLimitCorrect({ + customer: atCap, + featureId: TestFeature.Action1, + usage: 10, + limit: 10, + }); + }, +); + +// Balance-dim cap: the cap is denominated in CREDITS (cap on the credit pool +// itself), enforced while monthly credits remain. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement4: balance cap (1 credit/day) clamps while monthly credits remain")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-balance", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-enforce-balance-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + // 1 action1 = 0.2 credits, so 5 action1 = exactly 1 credit (the daily cap). + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 1, + interval: ResetInterval.Day, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + + // The credit-pool window is exhausted: the next track clamps to 0 (the + // window shortfall flows through the standard 'cap' overage behaviour), + // leaving the ~99 remaining monthly credits untouched. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 99, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 1, + limit: 1, + }); + }, +); + +// Balance-dim cap with TWO credits cusEnts: credits drained from BOTH must +// count toward the window. (The old entitlement-anchored counter only saw the +// anchor cusEnt's drain, silently under-counting multi-cusEnt consumption.) +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement5: balance cap counts drains across monthly and lifetime cusEnts")}`, + async () => { + const monthlyCreditsItem = items.monthlyCredits({ includedUsage: 1 }); + const lifetimeCreditsItem = constructFeatureItem({ + featureId: TestFeature.Credits, + includedUsage: 10, + interval: null, + }); + const freePlan = products.base({ + id: "uw-enforce-balance-multi", + items: [monthlyCreditsItem, lifetimeCreditsItem], + }); + + const customerId = "uw-enforce-balance-multi-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + // Cap the credit pool at 2/day. 1 action1 = 0.2 credits, so 10 action1 = + // 2 credits: 1 drains the monthly cusEnt, 1 spills into lifetime. Both + // drains must land on the same customer-level counter. + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 2, + interval: ResetInterval.Day, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 10, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + }); + + // The counter saw the full 2 credits (1 monthly + 1 lifetime), so the cap + // is exhausted: the next consumption clamps to 0 instead of being served + // from the 9 remaining lifetime credits. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + const afterClamp = + await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: afterClamp, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + }); + + // usage is served from the customer-scoped counter. + expectUsageLimitCorrect({ + customer: afterClamp, + featureId: TestFeature.Credits, + usage: 2, + limit: 2, + }); + }, +); + +// An overage spend_limit and a usage limit on the SAME feature are separate +// billing controls that coexist: the window must still clamp on its own. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement6: a usage limit clamps alongside an overage spend_limit on the same feature")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-compound", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-enforce-compound-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const billingControls: CustomerBillingControls = { + spend_limits: [ + { + feature_id: TestFeature.Action1, + enabled: true, + overage_limit: 20, + }, + ], + usage_limits: [ + { + feature_id: TestFeature.Action1, + limit: 5, + interval: ResetInterval.Month, + }, + ], + }; + await timeout(2000); + await autumnV2_3.customers.update(customerId, { + billing_controls: billingControls, + }); + await timeout(3000); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + // The window cap clamps the over-cap unit to 0 (the overage path is separate), + // so the track succeeds and credits are unchanged. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + remaining: 99, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + }, +); + +// Two concurrent tracks on the SAME customer's SAME window must serialize +// (Redis runs each deduction Lua atomically): combined value exceeds the cap, +// so the second track clamps and the counter reflects exactly the capped usage. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement7: concurrent tracks on one window serialize, total clamped to the cap")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-concurrent", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = `uw-enforce-concurrent-${Date.now()}`; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + // Cap action1 at 5/month; two concurrent tracks of 5 each => combined 10 > 5. + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + const results = await Promise.allSettled([ + autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }), + autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }), + ]); + + // Both succeed (clamp, not reject), but the window clamps the combined + // applied usage to the cap: one applies 5, the other clamps to 0. + expect(results.every((result) => result.status === "fulfilled")).toBe(true); + + await timeout(2000); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + remaining: 99, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + + // BOTH tracks record events (clamped tracks too, matching how + // balance-clamped tracks have always behaved): events reflect requests. + await expectCustomerEventsCorrect({ + customerId, + events: [{ value: 5 }, { value: 5 }], + }); + }, +); + +// Metered cap funded by MIXED entitlements: a native action1 cusEnt AND a +// credits cusEnt (different conversion rates in one deduction). Deduction +// order is native-first (see track-credit-system3), so a clamped track of 10 +// against cap 8 drains the 5 native units, then 3 units via credits at 0.2 +// credits/unit -- the counter must see all 8 tracked units across both. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement8: metered cap counts units across native and credit-system cusEnts")}`, + async () => { + const action1Item = items.free({ + featureId: TestFeature.Action1, + includedUsage: 5, + }); + const creditsItem = items.monthlyCredits({ includedUsage: 100 }); + const freePlan = products.base({ + id: "uw-enforce-mixed", + items: [action1Item, creditsItem], + }); + + const customerId = "uw-enforce-mixed-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 8, + }); + + // Track 10 against cap 8: applies 8 -- the native pool's 5 units, then 3 + // units from credits (3 x 0.2 = 0.6 credits). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 10, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + granted: 5, + remaining: 0, + usage: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 99.4, + usage: 0.6, + }); + + // The counter saw all 8 units (5 native + 3 credit-funded): exhausted, so + // a further track clamps to 0 despite the 99.4 remaining credits. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + const atCap = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: atCap, + featureId: TestFeature.Credits, + remaining: 99.4, + usage: 0.6, + }); + + expectUsageLimitCorrect({ + customer: atCap, + featureId: TestFeature.Action1, + usage: 8, + limit: 8, + }); + }, +); + +// A cap counts ITS OWN dimension: a metered cap on action1 must neither gate +// nor be incremented by tracking the credits feature directly, even when the +// cap is fully exhausted. +test.concurrent( + `${chalk.yellowBright("usage-window-enforcement9: an action1 cap does not touch direct credits tracking")}`, + async () => { + const customerProduct = products.base({ + id: "uw-enforce-scope", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-enforce-scope-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + limit: 5, + }); + + // Exhaust the action1 cap (5 units = 1 credit). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + // Direct credits tracking applies IN FULL: no clamp from the action1 cap... + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 10, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 89, + usage: 11, + }); + + // ...and the action1 counter never moved. + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-lock.test.ts b/server/tests/integration/balances/usage-windows/usage-window-lock.test.ts new file mode 100644 index 000000000..61ac126a4 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-lock.test.ts @@ -0,0 +1,315 @@ +/** + * TDD tests for usage limits on the LOCK / FINALIZE flow. + * + * Contract under test: + * - a lock is gated by window headroom and counts at lock time + * - finalize at the lock value does not double count + * - finalize below the lock decrements the counter (freed headroom reusable) + * - finalize ABOVE the lock is capped at the window limit: only the + * remaining headroom of the extra delta applies, and it is counted + */ + +import { expect, test } from "bun:test"; +import { ApiVersion } from "@autumn/shared"; +import { deleteLock } from "@tests/integration/balances/utils/lockUtils/deleteLock.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// ── Contract: locks are gated by headroom and count at lock time ── +test.concurrent( + `${chalk.yellowBright("usage-window-lock1: a lock consumes window headroom; an over-cap lock is rejected")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-lock", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-check-lock-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + await deleteLock({ ctx, lockId: `${customerId}-a` }); + await deleteLock({ ctx, lockId: `${customerId}-b` }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Lock 4 of the 5-unit cap: granted, and counted at lock time. + const granted = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 4, + lock: { enabled: true, lock_id: `${customerId}-a` }, + }); + expect(granted.allowed).toBe(true); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 96, + usage: 4, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 4, + limit: 5, + }); + + // A second lock of 2 exceeds the remaining headroom of 1: rejected, and + // neither the balance nor the counter moves. + const rejected = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 2, + lock: { enabled: true, lock_id: `${customerId}-b` }, + }); + expect(rejected.allowed).toBe(false); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 96, + usage: 4, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 4, + limit: 5, + }); + }, +); + +// ── Contract: finalize at lock value does not double count ──────── +test.concurrent( + `${chalk.yellowBright("usage-window-lock2: finalize at the lock value leaves the counter unchanged")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-confirm", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-check-confirm-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + await deleteLock({ ctx, lockId: customerId }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 3, + lock: { enabled: true, lock_id: customerId }, + }); + + await autumnV2_3.balances.finalize({ + lock_id: customerId, + action: "confirm", + }); + + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 97, + usage: 3, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + }, +); + +// ── Contract: finalize below the lock decrements the counter ────── +test.concurrent( + `${chalk.yellowBright("usage-window-lock3: finalize below the lock value frees window headroom")}`, + async () => { + const freePlan = products.base({ + id: "uw-check-unwind", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-check-unwind-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + await deleteLock({ ctx, lockId: customerId }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Lock 4, finalize at 1: the unwind must give 3 units of headroom back. + await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 4, + lock: { enabled: true, lock_id: customerId }, + }); + await autumnV2_3.balances.finalize({ + lock_id: customerId, + action: "confirm", + override_value: 1, + }); + + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 99, + usage: 1, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 1, + limit: 5, + }); + + // The freed headroom (4) is consumable: a track of 5 clamps to 4. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 95, + usage: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); + +// ── Contract: finalize above the lock is capped at the window limit ── +test.concurrent( + `${chalk.yellowBright("usage-window-lock4: finalize above the lock value is capped at the window limit")}`, + async () => { + const freePlan = products.base({ + id: "uw-lock-overfinal", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-lock-overfinal-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + await deleteLock({ ctx, lockId: customerId }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Lock 3 of the 5-unit cap (counter 3, headroom 2)... + const granted = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Messages, + required_balance: 3, + lock: { enabled: true, lock_id: customerId }, + }); + expect(granted.allowed).toBe(true); + + // ...then finalize at 6: the extra 3 must clamp to the remaining headroom + // of 2, landing the final usage exactly at the cap. + await autumnV2_3.balances.finalize({ + lock_id: customerId, + action: "confirm", + override_value: 6, + }); + + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 95, + usage: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // The cap is exhausted: a further track fully clamps. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 95, + usage: 5, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-multi-feature-caps.test.ts b/server/tests/integration/balances/usage-windows/usage-window-multi-feature-caps.test.ts new file mode 100644 index 000000000..71b0bd95f --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-multi-feature-caps.test.ts @@ -0,0 +1,177 @@ +import { expect, test } from "bun:test"; +import { ApiVersion, ResetInterval } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +/** + * TDD test for INDIVIDUAL usage limits on two member features (action1 + + * action2) of one credit system: each cap gates only its own feature while + * both features drain the shared credits pool. + * Credit costs: 1 action1 = 0.2 credits, 1 action2 = 0.6 credits. + * + * Contract under test: + * - caps armed together: action1 -> 5 units, action2 -> 4 units + * - each feature's checks/tracks are gated by ITS cap only; exhausting + * action1's cap leaves action2 open + * - both drain shared credits: final usage = 5*0.2 + 4*0.6 = 3.4 credits + * - each usage_limits entry reports its own window usage + * - a direct credits check is not gated by either member cap + */ + +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +test.concurrent( + `${chalk.yellowBright("uw-multi-cap1: individual caps on action1 and action2 over shared credits")}`, + async () => { + const freePlan = products.base({ + id: "uw-multi-cap-credits", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-multi-cap-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + // Arm BOTH caps in one update (billing_controls replaces the array). + await timeout(2000); + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [ + { + feature_id: TestFeature.Action1, + limit: 5, + interval: ResetInterval.Month, + }, + { + feature_id: TestFeature.Action2, + limit: 4, + interval: ResetInterval.Month, + }, + ], + }, + }); + await timeout(3000); + + // ── action1: 3 of 5 used (0.6 credits) ── + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 3, + }); + + const action1Within = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 2, + }); + expect(action1Within.allowed).toBe(true); + + const action1Beyond = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 3, + }); + expect(action1Beyond.allowed).toBe(false); + + // ── action2 is untouched by action1's usage: its own cap (4) gates it ── + const action2Fresh = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action2, + required_balance: 4, + }); + expect(action2Fresh.allowed).toBe(true); + + const action2FreshBeyond = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action2, + required_balance: 5, + }); + expect(action2FreshBeyond.allowed).toBe(false); + + // ── action2: 2 of 4 used (1.2 credits) ── + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action2, + value: 2, + }); + + // ── Exhaust action1 (track 5, clamps to 2 -> 5 total = 1 credit) ── + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + const action1Exhausted = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action1, + required_balance: 1, + }); + expect(action1Exhausted.allowed).toBe(false); + + // action2 still open for its remaining 2 units. + const action2StillOpen = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Action2, + required_balance: 2, + }); + expect(action2StillOpen.allowed).toBe(true); + + // ── Exhaust action2 (track 5, clamps to 2 -> 4 total = 2.4 credits) ── + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action2, + value: 5, + }); + + // ── Shared pool drained by both: 5*0.2 + 4*0.6 = 3.4 credits ── + await timeout(3000); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 96.6, + usage: 3.4, + }); + + // ── Each entry reports its own window usage ── + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action1, + usage: 5, + limit: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Action2, + usage: 4, + limit: 4, + }); + + // ── Neither member cap gates a direct credits check ── + const creditsCheck = await autumnV2_3.check({ + customer_id: customerId, + feature_id: TestFeature.Credits, + required_balance: 50, + }); + expect(creditsCheck.allowed).toBe(true); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-own-feature.test.ts b/server/tests/integration/balances/usage-windows/usage-window-own-feature.test.ts new file mode 100644 index 000000000..4baca374d --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-own-feature.test.ts @@ -0,0 +1,310 @@ +import { test } from "bun:test"; +import type { ApiCustomerV5 } from "@autumn/shared"; +import { ApiVersion, ResetInterval } from "@autumn/shared"; +import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect.js"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// Usage windows where the cap is set on the TRACKED feature's own id (no +// credit-system indirection): the customer holds cusEnts of the capped +// feature directly, including MULTIPLE cusEnts whose drains must aggregate +// onto one customer-scoped counter, and interval mismatches between the +// cusEnt's reset and the cap's window. + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// Same feature, same interval (credits/mo cusEnt + credits 5/mo cap), tracked +// directly: the cap binds independently of the 100-credit balance. +test.concurrent( + `${chalk.yellowBright("usage-window-own-feature1: cap on the tracked feature itself clamps at the cap")}`, + async () => { + const customerProduct = products.base({ + id: "uw-own-same-interval", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-own-same-interval-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + }); + + // Consume exactly to the cap, tracking the capped feature directly. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 5, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 95, + usage: 5, + }); + + // Over the cap: clamps to 0 with 95 credits still in the balance. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 95, + usage: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 5, + limit: 5, + }); + }, +); + +// Sub-interval cap: the cusEnt resets monthly but the cap windows daily. +test.concurrent( + `${chalk.yellowBright("usage-window-own-feature2: daily cap on a monthly cusEnt clamps while balance remains")}`, + async () => { + const customerProduct = products.base({ + id: "uw-own-day-cap", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-own-day-cap-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 2, + interval: ResetInterval.Day, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + + // The day window is exhausted; the next track clamps to 0 against the 98 + // remaining monthly credits. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + granted: 100, + remaining: 98, + usage: 2, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + usage: 2, + limit: 2, + }); + }, +); + +// MULTIPLE cusEnts of the capped feature (monthly 1 + lifetime 10): one +// direct track spans both, and both drains must land on the same counter. +test.concurrent( + `${chalk.yellowBright("usage-window-own-feature3: direct track across two cusEnts aggregates onto one counter")}`, + async () => { + const monthlyCreditsItem = items.monthlyCredits({ includedUsage: 1 }); + const lifetimeCreditsItem = constructFeatureItem({ + featureId: TestFeature.Credits, + includedUsage: 10, + interval: null, + }); + const freePlan = products.base({ + id: "uw-own-multi-credit", + items: [monthlyCreditsItem, lifetimeCreditsItem], + }); + + const customerId = "uw-own-multi-credit-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 2, + interval: ResetInterval.Day, + }); + + // Track 2 credits directly: 1 drains the monthly cusEnt, 1 spills into + // lifetime. The counter must see the SUM (2), not one cusEnt's drain. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 2, + }); + const atCap = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: atCap, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + breakdown: { + [ResetInterval.Month]: { remaining: 0, usage: 1 }, + [ResetInterval.OneOff]: { remaining: 9, usage: 1 }, + }, + }); + + // Cap exhausted: clamps to 0 instead of draining the 9 lifetime credits. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Credits, + value: 1, + }); + const afterClamp = + await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: afterClamp, + featureId: TestFeature.Credits, + granted: 11, + remaining: 9, + usage: 2, + }); + + expectUsageLimitCorrect({ + customer: afterClamp, + featureId: TestFeature.Credits, + usage: 2, + limit: 2, + }); + }, +); + +// Metered (non-credit) own-feature cap across two cusEnts: messages monthly +// 100 + lifetime 100, cap 150/mo. The second track must clamp to the exact +// remaining headroom after the first track spanned both cusEnts. +test.concurrent( + `${chalk.yellowBright("usage-window-own-feature4: metered cap aggregates across two cusEnts and clamps to headroom")}`, + async () => { + const monthlyMessagesItem = items.monthlyMessages({ includedUsage: 100 }); + const lifetimeMessagesItem = items.lifetimeMessages({ + includedUsage: 100, + }); + const freePlan = products.base({ + id: "uw-own-multi-messages", + items: [monthlyMessagesItem, lifetimeMessagesItem], + }); + + const customerId = "uw-own-multi-messages-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 150, + }); + + // Track 120: drains the monthly cusEnt (100) then 20 from lifetime. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 120, + }); + const afterSpan = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: afterSpan, + featureId: TestFeature.Messages, + granted: 200, + remaining: 80, + usage: 120, + breakdown: { + [ResetInterval.Month]: { remaining: 0, usage: 100 }, + [ResetInterval.OneOff]: { remaining: 80, usage: 20 }, + }, + }); + + // Counter = 120 across both cusEnts, so headroom is 30: track 50 applies + // exactly 30 from lifetime. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 50, + }); + const atCap = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: atCap, + featureId: TestFeature.Messages, + granted: 200, + remaining: 50, + usage: 150, + breakdown: { + [ResetInterval.Month]: { remaining: 0, usage: 100 }, + [ResetInterval.OneOff]: { remaining: 50, usage: 50 }, + }, + }); + + expectUsageLimitCorrect({ + customer: atCap, + featureId: TestFeature.Messages, + usage: 150, + limit: 150, + }); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-persistence.test.ts b/server/tests/integration/balances/usage-windows/usage-window-persistence.test.ts new file mode 100644 index 000000000..b98990bd6 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-persistence.test.ts @@ -0,0 +1,416 @@ +import { expect, test } from "bun:test"; +import { + type ApiCustomerV5, + ApiVersion, + EntInterval, + ResetInterval, +} from "@autumn/shared"; +import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect.js"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { sql } from "drizzle-orm"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; +import { + expectCustomerBalance, + expectCustomerUsageLimit, + setCustomerUsageLimit, +} from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { expireUsageWindowForReset } from "../utils/usage-limit-utils/expireUsageWindowForReset.js"; + +// Usage-window PERSISTENCE: the counter's fate across cache-invalidating +// events (config changes, re-grants, plan replacement) and cache loss. The +// counter survives via the batched Redis->PG sync + rebuild rehydration, so +// these tests wait ~4s after capped tracks before invalidating mutations. + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped +const queryRows = (result: unknown): any[] => + // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped + Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); + +// No manual sync flush: the counter must survive the mutation's cache +// invalidation on its own, else the cap silently resets and hands out fresh +// headroom. +test.concurrent( + `${chalk.yellowBright("usage-window-persistence1: lowering the cap below current usage keeps the counter (clamps, no reset)")}`, + async () => { + const customerProduct = products.base({ + id: "uw-persist-lowercap", + items: [items.monthlyMessages({ includedUsage: 1000 })], + }); + + const customerId = "uw-persist-lowercap-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 10, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 8, + }); + + // Let the batched sync flush the counter to Postgres before the + // cache-invalidating mutation (the rebuild rehydrates from PG). + await timeout(4000); + + await autumnV2_3.customers.update(customerId, { + billing_controls: { + usage_limits: [ + { + feature_id: TestFeature.Messages, + limit: 3, + interval: ResetInterval.Month, + }, + ], + }, + }); + + // The counter survived the cap change, so the next track fully clamps. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + remaining: 992, + usage: 8, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 8, + limit: 3, + }); + + // Write-through: after the sync flush, the same state must come back from + // Postgres (skip_cache bypasses Redis entirely). + await timeout(4000); + const fromDb = await autumnV2_3.customers.get(customerId, { + skip_cache: "true", + }); + expectBalanceCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + remaining: 992, + usage: 8, + }); + expectUsageLimitCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + usage: 8, + limit: 3, + }); + }, +); + +// A second balance grant (balances.create) is a cache-invalidating mutation; +// the cap counter must survive it. It used to reset to 0, opening fresh +// headroom. +test.concurrent( + `${chalk.yellowBright("usage-window-persistence2: the cap counter survives a re-grant (clamps)")}`, + async () => { + const customerProduct = products.base({ + id: "uw-persist-regrant", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-persist-regrant-1"; + await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + }); + + // Let the batched sync flush the counter to Postgres before the + // cache-invalidating mutation (the rebuild rehydrates from PG). + await timeout(4000); + + // Re-grant a second balance for the same feature while at the cap. + await autumnV2_3.post("/balances.create", { + customer_id: customerId, + feature_id: TestFeature.Messages, + included_grant: 100, + reset: { interval: EntInterval.Month }, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + await expectCustomerBalance({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + }); + await expectCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // Write-through: the post-regrant counter must come back from Postgres. + await timeout(4000); + const fromDb = await autumnV2_3.customers.get(customerId, { + skip_cache: "true", + }); + expectBalanceCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + granted: 200, + remaining: 195, + usage: 5, + }); + expectUsageLimitCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); + +// A missing '_usage_windows' field FAILS OPEN (deliberate for v1): the track +// succeeds and the window simply restarts from zero. This documents the +// accepted trade-off -- a lost counter field grants fresh headroom rather than +// erroring. Stale-cache guards may return in a future iteration. +test.concurrent( + `${chalk.yellowBright("usage-window-persistence4: missing _usage_windows field fails open (counter restarts)")}`, + async () => { + const freePlan = products.base({ + id: "uw-persist-failopen", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-persist-failopen-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Establish a counter. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2, + }); + + // Simulate a stale/partial cache: the counter field vanishes while the + // subject view stays valid. + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Messages, + }); + await ctx.redisV2.hdel(balanceKey, "_usage_windows"); + + // Fail open: the track succeeds; the window restarted, so only this track + // counts toward the cap (balance itself is unaffected: 3 tracked total). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 1, + }); + const afterRestart = + await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: afterRestart, + featureId: TestFeature.Messages, + remaining: 97, + usage: 3, + }); + expectUsageLimitCorrect({ + customer: afterRestart, + featureId: TestFeature.Messages, + usage: 1, + limit: 5, + }); + + // Enforcement continues from the restarted counter: headroom is 4, so a + // track of 5 clamps to 4. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 5, + }); + const atCap = await autumnV2_3.customers.get(customerId); + expectBalanceCorrect({ + customer: atCap, + featureId: TestFeature.Messages, + remaining: 93, + usage: 7, + }); + expectUsageLimitCorrect({ + customer: atCap, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + + // Write-through: the restarted counter upserts over the pre-restart row + // (same logical window key) and must come back from Postgres. + await timeout(4000); + const fromDb = await autumnV2_3.customers.get(customerId, { + skip_cache: "true", + }); + expectBalanceCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + remaining: 93, + usage: 7, + }); + expectUsageLimitCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + usage: 5, + limit: 5, + }); + }, +); + +// Lazy roll: a counter whose stored window closed must zero IN PLACE on any +// subject read -- and two CONCURRENT reads must both succeed (the roll is +// idempotent: PG update by id, atomic Lua cache patch). +test.concurrent( + `${chalk.yellowBright("usage-window-persistence5: an expired counter rolls to zero on (concurrent) reads")}`, + async () => { + const freePlan = products.base({ + id: "uw-persist-lazyreset", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-persist-lazyreset-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2, + }); + + // Flush, then close the counter's window in both stores. + await timeout(4000); + await expireUsageWindowForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + + // Concurrent reads: both succeed and both report the rolled count. + const [customer, concurrentCustomer] = await Promise.all([ + autumnV2_3.customers.get(customerId), + autumnV2_3.customers.get(customerId), + ]); + expectUsageLimitCorrect({ + customer, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + expectUsageLimitCorrect({ + customer: concurrentCustomer, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + + // The row persists, zeroed, with bounds advanced to the live cycle. + const rows = queryRows( + await ctx.db.execute(sql` + SELECT usage, window_end_at FROM usage_windows + WHERE feature_id = ${TestFeature.Messages} + AND internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + `), + ); + expect(rows).toHaveLength(1); + expect(Number(rows[0].usage)).toBe(0); + expect(Number(rows[0].window_end_at)).toBeGreaterThan(Date.now()); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-reset.test.ts b/server/tests/integration/balances/usage-windows/usage-window-reset.test.ts new file mode 100644 index 000000000..163aba43b --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-reset.test.ts @@ -0,0 +1,383 @@ +import { expect, test } from "bun:test"; +import { type ApiCustomerV5, ApiVersion } from "@autumn/shared"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import type { TestContext } from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { sql } from "drizzle-orm"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; +import { setCustomerUsageLimit } from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; +import { expireUsageWindowForReset } from "../utils/usage-limit-utils/expireUsageWindowForReset.js"; +import { fetchActivePlanCusEnt } from "../utils/usage-limit-utils/usageWindowDbTestUtils.js"; + +// Usage-window LAZY ROLL, per read path (mirrors reset/get-customer-reset): +// once a counter's stored window closes, ANY subject read -- DB (skip_cache), +// cached, or entity-scoped -- must report usage 0 and ROLL the row in place +// (usage zeroed, bounds advanced to the current derivation) in PG + cache. + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped +const queryRows = (result: unknown): any[] => + // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped + Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); + +const HOUR_MS = 60 * 60 * 1000; + +const fetchWindowRows = async ({ + ctx, + customerId, + featureId, +}: { + ctx: TestContext; + customerId: string; + featureId: string; +}) => + queryRows( + await ctx.db.execute(sql` + SELECT id, window_end_at, usage FROM usage_windows + WHERE feature_id = ${featureId} + AND internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + `), + ); + +// ───────────────────────────────────────────────────────────────── +// GET /customers (skip_cache) — DB path lazy reset +// ───────────────────────────────────────────────────────────────── + +test.concurrent( + `${chalk.yellowBright("usage-window-reset1 (DB): skip_cache GET prunes an expired window")}`, + async () => { + const freePlan = products.base({ + id: "uw-reset-db", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-reset-db-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Flush the counter to Postgres, then close its window in both stores. + await timeout(4000); + expect( + await fetchWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }), + ).toHaveLength(1); + await expireUsageWindowForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + + // DB-path read: reports a fresh window and prunes the expired row. + const fromDb = await autumnV2_3.customers.get(customerId, { + skip_cache: "true", + }); + expectUsageLimitCorrect({ + customer: fromDb, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + + // The row PERSISTS, rolled in place: usage zeroed, bounds advanced to the + // current derivation (the messages ent's cycle). + const messagesEnt = await fetchActivePlanCusEnt({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + const rolledRows = await fetchWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rolledRows).toHaveLength(1); + expect(Number(rolledRows[0].usage)).toBe(0); + expect(Number(rolledRows[0].window_end_at)).toBe( + Number(messagesEnt.next_reset_at), + ); + + // The cache field is rolled too (the DB-path roll patches both stores). + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Messages, + }); + const rolledJson = await ctx.redisV2.hget(balanceKey, "_usage_windows"); + expect(rolledJson).toBeTruthy(); + const rolledCache = JSON.parse(rolledJson as string); + expect(rolledCache).toHaveLength(1); + expect(Number(rolledCache[0].usage)).toBe(0); + }, +); + +// ───────────────────────────────────────────────────────────────── +// GET /customers (cached) — cache path lazy reset +// ───────────────────────────────────────────────────────────────── + +test.concurrent( + `${chalk.yellowBright("usage-window-reset2 (cache): cached GET prunes an expired window")}`, + async () => { + const freePlan = products.base({ + id: "uw-reset-cache", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-reset-cache-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 3, + }); + + // Warm the cache with the live counter visible. + const warm = await autumnV2_3.customers.get(customerId); + expectUsageLimitCorrect({ + customer: warm, + featureId: TestFeature.Messages, + usage: 3, + limit: 5, + }); + + // Flush to Postgres, then close the window in both stores. + await timeout(4000); + await expireUsageWindowForReset({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + + // Cache-path read: reports a fresh window and prunes the expired row. + const cached = await autumnV2_3.customers.get(customerId); + expectUsageLimitCorrect({ + customer: cached, + featureId: TestFeature.Messages, + usage: 0, + limit: 5, + }); + + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Messages, + }); + const rolledJson = await ctx.redisV2.hget(balanceKey, "_usage_windows"); + expect(rolledJson).toBeTruthy(); + const rolledCache = JSON.parse(rolledJson as string); + expect(rolledCache).toHaveLength(1); + expect(Number(rolledCache[0].usage)).toBe(0); + + const rolledRows = await fetchWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(rolledRows).toHaveLength(1); + expect(Number(rolledRows[0].usage)).toBe(0); + }, +); + +// ───────────────────────────────────────────────────────────────── +// GET /entities — entity-subject path lazy reset +// ───────────────────────────────────────────────────────────────── + +// Entity-scoped usage limits aren't writable in v1, but the roll machinery +// must already handle entity-scoped counter rows (seeded here directly) so the +// future entity path inherits a working lazy roll. The customer-scoped live +// counter must survive the entity-scoped zeroing. +test.concurrent( + `${chalk.yellowBright("usage-window-reset3 (entity): an entity read zeroes its expired window, customer counter untouched")}`, + async () => { + const freePlan = products.base({ + id: "uw-reset-entity", + items: [ + items.monthlyMessages({ includedUsage: 100 }), + items.monthlyUsers({ includedUsage: 5 }), + ], + }); + + const customerId = "uw-reset-entity-1"; + const { ctx, entities } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freePlan] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.billing.attach({ productId: freePlan.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Messages, + limit: 5, + }); + + // Live CUSTOMER-scoped counter in the current window; flush it to + // Postgres so the end-state assertion can see it. + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 2, + }); + await timeout(4000); + + const customerRow = queryRows( + await ctx.db.execute(sql` + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + `), + )[0]; + expect(customerRow?.internal_id).toBeTruthy(); + const entityRow = queryRows( + await ctx.db.execute(sql` + SELECT internal_id FROM entities + WHERE internal_customer_id = ${customerRow.internal_id} + AND id = ${entities[0].id} + LIMIT 1 + `), + )[0]; + expect(entityRow?.internal_id).toBeTruthy(); + const messagesEnt = queryRows( + await ctx.db.execute(sql` + SELECT internal_feature_id FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Messages} + LIMIT 1 + `), + )[0]; + expect(messagesEnt?.internal_feature_id).toBeTruthy(); + + // Seed a CLOSED, ENTITY-scoped window row into both stores. + const now = Date.now(); + const closedEntityWindow = { + id: "uw_test_entity_closed", + internal_customer_id: customerRow.internal_id, + internal_entity_id: entityRow.internal_id, + feature_id: TestFeature.Messages, + internal_feature_id: messagesEnt.internal_feature_id, + anchor_customer_entitlement_id: null, + window_start_at: now - 3 * HOUR_MS, + window_end_at: now - HOUR_MS, + usage: 4, + updated_at: now, + }; + await ctx.db.execute(sql` + INSERT INTO usage_windows ( + id, internal_customer_id, internal_entity_id, feature_id, + internal_feature_id, anchor_customer_entitlement_id, + window_start_at, window_end_at, usage, updated_at + ) VALUES ( + ${closedEntityWindow.id}, ${closedEntityWindow.internal_customer_id}, + ${closedEntityWindow.internal_entity_id}, ${closedEntityWindow.feature_id}, + ${closedEntityWindow.internal_feature_id}, NULL, + ${closedEntityWindow.window_start_at}, ${closedEntityWindow.window_end_at}, + ${closedEntityWindow.usage}, ${closedEntityWindow.updated_at} + ) + `); + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Messages, + }); + const liveJson = await ctx.redisV2.hget(balanceKey, "_usage_windows"); + expect(liveJson).toBeTruthy(); + await ctx.redisV2.hset( + balanceKey, + "_usage_windows", + JSON.stringify([...JSON.parse(liveJson as string), closedEntityWindow]), + ); + + // An ENTITY read runs the lazy reset on the entity subject, which carries + // the entity-scoped rows. + await autumnV2_3.entities.get(customerId, entities[0].id); + + // The expired entity row persists, ZEROED in place... + const entityRows = queryRows( + await ctx.db.execute(sql` + SELECT id, usage FROM usage_windows WHERE id = ${closedEntityWindow.id} + `), + ); + expect(entityRows).toHaveLength(1); + expect(Number(entityRows[0].usage)).toBe(0); + + // ...and in the cache field, while the live customer-scoped counter + // survives untouched. + const rolledJson = await ctx.redisV2.hget(balanceKey, "_usage_windows"); + expect(rolledJson).toBeTruthy(); + const rolledWindows = JSON.parse(rolledJson as string) as { + internal_entity_id: string | null; + usage: number; + }[]; + expect(rolledWindows).toHaveLength(2); + const customerRowCached = rolledWindows.find( + (w) => w.internal_entity_id == null, + ); + const entityRowCached = rolledWindows.find( + (w) => w.internal_entity_id != null, + ); + expect(Number(customerRowCached?.usage)).toBe(2); + expect(Number(entityRowCached?.usage)).toBe(0); + + const allRows = await fetchWindowRows({ + ctx, + customerId, + featureId: TestFeature.Messages, + }); + expect(allRows).toHaveLength(2); + }, +); diff --git a/server/tests/integration/balances/usage-windows/usage-window-sync.test.ts b/server/tests/integration/balances/usage-windows/usage-window-sync.test.ts new file mode 100644 index 000000000..a9efc0c58 --- /dev/null +++ b/server/tests/integration/balances/usage-windows/usage-window-sync.test.ts @@ -0,0 +1,392 @@ +import { expect, test } from "bun:test"; +import { ApiVersion, ResetInterval } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { sql } from "drizzle-orm"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js"; +import type { UsageWindowUpdate } from "@/internal/balances/utils/types/usageWindowUpdate.js"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; +import { setCustomerUsageLimit } from "../utils/usage-limit-utils/customerUsageLimitUtils.js"; + +// Usage-window SYNC & STORAGE: infrastructure state, not track responses. +// Where the counter lives in Redis (the reserved '_usage_windows' field), how +// it writes through to the customer-scoped usage_windows Postgres table, and +// the race-safety contract of that mirror (upsert on the logical key). + +// initScenario only exposes clients up to V2_2; build the latest-version client +// directly (same pattern as the v2.2-vs-v2.3 parity tests). +const autumnV2_3 = new AutumnInt({ version: ApiVersion.V2_3 }); + +// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped +const queryRows = (result: unknown): any[] => + // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped + Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); + +const HOUR_MS = 60 * 60 * 1000; + +// Storage shape: the counter lives in the capped feature's balance hash under +// the reserved '_usage_windows' field (customer-scoped rows), NOT inside any +// customer-entitlement blob. +test.concurrent( + `${chalk.yellowBright("usage-window-sync1: counter lives in the _usage_windows hash field, not the cus-ent blob")}`, + async () => { + const customerProduct = products.base({ + id: "uw-sync-storage", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-sync-storage-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + interval: ResetInterval.Day, + }); + + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 1, + }); + + const creditsEnt = queryRows( + await ctx.db.execute(sql` + SELECT id FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} + LIMIT 1 + `), + )[0]; + expect(creditsEnt?.id).toBeTruthy(); + + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Credits, + }); + + // Counter: one customer-scoped row in the reserved field, in credits units + // (1 action1 = 0.2 credits). + const usageWindowsJson = await ctx.redisV2.hget( + balanceKey, + "_usage_windows", + ); + expect(usageWindowsJson).toBeTruthy(); + const usageWindows = JSON.parse(usageWindowsJson as string); + expect(Array.isArray(usageWindows)).toBe(true); + expect(usageWindows).toHaveLength(1); + expect(usageWindows[0].feature_id).toBe(TestFeature.Credits); + expect(usageWindows[0].internal_entity_id).toBeNull(); + expect(typeof usageWindows[0].id).toBe("string"); + expect(Number(usageWindows[0].usage)).toBe(0.2); + + // The cus-ent blob no longer embeds windows. + const blobJson = await ctx.redisV2.hget(balanceKey, creditsEnt.id); + expect(blobJson).toBeTruthy(); + const blob = JSON.parse(blobJson as string); + expect(blob.usage_windows).toBeUndefined(); + + // The hash must carry a TTL (counters never outlive the cache contract). + const ttl = await ctx.redisV2.ttl(balanceKey); + expect(ttl).toBeGreaterThan(0); + }, +); + +// Write-through: the Redis counter must reach the customer-scoped +// usage_windows table via the shared sync. +test.concurrent( + `${chalk.yellowBright("usage-window-sync2: window counter writes through to the usage_windows table")}`, + async () => { + const customerProduct = products.base({ + id: "uw-sync-write", + items: [items.monthlyCredits({ includedUsage: 100 })], + }); + + const customerId = "uw-sync-write-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + await setCustomerUsageLimit({ + autumn: autumnV2_3, + customerId, + featureId: TestFeature.Credits, + limit: 5, + interval: ResetInterval.Day, + }); + + // 5 action1 = 1 credit; under the 5-credit/day cap. The counter is a + // customer-scoped row on the credits feature (balance dimension). + await autumnV2_3.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 5, + }); + + const customerRow = queryRows( + await ctx.db.execute(sql` + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + `), + )[0]; + expect(customerRow?.internal_id).toBeTruthy(); + + const creditsEnt = queryRows( + await ctx.db.execute(sql` + SELECT id, internal_feature_id FROM customer_entitlements + WHERE customer_id = ${customerId} AND feature_id = ${TestFeature.Credits} + LIMIT 1 + `), + )[0]; + expect(creditsEnt?.id).toBeTruthy(); + + // Build the typed update from the live counter field (production hands + // this down from the Lua result), then drive the write-through. + const usageWindowsJson = await ctx.redisV2.hget( + buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId: TestFeature.Credits, + }), + "_usage_windows", + ); + expect(usageWindowsJson).toBeTruthy(); + + await syncItemV4({ + ctx, + payload: { + customerId, + orgId: ctx.org.id, + env: ctx.env, + timestamp: Date.now(), + modifiedCusEntIdsByFeatureId: { + [TestFeature.Credits]: [creditsEnt.id], + }, + usageWindowUpdates: [ + { + internal_customer_id: customerRow.internal_id, + feature_id: TestFeature.Credits, + usage_windows: JSON.parse(usageWindowsJson as string), + }, + ], + }, + }); + + const windowRows = queryRows( + await ctx.db.execute(sql` + SELECT feature_id, internal_feature_id, internal_entity_id, + anchor_customer_entitlement_id, usage + FROM usage_windows + WHERE internal_customer_id = ${customerRow.internal_id} + AND feature_id = ${TestFeature.Credits} + `), + ); + expect(windowRows).toHaveLength(1); + expect(windowRows[0].feature_id).toBe(TestFeature.Credits); + expect(windowRows[0].internal_feature_id).toBe( + creditsEnt.internal_feature_id, + ); + // Customer scope (no entity) with bounds provenance from the credits ent. + expect(windowRows[0].internal_entity_id).toBeNull(); + expect(windowRows[0].anchor_customer_entitlement_id).toBe(creditsEnt.id); + expect(Number(windowRows[0].usage)).toBe(1); + }, +); + +/** + * Race-safety contract of the PG mirror (sync_balances_v2 STEP 4): + * - CREATE: syncing a snapshot for a scope with no existing row inserts it + * with the snapshot's id. + * - CONCURRENT CREATE: two parallel syncs for the same scope key + * (internal_customer_id, feature_id, entity-nullsafe) with DIFFERENT + * candidate ids both succeed -- no unique-violation abort -- and exactly + * one row exists, id = one of the candidates. + * - LAST-WRITE-WINS: the row's usage ends at the snapshot with the highest + * updated_at; a stale snapshot synced later never clobbers a newer value. + * - ID STABILITY: a newer snapshot with a different id updates the row but + * never changes the stored id (DO UPDATE excludes id). + * - ROLL FORWARD: a snapshot with advanced bounds moves the SAME row's + * window_start_at/window_end_at in place (one mutable row per scope). + */ +test.concurrent( + `${chalk.yellowBright("usage-window-sync3: PG mirror upserts on the scope key, race-safe on create")}`, + async () => { + const customerProduct = products.base({ + id: "uw-sync-upsert", + items: [items.monthlyMessages({ includedUsage: 100 })], + }); + + const customerId = "uw-sync-upsert-1"; + const { ctx } = await initScenario({ + customerId, + setup: [ + s.customer({ testClock: false }), + s.products({ list: [customerProduct] }), + ], + actions: [s.billing.attach({ productId: customerProduct.id })], + }); + + const customerRow = queryRows( + await ctx.db.execute(sql` + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + `), + )[0]; + expect(customerRow?.internal_id).toBeTruthy(); + const internalCustomerId = customerRow.internal_id as string; + + const messagesFeature = ctx.features.find( + (feature) => feature.id === TestFeature.Messages, + ); + expect(messagesFeature?.internal_id).toBeTruthy(); + + const now = Date.now(); + const activeWindowStart = now - HOUR_MS; + const activeWindowEnd = now + HOUR_MS; + + const buildWindowRow = ({ + id, + usage, + updatedAt, + windowStartAt = activeWindowStart, + windowEndAt = activeWindowEnd, + }: { + id: string; + usage: number; + updatedAt: number; + windowStartAt?: number; + windowEndAt?: number; + }) => ({ + id, + internal_customer_id: internalCustomerId, + internal_entity_id: null, + feature_id: TestFeature.Messages, + internal_feature_id: messagesFeature?.internal_id as string, + anchor_customer_entitlement_id: null, + window_start_at: windowStartAt, + window_end_at: windowEndAt, + usage, + updated_at: updatedAt, + }); + + const syncSnapshot = (usageWindows: UsageWindowUpdate["usage_windows"]) => + syncItemV4({ + ctx, + payload: { + customerId, + orgId: ctx.org.id, + env: ctx.env, + timestamp: Date.now(), + modifiedCusEntIdsByFeatureId: {}, + usageWindowUpdates: [ + { + internal_customer_id: internalCustomerId, + feature_id: TestFeature.Messages, + usage_windows: usageWindows, + }, + ], + }, + }); + + const fetchWindowRows = async () => + queryRows( + await ctx.db.execute(sql` + SELECT id, window_start_at, window_end_at, usage, updated_at + FROM usage_windows + WHERE internal_customer_id = ${internalCustomerId} + AND feature_id = ${TestFeature.Messages} + ORDER BY window_start_at ASC + `), + ); + + // ── CONCURRENT CREATE ──────────────────────────────────────────────── + // Two parallel syncs race to create the same logical window with + // different candidate ids: no unique-violation abort; one row, id from + // whichever inserted first. + const candidateA = buildWindowRow({ + id: "uw_test_a", + usage: 5, + updatedAt: now - 2000, + }); + const candidateB = buildWindowRow({ + id: "uw_test_b", + usage: 7, + updatedAt: now - 1000, + }); + + await Promise.all([syncSnapshot([candidateA]), syncSnapshot([candidateB])]); + + let windowRows = await fetchWindowRows(); + expect(windowRows).toHaveLength(1); + expect(["uw_test_a", "uw_test_b"]).toContain(windowRows[0].id); + const createdId = windowRows[0].id as string; + + // ── LAST-WRITE-WINS across orderings ───────────────────────────────── + // Whichever commit order the race produced, the surviving usage must be + // the snapshot with the HIGHEST updated_at (candidateB: 7). + expect(Number(windowRows[0].usage)).toBe(7); + + // A stale snapshot (older updated_at) synced afterwards must not clobber. + await syncSnapshot([ + buildWindowRow({ id: "uw_test_stale", usage: 6, updatedAt: now - 5000 }), + ]); + windowRows = await fetchWindowRows(); + expect(windowRows).toHaveLength(1); + expect(Number(windowRows[0].usage)).toBe(7); + + // ── ID STABILITY ───────────────────────────────────────────────────── + // A newer snapshot carrying a DIFFERENT candidate id (e.g. a fail-open + // counter restart minted a fresh ksuid) updates usage on the logical key + // but never replaces the stored id. + await syncSnapshot([ + buildWindowRow({ id: "uw_test_c", usage: 9, updatedAt: now }), + ]); + windowRows = await fetchWindowRows(); + expect(windowRows).toHaveLength(1); + expect(Number(windowRows[0].usage)).toBe(9); + expect(windowRows[0].id).toBe(createdId); + + // ── ROLL FORWARD ───────────────────────────────────────────────────── + // A newer snapshot with ADVANCED bounds moves the same row in place: + // still exactly one row per scope, same id, new window. + const rolledStart = now + HOUR_MS; + const rolledEnd = now + 2 * HOUR_MS; + await syncSnapshot([ + buildWindowRow({ + id: "uw_test_rolled", + usage: 0, + updatedAt: now + 1000, + windowStartAt: rolledStart, + windowEndAt: rolledEnd, + }), + ]); + windowRows = await fetchWindowRows(); + expect(windowRows).toHaveLength(1); + expect(windowRows[0].id).toBe(createdId); + expect(Number(windowRows[0].usage)).toBe(0); + expect(Number(windowRows[0].window_start_at)).toBe(rolledStart); + expect(Number(windowRows[0].window_end_at)).toBe(rolledEnd); + }, +); diff --git a/server/tests/integration/balances/utils/usage-limit-utils/customerUsageLimitUtils.ts b/server/tests/integration/balances/utils/usage-limit-utils/customerUsageLimitUtils.ts new file mode 100644 index 000000000..9a5cfca58 --- /dev/null +++ b/server/tests/integration/balances/utils/usage-limit-utils/customerUsageLimitUtils.ts @@ -0,0 +1,90 @@ +import { + type ApiCustomerV5, + type CustomerBillingControls, + ResetInterval, +} from "@autumn/shared"; +import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect.js"; +import { expectUsageLimitCorrect } from "@tests/integration/utils/expectUsageLimitCorrect.js"; +import type { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { timeout } from "@/utils/genUtils.js"; + +/** + * Arms a windowed hard usage cap via the customer's `usage_limits` billing + * control; `interval` sets the window. + */ +export const setCustomerUsageLimit = async ({ + autumn, + customerId, + featureId, + limit, + interval = ResetInterval.Month, +}: { + autumn: AutumnInt; + customerId: string; + featureId: string; + limit: number; + interval?: ResetInterval; +}) => { + const billingControls: CustomerBillingControls = { + usage_limits: [ + { + feature_id: featureId, + limit, + interval, + }, + ], + }; + + await timeout(2000); + await autumn.customers.update(customerId, { + billing_controls: billingControls, + }); + await timeout(3000); +}; + +/** Fetches the customer (cached read) and asserts a feature balance. */ +export const expectCustomerBalance = async ({ + autumn, + customerId, + featureId, + granted, + remaining, + usage, +}: { + autumn: AutumnInt; + customerId: string; + featureId: string; + granted?: number; + remaining?: number; + usage?: number; +}) => { + const customer = await autumn.customers.get(customerId); + expectBalanceCorrect({ customer, featureId, granted, remaining, usage }); +}; + +/** + * Fetches the customer and asserts the usage_limits entry's current window + * `usage` (and optionally the configured limit). `skipCache` reads through to + * Postgres, verifying the synced counter rather than the Redis one. + */ +export const expectCustomerUsageLimit = async ({ + autumn, + customerId, + featureId, + usage, + limit, + skipCache = false, +}: { + autumn: AutumnInt; + customerId: string; + featureId: string; + usage?: number; + limit?: number; + skipCache?: boolean; +}) => { + const customer = await autumn.customers.get( + customerId, + skipCache ? { skip_cache: "true" } : undefined, + ); + expectUsageLimitCorrect({ customer, featureId, usage, limit }); +}; diff --git a/server/tests/integration/balances/utils/usage-limit-utils/entityUsageLimitUtils.ts b/server/tests/integration/balances/utils/usage-limit-utils/entityUsageLimitUtils.ts new file mode 100644 index 000000000..bc19abbad --- /dev/null +++ b/server/tests/integration/balances/utils/usage-limit-utils/entityUsageLimitUtils.ts @@ -0,0 +1,87 @@ +import { expect } from "bun:test"; +import { + type ApiEntityV2, + type EntityBillingControls, + ResetInterval, +} from "@autumn/shared"; +import type { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { timeout } from "@/utils/genUtils.js"; + +/** + * Arms a windowed hard usage cap via the ENTITY's `usage_limits` billing + * control (sibling of setCustomerUsageLimit / setEntitySpendLimit). + */ +export const setEntityUsageLimit = async ({ + autumn, + customerId, + entityId, + featureId, + limit, + interval = ResetInterval.Month, +}: { + autumn: AutumnInt; + customerId: string; + entityId: string; + featureId: string; + limit: number; + interval?: ResetInterval; +}) => { + const billingControls: EntityBillingControls = { + usage_limits: [ + { + feature_id: featureId, + limit, + interval, + }, + ], + }; + + await timeout(2000); + await autumn.entities.update(customerId, entityId, { + billing_controls: billingControls, + }); + await timeout(3000); +}; + +/** + * Fetches the entity and asserts its own `billing_controls.usage_limits` + * entry: configured `limit` and the current window's `usage`. + */ +export const expectEntityUsageLimit = async ({ + autumn, + customerId, + entityId, + featureId, + usage, + limit, + skipCache = false, +}: { + autumn: AutumnInt; + customerId: string; + entityId: string; + featureId: string; + usage?: number; + limit?: number; + skipCache?: boolean; +}) => { + const entity = await autumn.entities.get( + customerId, + entityId, + skipCache ? { skip_cache: "true" } : undefined, + ); + const usageLimit = entity.billing_controls?.usage_limits?.find( + (entry) => entry.feature_id === featureId, + ); + expect( + usageLimit, + `Missing entity usage_limits entry for ${featureId}`, + ).toBeDefined(); + + if (typeof limit !== "undefined") { + expect(usageLimit?.limit).toBe(limit); + } + + if (typeof usage !== "undefined") { + expect(usageLimit?.usage ?? 0).toBe(usage); + } +}; diff --git a/server/tests/integration/balances/utils/usage-limit-utils/expireUsageWindowForReset.ts b/server/tests/integration/balances/utils/usage-limit-utils/expireUsageWindowForReset.ts new file mode 100644 index 000000000..cc51fd718 --- /dev/null +++ b/server/tests/integration/balances/utils/usage-limit-utils/expireUsageWindowForReset.ts @@ -0,0 +1,59 @@ +import type { TestContext } from "@tests/utils/testInitUtils/createTestContext.js"; +import { sql } from "drizzle-orm"; +import { buildSharedFullSubjectBalanceKey } from "@/internal/customers/cache/fullSubject/builders/buildSharedFullSubjectBalanceKey.js"; + +const THIRTY_FIVE_DAYS_MS = 35 * 24 * 60 * 60 * 1000; + +/** + * Backdates a feature's usage-window counters in BOTH stores so the window is + * wall-clock closed -- the windows analog of expireCusEntForReset. The next + * subject read should lazily prune the rows. + */ +export const expireUsageWindowForReset = async ({ + ctx, + customerId, + featureId, + shiftMs = THIRTY_FIVE_DAYS_MS, +}: { + ctx: TestContext; + customerId: string; + featureId: string; + shiftMs?: number; +}): Promise => { + await ctx.db.execute(sql` + UPDATE usage_windows + SET window_start_at = window_start_at - ${shiftMs}, + window_end_at = window_end_at - ${shiftMs} + WHERE feature_id = ${featureId} + AND internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + `); + + const balanceKey = buildSharedFullSubjectBalanceKey({ + orgId: ctx.org.id, + env: ctx.env, + customerId, + featureId, + }); + const rawWindows = await ctx.redisV2.hget(balanceKey, "_usage_windows"); + if (!rawWindows) return; + + // biome-ignore lint/suspicious/noExplicitAny: raw cached rows are untyped + const backdated = (JSON.parse(rawWindows) as any[]).map((usageWindow) => + usageWindow.feature_id === featureId + ? { + ...usageWindow, + window_start_at: Number(usageWindow.window_start_at) - shiftMs, + window_end_at: Number(usageWindow.window_end_at) - shiftMs, + } + : usageWindow, + ); + await ctx.redisV2.hset( + balanceKey, + "_usage_windows", + JSON.stringify(backdated), + ); +}; diff --git a/server/tests/integration/balances/utils/usage-limit-utils/usageWindowDbTestUtils.ts b/server/tests/integration/balances/utils/usage-limit-utils/usageWindowDbTestUtils.ts new file mode 100644 index 000000000..4370c6918 --- /dev/null +++ b/server/tests/integration/balances/utils/usage-limit-utils/usageWindowDbTestUtils.ts @@ -0,0 +1,88 @@ +import type { TestContext } from "@tests/utils/testInitUtils/createTestContext.js"; +import { sql } from "drizzle-orm"; + +// biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped +export const queryRows = (result: unknown): any[] => + // biome-ignore lint/suspicious/noExplicitAny: raw SQL rows are untyped + Array.isArray(result) ? result : ((result as { rows?: any[] })?.rows ?? []); + +/** The feature's cusEnt on the customer's ACTIVE plan (excludes loose grants). */ +export const fetchActivePlanCusEnt = async ({ + ctx, + customerId, + featureId, +}: { + ctx: TestContext; + customerId: string; + featureId: string; +}) => { + const rows = queryRows( + await ctx.db.execute(sql` + SELECT ce.id, ce.internal_feature_id, ce.next_reset_at + FROM customer_entitlements ce + JOIN customer_products cp ON cp.id = ce.customer_product_id + WHERE ce.internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + AND ce.feature_id = ${featureId} + AND cp.status = 'active' + LIMIT 1 + `), + ); + return rows[0]; +}; + +/** The feature's loose (product-less) cusEnt, e.g. a top-up grant. */ +export const fetchLooseCusEnt = async ({ + ctx, + customerId, + featureId, +}: { + ctx: TestContext; + customerId: string; + featureId: string; +}) => { + const rows = queryRows( + await ctx.db.execute(sql` + SELECT id, internal_feature_id, next_reset_at + FROM customer_entitlements + WHERE internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + AND feature_id = ${featureId} + AND customer_product_id IS NULL + ORDER BY created_at ASC + LIMIT 1 + `), + ); + return rows[0]; +}; + +/** All usage-window counter rows for a (customer, feature). */ +export const fetchUsageWindowRows = async ({ + ctx, + customerId, + featureId, +}: { + ctx: TestContext; + customerId: string; + featureId: string; +}) => + queryRows( + await ctx.db.execute(sql` + SELECT id, anchor_customer_entitlement_id, internal_entity_id, + window_start_at, window_end_at, usage + FROM usage_windows + WHERE feature_id = ${featureId} + AND internal_customer_id = ( + SELECT internal_id FROM customers + WHERE id = ${customerId} AND org_id = ${ctx.org.id} AND env = ${ctx.env} + LIMIT 1 + ) + ORDER BY window_start_at ASC + `), + ); diff --git a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts index c007c2710..ff470263b 100644 --- a/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts +++ b/server/tests/integration/db/full-subject/utils/fullSubjectScenarioBuilders.ts @@ -93,6 +93,7 @@ const buildCustomer = ({ send_email_receipts: false, auto_topups: null, spend_limits: null, + usage_limits: null, usage_alerts: null, overage_allowed: null, }); diff --git a/server/tests/integration/utils/expectUsageLimitCorrect.ts b/server/tests/integration/utils/expectUsageLimitCorrect.ts new file mode 100644 index 000000000..781bc097a --- /dev/null +++ b/server/tests/integration/utils/expectUsageLimitCorrect.ts @@ -0,0 +1,39 @@ +import { expect } from "bun:test"; +import type { ApiCustomerV5, ResetInterval } from "@autumn/shared"; + +const roundTo8Dp = (value: number) => Math.round(value * 1e8) / 1e8; + +/** Asserts the customer's `billing_controls.usage_limits` entry for a feature. */ +export const expectUsageLimitCorrect = ({ + customer, + featureId, + usage, + limit, + interval, +}: { + customer: ApiCustomerV5; + featureId: string; + usage?: number; + limit?: number; + interval?: ResetInterval; +}) => { + const usageLimit = customer.billing_controls?.usage_limits?.find( + (entry) => entry.feature_id === featureId, + ); + expect( + usageLimit, + `Missing usage_limits entry for ${featureId}`, + ).toBeDefined(); + + if (typeof limit !== "undefined") { + expect(usageLimit?.limit).toBe(limit); + } + + if (typeof interval !== "undefined") { + expect(usageLimit?.interval).toBe(interval); + } + + if (typeof usage !== "undefined") { + expect(roundTo8Dp(usageLimit?.usage ?? 0)).toBe(roundTo8Dp(usage)); + } +}; diff --git a/server/tests/unit/billing/interval/get-cycle-end/get-cycle-end-eom-clamp.test.ts b/server/tests/unit/billing/interval/get-cycle-end/get-cycle-end-eom-clamp.test.ts new file mode 100644 index 000000000..cd0374b95 --- /dev/null +++ b/server/tests/unit/billing/interval/get-cycle-end/get-cycle-end-eom-clamp.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, test } from "bun:test"; +import { BillingInterval, getCycleEnd, getCycleStart } from "@autumn/shared"; +import { fromUnix, toUnix } from "@tests/utils/testIntervalUtils/testUnixUtils"; + +/** + * TDD test for the date-fns clamp-shave bug: when `now` sits on a clamped + * end-of-month boundary 2+ cycles from the anchor, differenceInMonths + * under-counts by one (e.g. differenceInMonths(Apr 30, Jan 31) === 2, not 3), + * and getCycleEnd's single overshoot correction cannot recover. + * + * Red-failure mode (pre-fix): for a few hours after the clamped boundary, + * getCycleEnd returns the boundary itself — a cycle end AT OR BEFORE `now`. + * + * Green-success criteria: getCycleEnd is always strictly after `now`, and + * [getCycleStart, getCycleEnd) always brackets `now`. + * + * date-fns special-cases the 1-month clamp (isLastDayOfMonth && difference + * === 1), which the existing "31 Mar -> 30 Apr 12:01" tests exercise; these + * tests pin the multi-month clamps that the special case does not cover. + * + * Test suite (anchor 31 Jan 2026 10:00 unless noted): + * 1. now 30 Apr 09:00 (before boundary) -> 30 Apr 10:00 (control, passes pre-fix) + * 2. now 30 Apr 10:00 (boundary instant) -> 31 May 10:00 + * 3. now 30 Apr 11:00 (after boundary) -> 31 May 10:00 + * 4. (intervalCount = 3) now 30 Apr 11:00 -> 31 Jul 10:00 + * 5. anchor 31 Mar 12:00, now 30 Jun 12:01 (3 clamped months) -> 31 Jul 12:00 + * 6. annual leap anchor 29 Feb 2024, now 28 Feb 2025 22:00 -> 28 Feb 2026 + * 7. invariant: start <= now < end across the clamped boundary day + */ +describe("get-cycle-end-eom-clamp: clamped EOM boundary 2+ cycles from anchor", () => { + const anchor = toUnix({ year: 2026, month: 1, day: 31, hour: 10 }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 09:00 -> end of cycle should be 30 Apr 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 9 }); + const result = getCycleEnd({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(4); + expect(day).toBe(30); + expect(hour).toBe(10); + }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 10:00 (boundary instant) -> end of cycle should be 31 May 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 10 }); + const result = getCycleEnd({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(5); + expect(day).toBe(31); + expect(hour).toBe(10); + expect(result).toBeGreaterThan(now); + }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 11:00 -> end of cycle should be 31 May 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 11 }); + const result = getCycleEnd({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(5); + expect(day).toBe(31); + expect(hour).toBe(10); + expect(result).toBeGreaterThan(now); + }); + + test("(intervalCount = 3) anchor: 31 Jan 10:00, now: 30 Apr 11:00 -> end of cycle should be 31 Jul 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 11 }); + const result = getCycleEnd({ + anchor, + interval: BillingInterval.Month, + intervalCount: 3, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(7); + expect(day).toBe(31); + expect(hour).toBe(10); + expect(result).toBeGreaterThan(now); + }); + + test("anchor: 31 Mar 12:00, now: 30 Jun 12:01 (3 clamped months) -> end of cycle should be 31 Jul 12:00", () => { + const marchAnchor = toUnix({ year: 2026, month: 3, day: 31, hour: 12 }); + const now = toUnix({ + year: 2026, + month: 6, + day: 30, + hour: 12, + minute: 1, + }); + const result = getCycleEnd({ + anchor: marchAnchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(7); + expect(day).toBe(31); + expect(hour).toBe(12); + expect(result).toBeGreaterThan(now); + }); + + test("annual, leap anchor: 29 Feb 2024 12:00, now: 28 Feb 2025 22:00 -> end of cycle should be 28 Feb 2026 12:00", () => { + const leapAnchor = toUnix({ year: 2024, month: 2, day: 29, hour: 12 }); + const now = toUnix({ year: 2025, month: 2, day: 28, hour: 22 }); + const result = getCycleEnd({ + anchor: leapAnchor, + interval: BillingInterval.Year, + intervalCount: 1, + now, + }); + + const { year, month, day, hour } = fromUnix(result); + expect(year).toBe(2026); + expect(month).toBe(2); + expect(day).toBe(28); + expect(hour).toBe(12); + expect(result).toBeGreaterThan(now); + }); + + test("invariant: cycleStart <= now < cycleEnd across the clamped boundary day", () => { + const nows = [ + toUnix({ year: 2026, month: 4, day: 30, hour: 9 }), + toUnix({ year: 2026, month: 4, day: 30, hour: 10 }), + toUnix({ year: 2026, month: 4, day: 30, hour: 11 }), + toUnix({ year: 2026, month: 4, day: 30, hour: 23, minute: 59 }), + toUnix({ year: 2026, month: 5, day: 1, hour: 0, minute: 1 }), + ]; + + for (const now of nows) { + const start = getCycleStart({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + const end = getCycleEnd({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + expect(start).toBeLessThanOrEqual(now); + expect(end).toBeGreaterThan(now); + } + }); +}); diff --git a/server/tests/unit/billing/interval/get-cycle-start/get-cycle-start-eom-clamp.test.ts b/server/tests/unit/billing/interval/get-cycle-start/get-cycle-start-eom-clamp.test.ts new file mode 100644 index 000000000..8f0ec9131 --- /dev/null +++ b/server/tests/unit/billing/interval/get-cycle-start/get-cycle-start-eom-clamp.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, test } from "bun:test"; +import { BillingInterval, getCycleStart } from "@autumn/shared"; +import { fromUnix, toUnix } from "@tests/utils/testIntervalUtils/testUnixUtils"; + +/** + * TDD test for the date-fns clamp-shave bug: when `now` sits on a clamped + * end-of-month boundary 2+ cycles from the anchor, differenceInMonths + * under-counts by one (e.g. differenceInMonths(Apr 30, Jan 31) === 2, not 3). + * + * Red-failure mode (pre-fix): getCycleStart only corrects overshoot + * (estimate > now), never undershoot, so for the rest of the clamped + * boundary day it returns the PREVIOUS cycle's start (one cycle stale). + * + * Green-success criteria: getCycleStart returns the latest boundary <= now. + * + * Test suite (anchor 31 Jan 2026 10:00): + * 1. now 30 Apr 09:00 (before boundary) -> 31 Mar 10:00 (control, passes pre-fix) + * 2. now 30 Apr 10:00 (boundary instant) -> 30 Apr 10:00 + * 3. now 30 Apr 11:00 (after boundary) -> 30 Apr 10:00 + * 4. annual leap anchor 29 Feb 2024, now 28 Feb 2025 22:00 -> 28 Feb 2025 + * 5. (intervalCount = 3) now 30 Apr 11:00 -> 30 Apr 10:00 (clamped quarter boundary) + */ +describe("get-cycle-start-eom-clamp: clamped EOM boundary 2+ cycles from anchor", () => { + const anchor = toUnix({ year: 2026, month: 1, day: 31, hour: 10 }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 09:00 -> cycle start should be 31 Mar 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 9 }); + const result = getCycleStart({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(3); + expect(day).toBe(31); + expect(hour).toBe(10); + }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 10:00 (boundary instant) -> cycle start should be 30 Apr 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 10 }); + const result = getCycleStart({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(4); + expect(day).toBe(30); + expect(hour).toBe(10); + expect(result).toBeLessThanOrEqual(now); + }); + + test("anchor: 31 Jan 10:00, now: 30 Apr 11:00 -> cycle start should be 30 Apr 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 11 }); + const result = getCycleStart({ + anchor, + interval: BillingInterval.Month, + intervalCount: 1, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(4); + expect(day).toBe(30); + expect(hour).toBe(10); + }); + + test("annual, leap anchor: 29 Feb 2024 12:00, now: 28 Feb 2025 22:00 -> cycle start should be 28 Feb 2025 12:00", () => { + const leapAnchor = toUnix({ year: 2024, month: 2, day: 29, hour: 12 }); + const now = toUnix({ year: 2025, month: 2, day: 28, hour: 22 }); + const result = getCycleStart({ + anchor: leapAnchor, + interval: BillingInterval.Year, + intervalCount: 1, + now, + }); + + const { year, month, day, hour } = fromUnix(result); + expect(year).toBe(2025); + expect(month).toBe(2); + expect(day).toBe(28); + expect(hour).toBe(12); + expect(result).toBeLessThanOrEqual(now); + }); + + test("(intervalCount = 3) anchor: 31 Jan 10:00, now: 30 Apr 11:00 -> cycle start should be 30 Apr 10:00", () => { + const now = toUnix({ year: 2026, month: 4, day: 30, hour: 11 }); + const result = getCycleStart({ + anchor, + interval: BillingInterval.Month, + intervalCount: 3, + now, + }); + + const { month, day, hour } = fromUnix(result); + expect(month).toBe(4); + expect(day).toBe(30); + expect(hour).toBe(10); + }); +}); diff --git a/server/tests/unit/billing/invoice-matched-credits/invoice-credit-matcher.spec.ts b/server/tests/unit/billing/invoice-matched-credits/invoice-credit-matcher.spec.ts index 59a9bbb0d..bbcf88558 100644 --- a/server/tests/unit/billing/invoice-matched-credits/invoice-credit-matcher.spec.ts +++ b/server/tests/unit/billing/invoice-matched-credits/invoice-credit-matcher.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { BillingContext, DbInvoiceLineItem } from "@autumn/shared"; +import type { BillingContext, DbInvoiceLineItem, LineItem } from "@autumn/shared"; import { contexts } from "@tests/utils/fixtures/db/contexts"; import { customerProducts } from "@tests/utils/fixtures/db/customerProducts"; import { prices } from "@tests/utils/fixtures/db/prices"; @@ -266,6 +266,28 @@ describe(chalk.yellowBright("invoiceCreditFromStoredLineItems"), () => { expect(result.resolvedPriceIds).toEqual(["price_pro", "price_addon"]); expect(result.lineItems).toHaveLength(2); }); + + test("matches charge rows that start exactly at the current timestamp", () => { + const { ctx, customerProduct, billingContext } = buildMultiPriceContext({ + storedChargeLineItems: [ + makeChargeRow({ + id: "li_charge_boundary", + price_id: "price_pro", + effective_period_start: MID_CYCLE, + effective_period_end: PERIOD_END, + }), + ], + }); + + const result = invoiceCreditFromStoredLineItems({ + ctx, + customerProduct, + billingContext, + }); + + expect(result.resolvedPriceIds).toEqual(["price_pro"]); + expect(result.lineItems).toHaveLength(1); + }); }); describe(chalk.yellowBright("getRefundLineItemsForPrice"), () => { @@ -314,4 +336,48 @@ describe(chalk.yellowBright("getRefundLineItemsForPrice"), () => { expect(lineItem.amount).toBeLessThan(0); } }); + + test("keeps refunds for deferred stored charges deferred", () => { + const { ctx, customerProduct, billingContext } = buildSinglePriceContext({ + storedChargeLineItems: [ + makeChargeRow({ id: "li_charge_deferred", invoice_id: null }), + ], + }); + + const result = getRefundLineItemsForPrice({ + ctx, + customerProduct, + billingContext, + priceId: "price_pro", + catalogFallback: undefined, + }); + + expect(result).toHaveLength(1); + expect(result[0].chargeImmediately).toBe(false); + }); + + test("uses the explicit fallback when no stored credit matches the price", () => { + const { ctx, customerProduct, billingContext } = buildSinglePriceContext({ + storedChargeLineItems: [], + }); + const price = customerProduct.customer_prices[0].price; + const catalogFallback = { + id: "invoice_li_explicit_fallback", + amount: -100, + amountAfterDiscounts: -100, + context: { price }, + } as LineItem; + + const result = getRefundLineItemsForPrice({ + ctx, + customerProduct, + billingContext, + priceId: price.id, + catalogFallback, + }); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe(catalogFallback.id); + expect(result[0].amountAfterDiscounts).toBe(-100); + }); }); diff --git a/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts b/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts index 0afd22cb4..af1d0aba6 100644 --- a/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts +++ b/server/tests/unit/full-subject-cache/setSharedFullSubjectBalances.test.ts @@ -84,7 +84,6 @@ const buildNormalized = (): NormalizedFullSubject => }, }, rollovers: [], - usage_windows: [], replaceables: [], customerPrice: null, customerProductOptions: null, @@ -93,6 +92,7 @@ const buildNormalized = (): NormalizedFullSubject => }, ], customer_prices: [], + usage_windows: [], flags: {}, products: [], entitlements: [], @@ -128,4 +128,35 @@ describe("setSharedFullSubjectBalances", () => { cus_ent_1: JSON.stringify(normalized.customer_entitlements[0]), }); }); + + test("always writes _usage_windows for capped features, even with no entitlements", () => { + const normalized = buildNormalized(); + + const writes = buildSharedBalanceWrites({ + orgId: "org_1", + env: AppEnv.Live, + customerId: "cus_1", + customerEntitlements: normalized.customer_entitlements, + aggregatedCustomerEntitlements: [], + usageWindows: [], + usageWindowFeatureIds: ["messages", "action1"], + }); + + expect(writes).toHaveLength(2); + + const messagesWrite = writes.find((write) => + write.balanceKey.endsWith(":messages"), + ); + expect(messagesWrite?.fields).toEqual({ + cus_ent_1: JSON.stringify(normalized.customer_entitlements[0]), + _usage_windows: "[]", + }); + + // action1 has no entitlements: the write exists purely to seed the + // fail-closed `_usage_windows` field. + const actionWrite = writes.find((write) => + write.balanceKey.endsWith(":action1"), + ); + expect(actionWrite?.fields).toEqual({ _usage_windows: "[]" }); + }); }); diff --git a/server/tests/unit/usage-windows/computeUsageWindowRolls.test.ts b/server/tests/unit/usage-windows/computeUsageWindowRolls.test.ts new file mode 100644 index 000000000..e51f4e29d --- /dev/null +++ b/server/tests/unit/usage-windows/computeUsageWindowRolls.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, test } from "bun:test"; +import type { UsageWindow, UsageWindowLimit } from "@autumn/shared"; +import { computeUsageWindowRolls } from "@/internal/customers/actions/resetUsageWindows/computeUsageWindowRolls.js"; + +const NOW = Date.UTC(2026, 5, 15, 12, 0, 0); +const HOUR = 60 * 60 * 1000; + +const row = (overrides: Partial): UsageWindow => + ({ + id: "uw_1", + internal_customer_id: "cus_int_1", + internal_entity_id: null, + feature_id: "messages", + internal_feature_id: "imessages", + anchor_customer_entitlement_id: "ce_old", + window_start_at: NOW - HOUR, + window_end_at: NOW + HOUR, + usage: 3, + updated_at: NOW - HOUR, + ...overrides, + }) as UsageWindow; + +const limit = (overrides: Partial): UsageWindowLimit => + ({ + feature_id: "messages", + internal_entity_id: null, + window_start_at: NOW - HOUR, + window_end_at: NOW + HOUR, + anchor_customer_entitlement_id: "ce_old", + ...overrides, + }) as UsageWindowLimit; + +describe("computeUsageWindowRolls", () => { + test("live row matching its limit's derivation: no roll", () => { + const rolls = computeUsageWindowRolls({ + usageWindows: [row({})], + limits: [limit({})], + now: NOW, + }); + expect(rolls).toHaveLength(0); + }); + + test("plan change (bounds moved, not expired): re-bound, count zeroed", () => { + const rolls = computeUsageWindowRolls({ + usageWindows: [row({})], + limits: [ + limit({ + window_start_at: NOW - 2 * HOUR, + window_end_at: NOW + 5 * HOUR, + anchor_customer_entitlement_id: "ce_new", + }), + ], + now: NOW, + }); + expect(rolls).toHaveLength(1); + expect(rolls[0]).toMatchObject({ + id: "uw_1", + zero_usage: true, + window_start_at: NOW - 2 * HOUR, + window_end_at: NOW + 5 * HOUR, + anchor_customer_entitlement_id: "ce_new", + }); + }); + + test("anchor-only re-point (same window, ent recreated): count kept", () => { + const rolls = computeUsageWindowRolls({ + usageWindows: [row({})], + limits: [limit({ anchor_customer_entitlement_id: "ce_recreated" })], + now: NOW, + }); + expect(rolls).toHaveLength(1); + expect(rolls[0]).toMatchObject({ + zero_usage: false, + window_start_at: NOW - HOUR, + window_end_at: NOW + HOUR, + anchor_customer_entitlement_id: "ce_recreated", + }); + }); + + test("expired row: re-bound to the current derivation, count zeroed", () => { + const rolls = computeUsageWindowRolls({ + usageWindows: [ + row({ window_start_at: NOW - 3 * HOUR, window_end_at: NOW - HOUR }), + ], + limits: [limit({})], + now: NOW, + }); + expect(rolls).toHaveLength(1); + expect(rolls[0]).toMatchObject({ + zero_usage: true, + window_start_at: NOW - HOUR, + window_end_at: NOW + HOUR, + }); + }); + + test("expired row with no resolvable limit (entity scope, v1): zero-only, bounds kept", () => { + const rolls = computeUsageWindowRolls({ + usageWindows: [ + row({ + internal_entity_id: "ient_1", + window_start_at: NOW - 3 * HOUR, + window_end_at: NOW - HOUR, + }), + ], + limits: [limit({})], // customer-scope limit doesn't match the entity row + now: NOW, + }); + expect(rolls).toHaveLength(1); + expect(rolls[0]).toMatchObject({ + zero_usage: true, + internal_entity_id: "ient_1", + window_start_at: NOW - 3 * HOUR, + window_end_at: NOW - HOUR, + anchor_customer_entitlement_id: "ce_old", + }); + }); +}); diff --git a/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts index a0c7e97b9..30dc545da 100644 --- a/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts +++ b/server/tests/unit/usage-windows/fullSubjectToUsageWindowLimits.test.ts @@ -3,12 +3,14 @@ import { buildUsageWindowKey, CusProductStatus, type DbSpendLimit, + type DbUsageLimit, EntInterval, type Feature, FeatureType, type FullSubject, fullSubjectToUsageWindowLimits, getUsageWindowBounds, + ResetInterval, } from "@autumn/shared"; const NOW = Date.UTC(2026, 5, 15, 12, 0, 0); @@ -37,33 +39,14 @@ const credits2ContainingAction1 = { config: { schema: [{ metered_feature_id: "action1", credit_amount: 3 }] }, } as unknown as Feature; -// Test-input shape for a windowed usage cap. usage_limit arms the cap; `interval` -// is the optional override (omit it to test inheriting from the entitlement). -type UsageCap = { - feature_id: string; - limit: number; - interval?: EntInterval; -}; - -const toSpendLimit = (cap: UsageCap): DbSpendLimit => ({ - feature_id: cap.feature_id, - // Entry-level enabled gates the (absent) overage cap, not the usage window. - enabled: false, - usage_limit: cap.limit, - usage_limit_interval: cap.interval, -}); - -// Minimal loose (product-less) customer entitlement for anchor/inherit tests. -// `interval` is the entitlement's reset interval (the inherited window source). +// Minimal loose (product-less) customer entitlement for anchor tests. const looseEntitlement = ({ id, featureId, - usageLimit, interval = EntInterval.Month, }: { id: string; featureId: string; - usageLimit?: number; interval?: EntInterval | null; }) => ({ @@ -80,7 +63,6 @@ const looseEntitlement = ({ id: `ent_${id}`, feature_id: featureId, interval, - usage_limit: usageLimit ?? null, feature: { id: featureId, internal_id: featureId }, }, rollovers: [], @@ -94,13 +76,13 @@ const looseEntitlement = ({ const customerProductWithEntitlement = ({ id, featureId, - usageLimit, cycleAnchor, + nextResetAt, }: { id: string; featureId: string; - usageLimit?: number; cycleAnchor?: number; + nextResetAt?: number; }) => ({ id: `cusprod_${id}`, @@ -119,11 +101,11 @@ const customerProductWithEntitlement = ({ created_at: 1000, balance: 0, expires_at: null, + next_reset_at: nextResetAt ?? null, entitlement: { id: `ent_${id}`, feature_id: featureId, interval: EntInterval.Month, - usage_limit: usageLimit ?? null, feature: { id: featureId, internal_id: featureId }, }, rollovers: [], @@ -133,43 +115,32 @@ const customerProductWithEntitlement = ({ }) as unknown as FullSubject["customer_products"][number]; const buildSubject = ({ - customerLimits = [], - entityLimits, + usageLimits = [], + spendLimits = [], looseEntitlements = [], - extraCustomerSpendLimits = [], customerProducts = [], }: { - customerLimits?: UsageCap[]; - entityLimits?: UsageCap[]; + // Entries injected as-is; pass Partial shapes to simulate stale stored data. + usageLimits?: Partial[]; + spendLimits?: DbSpendLimit[]; looseEntitlements?: FullSubject["extra_customer_entitlements"]; - // Raw spend-limit entries (e.g. overage-only or both-cap) injected as-is. - extraCustomerSpendLimits?: DbSpendLimit[]; customerProducts?: FullSubject["customer_products"]; }): FullSubject => ({ customer: { - spend_limits: [ - ...customerLimits.map(toSpendLimit), - ...extraCustomerSpendLimits, - ], + usage_limits: usageLimits, + spend_limits: spendLimits, }, customer_products: customerProducts, extra_customer_entitlements: looseEntitlements, - entity: entityLimits - ? { - id: "ent_1", - internal_id: "ient_1", - spend_limits: entityLimits.map(toSpendLimit), - } - : undefined, }) as unknown as FullSubject; describe("fullSubjectToUsageWindowLimits", () => { test("resolves a customer-level metered-feature cap", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, ], }), featureIds: ["action1"], @@ -208,8 +179,8 @@ describe("fullSubjectToUsageWindowLimits", () => { test("skips a cap whose feature is absent from the catalog (unresolvable internal_feature_id)", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, ], }), featureIds: ["action1"], @@ -223,8 +194,8 @@ describe("fullSubjectToUsageWindowLimits", () => { test("a credit-system feature resolves to the balance dimension", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, ], }), featureIds: ["credits"], @@ -239,34 +210,12 @@ describe("fullSubjectToUsageWindowLimits", () => { }); }); - test("inherits the interval from the anchor entitlement when no override", () => { + test("the window interval comes from the entry, independent of the entitlement's reset interval", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - // No `interval` => inherit the entitlement's reset interval (Month). - customerLimits: [{ feature_id: "credits", limit: 5 }], - looseEntitlements: [ - looseEntitlement({ id: "ce_credits", featureId: "credits" }), - ], - }), - featureIds: ["credits"], - features: [creditsFeature], - now: NOW, - }); - - expect(limits).toHaveLength(1); - expect(limits[0]).toMatchObject({ - limit: 5, - interval: EntInterval.Month, - anchor_customer_entitlement_id: "ce_credits", - }); - }); - - test("an explicit usage_limit_interval overrides the inherited interval", () => { - const limits = fullSubjectToUsageWindowLimits({ - fullSubject: buildSubject({ - // Entitlement interval is Month; the cap overrides to Day. - customerLimits: [ - { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + // Entitlement resets monthly; the cap windows daily. + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, ], looseEntitlements: [ looseEntitlement({ id: "ce_credits", featureId: "credits" }), @@ -281,16 +230,12 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits[0]).toMatchObject({ limit: 3, interval: EntInterval.Day }); }); - test("a usage_limit with no override and a null-interval entitlement resolves nothing", () => { + test("a stale entry missing its interval resolves nothing (fail-safe)", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [{ feature_id: "credits", limit: 3 }], + usageLimits: [{ feature_id: "credits", limit: 3 }], looseEntitlements: [ - looseEntitlement({ - id: "ce_credits", - featureId: "credits", - interval: null, - }), + looseEntitlement({ id: "ce_credits", featureId: "credits" }), ], }), featureIds: ["credits"], @@ -301,11 +246,11 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits).toHaveLength(0); }); - test("a usage_limit of 0 is a valid hard cap (blocks all usage)", () => { + test("a limit of 0 is a valid hard cap (blocks all usage)", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "credits", limit: 0, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "credits", limit: 0, interval: ResetInterval.Day }, ], }), featureIds: ["credits"], @@ -322,8 +267,8 @@ describe("fullSubjectToUsageWindowLimits", () => { const cycleAnchor = Date.UTC(2026, 0, 9, 15, 30, 0); const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, ], customerProducts: [ customerProductWithEntitlement({ @@ -355,29 +300,51 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(aligned.windowStartAt).not.toBe(calendar.windowStartAt); }); - test("a spend_limit with usage_limit_interval but no usage_limit is not armed", () => { + test("the anchor ent's next_reset_at outranks the billing-cycle anchor for bounds", () => { + // Non-calendar, non-cycle-anchor timestamps so all three alignments differ. + const cycleAnchor = Date.UTC(2026, 0, 9, 15, 30, 0); + const nextResetAt = Date.UTC(2026, 5, 22, 8, 45, 0); const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - extraCustomerSpendLimits: [ - { - feature_id: "action1", - enabled: false, - usage_limit_interval: EntInterval.Month, - }, + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, + ], + customerProducts: [ + customerProductWithEntitlement({ + id: "ce_credits", + featureId: "credits", + cycleAnchor, + nextResetAt, + }), ], }), - featureIds: ["action1"], - features: [meteredAction1], + featureIds: ["credits"], + features: [creditsFeature], now: NOW, }); - expect(limits).toHaveLength(0); + const resetAligned = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + anchor: nextResetAt, + }); + const cycleAligned = getUsageWindowBounds({ + interval: EntInterval.Day, + now: NOW, + anchor: cycleAnchor, + }); + + expect(limits).toHaveLength(1); + expect(limits[0].window_start_at).toBe(resetAligned.windowStartAt); + expect(limits[0].window_end_at).toBe(resetAligned.windowEndAt); + // Sanity: the reset-cycle window genuinely differs from the cycle-anchor one. + expect(resetAligned.windowStartAt).not.toBe(cycleAligned.windowStartAt); }); - test("ignores an overage-only spend_limit (no usage_limit)", () => { + test("an overage spend_limit does not arm a usage window", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - extraCustomerSpendLimits: [ + spendLimits: [ { feature_id: "action1", enabled: true, overage_limit: 20 }, ], }), @@ -389,17 +356,14 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits).toHaveLength(0); }); - test("resolves the window when one entry carries both overage and usage caps", () => { + test("a usage limit coexists with an overage spend_limit on the same feature", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - extraCustomerSpendLimits: [ - { - feature_id: "action1", - enabled: true, - overage_limit: 20, - usage_limit: 5, - usage_limit_interval: EntInterval.Month, - }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, + ], + spendLimits: [ + { feature_id: "action1", enabled: true, overage_limit: 20 }, ], }), featureIds: ["action1"], @@ -411,48 +375,9 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits[0]).toMatchObject({ feature_id: "action1", limit: 5 }); }); - test("ignores entity-scoped usage windows in v1; the customer cap applies", () => { - const limits = fullSubjectToUsageWindowLimits({ - fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, - ], - entityLimits: [ - { feature_id: "action1", limit: 2, interval: EntInterval.Month }, - ], - }), - featureIds: ["action1"], - features: [meteredAction1], - now: NOW, - }); - - expect(limits).toHaveLength(1); - expect(limits[0]).toMatchObject({ - scope_type: "customer", - entity_id: null, - internal_entity_id: null, - limit: 5, - }); - }); - - test("an entity-only usage window resolves nothing in v1", () => { - const limits = fullSubjectToUsageWindowLimits({ - fullSubject: buildSubject({ - entityLimits: [ - { feature_id: "action1", limit: 2, interval: EntInterval.Month }, - ], - }), - featureIds: ["action1"], - features: [meteredAction1], - now: NOW, - }); - - expect(limits).toHaveLength(0); - }); - test("returns nothing when no cap matches the feature", () => { const limits = fullSubjectToUsageWindowLimits({ - fullSubject: buildSubject({ customerLimits: [] }), + fullSubject: buildSubject({ usageLimits: [] }), featureIds: ["action1"], features: [meteredAction1], now: NOW, @@ -469,9 +394,9 @@ describe("fullSubjectToUsageWindowLimits", () => { } as Feature; const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, - { feature_id: "action2", limit: 9, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, + { feature_id: "action2", limit: 9, interval: ResetInterval.Day }, ], }), featureIds: ["action1", "action2"], @@ -489,8 +414,8 @@ describe("fullSubjectToUsageWindowLimits", () => { test("resolves the anchor to the owning entitlement (balance dim)", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, ], looseEntitlements: [ looseEntitlement({ id: "ce_credits", featureId: "credits" }), @@ -504,15 +429,14 @@ describe("fullSubjectToUsageWindowLimits", () => { expect(limits).toHaveLength(1); expect(limits[0]).toMatchObject({ anchor_customer_entitlement_id: "ce_credits", - anchor_feature_id: "credits", }); }); test("metered cap with no native entitlement anchors to the containing credit system", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, ], looseEntitlements: [ looseEntitlement({ id: "ce_credits", featureId: "credits" }), @@ -528,15 +452,14 @@ describe("fullSubjectToUsageWindowLimits", () => { dimension_type: "metered_feature", dimension_feature_id: "action1", anchor_customer_entitlement_id: "ce_credits", - anchor_feature_id: "credits", }); }); - test("anchor is null when no owning entitlement exists (fail-closed signal)", () => { + test("anchor is null when no reference entitlement exists (calendar bounds, no provenance)", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "credits", limit: 3, interval: EntInterval.Day }, + usageLimits: [ + { feature_id: "credits", limit: 3, interval: ResetInterval.Day }, ], }), featureIds: ["credits"], @@ -551,8 +474,8 @@ describe("fullSubjectToUsageWindowLimits", () => { test("metered cap with a containing credit system but no entitlement resolves a null anchor", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, ], }), featureIds: ["action1"], @@ -567,8 +490,8 @@ describe("fullSubjectToUsageWindowLimits", () => { test("metered cap contained by two credit systems anchors deterministically", () => { const limits = fullSubjectToUsageWindowLimits({ fullSubject: buildSubject({ - customerLimits: [ - { feature_id: "action1", limit: 5, interval: EntInterval.Month }, + usageLimits: [ + { feature_id: "action1", limit: 5, interval: ResetInterval.Month }, ], looseEntitlements: [ looseEntitlement({ id: "ce_credits", featureId: "credits" }), diff --git a/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts b/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts index 6fea01f1b..a32dcb9a4 100644 --- a/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts +++ b/server/tests/unit/usage-windows/pickAnchorCustomerEntitlementId.test.ts @@ -8,12 +8,25 @@ const candidate = (overrides: Partial): AnchorCandidate => ({ id: "ce_1", is_entity_scoped: false, is_add_on: false, + is_plan_backed: true, status_rank: 0, created_at: 1000, ...overrides, }); describe("pickAnchorCustomerEntitlementId", () => { + test("a plan-backed ent outranks an older loose/top-up ent", () => { + const id = pickAnchorCustomerEntitlementId({ + candidates: [ + candidate({ id: "ce_topup", is_plan_backed: false, created_at: 500 }), + candidate({ id: "ce_plan", is_plan_backed: true, created_at: 2000 }), + ], + scopeType: "customer", + }); + + expect(id).toBe("ce_plan"); + }); + test("returns null when there are no candidates", () => { expect( pickAnchorCustomerEntitlementId({ diff --git a/shared/api/billingControls/customerBillingControls.ts b/shared/api/billingControls/customerBillingControls.ts new file mode 100644 index 000000000..d2f5c9504 --- /dev/null +++ b/shared/api/billingControls/customerBillingControls.ts @@ -0,0 +1,39 @@ +import { z } from "zod/v4"; +import { + AutoTopupResponseSchema, + DbOverageAllowedSchema, + DbUsageAlertSchema, +} from "../../models/cusModels/billingControls/customerBillingControls.js"; +import { ApiSpendLimitSchema } from "./spendLimit.js"; +import { ApiUsageLimitSchema } from "./usageLimit.js"; + +/** + * Response-only variant of CustomerBillingControlsSchema: `auto_topups` may + * carry the expanded runtime purchase-limit shape, and `usage_limits` carry + * the current window `usage`. Input/params validation continues to use + * `CustomerBillingControlsParamsSchema` (models), which remains strict. + */ +export const CustomerBillingControlsResponseSchema = z.object({ + auto_topups: z.array(AutoTopupResponseSchema).optional().meta({ + description: "List of auto top-up configurations per feature.", + }), + spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ + description: + "List of overage spend limits per feature (caps overage spend).", + }), + usage_limits: z.array(ApiUsageLimitSchema).optional().meta({ + description: + "List of windowed hard usage caps per feature, with current window usage.", + }), + usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), + overage_allowed: z.array(DbOverageAllowedSchema).optional().meta({ + description: + "List of overage allowed controls per feature. When enabled, usage can exceed balance.", + }), +}); + +export type CustomerBillingControlsResponse = z.infer< + typeof CustomerBillingControlsResponseSchema +>; diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index b267214dd..2e65759bd 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -1,14 +1,20 @@ import { z } from "zod/v4"; import { DbSpendLimitSchema } from "../../models/cusModels/billingControls/spendLimit.js"; +import { DbUsageLimitSchema } from "../../models/cusModels/billingControls/usageLimit.js"; import { ApiOverageAllowedSchema } from "./overageAllowed.js"; import { ApiSpendLimitSchema } from "./spendLimit.js"; import { ApiUsageAlertSchema } from "./usageAlert.js"; +import { ApiUsageLimitSchema } from "./usageLimit.js"; export const ApiEntityBillingControlsSchema = z.object({ spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ description: "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), + usage_limits: z.array(ApiUsageLimitSchema).optional().meta({ + description: + "List of windowed hard usage caps per feature for this entity. An entity entry overrides the customer's for that feature.", + }), usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", }), @@ -23,6 +29,10 @@ const ApiEntityBillingControlsParamsBaseSchema = z.object({ description: "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), + usage_limits: z.array(DbUsageLimitSchema).optional().meta({ + description: + "List of windowed hard usage caps per feature for this entity. An entity entry overrides the customer's for that feature.", + }), usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", }), @@ -57,6 +67,24 @@ export const ApiEntityBillingControlsParamsSchema = spendLimitFeatureIds.add(spendLimit.feature_id); } + const usageLimitFeatureIds = new Set(); + + for (const [index, usageLimit] of ( + billingControls.usage_limits ?? [] + ).entries()) { + if (usageLimitFeatureIds.has(usageLimit.feature_id)) { + ctx.issues.push({ + code: "custom", + message: "Only one usage limit entry is allowed per feature_id", + input: usageLimit.feature_id, + path: ["usage_limits", index, "feature_id"], + }); + return; + } + + usageLimitFeatureIds.add(usageLimit.feature_id); + } + const overageAllowedFeatureIds = new Set(); for (const [index, overageAllowed] of ( diff --git a/shared/api/billingControls/index.ts b/shared/api/billingControls/index.ts index c179c7c84..4ada5d145 100644 --- a/shared/api/billingControls/index.ts +++ b/shared/api/billingControls/index.ts @@ -1,4 +1,6 @@ +export * from "./customerBillingControls.js"; export * from "./entityBillingControls.js"; export * from "./overageAllowed.js"; export * from "./spendLimit.js"; export * from "./usageAlert.js"; +export * from "./usageLimit.js"; diff --git a/shared/api/billingControls/spendLimit.ts b/shared/api/billingControls/spendLimit.ts index dc7d6d949..5cdcb828a 100644 --- a/shared/api/billingControls/spendLimit.ts +++ b/shared/api/billingControls/spendLimit.ts @@ -1,6 +1,8 @@ import type { z } from "zod/v4"; -import { SpendLimitResponseSchema } from "../../models/cusModels/billingControls/spendLimit.js"; +import { DbSpendLimitSchema } from "../../models/cusModels/billingControls/spendLimit.js"; -export const ApiSpendLimitSchema = SpendLimitResponseSchema; +// Spend limits (overage caps) carry no runtime state on responses; the API +// shape is the stored shape. +export const ApiSpendLimitSchema = DbSpendLimitSchema; export type ApiSpendLimit = z.infer; diff --git a/shared/api/billingControls/usageLimit.ts b/shared/api/billingControls/usageLimit.ts new file mode 100644 index 000000000..6183bb983 --- /dev/null +++ b/shared/api/billingControls/usageLimit.ts @@ -0,0 +1,15 @@ +import { z } from "zod/v4"; +import { DbUsageLimitSchema } from "../../models/cusModels/billingControls/usageLimit.js"; + +/** + * Response variant of a usage limit: the stored config plus the usage already + * consumed in the active window (read from the usage-window counter). + */ +export const ApiUsageLimitSchema = DbUsageLimitSchema.extend({ + usage: z.number().min(0).optional().meta({ + description: + "Current usage already consumed in the active window. Response-only; not stored on billing controls.", + }), +}); + +export type ApiUsageLimit = z.infer; diff --git a/shared/api/customers/baseApiCustomer.ts b/shared/api/customers/baseApiCustomer.ts index 4d51a1c25..046e38301 100644 --- a/shared/api/customers/baseApiCustomer.ts +++ b/shared/api/customers/baseApiCustomer.ts @@ -1,6 +1,6 @@ -import { CustomerBillingControlsResponseSchema } from "@models/cusModels/billingControls/customerBillingControls"; import { AppEnv } from "@models/genModels/genEnums"; import { z } from "zod/v4"; +import { CustomerBillingControlsResponseSchema } from "../billingControls/customerBillingControls.js"; export const BaseApiCustomerSchema = z.object({ autumn_id: z.string().optional().meta({ diff --git a/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts b/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts index 2961a40ff..b289f2b62 100644 --- a/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts +++ b/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts @@ -2,13 +2,14 @@ import type { ApiSubjectV0 } from "@api/customers/apiSubjectV0"; import type { ApiBalanceV1 } from "@api/customers/cusFeatures/apiBalanceV1"; import { apiBalanceV1ToAvailableOverage } from "@api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage"; import { apiSubjectToOverageAllowedControl } from "@api/customers/utils/apiSubjectToOverageAllowed"; +import { apiSubjectToUsageLimitHeadroom } from "@api/customers/utils/apiSubjectToUsageLimitHeadroom"; import type { Feature } from "@models/featureModels/featureModels"; import { isBooleanFeature, notNullish } from "@utils/index"; import { Decimal } from "decimal.js"; export type AllowedResult = { allowed: boolean; - limitType?: "included" | "max_purchase" | "spend_limit"; + limitType?: "included" | "max_purchase" | "spend_limit" | "usage_limit"; }; export type ApiBalanceInput = { @@ -16,6 +17,9 @@ export type ApiBalanceInput = { apiSubject: ApiSubjectV0; feature: Feature; requiredBalance: number; + /** The checked feature when it differs from the evaluated one (credit + * system member), so metered usage caps on it can gate the check. */ + originalFeature?: Feature; }; export const apiBalanceToAllowed = ({ @@ -23,6 +27,7 @@ export const apiBalanceToAllowed = ({ apiSubject, feature, requiredBalance, + originalFeature, }: ApiBalanceInput): AllowedResult => { if (!apiBalance) return { allowed: false }; @@ -32,6 +37,19 @@ export const apiBalanceToAllowed = ({ if (requiredBalance < 0) return { allowed: true }; + // Windowed usage caps gate regardless of balance or overage availability. + const usageLimitHeadroom = apiSubjectToUsageLimitHeadroom({ + apiSubject, + feature, + originalFeature, + }); + if ( + notNullish(usageLimitHeadroom) && + new Decimal(requiredBalance).gt(usageLimitHeadroom) + ) { + return { allowed: false, limitType: "usage_limit" }; + } + const overageAllowedControl = apiSubjectToOverageAllowedControl({ subject: apiSubject, feature, diff --git a/shared/api/customers/utils/apiSubjectToUsageLimitHeadroom.ts b/shared/api/customers/utils/apiSubjectToUsageLimitHeadroom.ts new file mode 100644 index 000000000..0b9ffb05b --- /dev/null +++ b/shared/api/customers/utils/apiSubjectToUsageLimitHeadroom.ts @@ -0,0 +1,63 @@ +import type { ApiSubjectV0 } from "@api/customers/apiSubjectV0"; +import type { Feature } from "@models/featureModels/featureModels"; +import { Decimal } from "decimal.js"; + +/** + * Remaining usage-window headroom for a check, in the EVALUATED feature's + * units (credits when the evaluated feature is a credit system). Considers + * both the cap on the evaluated feature itself and -- when checking a + * credit-system member -- the metered cap on the original feature, converted + * via its credit cost. Null when no armed cap applies. + */ +export const apiSubjectToUsageLimitHeadroom = ({ + apiSubject, + feature, + originalFeature, +}: { + apiSubject: ApiSubjectV0; + feature: Feature; + originalFeature?: Feature; +}): number | null => { + // Entity subjects see inherited customer entries via + // mergeCustomerBillingControlsForCheck; entity's own entry wins per feature. + const billingControls = apiSubject.billing_controls; + const usageLimits = + billingControls && "usage_limits" in billingControls + ? billingControls.usage_limits + : undefined; + if (!usageLimits || usageLimits.length === 0) return null; + + const headrooms: Decimal[] = []; + + const capOnEvaluated = usageLimits.find( + (usageLimit) => usageLimit.feature_id === feature.id, + ); + if (capOnEvaluated) { + headrooms.push( + Decimal.max( + 0, + new Decimal(capOnEvaluated.limit).sub(capOnEvaluated.usage ?? 0), + ), + ); + } + + if (originalFeature && originalFeature.id !== feature.id) { + const capOnOriginal = usageLimits.find( + (usageLimit) => usageLimit.feature_id === originalFeature.id, + ); + const schemaItem = feature.config?.schema?.find( + (item: { metered_feature_id: string }) => + item.metered_feature_id === originalFeature.id, + ); + if (capOnOriginal && schemaItem) { + const headroomUnits = Decimal.max( + 0, + new Decimal(capOnOriginal.limit).sub(capOnOriginal.usage ?? 0), + ); + headrooms.push(headroomUnits.mul(schemaItem.credit_amount ?? 1)); + } + } + + if (headrooms.length === 0) return null; + return Decimal.min(...headrooms).toNumber(); +}; diff --git a/shared/drizzle/0009_usage_windows.sql b/shared/drizzle/0009_usage_windows.sql index 3f83055fb..14b675abb 100644 --- a/shared/drizzle/0009_usage_windows.sql +++ b/shared/drizzle/0009_usage_windows.sql @@ -1,8 +1,10 @@ CREATE TABLE "usage_windows" ( "id" text PRIMARY KEY NOT NULL, - "customer_entitlement_id" text NOT NULL, + "internal_customer_id" text NOT NULL, + "internal_entity_id" text, "feature_id" text NOT NULL, "internal_feature_id" text NOT NULL, + "anchor_customer_entitlement_id" text, "window_start_at" numeric NOT NULL, "window_end_at" numeric NOT NULL, "usage" numeric DEFAULT 0 NOT NULL, @@ -10,7 +12,9 @@ CREATE TABLE "usage_windows" ( ); --> statement-breakpoint ALTER TABLE "usage_windows" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_customer_entitlement_id_fkey" FOREIGN KEY ("customer_entitlement_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX CONCURRENTLY "idx_usage_windows_customer_entitlement_id" ON "usage_windows" USING btree ("customer_entitlement_id");--> statement-breakpoint -CREATE UNIQUE INDEX CONCURRENTLY "idx_usage_windows_cus_ent_feature_window" ON "usage_windows" USING btree ("customer_entitlement_id","feature_id","window_start_at"); \ No newline at end of file +ALTER TABLE "usage_windows" ADD CONSTRAINT "usage_windows_anchor_customer_entitlement_id_fkey" FOREIGN KEY ("anchor_customer_entitlement_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX CONCURRENTLY "idx_usage_windows_internal_customer_id" ON "usage_windows" USING btree ("internal_customer_id");--> statement-breakpoint +CREATE UNIQUE INDEX CONCURRENTLY "idx_usage_windows_customer_feature_scope" ON "usage_windows" USING btree ("internal_customer_id","internal_feature_id",COALESCE("internal_entity_id", '')); diff --git a/shared/drizzle/0010_usage_limits_control.sql b/shared/drizzle/0010_usage_limits_control.sql new file mode 100644 index 000000000..d14b0dded --- /dev/null +++ b/shared/drizzle/0010_usage_limits_control.sql @@ -0,0 +1,2 @@ +ALTER TABLE "customers" ADD COLUMN "usage_limits" jsonb;--> statement-breakpoint +ALTER TABLE "entities" ADD COLUMN "usage_limits" jsonb; diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json index f114f8bf1..62287812d 100644 --- a/shared/drizzle/meta/_journal.json +++ b/shared/drizzle/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1780916308859, "tag": "0009_usage_windows", "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1781100000000, + "tag": "0010_usage_limits_control", + "breakpoints": true } ] } \ No newline at end of file diff --git a/shared/index.ts b/shared/index.ts index d1ec8aca1..1f302b57b 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -16,6 +16,7 @@ export * from "./api/billing/createSchedule/createScheduleResponse"; export * from "./api/billing/openBillingPortal/openBillingPortalParamsV1"; export * from "./api/billing/openBillingPortal/openBillingPortalResponse"; export * from "./api/billing/updateSubscription/previewUpdateSubscriptionResponse"; +export * from "./api/billingControls/index"; // Cursor pagination utilities export * from "./api/common/cursorPaginationSchemas"; export * from "./api/common/paginationConfigs"; @@ -212,10 +213,6 @@ export * from "./utils/cusEntUtils/balanceUtils/cusEntsToUsage"; export * from "./utils/cusEntUtils/balanceUtils/cusEntToMinBalance"; export * from "./utils/cusEntUtils/balanceUtils/cusEntToUsageAllowed"; export * from "./utils/cusEntUtils/index"; -// Utils -export * from "./utils/usageWindowUtils/buildUsageWindowKey"; -export * from "./utils/usageWindowUtils/getUsageWindowBounds"; -export * from "./utils/usageWindowUtils/pickAnchorCustomerEntitlementId"; export * from "./utils/displayUtils"; export * from "./utils/fullSubjectUtils"; export * from "./utils/index"; @@ -244,3 +241,15 @@ export * from "./utils/productV3Utils/productItemUtils/productV3ItemUtils"; export * from "./utils/rewardUtils/rewardFilterUtils"; export * from "./utils/rewardUtils/rewardMigrationUtils"; export * from "./utils/scopeDefinitions"; +// Utils +export * from "./utils/usageWindowUtils/buildUsageWindowKey"; +export * from "./utils/usageWindowUtils/classifyUsageWindow/usageWindowMatchesLimit"; +export * from "./utils/usageWindowUtils/convertUsageWindow/getUsageWindowDimension"; +export * from "./utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit"; +export * from "./utils/usageWindowUtils/findUsageWindow/findUsageWindowByLimit"; +export * from "./utils/usageWindowUtils/findUsageWindow/findUsageWindowLimitByWindow"; +export * from "./utils/usageWindowUtils/findUsageWindowAnchor/findUsageWindowAnchor"; +export * from "./utils/usageWindowUtils/findUsageWindowAnchor/pickAnchorCustomerEntitlementId"; +export * from "./utils/usageWindowUtils/getCurrentUsageWindowUsage"; +export * from "./utils/usageWindowUtils/getUsageWindowAnchorTimestamp"; +export * from "./utils/usageWindowUtils/getUsageWindowBounds"; diff --git a/shared/models/cusModels/billingControls/customerBillingControls.ts b/shared/models/cusModels/billingControls/customerBillingControls.ts index 182bf98dc..45e9d1dd9 100644 --- a/shared/models/cusModels/billingControls/customerBillingControls.ts +++ b/shared/models/cusModels/billingControls/customerBillingControls.ts @@ -9,12 +9,9 @@ import { DbOverageAllowedSchema, } from "./overageAllowed.js"; import { PurchaseLimitIntervalEnum } from "./purchaseLimitInterval.js"; -import { - type DbSpendLimit, - DbSpendLimitSchema, - SpendLimitResponseSchema, -} from "./spendLimit.js"; +import { type DbSpendLimit, DbSpendLimitSchema } from "./spendLimit.js"; import { type DbUsageAlert, DbUsageAlertSchema } from "./usageAlert.js"; +import { type DbUsageLimit, DbUsageLimitSchema } from "./usageLimit.js"; export const AutoTopupPurchaseLimitSchema = z.object({ interval: PurchaseLimitIntervalEnum.meta({ @@ -105,33 +102,11 @@ export const CustomerBillingControlsSchema = z.object({ }), spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: - "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", + "List of overage spend limits per feature (caps overage spend).", }), - usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ - description: "List of usage alert configurations per feature.", - }), - overage_allowed: z.array(DbOverageAllowedSchema).optional().meta({ + usage_limits: z.array(DbUsageLimitSchema).optional().meta({ description: - "List of overage allowed controls per feature. When enabled, usage can exceed balance.", - }), -}); - -/** - * Response-only variant of CustomerBillingControlsSchema that uses - * `AutoTopupResponseSchema` for `auto_topups` so the `purchase_limit` field - * may be either the static config shape or the expanded runtime shape (when - * expand=billing_controls.auto_topups.purchase_limit is requested). - * - * Input/params validation continues to use `CustomerBillingControlsSchema` / - * `CustomerBillingControlsParamsSchema`, which remain strict. - */ -export const CustomerBillingControlsResponseSchema = z.object({ - auto_topups: z.array(AutoTopupResponseSchema).optional().meta({ - description: "List of auto top-up configurations per feature.", - }), - spend_limits: z.array(SpendLimitResponseSchema).optional().meta({ - description: - "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", + "List of windowed hard usage caps per feature (max units per interval window).", }), usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", @@ -167,6 +142,24 @@ export const CustomerBillingControlsParamsSchema = spendLimitFeatureIds.add(spendLimit.feature_id); } + const usageLimitFeatureIds = new Set(); + + for (const [index, usageLimit] of ( + billingControls.usage_limits ?? [] + ).entries()) { + if (usageLimitFeatureIds.has(usageLimit.feature_id)) { + ctx.issues.push({ + code: "custom", + message: "Only one usage limit entry is allowed per feature_id", + input: usageLimit.feature_id, + path: ["usage_limits", index, "feature_id"], + }); + return; + } + + usageLimitFeatureIds.add(usageLimit.feature_id); + } + const overageAllowedFeatureIds = new Set(); for (const [index, overageAllowed] of ( @@ -195,9 +188,6 @@ export type AutoTopupResponse = z.infer; export type CustomerBillingControls = z.infer< typeof CustomerBillingControlsSchema >; -export type CustomerBillingControlsResponse = z.infer< - typeof CustomerBillingControlsResponseSchema ->; export type CustomerBillingControlsParams = z.input< typeof CustomerBillingControlsParamsSchema @@ -207,6 +197,7 @@ export type { DbOverageAllowed, DbSpendLimit, DbUsageAlert, + DbUsageLimit, EntityBillingControls, EntityBillingControlsParams, }; @@ -214,5 +205,6 @@ export { DbOverageAllowedSchema, DbSpendLimitSchema, DbUsageAlertSchema, + DbUsageLimitSchema, EntityBillingControlsSchema, }; diff --git a/shared/models/cusModels/billingControls/entityBillingControls.ts b/shared/models/cusModels/billingControls/entityBillingControls.ts index 58716f0b2..985dd285e 100644 --- a/shared/models/cusModels/billingControls/entityBillingControls.ts +++ b/shared/models/cusModels/billingControls/entityBillingControls.ts @@ -2,12 +2,17 @@ import { z } from "zod/v4"; import { DbOverageAllowedSchema } from "./overageAllowed.js"; import { DbSpendLimitSchema } from "./spendLimit.js"; import { DbUsageAlertSchema } from "./usageAlert.js"; +import { DbUsageLimitSchema } from "./usageLimit.js"; export const EntityBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: "List of spend limits per feature. Each entry caps overage (overage_limit) and/or windowed usage (usage_limit).", }), + usage_limits: z.array(DbUsageLimitSchema).optional().meta({ + description: + "List of windowed hard usage caps per feature for this entity (max units per interval window). An entity entry overrides the customer's for that feature.", + }), usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ description: "List of usage alert configurations per feature.", }), diff --git a/shared/models/cusModels/billingControls/spendLimit.ts b/shared/models/cusModels/billingControls/spendLimit.ts index a429db15e..68d9bb6d8 100644 --- a/shared/models/cusModels/billingControls/spendLimit.ts +++ b/shared/models/cusModels/billingControls/spendLimit.ts @@ -1,5 +1,4 @@ import { z } from "zod/v4"; -import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; export const DbSpendLimitSchema = z .object({ @@ -12,33 +11,13 @@ export const DbSpendLimitSchema = z overage_limit: z.number().min(0).optional().meta({ description: "Maximum allowed overage spend for the target feature.", }), - usage_limit: z.number().min(0).optional().meta({ - description: - "Windowed usage cap: max units allowed per window. Its presence arms the cap (hard pre-write reject); absent means no usage cap.", - }), - usage_limit_interval: z.enum(EntInterval).optional().meta({ - description: - "Optional window/reset interval for the usage cap, aligned to the customer's billing cycle. When omitted, defaults to the feature entitlement's own reset interval. Only meaningful with usage_limit set.", - }), }) .refine( - (data) => - !(data.overage_limit !== undefined || data.usage_limit !== undefined) || - data.feature_id !== undefined, + (data) => data.overage_limit === undefined || data.feature_id !== undefined, { - message: - "feature_id is required when overage_limit or usage_limit is provided", + message: "feature_id is required when overage_limit is provided", path: ["feature_id"], }, ); export type DbSpendLimit = z.infer; - -export const SpendLimitResponseSchema = DbSpendLimitSchema.extend({ - usage_limit_used: z.number().min(0).optional().meta({ - description: - "Current usage already consumed in the active usage_limit window. Response-only; not stored on billing controls.", - }), -}); - -export type SpendLimitResponse = z.infer; diff --git a/shared/models/cusModels/billingControls/usageLimit.ts b/shared/models/cusModels/billingControls/usageLimit.ts new file mode 100644 index 000000000..6b843569b --- /dev/null +++ b/shared/models/cusModels/billingControls/usageLimit.ts @@ -0,0 +1,28 @@ +import { z } from "zod/v4"; +import { ResetInterval } from "../../productModels/intervals/resetInterval.js"; + +/** + * A windowed hard usage cap on one feature: at most `limit` units per + * `interval` window. Stored on the customer's `usage_limits` billing-control + * column; an entry's presence arms the cap. Enforcement happens in the + * deduction script against customer-scoped usage-window counters. + */ +export const DbUsageLimitSchema = z + .object({ + feature_id: z.string().meta({ + description: "The feature this usage limit applies to.", + }), + limit: z.number().min(0).meta({ + description: "Maximum units allowed per window.", + }), + interval: z.enum(ResetInterval).meta({ + description: + "Window interval for the cap, aligned to the customer's billing cycle.", + }), + }) + .refine((data) => data.interval !== ResetInterval.OneOff, { + message: "interval cannot be one_off for a usage limit", + path: ["interval"], + }); + +export type DbUsageLimit = z.infer; diff --git a/shared/models/cusModels/cusModels.ts b/shared/models/cusModels/cusModels.ts index 9412359ef..dd9a8077e 100644 --- a/shared/models/cusModels/cusModels.ts +++ b/shared/models/cusModels/cusModels.ts @@ -6,6 +6,7 @@ import { DbOverageAllowedSchema, DbSpendLimitSchema, DbUsageAlertSchema, + DbUsageLimitSchema, } from "./billingControls/customerBillingControls.js"; export const CustomerSchema = z.object({ @@ -25,6 +26,7 @@ export const CustomerSchema = z.object({ send_email_receipts: z.boolean().default(false), auto_topups: z.array(AutoTopupSchema).nullish(), spend_limits: z.array(DbSpendLimitSchema).nullish(), + usage_limits: z.array(DbUsageLimitSchema).nullish(), usage_alerts: z.array(DbUsageAlertSchema).nullish(), overage_allowed: z.array(DbOverageAllowedSchema).nullish(), config: z diff --git a/shared/models/cusModels/cusTable.ts b/shared/models/cusModels/cusTable.ts index c0be9f954..81db0e112 100644 --- a/shared/models/cusModels/cusTable.ts +++ b/shared/models/cusModels/cusTable.ts @@ -18,6 +18,7 @@ import type { DbOverageAllowed, DbSpendLimit, DbUsageAlert, + DbUsageLimit, } from "./billingControls/customerBillingControls.js"; export type CustomerConfig = { @@ -48,6 +49,7 @@ export const customers = pgTable( send_email_receipts: boolean("send_email_receipts").default(false), auto_topups: jsonb().$type(), spend_limits: jsonb().$type(), + usage_limits: jsonb().$type(), usage_alerts: jsonb().$type(), overage_allowed: jsonb().$type(), config: jsonb().$type().default({}), diff --git a/shared/models/cusModels/entityModels/entityModels.ts b/shared/models/cusModels/entityModels/entityModels.ts index a6bca9b4f..92b41df27 100644 --- a/shared/models/cusModels/entityModels/entityModels.ts +++ b/shared/models/cusModels/entityModels/entityModels.ts @@ -4,6 +4,7 @@ import { DbOverageAllowedSchema, DbSpendLimitSchema, DbUsageAlertSchema, + DbUsageLimitSchema, } from "../billingControls/customerBillingControls.js"; export const EntitySchema = z.object({ @@ -18,6 +19,7 @@ export const EntitySchema = z.object({ feature_id: z.string(), internal_feature_id: z.string(), spend_limits: z.array(DbSpendLimitSchema).nullish(), + usage_limits: z.array(DbUsageLimitSchema).nullish(), usage_alerts: z.array(DbUsageAlertSchema).nullish(), overage_allowed: z.array(DbOverageAllowedSchema).nullish(), }); diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index 307216ce4..777107226 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -15,6 +15,7 @@ import type { DbOverageAllowed, DbSpendLimit, DbUsageAlert, + DbUsageLimit, } from "../billingControls/customerBillingControls.js"; import { customers } from "../cusTable.js"; @@ -31,6 +32,7 @@ export const entities = pgTable( deleted: boolean().default(false).notNull(), internal_feature_id: text("internal_feature_id"), spend_limits: jsonb().$type(), + usage_limits: jsonb().$type(), usage_alerts: jsonb().$type(), overage_allowed: jsonb().$type(), diff --git a/shared/models/cusModels/fullSubject/fullSubjectModel.ts b/shared/models/cusModels/fullSubject/fullSubjectModel.ts index 17899c6ea..624f6c7d0 100644 --- a/shared/models/cusModels/fullSubject/fullSubjectModel.ts +++ b/shared/models/cusModels/fullSubject/fullSubjectModel.ts @@ -1,6 +1,7 @@ import { z } from "zod/v4"; import { FullAggregatedFeatureBalanceSchema } from "../../cusProductModels/cusEntModels/aggregatedCusEnt.js"; import { FullCustomerEntitlementSchema } from "../../cusProductModels/cusEntModels/cusEntModels.js"; +import { UsageWindowSchema } from "../../cusProductModels/cusEntModels/usageWindowTable.js"; import { FullCusProductSchema } from "../../cusProductModels/cusProductModels.js"; import { MigrationItemRunSchema } from "../../migrationV2Models/migrationItemRunSchema.js"; import { SubscriptionSchema } from "../../subModels/subModels.js"; @@ -29,6 +30,12 @@ export const FullSubjectSchema = z.object({ customer_products: z.array(FullCusProductSchema), extra_customer_entitlements: z.array(FullCustomerEntitlementSchema), + // Customer- or entity-scoped windowed-cap counters (one row per capped + // feature + window; internal_entity_id null = customer scope). On an entity + // subject this carries ONLY that entity's rows. Live data read from the + // per-feature balance hashes, never from the cached subject view. + usage_windows: z.array(UsageWindowSchema).optional(), + subscriptions: z.array(SubscriptionSchema).optional(), invoices: z.array(InvoiceSchema), diff --git a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts index 2e0736244..f7aec95ef 100644 --- a/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts +++ b/shared/models/cusModels/fullSubject/normalizedFullSubjectModel.ts @@ -7,9 +7,9 @@ import { type EntityBalance, FullCustomerEntitlementSchema, } from "../../cusProductModels/cusEntModels/cusEntModels.js"; -import type { UsageWindow } from "../../cusProductModels/cusEntModels/usageWindowTable.js"; import type { Replaceable } from "../../cusProductModels/cusEntModels/replaceableTable.js"; import type { DbRollover } from "../../cusProductModels/cusEntModels/rolloverModels/rolloverTable.js"; +import type { UsageWindow } from "../../cusProductModels/cusEntModels/usageWindowTable.js"; import type { FullCustomerPrice } from "../../cusProductModels/cusPriceModels/cusPriceModels.js"; import { FullCustomerPriceSchema } from "../../cusProductModels/cusPriceModels/cusPriceModels.js"; import type { DbCustomerPrice } from "../../cusProductModels/cusPriceModels/cusPriceTable.js"; @@ -90,7 +90,6 @@ export type SubjectBalance = { expires_at: number | null; external_id: string | null; entities: Record | null; - usage_windows: UsageWindow[]; cache_version: number | null; created_at: number; customer_id?: string | null; @@ -170,6 +169,12 @@ export type NormalizedFullSubject = { customer_entitlements: SubjectBalance[]; customer_prices: DbCustomerPrice[]; + /** Windowed-cap counter rows for ALL scopes (customer + entity; + * internal_entity_id null = customer scope), live-read from the + * per-feature balance hashes' `_usage_windows` field — never the cached + * subject view. `normalizedToFullSubject` narrows to the subject's scope. */ + usage_windows: UsageWindow[]; + flags: Record; products: DbProduct[]; diff --git a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts index 1bdf6005c..32c01941b 100644 --- a/shared/models/cusProductModels/cusEntModels/cusEntModels.ts +++ b/shared/models/cusProductModels/cusEntModels/cusEntModels.ts @@ -3,7 +3,6 @@ import { EntitlementWithFeatureSchema } from "../../productModels/entModels/entM import { EntInterval } from "../../productModels/intervals/entitlementInterval.js"; import { ReplaceableSchema } from "./replaceableSchema.js"; import { RolloverSchema } from "./rolloverModels/rolloverTable.js"; -import { UsageWindowSchema } from "./usageWindowTable.js"; export const CustomerEntitlementFiltersSchema = z.object({ cusEntIds: z.array(z.string()).optional(), @@ -56,10 +55,6 @@ export const FullCustomerEntitlementSchema = CustomerEntitlementSchema.extend({ entitlement: EntitlementWithFeatureSchema, replaceables: z.array(ReplaceableSchema), rollovers: z.array(RolloverSchema), - - // Windowed usage-limit counters, persisted as their own rows (second limit - // dimension on top of balance). - usage_windows: z.array(UsageWindowSchema).nullish(), }); export type CustomerEntitlementFilters = z.infer< diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts index c0bf88d91..01d5cd1c9 100644 --- a/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts +++ b/shared/models/cusProductModels/cusEntModels/usageWindowModels.ts @@ -28,6 +28,7 @@ export type UsageWindowScope = z.infer; export const UsageWindowLimitSchema = z.object({ feature_id: z.string(), internal_feature_id: z.string(), + internal_customer_id: z.string(), key: z.string(), dimension_type: UsageWindowDimensionSchema, dimension_feature_id: z.string().nullable(), @@ -38,12 +39,15 @@ export const UsageWindowLimitSchema = z.object({ window_start_at: z.number(), window_end_at: z.number(), limit: z.number(), - // The single entitlement that owns this counter, resolved in TS so it is - // deduction-order-independent. Null when no eligible owner exists (e.g. a - // customer-scope cap with only entity-scoped entitlements) -> enforcement - // must fail closed rather than split or silently allow. + // Bounds/interval provenance: the entitlement whose reset interval and + // billing-cycle anchor shaped this window. Stamped onto the counter row at + // creation; storage no longer depends on it, so null just means calendar + // bounds with no provenance. anchor_customer_entitlement_id: z.string().nullable(), - anchor_feature_id: z.string().nullable(), + // Candidate row id (ksuid) minted server-side per request. Lua uses it ONLY + // when this request creates the counter row; lookups match the logical key + // (window_start_at + entity), never the id. + new_window_id: z.string().optional(), }); export type UsageWindowLimit = z.infer; diff --git a/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts b/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts index dd40f5a4e..2c96b607b 100644 --- a/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts +++ b/shared/models/cusProductModels/cusEntModels/usageWindowTable.ts @@ -1,3 +1,4 @@ +import { sql } from "drizzle-orm"; import { foreignKey, index, @@ -7,20 +8,30 @@ import { uniqueIndex, } from "drizzle-orm/pg-core"; import { z } from "zod/v4"; +import { customers } from "../../cusModels/cusTable.js"; +import { entities } from "../../cusModels/entityModels/entityTable.js"; import { features } from "../../featureModels/featureTable.js"; import { customerEntitlements } from "./cusEntTable.js"; /** - * A single windowed usage counter, persisted as its own row beneath a customer - * entitlement. `usage` is the running total consumed within [window_start_at, - * window_end_at). The enforced limit is resolved at deduction time, so it is not - * stored here. + * A single windowed usage counter, persisted as its own row scoped to the + * CUSTOMER (not an entitlement): one row per (customer, capped feature, + * window). `usage` is the running total consumed within [window_start_at, + * window_end_at). The enforced limit is resolved at deduction time, so it is + * not stored here. + * + * `internal_entity_id` is NULL for customer-scope counters; entity-scoped + * windows (v2) will set it. `anchor_customer_entitlement_id` records which + * entitlement supplied the window bounds at initialization (provenance only -- + * deleting that entitlement must never erase usage, hence ON DELETE SET NULL). */ export const UsageWindowSchema = z.object({ id: z.string(), - customer_entitlement_id: z.string(), + internal_customer_id: z.string(), + internal_entity_id: z.string().nullable(), feature_id: z.string(), internal_feature_id: z.string(), + anchor_customer_entitlement_id: z.string().nullable(), window_start_at: z.number(), window_end_at: z.number(), usage: z.number(), @@ -31,9 +42,11 @@ export const usageWindows = pgTable( "usage_windows", { id: text("id").primaryKey().notNull(), - customer_entitlement_id: text("customer_entitlement_id").notNull(), + internal_customer_id: text("internal_customer_id").notNull(), + internal_entity_id: text("internal_entity_id"), feature_id: text("feature_id").notNull(), internal_feature_id: text("internal_feature_id").notNull(), + anchor_customer_entitlement_id: text("anchor_customer_entitlement_id"), window_start_at: numeric({ mode: "number" }).notNull(), window_end_at: numeric({ mode: "number" }).notNull(), usage: numeric({ mode: "number" }).notNull().default(0), @@ -41,25 +54,40 @@ export const usageWindows = pgTable( }, (table) => [ foreignKey({ - columns: [table.customer_entitlement_id], - foreignColumns: [customerEntitlements.id], - name: "usage_windows_customer_entitlement_id_fkey", - }) - .onUpdate("cascade") - .onDelete("cascade"), + columns: [table.internal_customer_id], + foreignColumns: [customers.internal_id], + name: "usage_windows_internal_customer_id_fkey", + }).onDelete("cascade"), + foreignKey({ + columns: [table.internal_entity_id], + foreignColumns: [entities.internal_id], + name: "usage_windows_internal_entity_id_fkey", + }).onDelete("cascade"), foreignKey({ columns: [table.internal_feature_id], foreignColumns: [features.internal_id], name: "usage_windows_internal_feature_id_fkey", }).onDelete("cascade"), + // Provenance only: the anchor supplied the window bounds at init; its + // deletion must not erase accumulated usage. + foreignKey({ + columns: [table.anchor_customer_entitlement_id], + foreignColumns: [customerEntitlements.id], + name: "usage_windows_anchor_customer_entitlement_id_fkey", + }) + .onUpdate("cascade") + .onDelete("set null"), - index("idx_usage_windows_customer_entitlement_id").on( - table.customer_entitlement_id, + index("idx_usage_windows_internal_customer_id").on( + table.internal_customer_id, ), - uniqueIndex("idx_usage_windows_cus_ent_feature_window").on( - table.customer_entitlement_id, - table.feature_id, - table.window_start_at, + // ONE mutable counter row per scope: bounds roll forward in place, usage + // zeroes when its window closes. NULL internal_entity_id = customer + // scope; COALESCE makes the key unique across both scopes. + uniqueIndex("idx_usage_windows_customer_feature_scope").on( + table.internal_customer_id, + table.internal_feature_id, + sql`COALESCE(${table.internal_entity_id}, '')`, ), ], ).enableRLS(); diff --git a/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts deleted file mode 100644 index 890821688..000000000 --- a/shared/utils/fullSubjectUtils/fullSubjectToApiSpendLimits.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { SpendLimitResponse } from "../../models/cusModels/billingControls/spendLimit.js"; -import type { FullSubject } from "../../models/cusModels/fullSubject/fullSubjectModel.js"; -import type { FullCustomerEntitlement } from "../../models/cusProductModels/cusEntModels/cusEntModels.js"; -import type { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; -import type { Feature } from "../../models/featureModels/featureModels.js"; -import { fullSubjectToUsageWindowLimits } from "./fullSubjectToUsageWindowLimits.js"; - -const fullSubjectToAllCustomerEntitlements = ({ - fullSubject, -}: { - fullSubject: FullSubject; -}): FullCustomerEntitlement[] => [ - ...fullSubject.customer_products.flatMap( - (customerProduct) => customerProduct.customer_entitlements, - ), - ...(fullSubject.extra_customer_entitlements ?? []), -]; - -/** - * Response decorator for customer spend limits. `usage_limit_used` is runtime - * state read from the current usage-window counter, not stored billing config. - */ -export const fullSubjectToApiSpendLimits = ({ - fullSubject, - features, - now = Date.now(), - inStatuses, -}: { - fullSubject: FullSubject; - features: Feature[]; - now?: number; - inStatuses?: CusProductStatus[]; -}): SpendLimitResponse[] | undefined => { - const spendLimits = fullSubject.customer.spend_limits; - if (spendLimits == null) return undefined; - - const usageLimitFeatureIds = spendLimits - .filter( - (spendLimit) => - spendLimit.feature_id != null && spendLimit.usage_limit != null, - ) - .map((spendLimit) => spendLimit.feature_id!); - - const usageWindowLimits = - usageLimitFeatureIds.length > 0 - ? fullSubjectToUsageWindowLimits({ - fullSubject, - featureIds: usageLimitFeatureIds, - features, - now, - inStatuses, - }) - : []; - - const allCustomerEntitlements = fullSubjectToAllCustomerEntitlements({ - fullSubject, - }); - const usageLimitUsedByFeatureId = new Map(); - - for (const limit of usageWindowLimits) { - if (limit.anchor_customer_entitlement_id == null) continue; - - const anchorCustomerEntitlement = allCustomerEntitlements.find( - (customerEntitlement) => - customerEntitlement.id === limit.anchor_customer_entitlement_id, - ); - const usageWindow = anchorCustomerEntitlement?.usage_windows?.find( - (window) => - window.feature_id === limit.feature_id && - Number(window.window_start_at) === limit.window_start_at, - ); - const usage = Number(usageWindow?.usage ?? 0); - - usageLimitUsedByFeatureId.set( - limit.feature_id, - Number.isFinite(usage) ? Math.max(0, usage) : 0, - ); - } - - return spendLimits.map((spendLimit) => { - if (spendLimit.usage_limit == null) return spendLimit; - - return { - ...spendLimit, - usage_limit_used: - spendLimit.feature_id == null - ? 0 - : (usageLimitUsedByFeatureId.get(spendLimit.feature_id) ?? 0), - }; - }); -}; diff --git a/shared/utils/fullSubjectUtils/fullSubjectToApiUsageLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToApiUsageLimits.ts new file mode 100644 index 000000000..810102840 --- /dev/null +++ b/shared/utils/fullSubjectUtils/fullSubjectToApiUsageLimits.ts @@ -0,0 +1,53 @@ +import type { ApiUsageLimit } from "../../api/billingControls/usageLimit.js"; +import type { FullSubject } from "../../models/cusModels/fullSubject/fullSubjectModel.js"; +import type { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; +import type { Feature } from "../../models/featureModels/featureModels.js"; +import { getCurrentUsageWindowUsage } from "../usageWindowUtils/getCurrentUsageWindowUsage.js"; +import { fullSubjectToUsageWindowLimits } from "./fullSubjectToUsageWindowLimits.js"; + +/** + * Response decorator for one arm's stored usage limits: each entry plus + * `usage` -- the amount already consumed in the active window, read from the + * subject's usage-window counters. `source` is explicit (not inferred from + * subjectType) because check builds the CUSTOMER arm from an entity subject. + */ +export const fullSubjectToApiUsageLimits = ({ + fullSubject, + features, + now = Date.now(), + inStatuses, + source = "customer", +}: { + fullSubject: FullSubject; + features: Feature[]; + now?: number; + inStatuses?: CusProductStatus[]; + source?: "customer" | "entity"; +}): ApiUsageLimit[] | undefined => { + const usageLimits = + source === "entity" + ? fullSubject.entity?.usage_limits + : fullSubject.customer.usage_limits; + if (usageLimits == null) return undefined; + + const resolvedLimits = fullSubjectToUsageWindowLimits({ + fullSubject, + featureIds: usageLimits.map((usageLimit) => usageLimit.feature_id), + features, + now, + inStatuses, + }); + const usageWindows = fullSubject.usage_windows ?? []; + + return usageLimits.map((usageLimit) => { + const resolved = resolvedLimits.find( + (limit) => limit.feature_id === usageLimit.feature_id, + ); + if (!resolved) return usageLimit; + + return { + ...usageLimit, + usage: getCurrentUsageWindowUsage({ usageWindows, limit: resolved, now }), + }; + }); +}; diff --git a/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts index c29bd7f89..d35926266 100644 --- a/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts +++ b/shared/utils/fullSubjectUtils/fullSubjectToUsageWindowLimits.ts @@ -1,76 +1,15 @@ import type { FullSubject } from "../../models/cusModels/fullSubject/fullSubjectModel.js"; -import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels/cusEntModels/cusEntWithProduct.js"; -import type { - UsageWindowDimension, - UsageWindowLimit, -} from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; -import { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; -import { FeatureType } from "../../models/featureModels/featureEnums.js"; +import type { UsageWindowLimit } from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; import type { Feature } from "../../models/featureModels/featureModels.js"; -import { getRelevantFeatures } from "../featureUtils.js"; -import { buildUsageWindowKey } from "../usageWindowUtils/buildUsageWindowKey.js"; -import { getUsageWindowBounds } from "../usageWindowUtils/getUsageWindowBounds.js"; -import { - type AnchorCandidate, - pickAnchorCustomerEntitlementId, -} from "../usageWindowUtils/pickAnchorCustomerEntitlementId.js"; -import { fullSubjectToCustomerEntitlements } from "./fullSubjectToCustomerEntitlements.js"; - -// Lower rank wins in the anchor tie-break. Loose entitlements (no product) are -// treated as active grants. -const customerProductStatusToAnchorRank = ( - status: CusProductStatus | undefined, -): number => { - switch (status) { - case undefined: - case CusProductStatus.Active: - return 0; - case CusProductStatus.PastDue: - return 1; - case CusProductStatus.Scheduled: - return 2; - case CusProductStatus.Trialing: - return 3; - default: - return 999; - } -}; - -const toAnchorCandidate = ( - customerEntitlement: FullCusEntWithFullCusProduct, -): AnchorCandidate => ({ - id: customerEntitlement.id, - is_entity_scoped: customerEntitlement.internal_entity_id !== null, - is_add_on: customerEntitlement.customer_product?.product.is_add_on ?? false, - status_rank: customerProductStatusToAnchorRank( - customerEntitlement.customer_product?.status, - ), - created_at: - customerEntitlement.customer_product?.created_at ?? - customerEntitlement.created_at, -}); +import { usageLimitToUsageWindowLimit } from "../usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.js"; /** - * Resolves the enforceable usage-window limits for the requested features from a - * FullSubject. A windowed cap is armed by setting `usage_limit` on a customer - * `spend_limit` entry (flat config; its presence is the switch, independent of the - * entry-level `enabled` which gates the overage cap). - * v1 reads ONLY customer-scoped spend_limits; entity usage windows are out of scope. - * - * The window interval is the entry's `usage_limit_interval` if set, else inherited - * from the anchor entitlement's reset interval (`entitlement.interval`) - so a cap - * defaults to the billing cycle and you usually set only `usage_limit`. No - * resolvable interval (e.g. a boolean entitlement with no interval and no override) - * means no enforceable cap, so the feature is skipped. - * - * A cap on a credit-system feature targets the credit pool (`balance` dimension); - * a cap on any other feature targets that feature's usage (`metered_feature`). - * Window bounds align to the customer's billing cycle (the anchor entitlement's - * `billing_cycle_anchor_resets_at`), falling back to UTC calendar when absent. - * - * Each limit gets a single owning `anchor_customer_entitlement_id`, resolved - * deduction-order-independently so one counter never splits across pools. Null - * anchor means no eligible owner; the enforcement layer fails closed. + * Resolves the enforceable usage-window limits for the requested features. + * Exactly ONE cap per feature per subject, mirroring spend-limit inheritance + * (fullSubjectToSpendLimitByFeatureId): the entity's own `usage_limits` entry + * wins (entity-scoped counter), else the customer's entry fills the gap at + * customer scope (the shared aggregate counter). */ export const fullSubjectToUsageWindowLimits = ({ fullSubject, @@ -84,110 +23,43 @@ export const fullSubjectToUsageWindowLimits = ({ features: Feature[]; now: number; // Status filter for entitlement lookups; pass the caller's orgToInStatuses so - // the cap's value/anchor resolution matches what the deduction can act on. + // the cap's anchor resolution matches what the deduction can act on. inStatuses?: CusProductStatus[]; }): UsageWindowLimit[] => { - // v1: customer-scoped caps only; entity-scoped usage windows are out of scope. - const customerSpendLimits = fullSubject.customer.spend_limits ?? []; + const entityUsageLimits = fullSubject.entity?.usage_limits ?? []; + const customerUsageLimits = fullSubject.customer.usage_limits ?? []; const limits: UsageWindowLimit[] = []; for (const featureId of [...new Set(featureIds)]) { - const spendLimit = customerSpendLimits.find( - (candidate) => - candidate.feature_id === featureId && candidate.usage_limit != null, + const entityUsageLimit = entityUsageLimits.find( + (candidate) => candidate.feature_id === featureId, ); - const limit = spendLimit?.usage_limit; - if (spendLimit == null || limit == null) continue; + const usageLimit = + entityUsageLimit ?? + customerUsageLimits.find( + (candidate) => candidate.feature_id === featureId, + ); + if (!usageLimit) continue; - const scopeType = "customer" as const; - const entityId = null; - const internalEntityId = null; + const feature = features.find((candidate) => candidate.id === featureId); + if (!feature) continue; - const featureObject = features.find((feature) => feature.id === featureId); - // No catalog feature => no internal_feature_id (a NOT NULL FK on the windows - // table); the cap is unenforceable and unstorable, so skip it. - if (featureObject?.internal_id == null) continue; - const isCreditSystem = featureObject.type === FeatureType.CreditSystem; - const dimensionType: UsageWindowDimension = isCreditSystem - ? "balance" - : "metered_feature"; - const dimensionFeatureId = isCreditSystem ? null : featureId; - - // Balance dim is owned by the credit-system entitlement. Metered dim - // prefers the member feature's own entitlement, then falls back to a - // credit system that contains it. - const containingCreditSystemFeatureIds = getRelevantFeatures({ + const limit = usageLimitToUsageWindowLimit({ + fullSubject, + usageLimit, + feature, features, - featureId, - }) - .map((feature) => feature.id) - .filter((relevantFeatureId) => relevantFeatureId !== featureId); - const ownerFeatureIdsByPreference = isCreditSystem - ? [[featureId]] - : [[featureId], containingCreditSystemFeatureIds]; - - let anchorId: string | null = null; - let anchorFeatureId: string | null = null; - let anchorCustomerEntitlement: FullCusEntWithFullCusProduct | undefined; - for (const ownerFeatureIds of ownerFeatureIdsByPreference) { - if (ownerFeatureIds.length === 0) continue; - const candidateEntitlements = fullSubjectToCustomerEntitlements({ - fullSubject, - featureIds: ownerFeatureIds, - inStatuses, - }); - anchorId = pickAnchorCustomerEntitlementId({ - candidates: candidateEntitlements.map(toAnchorCandidate), - scopeType, - }); - if (anchorId) { - anchorCustomerEntitlement = candidateEntitlements.find( - (customerEntitlement) => customerEntitlement.id === anchorId, - ); - anchorFeatureId = anchorCustomerEntitlement?.feature_id ?? null; - break; - } - } - - const interval = - spendLimit.usage_limit_interval ?? - anchorCustomerEntitlement?.entitlement.interval; - if (interval == null) continue; - - // Align window bounds to the customer's billing cycle when the anchor has a - // cycle anchor; otherwise getUsageWindowBounds falls back to UTC calendar. - const cycleAnchor = - anchorCustomerEntitlement?.customer_product - ?.billing_cycle_anchor_resets_at ?? null; - const { windowStartAt, windowEndAt } = getUsageWindowBounds({ - interval, now, - anchor: cycleAnchor, - }); - - limits.push({ - feature_id: featureId, - internal_feature_id: featureObject.internal_id, - key: buildUsageWindowKey({ - scopeType, - internalEntityId, - dimensionType, - dimensionFeatureId, - interval, - windowStartAt, - }), - dimension_type: dimensionType, - dimension_feature_id: dimensionFeatureId, - scope_type: scopeType, - entity_id: entityId, - internal_entity_id: internalEntityId, - interval, - window_start_at: windowStartAt, - window_end_at: windowEndAt, - limit, - anchor_customer_entitlement_id: anchorId, - anchor_feature_id: anchorFeatureId, + inStatuses, + entityScope: + entityUsageLimit && fullSubject.entity + ? { + entityId: fullSubject.entity.id, + internalEntityId: fullSubject.entity.internal_id, + } + : null, }); + if (limit) limits.push(limit); } return limits; diff --git a/shared/utils/fullSubjectUtils/index.ts b/shared/utils/fullSubjectUtils/index.ts index fafd46ba9..f9b5fbc87 100644 --- a/shared/utils/fullSubjectUtils/index.ts +++ b/shared/utils/fullSubjectUtils/index.ts @@ -2,7 +2,7 @@ export * from "./aggregatedUtils/index.js"; export { fullSubjectHasUsageBasedAllocated } from "./classifyFullSubject.js"; export { fullCustomerToFullSubject } from "./fullCustomerToFullSubject.js"; export { fullSubjectToApiCustomerProducts } from "./fullSubjectToApiCustomerProducts.js"; -export { fullSubjectToApiSpendLimits } from "./fullSubjectToApiSpendLimits.js"; +export { fullSubjectToApiUsageLimits } from "./fullSubjectToApiUsageLimits.js"; export { fullSubjectToCustomerEntitlements } from "./fullSubjectToCustomerEntitlements.js"; export { fullSubjectToFullCustomer } from "./fullSubjectToFullCustomer.js"; export { fullSubjectToOverageAllowedByFeatureId } from "./fullSubjectToOverageAllowed.js"; diff --git a/shared/utils/fullSubjectUtils/mergeCustomerBillingControlsForCheck.ts b/shared/utils/fullSubjectUtils/mergeCustomerBillingControlsForCheck.ts index d91ab54b7..a93907553 100644 --- a/shared/utils/fullSubjectUtils/mergeCustomerBillingControlsForCheck.ts +++ b/shared/utils/fullSubjectUtils/mergeCustomerBillingControlsForCheck.ts @@ -3,12 +3,12 @@ import type { ApiEntityV2 } from "../../api/entities/apiEntityV2.js"; /** * Build a new entity apiSubject whose billing_controls inherit the customer's - * spend_limits and overage_allowed entries per feature_id. Entity's own entry - * always wins per feature; customer's entries fill any gaps. + * spend_limits, usage_limits and overage_allowed entries per feature_id. + * Entity's own entry always wins per feature; customer's entries fill gaps. * - * Used at check time so `apiSubjectToSpendLimit` / `apiSubjectToOverageAllowedControl` - * (which read from `subject.billing_controls`) see the inherited controls - * without needing to know about the customer separately. + * Used at check time so `apiSubjectToSpendLimit` / `apiSubjectToUsageLimitHeadroom` + * / `apiSubjectToOverageAllowedControl` (which read from `subject.billing_controls`) + * see the inherited controls without needing to know about the customer separately. * * Pure — does not mutate inputs. */ @@ -19,11 +19,16 @@ export const mergeCustomerBillingControlsForCheck = ({ entityApiSubject: ApiEntityV2; customerApiSubject: ApiCustomerV5; }): ApiEntityV2 => { - const entitySpendLimits = entityApiSubject.billing_controls?.spend_limits ?? []; + const entitySpendLimits = + entityApiSubject.billing_controls?.spend_limits ?? []; + const entityUsageLimits = + entityApiSubject.billing_controls?.usage_limits ?? []; const entityOverageAllowed = entityApiSubject.billing_controls?.overage_allowed ?? []; const customerSpendLimits = customerApiSubject.billing_controls?.spend_limits ?? []; + const customerUsageLimits = + customerApiSubject.billing_controls?.usage_limits ?? []; const customerOverageAllowed = customerApiSubject.billing_controls?.overage_allowed ?? []; @@ -32,6 +37,9 @@ export const mergeCustomerBillingControlsForCheck = ({ .map((entry) => entry.feature_id) .filter((id): id is string => !!id), ); + const entityUsageLimitFeatureIds = new Set( + entityUsageLimits.map((entry) => entry.feature_id), + ); const entityOverageAllowedFeatureIds = new Set( entityOverageAllowed.map((entry) => entry.feature_id), ); @@ -40,12 +48,18 @@ export const mergeCustomerBillingControlsForCheck = ({ (entry) => !!entry.feature_id && !entitySpendLimitFeatureIds.has(entry.feature_id), ); + // Inherited entries keep the CUSTOMER-window `usage`: the gap-filling cap is + // the shared aggregate window, not a per-entity copy. + const inheritedUsageLimits = customerUsageLimits.filter( + (entry) => !entityUsageLimitFeatureIds.has(entry.feature_id), + ); const inheritedOverageAllowed = customerOverageAllowed.filter( (entry) => !entityOverageAllowedFeatureIds.has(entry.feature_id), ); if ( inheritedSpendLimits.length === 0 && + inheritedUsageLimits.length === 0 && inheritedOverageAllowed.length === 0 ) { return entityApiSubject; @@ -56,6 +70,7 @@ export const mergeCustomerBillingControlsForCheck = ({ billing_controls: { ...entityApiSubject.billing_controls, spend_limits: [...entitySpendLimits, ...inheritedSpendLimits], + usage_limits: [...entityUsageLimits, ...inheritedUsageLimits], overage_allowed: [...entityOverageAllowed, ...inheritedOverageAllowed], }, }; diff --git a/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts b/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts index 4f056aeaa..597339e6c 100644 --- a/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts +++ b/shared/utils/fullSubjectUtils/normalizedToFullSubject.ts @@ -48,11 +48,6 @@ const subjectBalanceToFullCustomerEntitlement = ({ const rollovers = getArrayEntries({ value: subjectBalance.rollovers, }); - const usageWindows = getArrayEntries< - SubjectBalance["usage_windows"][number] - >({ - value: subjectBalance.usage_windows, - }); return { id: subjectBalance.id, @@ -81,7 +76,6 @@ const subjectBalanceToFullCustomerEntitlement = ({ getRolloverSortValue({ rollover: left }) - getRolloverSortValue({ rollover: right }), ), - usage_windows: usageWindows, } as FullCustomerEntitlement; }; @@ -392,6 +386,19 @@ export const normalizedToFullSubject = ({ customer: normalized.customer, customer_products: customerProducts, extra_customer_entitlements: extraCustomerEntitlements, + // Normalized carries ALL scopes (the balance-hash field must stay + // complete); the subject view narrows to the rows that can gate it -- + // an entity sees its own rows plus the inheritable customer-scope ones. + usage_windows: getArrayEntries< + NormalizedFullSubject["usage_windows"][number] + >({ + value: normalized.usage_windows, + }).filter((usageWindow) => + normalized.internalEntityId + ? usageWindow.internal_entity_id === normalized.internalEntityId || + usageWindow.internal_entity_id == null + : true, + ), subscriptions, invoices, ...(aggregatedCustomerProducts diff --git a/shared/utils/usageWindowUtils/classifyUsageWindow/usageWindowMatchesLimit.ts b/shared/utils/usageWindowUtils/classifyUsageWindow/usageWindowMatchesLimit.ts new file mode 100644 index 000000000..ccbd305e4 --- /dev/null +++ b/shared/utils/usageWindowUtils/classifyUsageWindow/usageWindowMatchesLimit.ts @@ -0,0 +1,16 @@ +import type { UsageWindowLimit } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindow } from "../../../models/cusProductModels/cusEntModels/usageWindowTable.js"; + +/** + * Whether a counter row and a resolved limit describe the same counter: + * same feature, same scope (null entity = customer scope). + */ +export const usageWindowMatchesLimit = ({ + usageWindow, + limit, +}: { + usageWindow: UsageWindow; + limit: UsageWindowLimit; +}): boolean => + usageWindow.feature_id === limit.feature_id && + (usageWindow.internal_entity_id ?? null) === limit.internal_entity_id; diff --git a/shared/utils/usageWindowUtils/convertUsageWindow/getUsageWindowDimension.ts b/shared/utils/usageWindowUtils/convertUsageWindow/getUsageWindowDimension.ts new file mode 100644 index 000000000..e1ebba4d5 --- /dev/null +++ b/shared/utils/usageWindowUtils/convertUsageWindow/getUsageWindowDimension.ts @@ -0,0 +1,26 @@ +import type { UsageWindowDimension } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import { FeatureType } from "../../../models/featureModels/featureEnums.js"; +import type { Feature } from "../../../models/featureModels/featureModels.js"; + +/** + * Which dimension a usage limit on `feature` counts against: + * - a credit-system feature caps the credit POOL (`balance` dimension, + * counted in credits drained); + * - any other feature caps that feature's own usage (`metered_feature` + * dimension, counted in tracked units). + */ +export const getUsageWindowDimension = ({ + feature, +}: { + feature: Feature; +}): { + dimensionType: UsageWindowDimension; + dimensionFeatureId: string | null; +} => { + const isCreditSystem = feature.type === FeatureType.CreditSystem; + + return { + dimensionType: isCreditSystem ? "balance" : "metered_feature", + dimensionFeatureId: isCreditSystem ? null : feature.id, + }; +}; diff --git a/shared/utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.ts b/shared/utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.ts new file mode 100644 index 000000000..0dd3c8f26 --- /dev/null +++ b/shared/utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.ts @@ -0,0 +1,94 @@ +import type { DbUsageLimit } from "../../../models/cusModels/billingControls/usageLimit.js"; +import type { FullSubject } from "../../../models/cusModels/fullSubject/fullSubjectModel.js"; +import type { UsageWindowLimit } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { CusProductStatus } from "../../../models/cusProductModels/cusProductEnums.js"; +import type { Feature } from "../../../models/featureModels/featureModels.js"; +import { resetIntvToEntIntv } from "../../productV2Utils/productItemUtils/convertProductItem/planItemIntervals.js"; +import { buildUsageWindowKey } from "../buildUsageWindowKey.js"; +import { findUsageWindowAnchor } from "../findUsageWindowAnchor/findUsageWindowAnchor.js"; +import { getUsageWindowAnchorTimestamp } from "../getUsageWindowAnchorTimestamp.js"; +import { getUsageWindowBounds } from "../getUsageWindowBounds.js"; +import { getUsageWindowDimension } from "./getUsageWindowDimension.js"; + +/** Non-null = the cap is the entity's own: its counter is entity-scoped. */ +export type UsageWindowEntityScope = { + entityId: string | null; + internalEntityId: string; +}; + +/** + * Resolves one stored usage-limit entry into the enforceable UsageWindowLimit + * handed to the deduction script. The entry's ResetInterval converts to the + * internal EntInterval here -- the single edge between the API/storage + * vocabulary and the window internals. Window bounds align to the customer's + * billing cycle via the anchor entitlement when one exists, else UTC calendar. + */ +export const usageLimitToUsageWindowLimit = ({ + fullSubject, + usageLimit, + feature, + features, + now, + inStatuses, + entityScope = null, +}: { + fullSubject: FullSubject; + usageLimit: DbUsageLimit; + feature: Feature; + features: Feature[]; + now: number; + inStatuses?: CusProductStatus[]; + entityScope?: UsageWindowEntityScope | null; +}): UsageWindowLimit | null => { + // No catalog internal_id => unstorable counter row (NOT NULL FK); the cap + // is unenforceable, so skip it. + if (feature.internal_id == null) return null; + + const interval = resetIntvToEntIntv({ resetIntv: usageLimit.interval }); + if (interval == null) return null; + + const { dimensionType, dimensionFeatureId } = getUsageWindowDimension({ + feature, + }); + + const scopeType = entityScope ? "entity" : "customer"; + const { anchorCustomerEntitlementId, anchorCustomerEntitlement } = + findUsageWindowAnchor({ + fullSubject, + featureId: feature.id, + features, + isCreditSystem: dimensionType === "balance", + inStatuses, + scopeType, + }); + + const { windowStartAt, windowEndAt } = getUsageWindowBounds({ + interval, + now, + anchor: getUsageWindowAnchorTimestamp({ anchorCustomerEntitlement }), + }); + + return { + feature_id: feature.id, + internal_feature_id: feature.internal_id, + internal_customer_id: fullSubject.internalCustomerId, + key: buildUsageWindowKey({ + scopeType, + internalEntityId: entityScope?.internalEntityId ?? null, + dimensionType, + dimensionFeatureId, + interval, + windowStartAt, + }), + dimension_type: dimensionType, + dimension_feature_id: dimensionFeatureId, + scope_type: scopeType, + entity_id: entityScope?.entityId ?? null, + internal_entity_id: entityScope?.internalEntityId ?? null, + interval, + window_start_at: windowStartAt, + window_end_at: windowEndAt, + limit: usageLimit.limit, + anchor_customer_entitlement_id: anchorCustomerEntitlementId, + }; +}; diff --git a/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowByLimit.ts b/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowByLimit.ts new file mode 100644 index 000000000..86cedc935 --- /dev/null +++ b/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowByLimit.ts @@ -0,0 +1,15 @@ +import type { UsageWindowLimit } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindow } from "../../../models/cusProductModels/cusEntModels/usageWindowTable.js"; +import { usageWindowMatchesLimit } from "../classifyUsageWindow/usageWindowMatchesLimit.js"; + +/** The limit's counter row (one mutable row per scope), if it exists yet. */ +export const findUsageWindowByLimit = ({ + usageWindows, + limit, +}: { + usageWindows: UsageWindow[]; + limit: UsageWindowLimit; +}): UsageWindow | undefined => + usageWindows.find((usageWindow) => + usageWindowMatchesLimit({ usageWindow, limit }), + ); diff --git a/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowLimitByWindow.ts b/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowLimitByWindow.ts new file mode 100644 index 000000000..9da2748e4 --- /dev/null +++ b/shared/utils/usageWindowUtils/findUsageWindow/findUsageWindowLimitByWindow.ts @@ -0,0 +1,13 @@ +import type { UsageWindowLimit } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindow } from "../../../models/cusProductModels/cusEntModels/usageWindowTable.js"; +import { usageWindowMatchesLimit } from "../classifyUsageWindow/usageWindowMatchesLimit.js"; + +/** The resolved limit governing a counter row, if one is armed for its scope. */ +export const findUsageWindowLimitByWindow = ({ + limits, + usageWindow, +}: { + limits: UsageWindowLimit[]; + usageWindow: UsageWindow; +}): UsageWindowLimit | undefined => + limits.find((limit) => usageWindowMatchesLimit({ usageWindow, limit })); diff --git a/shared/utils/usageWindowUtils/findUsageWindowAnchor/findUsageWindowAnchor.ts b/shared/utils/usageWindowUtils/findUsageWindowAnchor/findUsageWindowAnchor.ts new file mode 100644 index 000000000..62a7bbe64 --- /dev/null +++ b/shared/utils/usageWindowUtils/findUsageWindowAnchor/findUsageWindowAnchor.ts @@ -0,0 +1,112 @@ +import type { FullSubject } from "../../../models/cusModels/fullSubject/fullSubjectModel.js"; +import type { FullCusEntWithFullCusProduct } from "../../../models/cusProductModels/cusEntModels/cusEntWithProduct.js"; +import type { UsageWindowScope } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import { CusProductStatus } from "../../../models/cusProductModels/cusProductEnums.js"; +import type { Feature } from "../../../models/featureModels/featureModels.js"; +import { getRelevantFeatures } from "../../featureUtils.js"; +import { fullSubjectToCustomerEntitlements } from "../../fullSubjectUtils/fullSubjectToCustomerEntitlements.js"; +import { + type AnchorCandidate, + pickAnchorCustomerEntitlementId, +} from "./pickAnchorCustomerEntitlementId.js"; + +// Lower rank wins in the anchor tie-break. Loose entitlements (no product) are +// treated as active grants. +const customerProductStatusToAnchorRank = ( + status: CusProductStatus | undefined, +): number => { + switch (status) { + case undefined: + case CusProductStatus.Active: + return 0; + case CusProductStatus.PastDue: + return 1; + case CusProductStatus.Scheduled: + return 2; + case CusProductStatus.Trialing: + return 3; + default: + return 999; + } +}; + +const toAnchorCandidate = ( + customerEntitlement: FullCusEntWithFullCusProduct, +): AnchorCandidate => ({ + id: customerEntitlement.id, + is_entity_scoped: customerEntitlement.internal_entity_id !== null, + is_add_on: customerEntitlement.customer_product?.product.is_add_on ?? false, + is_plan_backed: customerEntitlement.customer_product != null, + status_rank: customerProductStatusToAnchorRank( + customerEntitlement.customer_product?.status, + ), + created_at: + customerEntitlement.customer_product?.created_at ?? + customerEntitlement.created_at, +}); + +/** + * Finds the usage window's ANCHOR entitlement: a bounds/billing-cycle + * reference only (counters are customer-scoped rows, never entitlement-owned). + * It supplies billing-cycle alignment for the window bounds and is stamped on + * counter rows at creation as provenance. + * + * Owner preference: the capped feature's own entitlements first, then (for + * non-credit features) entitlements of credit systems that contain it. Null + * when no eligible entitlement exists -- the cap stays enforceable with + * calendar-aligned bounds. + */ +export const findUsageWindowAnchor = ({ + fullSubject, + featureId, + features, + isCreditSystem, + inStatuses, + scopeType = "customer", +}: { + fullSubject: FullSubject; + featureId: string; + features: Feature[]; + isCreditSystem: boolean; + inStatuses?: CusProductStatus[]; + scopeType?: UsageWindowScope; +}): { + anchorCustomerEntitlementId: string | null; + anchorCustomerEntitlement?: FullCusEntWithFullCusProduct; +} => { + const containingCreditSystemFeatureIds = getRelevantFeatures({ + features, + featureId, + }) + .map((feature) => feature.id) + .filter((relevantFeatureId) => relevantFeatureId !== featureId); + const ownerFeatureIdsByPreference = isCreditSystem + ? [[featureId]] + : [[featureId], containingCreditSystemFeatureIds]; + + for (const ownerFeatureIds of ownerFeatureIdsByPreference) { + if (ownerFeatureIds.length === 0) continue; + + const candidateEntitlements = fullSubjectToCustomerEntitlements({ + fullSubject, + featureIds: ownerFeatureIds, + inStatuses, + }); + const anchorCustomerEntitlementId = pickAnchorCustomerEntitlementId({ + candidates: candidateEntitlements.map(toAnchorCandidate), + scopeType, + }); + + if (anchorCustomerEntitlementId) { + return { + anchorCustomerEntitlementId, + anchorCustomerEntitlement: candidateEntitlements.find( + (customerEntitlement) => + customerEntitlement.id === anchorCustomerEntitlementId, + ), + }; + } + } + + return { anchorCustomerEntitlementId: null }; +}; diff --git a/shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts b/shared/utils/usageWindowUtils/findUsageWindowAnchor/pickAnchorCustomerEntitlementId.ts similarity index 84% rename from shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts rename to shared/utils/usageWindowUtils/findUsageWindowAnchor/pickAnchorCustomerEntitlementId.ts index 7baba13d0..ca0e291b0 100644 --- a/shared/utils/usageWindowUtils/pickAnchorCustomerEntitlementId.ts +++ b/shared/utils/usageWindowUtils/findUsageWindowAnchor/pickAnchorCustomerEntitlementId.ts @@ -1,4 +1,4 @@ -import type { UsageWindowScope } from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindowScope } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js"; /** * A candidate customer entitlement for owning a usage-window counter, reduced to @@ -10,6 +10,9 @@ export type AnchorCandidate = { id: string; is_entity_scoped: boolean; is_add_on: boolean; + // Product-backed ents outrank loose/top-up grants: their reset cycle is + // what window bounds align to. + is_plan_backed: boolean; // Lower rank = higher priority (e.g. active before past_due). status_rank: number; created_at: number; @@ -46,6 +49,7 @@ export const pickAnchorCustomerEntitlementId = ({ const sorted = [...eligible].sort((a, b) => { if (a.status_rank !== b.status_rank) return a.status_rank - b.status_rank; + if (a.is_plan_backed !== b.is_plan_backed) return a.is_plan_backed ? -1 : 1; if (a.is_add_on !== b.is_add_on) return a.is_add_on ? 1 : -1; if (a.created_at !== b.created_at) return a.created_at - b.created_at; return a.id < b.id ? -1 : 1; diff --git a/shared/utils/usageWindowUtils/getCurrentUsageWindowUsage.ts b/shared/utils/usageWindowUtils/getCurrentUsageWindowUsage.ts new file mode 100644 index 000000000..f150096d2 --- /dev/null +++ b/shared/utils/usageWindowUtils/getCurrentUsageWindowUsage.ts @@ -0,0 +1,30 @@ +import type { UsageWindowLimit } from "../../models/cusProductModels/cusEntModels/usageWindowModels.js"; +import type { UsageWindow } from "../../models/cusProductModels/cusEntModels/usageWindowTable.js"; +import { findUsageWindowByLimit } from "./findUsageWindow/findUsageWindowByLimit.js"; + +/** + * Usage already consumed in the limit's current window: the scope's single + * mutable counter row, derived as 0 when its stored window closed OR no + * longer matches the current derivation (the lazy roll persists the zero; + * reads never trust a dead count). + */ +export const getCurrentUsageWindowUsage = ({ + usageWindows, + limit, + now = Date.now(), +}: { + usageWindows: UsageWindow[]; + limit: UsageWindowLimit; + now?: number; +}): number => { + const scopeRow = findUsageWindowByLimit({ usageWindows, limit }); + if ( + !scopeRow || + Number(scopeRow.window_end_at) <= now || + Number(scopeRow.window_start_at) !== limit.window_start_at + ) + return 0; + + const usage = Number(scopeRow.usage); + return Number.isFinite(usage) ? Math.max(0, usage) : 0; +}; diff --git a/shared/utils/usageWindowUtils/getUsageWindowAnchorTimestamp.ts b/shared/utils/usageWindowUtils/getUsageWindowAnchorTimestamp.ts new file mode 100644 index 000000000..e72f12e8c --- /dev/null +++ b/shared/utils/usageWindowUtils/getUsageWindowAnchorTimestamp.ts @@ -0,0 +1,16 @@ +import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels/cusEntModels/cusEntWithProduct.js"; + +/** + * The timestamp a usage window's bounds align to: the anchor entitlement's + * own reset cycle, falling back to the product's billing-cycle anchor, else + * null (UTC calendar). Windows therefore roll WITH the entitlement's cycle -- + * and a plan change that restarts the cycle restarts the window. + */ +export const getUsageWindowAnchorTimestamp = ({ + anchorCustomerEntitlement, +}: { + anchorCustomerEntitlement?: FullCusEntWithFullCusProduct; +}): number | null => + anchorCustomerEntitlement?.next_reset_at ?? + anchorCustomerEntitlement?.customer_product?.billing_cycle_anchor_resets_at ?? + null; diff --git a/statement-breakpoint b/statement-breakpoint new file mode 100644 index 000000000..e69de29bb diff --git a/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx b/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx index 0b748eab8..827b0e44c 100644 --- a/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx +++ b/vite/src/views/customers2/components/CustomerBillingControlsSection.tsx @@ -3,6 +3,7 @@ import type { DbOverageAllowed, DbSpendLimit, DbUsageAlert, + DbUsageLimit, Entity, Feature, FullCustomer, @@ -45,7 +46,9 @@ const StatusPill = ({ enabled }: { enabled: boolean }) => ( {enabled ? "Enabled" : "Disabled"} @@ -105,18 +108,20 @@ const AutoTopupRow = ({
Threshold: {autoTopup.threshold.toLocaleString()} Qty: {autoTopup.quantity.toLocaleString()} - {purchaseLimit && purchaseLimit.limit != null && purchaseLimit.interval != null && ( - - {hasExpandedLimit - ? `${purchaseLimit.count}/${purchaseLimit.limit} per ${purchaseLimit.interval}` - : `Limit: ${purchaseLimit.limit} per ${purchaseLimit.interval}`} - - )} - {hasExpandedLimit && purchaseLimit.next_reset_at && ( - - Resets {format(new Date(purchaseLimit.next_reset_at), "MMM d")} - - )} + {purchaseLimit && + purchaseLimit.limit != null && + purchaseLimit.interval != null && ( + + {hasExpandedLimit + ? `${purchaseLimit.count}/${purchaseLimit.limit} per ${purchaseLimit.interval}` + : `Limit: ${purchaseLimit.limit} per ${purchaseLimit.interval}`} + + )} + {hasExpandedLimit && purchaseLimit.next_reset_at && ( + + Resets {format(new Date(purchaseLimit.next_reset_at), "MMM d")} + + )}
); @@ -155,12 +160,13 @@ const UsageLimitRow = ({ featureNameById, onClick, }: { - usageLimit: DbSpendLimit; + usageLimit: DbUsageLimit; featureNameById: Map; onClick: () => void; }) => ( @@ -274,19 +278,15 @@ export function CustomerBillingControlsSection() { const allSpendLimits = selectedEntity ? (selectedEntity.spend_limits ?? []) : (fullCustomer?.spend_limits ?? []); - // Usage caps are folded into spend_limits (usage_limit set); surface them as a - // separate "Usage limits" control. Keep each entry's original index so edit/delete - // target the right slot in the full spend_limits array. - const indexedSpendLimits = allSpendLimits.map((item, index) => ({ + const spendLimits = allSpendLimits.map((item, index) => ({ item, index, })); - const spendLimits = indexedSpendLimits.filter( - ({ item }) => item.usage_limit == null, - ); - const usageLimits = indexedSpendLimits.filter( - ({ item }) => item.usage_limit != null, - ); + // Usage limits are their own customer-scoped billing control (no entity + // variant in v1). + const usageLimits = ( + selectedEntity ? [] : (fullCustomer?.usage_limits ?? []) + ).map((item: DbUsageLimit, index: number) => ({ item, index })); const usageAlerts = selectedEntity ? (selectedEntity.usage_alerts ?? []) : (fullCustomer?.usage_alerts ?? []); diff --git a/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx b/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx index 4165ac38e..74ff37539 100644 --- a/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx +++ b/vite/src/views/customers2/components/sheets/BillingUsageLimitSheet.tsx @@ -1,9 +1,9 @@ import { - type DbSpendLimit, - EntInterval, + type DbUsageLimit, type Feature, FeatureType, type FullCustomer, + ResetInterval, } from "@autumn/shared"; import { useState } from "react"; import { toast } from "sonner"; @@ -30,43 +30,28 @@ import { CusService } from "@/services/customers/CusService"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery"; -import { useCustomerContext } from "../../customer/CustomerContext"; - -// The empty value means "inherit the feature entitlement's reset interval" -// (usage_limit_interval omitted -> backend defaults to the billing cycle). -export const INHERIT_WINDOW = "inherit"; +// Interval is required (no inherit) and one_off windows are not supported. const WINDOW_OPTIONS: Record = { - [INHERIT_WINDOW]: "Inherit (billing cycle)", - [EntInterval.Day]: "Day", - [EntInterval.Week]: "Week", - [EntInterval.Month]: "Month", - [EntInterval.Year]: "Year", + [ResetInterval.Day]: "Day", + [ResetInterval.Week]: "Week", + [ResetInterval.Month]: "Month", + [ResetInterval.Year]: "Year", }; -/** - * Build the spend_limit entry for a usage cap. The cap is folded into spend_limits - * (presence of usage_limit arms it); window === INHERIT_WINDOW omits the interval so - * the backend inherits the entitlement's reset interval. Any co-located overage limit - * on an edited entry is preserved. - */ +/** Build the usage_limits entry for a windowed hard cap. */ export const buildUsageLimitItem = ({ - existing, featureId, - usageLimit, + limit, window, }: { - existing?: DbSpendLimit; featureId: string; - usageLimit: number; + limit: number; window: string; -}): DbSpendLimit => ({ - ...existing, - feature_id: featureId || undefined, - enabled: existing?.enabled ?? false, - usage_limit: usageLimit, - usage_limit_interval: - window === INHERIT_WINDOW ? undefined : (window as EntInterval), +}): DbUsageLimit => ({ + feature_id: featureId, + limit, + interval: window as ResetInterval, }); export function BillingUsageLimitSheet() { @@ -74,57 +59,42 @@ export function BillingUsageLimitSheet() { const sheetData = useSheetStore((s) => s.data); const sheetType = useSheetStore((s) => s.type); const { customer, refetch } = useCusQuery(); - const { entityId } = useCustomerContext(); const { features } = useFeaturesQuery(); const axiosInstance = useAxiosInstance(); const isEdit = sheetType === "billing-usage-limit-edit"; - const existingItem = sheetData?.item as DbSpendLimit | undefined; + const existingItem = sheetData?.item as DbUsageLimit | undefined; const existingIndex = sheetData?.index as number | undefined; + // v1: usage limits are customer-scoped only (no entity variant). const fullCustomer = customer as FullCustomer | undefined; - const selectedEntity = entityId - ? fullCustomer?.entities?.find( - (e) => e.id === entityId || e.internal_id === entityId, - ) - : null; const [isSaving, setIsSaving] = useState(false); const [featureId, setFeatureId] = useState(existingItem?.feature_id ?? ""); const [usageLimit, setUsageLimit] = useState( - existingItem?.usage_limit?.toString() ?? "", + existingItem?.limit?.toString() ?? "", ); const [windowInterval, setWindowInterval] = useState( - existingItem?.usage_limit_interval ?? INHERIT_WINDOW, + existingItem?.interval ?? ResetInterval.Month, ); const nonArchivedFeatures = (features ?? []).filter( (f: Feature) => !f.archived && f.type !== FeatureType.Boolean, ); - const getCurrentSpendLimits = (): DbSpendLimit[] => { - if (selectedEntity) return [...(selectedEntity.spend_limits ?? [])]; - return [...(fullCustomer?.spend_limits ?? [])]; - }; + const getCurrentUsageLimits = (): DbUsageLimit[] => [ + ...(fullCustomer?.usage_limits ?? []), + ]; - const saveBillingControls = async (spendLimits: DbSpendLimit[]) => { + const saveBillingControls = async (usageLimits: DbUsageLimit[]) => { const customerId = fullCustomer?.id || fullCustomer?.internal_id; if (!customerId) return; - if (selectedEntity) { - await CusService.updateEntity({ - axios: axiosInstance, - customerId, - entityId: selectedEntity.id || selectedEntity.internal_id, - billingControls: { spend_limits: spendLimits }, - }); - } else { - await CusService.updateCustomer({ - axios: axiosInstance, - customer_id: customerId, - data: { billing_controls: { spend_limits: spendLimits } }, - }); - } + await CusService.updateCustomer({ + axios: axiosInstance, + customer_id: customerId, + data: { billing_controls: { usage_limits: usageLimits } }, + }); }; const handleSave = async () => { @@ -140,22 +110,21 @@ export function BillingUsageLimitSheet() { } const item = buildUsageLimitItem({ - existing: existingItem, featureId, - usageLimit: parsedLimit, + limit: parsedLimit, window: windowInterval, }); - const spendLimits = getCurrentSpendLimits(); + const usageLimits = getCurrentUsageLimits(); if (isEdit && existingIndex !== undefined) { - spendLimits[existingIndex] = item; + usageLimits[existingIndex] = item; } else { - spendLimits.push(item); + usageLimits.push(item); } setIsSaving(true); try { - await saveBillingControls(spendLimits); + await saveBillingControls(usageLimits); await refetch(); closeSheet(); toast.success(isEdit ? "Usage limit updated" : "Usage limit added"); @@ -169,22 +138,12 @@ export function BillingUsageLimitSheet() { const handleDelete = async () => { if (existingIndex === undefined) return; - const spendLimits = getCurrentSpendLimits(); - const existing = spendLimits[existingIndex]; - // Preserve a co-located overage limit; otherwise drop the entry entirely. - if (existing?.overage_limit != null || existing?.enabled) { - spendLimits[existingIndex] = { - ...existing, - usage_limit: undefined, - usage_limit_interval: undefined, - }; - } else { - spendLimits.splice(existingIndex, 1); - } + const usageLimits = getCurrentUsageLimits(); + usageLimits.splice(existingIndex, 1); setIsSaving(true); try { - await saveBillingControls(spendLimits); + await saveBillingControls(usageLimits); await refetch(); closeSheet(); toast.success("Usage limit deleted"); diff --git a/vite/src/views/customers2/components/sheets/RecordUsageSheet.tsx b/vite/src/views/customers2/components/sheets/RecordUsageSheet.tsx index 65d6404e4..e1c8a011f 100644 --- a/vite/src/views/customers2/components/sheets/RecordUsageSheet.tsx +++ b/vite/src/views/customers2/components/sheets/RecordUsageSheet.tsx @@ -1,24 +1,33 @@ -import type { Entity, FullCustomer } from "@autumn/shared"; -import { LATEST_VERSION } from "@autumn/shared"; +import type { + CreditSystemConfig, + Entity, + Feature, + FullCustomer, +} from "@autumn/shared"; +import { FeatureType, LATEST_VERSION } from "@autumn/shared"; import { PlusIcon, TrashIcon } from "@phosphor-icons/react"; import { useQueryClient } from "@tanstack/react-query"; -import { useState } from "react"; +import { CheckIcon } from "lucide-react"; +import { useMemo, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/v2/buttons/Button"; import { ShortcutButton } from "@/components/v2/buttons/ShortcutButton"; import { FormLabel } from "@/components/v2/form/FormLabel"; import { Input } from "@/components/v2/inputs/Input"; +import { SearchableSelect } from "@/components/v2/selects/SearchableSelect"; import { LayoutGroup, SheetFooter, SheetHeader, SheetSection, } from "@/components/v2/sheets/SharedSheetComponents"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { useSheetStore } from "@/hooks/stores/useSheetStore"; import { useSheetScopeEntityId } from "@/hooks/useSheetScopeEntityId"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery"; +import { getFeatureIcon } from "@/views/products/features/utils/getFeatureIcon"; import { EntityScopeSelector } from "./EntityScopeSelector"; export function RecordUsageSheet() { @@ -40,6 +49,32 @@ export function RecordUsageSheet() { const featureId = sheetData?.featureId as string | undefined; const featureName = sheetData?.featureName as string | undefined; + const { features } = useFeaturesQuery(); + const creditSystem = features.find((f) => f.id === featureId); + const isCreditSystem = creditSystem?.type === FeatureType.CreditSystem; + + // Credit systems can deduct from the credit balance directly (default) or + // from any metered feature in their schema that still exists. + const featureOptions = useMemo(() => { + if (!(isCreditSystem && creditSystem)) return []; + const schema = + (creditSystem.config as CreditSystemConfig | undefined)?.schema ?? []; + const schemaFeatures = schema + .map((item) => features.find((f) => f.id === item.metered_feature_id)) + .filter((f): f is Feature => Boolean(f)); + return [creditSystem, ...schemaFeatures]; + }, [isCreditSystem, creditSystem, features]); + + const [selectedFeatureId, setSelectedFeatureId] = useState< + string | undefined + >(undefined); + const trackingFeatureId = selectedFeatureId ?? featureId; + const trackingFeatureName = + features.find((f) => f.id === trackingFeatureId)?.name ?? + featureName ?? + featureId; + const showFeatureSelect = isCreditSystem && featureOptions.length > 1; + const [isSubmitting, setIsSubmitting] = useState(false); const [value, setValue] = useState("1"); const [properties, setProperties] = useState< @@ -72,7 +107,7 @@ export function RecordUsageSheet() { const handleSubmit = async () => { const customerId = customer?.id || customer?.internal_id; - if (!customerId || !featureId) return; + if (!customerId || !trackingFeatureId) return; const parsedValue = value.trim() === "" ? 1 : Number.parseFloat(value); if (Number.isNaN(parsedValue)) { @@ -90,7 +125,7 @@ export function RecordUsageSheet() { const params: Record = { customer_id: customerId, - feature_id: featureId, + feature_id: trackingFeatureId, value: parsedValue, }; @@ -128,7 +163,7 @@ export function RecordUsageSheet() { description={ scopeEntityId ? `Tracking for entity ${fullEntity?.name || scopeEntityId}` - : `Record usage for ${featureName ?? featureId}` + : `Record usage for ${trackingFeatureName}` } /> @@ -140,6 +175,50 @@ export function RecordUsageSheet() { /> )} + {showFeatureSelect && ( + + Feature + + value={trackingFeatureId ?? null} + onValueChange={setSelectedFeatureId} + options={featureOptions} + getOptionValue={(feature) => feature.id} + getOptionLabel={(feature) => feature.name} + triggerClassName="w-full" + renderValue={(option) => + option ? ( + + + {getFeatureIcon({ feature: option })} + + {option.name} + + ) : ( + + Select feature + + ) + } + renderOption={(option, isSelected) => ( + <> +
+ + {getFeatureIcon({ feature: option })} + + {option.name} + {option.id === featureId && ( + + Credit system + + )} +
+ {isSelected && } + + )} + /> +
+ )} + Value { - test("new cap with inherited window omits the interval (cap armed by usage_limit)", () => { + test("builds a usage_limits entry (feature, limit, interval)", () => { const item = buildUsageLimitItem({ featureId: "credits", - usageLimit: 5, - window: INHERIT_WINDOW, + limit: 5, + window: ResetInterval.Month, }); expect(item.feature_id).toBe("credits"); - expect(item.usage_limit).toBe(5); - expect(item.usage_limit_interval).toBeUndefined(); - expect(item.enabled).toBe(false); + expect(item.limit).toBe(5); + expect(item.interval).toBe(ResetInterval.Month); }); - test("explicit window sets usage_limit_interval", () => { + test("window selection carries through as the interval", () => { const item = buildUsageLimitItem({ featureId: "credits", - usageLimit: 10, - window: EntInterval.Day, + limit: 10, + window: ResetInterval.Day, }); - expect(item.usage_limit).toBe(10); - expect(item.usage_limit_interval).toBe(EntInterval.Day); - }); - - test("editing preserves a co-located overage limit + enabled", () => { - const existing: DbSpendLimit = { - feature_id: "credits", - enabled: true, - overage_limit: 100, - }; - const item = buildUsageLimitItem({ - existing, - featureId: "credits", - usageLimit: 3, - window: EntInterval.Month, - }); - expect(item.overage_limit).toBe(100); - expect(item.enabled).toBe(true); - expect(item.usage_limit).toBe(3); - expect(item.usage_limit_interval).toBe(EntInterval.Month); - }); - - test("empty feature falls back to undefined feature_id", () => { - const item = buildUsageLimitItem({ - featureId: "", - usageLimit: 1, - window: INHERIT_WINDOW, - }); - expect(item.feature_id).toBeUndefined(); + expect(item.interval).toBe(ResetInterval.Day); }); });