Merge pull request #20 from kubiks-inc/autumn

Autumn
This commit is contained in:
Alex Holovach
2025-10-09 23:45:20 -05:00
committed by GitHub
11 changed files with 1449 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ Our goal is to bring the TypeScript ecosystem the observability tools its bee
## Supported integrations
- [`@kubiks/otel-autumn`](./packages/otel-autumn/README.md)
- [`@kubiks/otel-better-auth`](./packages/otel-better-auth/README.md)
- [`@kubiks/otel-drizzle`](./packages/otel-drizzle/README.md)
- [`@kubiks/otel-resend`](./packages/otel-resend/README.md)
@@ -23,7 +24,6 @@ Our goal is to bring the TypeScript ecosystem the observability tools its bee
- [Stripe](https://stripe.com/)
- [Polar.sh](https://polar.sh/)
- [Autumn](https://useautumn.com/)
- [ClickHouse](https://clickhouse.com/)
- [AI SDK](https://ai-sdk.dev/)
- [Mastra](https://mastra.ai/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -0,0 +1,5 @@
src/
tsconfig.json
*.test.ts
.gitignore
node_modules/

View File

@@ -0,0 +1,13 @@
# @kubiks/otel-autumn
## 1.0.0
### Major Changes
- Initial release of OpenTelemetry instrumentation for Autumn billing SDK
- Instrument core billing operations: check, track, checkout, attach, cancel
- Comprehensive span attributes for billing observability
- Support for feature access control monitoring
- Usage tracking instrumentation
- Checkout flow tracing with Stripe integration details
- Product lifecycle management (attach/cancel) tracing

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Kubiks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,207 @@
# @kubiks/otel-autumn
OpenTelemetry instrumentation for the [Autumn](https://useautumn.com) billing SDK.
Capture spans for every billing operation including feature checks, usage tracking, checkout flows, product attachments, and cancellations with detailed metadata.
![Autumn Trace Visualization](https://github.com/kubiks-inc/otel/blob/main/images/otel-autumn-trace.png)
_Visualize your billing operations with detailed span information including operation type, customer IDs, feature IDs, and billing metadata._
## Installation
```bash
npm install @kubiks/otel-autumn
# or
pnpm add @kubiks/otel-autumn
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `autumn-js` >= 0.1.0
## Quick Start
```ts
import { Autumn } from "autumn-js";
import { instrumentAutumn } from "@kubiks/otel-autumn";
const autumn = instrumentAutumn(
new Autumn({
secretKey: process.env.AUTUMN_SECRET_KEY!,
}),
);
// All operations are now automatically traced
const checkResult = await autumn.check({
customer_id: "user_123",
feature_id: "messages",
});
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
});
```
`instrumentAutumn` wraps your Autumn client instance — no configuration changes needed. Every SDK call creates a client span with detailed billing attributes.
## What Gets Traced
This instrumentation wraps the core Autumn billing methods:
- **`check`** - Feature access and product status checks
- **`track`** - Usage event tracking
- **`checkout`** - Checkout session creation
- **`attach`** - Product attachment to customers
- **`cancel`** - Product cancellation
Each operation creates a dedicated span with operation-specific attributes.
## Span Attributes
### Common Attributes (All Operations)
| Attribute | Description | Example |
| -------------------- | ------------------------- | ---------------------- |
| `billing.system` | Constant value `autumn` | `autumn` |
| `billing.operation` | Operation type | `check`, `track` |
| `autumn.resource` | Resource being accessed | `features`, `products` |
| `autumn.target` | Full operation target | `features.check` |
| `autumn.customer_id` | Customer ID | `user_123` |
| `autumn.entity_id` | Entity ID (if applicable) | `org_456` |
### Check Operation
| Attribute | Description | Example |
| ------------------------- | ------------------------------ | ---------- |
| `autumn.feature_id` | Feature being checked | `messages` |
| `autumn.product_id` | Product being checked | `pro` |
| `autumn.allowed` | Whether access is allowed | `true` |
| `autumn.balance` | Current balance/remaining uses | `42` |
| `autumn.usage` | Current usage | `8` |
| `autumn.included_usage` | Included usage in plan | `50` |
| `autumn.unlimited` | Whether usage is unlimited | `false` |
| `autumn.required_balance` | Required balance for operation | `1` |
### Track Operation
| Attribute | Description | Example |
| ------------------------ | ------------------------- | -------------- |
| `autumn.feature_id` | Feature being tracked | `messages` |
| `autumn.event_name` | Custom event name | `message_sent` |
| `autumn.value` | Usage value tracked | `1` |
| `autumn.event_id` | Generated event ID | `evt_123` |
| `autumn.idempotency_key` | Idempotency key for dedup | `msg_456` |
### Checkout Operation
| Attribute | Description | Example |
| ----------------------- | ----------------------------------- | --------------------------------- |
| `autumn.product_id` | Product being purchased | `pro` |
| `autumn.product_ids` | Multiple products (comma-separated) | `pro, addon_analytics` |
| `autumn.checkout_url` | Stripe checkout URL | `https://checkout.stripe.com/...` |
| `autumn.has_prorations` | Whether prorations apply | `true` |
| `autumn.total_amount` | Total checkout amount | `2000` (cents) |
| `autumn.currency` | Currency code | `usd` |
| `autumn.force_checkout` | Whether to force Stripe checkout | `false` |
| `autumn.invoice` | Whether to create invoice | `true` |
### Attach Operation
| Attribute | Description | Example |
| --------------------- | ------------------------------ | --------------------------------- |
| `autumn.product_id` | Product being attached | `pro` |
| `autumn.success` | Whether attachment succeeded | `true` |
| `autumn.checkout_url` | Checkout URL if payment needed | `https://checkout.stripe.com/...` |
### Cancel Operation
| Attribute | Description | Example |
| ------------------- | ------------------------------ | ------- |
| `autumn.product_id` | Product being cancelled | `pro` |
| `autumn.success` | Whether cancellation succeeded | `true` |
## Configuration
You can optionally configure the instrumentation:
```ts
import { instrumentAutumn } from "@kubiks/otel-autumn";
const autumn = instrumentAutumn(client, {
// Capture customer data in spans (default: false)
captureCustomerData: true,
// Capture product options/configuration (default: false)
captureOptions: true,
});
```
## Usage Examples
### Feature Access Control
```ts
const autumn = instrumentAutumn(
new Autumn({ secretKey: process.env.AUTUMN_SECRET_KEY! }),
);
// Check if user can access a feature
const result = await autumn.check({
customer_id: "user_123",
feature_id: "messages",
required_balance: 1,
});
if (result.data?.allowed) {
// User has access
console.log(`Remaining: ${result.data.balance}`);
}
```
### Usage Tracking
```ts
// Track feature usage
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
idempotency_key: `msg_${messageId}`, // Prevent double-counting
});
```
### Checkout Flow
```ts
// Create a checkout session for a product
const result = await autumn.checkout({
customer_id: "user_123",
product_id: "pro",
force_checkout: false, // Use billing portal if payment method exists
});
if (result.data?.url) {
// Redirect to Stripe checkout
console.log(`Checkout URL: ${result.data.url}`);
}
```
### Product Management
```ts
// Attach a free product
const attachResult = await autumn.attach({
customer_id: "user_123",
product_id: "free",
});
// Cancel a subscription
const cancelResult = await autumn.cancel({
customer_id: "user_123",
product_id: "pro",
});
```
## License
MIT

View File

@@ -0,0 +1,53 @@
{
"name": "@kubiks/otel-autumn",
"version": "1.0.0",
"private": false,
"publishConfig": {
"access": "public"
},
"description": "OpenTelemetry instrumentation for the Autumn billing SDK",
"author": "Kubiks",
"license": "MIT",
"repository": "kubiks-inc/otel",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist",
"LICENSE",
"README.md"
],
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"scripts": {
"build": "pnpm clean && tsc",
"clean": "rimraf dist",
"prepublishOnly": "pnpm build",
"type-check": "tsc --noEmit",
"unit-test": "vitest --run",
"unit-test-watch": "vitest"
},
"dependencies": {},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@types/node": "18.15.11",
"autumn-js": "^0.1.40",
"rimraf": "3.0.2",
"typescript": "^5",
"vitest": "0.33.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.9.0 <2.0.0",
"autumn-js": ">=0.1.0"
}
}

View File

@@ -0,0 +1,204 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { trace, type Tracer, type Span } from "@opentelemetry/api";
import { instrumentAutumn } from "./index";
import type { Autumn } from "autumn-js";
describe("instrumentAutumn", () => {
let mockClient: Autumn;
let mockTracer: Tracer;
let mockSpan: Span;
beforeEach(() => {
// Mock span
mockSpan = {
setAttributes: vi.fn(),
setAttribute: vi.fn(),
setStatus: vi.fn(),
end: vi.fn(),
recordException: vi.fn(),
} as any;
// Mock tracer
mockTracer = {
startSpan: vi.fn().mockReturnValue(mockSpan),
} as any;
// Mock trace.getTracer
vi.spyOn(trace, "getTracer").mockReturnValue(mockTracer);
// Mock Autumn client
mockClient = {
check: vi.fn().mockResolvedValue({ data: { allowed: true, balance: 5 } }),
track: vi.fn().mockResolvedValue({ data: { id: "evt_123" } }),
checkout: vi.fn().mockResolvedValue({ data: { url: "https://checkout.stripe.com" } }),
attach: vi.fn().mockResolvedValue({ data: { success: true } }),
cancel: vi.fn().mockResolvedValue({ data: { success: true } }),
} as any;
});
it("should not instrument the same client twice", () => {
const instrumented1 = instrumentAutumn(mockClient);
const instrumented2 = instrumentAutumn(instrumented1);
expect(instrumented1).toBe(instrumented2);
});
describe("check method", () => {
it("should create a span for check operations", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.check({
customer_id: "user_123",
feature_id: "messages",
});
expect(mockTracer.startSpan).toHaveBeenCalledWith(
"autumn.check",
expect.objectContaining({ kind: expect.any(Number) })
);
});
it("should set span attributes for check", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.check({
customer_id: "user_123",
feature_id: "messages",
required_balance: 1,
});
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.customer_id", "user_123");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.feature_id", "messages");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.required_balance", 1);
});
it("should handle check errors", async () => {
mockClient.check = vi.fn().mockRejectedValue(new Error("API Error"));
const instrumented = instrumentAutumn(mockClient);
await expect(
instrumented.check({ customer_id: "user_123", feature_id: "messages" })
).rejects.toThrow("API Error");
expect(mockSpan.recordException).toHaveBeenCalled();
});
});
describe("track method", () => {
it("should create a span for track operations", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
});
expect(mockTracer.startSpan).toHaveBeenCalledWith(
"autumn.track",
expect.objectContaining({ kind: expect.any(Number) })
);
});
it("should set span attributes for track", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
idempotency_key: "msg_456",
});
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.customer_id", "user_123");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.feature_id", "messages");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.value", 1);
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.idempotency_key", "msg_456");
});
});
describe("checkout method", () => {
it("should create a span for checkout operations", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.checkout({
customer_id: "user_123",
product_id: "pro",
});
expect(mockTracer.startSpan).toHaveBeenCalledWith(
"autumn.checkout",
expect.objectContaining({ kind: expect.any(Number) })
);
});
it("should set span attributes for checkout", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.checkout({
customer_id: "user_123",
product_id: "pro",
force_checkout: true,
});
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.customer_id", "user_123");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.product_id", "pro");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.force_checkout", true);
});
it("should handle multiple product IDs", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.checkout({
customer_id: "user_123",
product_ids: ["pro", "addon_analytics"],
});
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.product_ids", "pro, addon_analytics");
});
});
describe("attach method", () => {
it("should create a span for attach operations", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.attach({
customer_id: "user_123",
product_id: "free",
});
expect(mockTracer.startSpan).toHaveBeenCalledWith(
"autumn.attach",
expect.objectContaining({ kind: expect.any(Number) })
);
});
});
describe("cancel method", () => {
it("should create a span for cancel operations", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.cancel({
customer_id: "user_123",
product_id: "pro",
});
expect(mockTracer.startSpan).toHaveBeenCalledWith(
"autumn.cancel",
expect.objectContaining({ kind: expect.any(Number) })
);
});
it("should set span attributes for cancel", async () => {
const instrumented = instrumentAutumn(mockClient);
await instrumented.cancel({
customer_id: "user_123",
product_id: "pro",
});
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.customer_id", "user_123");
expect(mockSpan.setAttribute).toHaveBeenCalledWith("autumn.product_id", "pro");
});
});
});

View File

@@ -0,0 +1,529 @@
import {
context,
SpanKind,
SpanStatusCode,
trace,
type Span,
} from "@opentelemetry/api";
import type { Autumn } from "autumn-js";
const DEFAULT_TRACER_NAME = "@kubiks/otel-autumn";
const INSTRUMENTED_FLAG = Symbol("kubiksOtelAutumnInstrumented");
// Semantic attribute constants
export const SEMATTRS_BILLING_SYSTEM = "billing.system" as const;
export const SEMATTRS_BILLING_OPERATION = "billing.operation" as const;
export const SEMATTRS_AUTUMN_RESOURCE = "autumn.resource" as const;
export const SEMATTRS_AUTUMN_TARGET = "autumn.target" as const;
// Customer attributes
export const SEMATTRS_AUTUMN_CUSTOMER_ID = "autumn.customer_id" as const;
export const SEMATTRS_AUTUMN_ENTITY_ID = "autumn.entity_id" as const;
// Product attributes
export const SEMATTRS_AUTUMN_PRODUCT_ID = "autumn.product_id" as const;
export const SEMATTRS_AUTUMN_PRODUCT_IDS = "autumn.product_ids" as const;
export const SEMATTRS_AUTUMN_PRODUCT_NAME = "autumn.product_name" as const;
export const SEMATTRS_AUTUMN_PRODUCT_SCENARIO = "autumn.product_scenario" as const;
// Feature attributes
export const SEMATTRS_AUTUMN_FEATURE_ID = "autumn.feature_id" as const;
export const SEMATTRS_AUTUMN_FEATURE_NAME = "autumn.feature_name" as const;
export const SEMATTRS_AUTUMN_ALLOWED = "autumn.allowed" as const;
export const SEMATTRS_AUTUMN_BALANCE = "autumn.balance" as const;
export const SEMATTRS_AUTUMN_USAGE = "autumn.usage" as const;
export const SEMATTRS_AUTUMN_INCLUDED_USAGE = "autumn.included_usage" as const;
export const SEMATTRS_AUTUMN_UNLIMITED = "autumn.unlimited" as const;
export const SEMATTRS_AUTUMN_REQUIRED_BALANCE = "autumn.required_balance" as const;
// Checkout attributes
export const SEMATTRS_AUTUMN_CHECKOUT_URL = "autumn.checkout_url" as const;
export const SEMATTRS_AUTUMN_HAS_PRORATIONS = "autumn.has_prorations" as const;
export const SEMATTRS_AUTUMN_TOTAL_AMOUNT = "autumn.total_amount" as const;
export const SEMATTRS_AUTUMN_CURRENCY = "autumn.currency" as const;
export const SEMATTRS_AUTUMN_FORCE_CHECKOUT = "autumn.force_checkout" as const;
export const SEMATTRS_AUTUMN_INVOICE = "autumn.invoice" as const;
// Track attributes
export const SEMATTRS_AUTUMN_EVENT_NAME = "autumn.event_name" as const;
export const SEMATTRS_AUTUMN_VALUE = "autumn.value" as const;
export const SEMATTRS_AUTUMN_EVENT_ID = "autumn.event_id" as const;
export const SEMATTRS_AUTUMN_IDEMPOTENCY_KEY = "autumn.idempotency_key" as const;
// Attach/Cancel attributes
export const SEMATTRS_AUTUMN_SUCCESS = "autumn.success" as const;
export interface InstrumentationConfig {
/**
* Whether to capture customer data in spans.
* @default false
*/
captureCustomerData?: boolean;
/**
* Whether to capture product options/configuration in spans.
* @default false
*/
captureOptions?: boolean;
}
interface InstrumentedAutumn extends Autumn {
[INSTRUMENTED_FLAG]?: true;
}
function finalizeSpan(span: Span, error?: unknown): void {
if (error) {
if (error instanceof Error) {
span.recordException(error);
} else {
span.recordException(new Error(String(error)));
}
span.setStatus({ code: SpanStatusCode.ERROR });
} else {
span.setStatus({ code: SpanStatusCode.OK });
}
span.end();
}
function annotateCheckSpan(
span: Span,
params: {
customer_id: string;
feature_id?: string;
product_id?: string;
entity_id?: string;
required_balance?: number;
},
): void {
span.setAttributes({
[SEMATTRS_BILLING_SYSTEM]: "autumn",
[SEMATTRS_BILLING_OPERATION]: "check",
[SEMATTRS_AUTUMN_RESOURCE]: params.feature_id ? "features" : "products",
[SEMATTRS_AUTUMN_TARGET]: params.feature_id
? "features.check"
: "products.check",
});
span.setAttribute(SEMATTRS_AUTUMN_CUSTOMER_ID, params.customer_id);
if (params.feature_id) {
span.setAttribute(SEMATTRS_AUTUMN_FEATURE_ID, params.feature_id);
}
if (params.product_id) {
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_ID, params.product_id);
}
if (params.entity_id) {
span.setAttribute(SEMATTRS_AUTUMN_ENTITY_ID, params.entity_id);
}
if (typeof params.required_balance === "number") {
span.setAttribute(SEMATTRS_AUTUMN_REQUIRED_BALANCE, params.required_balance);
}
}
function annotateCheckResponse(span: Span, response: Record<string, unknown>): void {
if (typeof response.allowed === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_ALLOWED, response.allowed);
}
if (typeof response.balance === "number") {
span.setAttribute(SEMATTRS_AUTUMN_BALANCE, response.balance);
}
if (typeof response.usage === "number") {
span.setAttribute(SEMATTRS_AUTUMN_USAGE, response.usage);
}
if (typeof response.included_usage === "number") {
span.setAttribute(SEMATTRS_AUTUMN_INCLUDED_USAGE, response.included_usage);
}
if (typeof response.unlimited === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_UNLIMITED, response.unlimited);
}
if (typeof response.status === "string") {
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_SCENARIO, response.status);
}
}
function annotateTrackSpan(
span: Span,
params: {
customer_id: string;
feature_id?: string;
event_name?: string;
value?: number;
entity_id?: string;
idempotency_key?: string;
},
): void {
span.setAttributes({
[SEMATTRS_BILLING_SYSTEM]: "autumn",
[SEMATTRS_BILLING_OPERATION]: "track",
[SEMATTRS_AUTUMN_RESOURCE]: "events",
[SEMATTRS_AUTUMN_TARGET]: "events.track",
});
span.setAttribute(SEMATTRS_AUTUMN_CUSTOMER_ID, params.customer_id);
if (params.feature_id) {
span.setAttribute(SEMATTRS_AUTUMN_FEATURE_ID, params.feature_id);
}
if (params.event_name) {
span.setAttribute(SEMATTRS_AUTUMN_EVENT_NAME, params.event_name);
}
if (typeof params.value === "number") {
span.setAttribute(SEMATTRS_AUTUMN_VALUE, params.value);
}
if (params.entity_id) {
span.setAttribute(SEMATTRS_AUTUMN_ENTITY_ID, params.entity_id);
}
if (params.idempotency_key) {
span.setAttribute(SEMATTRS_AUTUMN_IDEMPOTENCY_KEY, params.idempotency_key);
}
}
function annotateTrackResponse(span: Span, response: { id?: string }): void {
if (response.id) {
span.setAttribute(SEMATTRS_AUTUMN_EVENT_ID, response.id);
}
}
function annotateCheckoutSpan(
span: Span,
params: {
customer_id: string;
product_id?: string;
product_ids?: string[];
entity_id?: string;
force_checkout?: boolean;
invoice?: boolean;
},
config?: InstrumentationConfig,
): void {
span.setAttributes({
[SEMATTRS_BILLING_SYSTEM]: "autumn",
[SEMATTRS_BILLING_OPERATION]: "checkout",
[SEMATTRS_AUTUMN_RESOURCE]: "checkout",
[SEMATTRS_AUTUMN_TARGET]: "checkout.create",
});
span.setAttribute(SEMATTRS_AUTUMN_CUSTOMER_ID, params.customer_id);
if (params.product_id) {
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_ID, params.product_id);
}
if (params.product_ids && params.product_ids.length > 0) {
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_IDS, params.product_ids.join(", "));
}
if (params.entity_id) {
span.setAttribute(SEMATTRS_AUTUMN_ENTITY_ID, params.entity_id);
}
if (typeof params.force_checkout === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_FORCE_CHECKOUT, params.force_checkout);
}
if (typeof params.invoice === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_INVOICE, params.invoice);
}
}
function annotateCheckoutResponse(
span: Span,
response: {
url?: string;
has_prorations?: boolean;
total?: number;
currency?: string;
},
): void {
if (response.url) {
span.setAttribute(SEMATTRS_AUTUMN_CHECKOUT_URL, response.url);
}
if (typeof response.has_prorations === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_HAS_PRORATIONS, response.has_prorations);
}
if (typeof response.total === "number") {
span.setAttribute(SEMATTRS_AUTUMN_TOTAL_AMOUNT, response.total);
}
if (response.currency) {
span.setAttribute(SEMATTRS_AUTUMN_CURRENCY, response.currency);
}
}
function annotateAttachSpan(
span: Span,
params: {
customer_id: string;
product_id: string;
entity_id?: string;
},
): void {
span.setAttributes({
[SEMATTRS_BILLING_SYSTEM]: "autumn",
[SEMATTRS_BILLING_OPERATION]: "attach",
[SEMATTRS_AUTUMN_RESOURCE]: "products",
[SEMATTRS_AUTUMN_TARGET]: "products.attach",
});
span.setAttribute(SEMATTRS_AUTUMN_CUSTOMER_ID, params.customer_id);
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_ID, params.product_id);
if (params.entity_id) {
span.setAttribute(SEMATTRS_AUTUMN_ENTITY_ID, params.entity_id);
}
}
function annotateAttachResponse(
span: Span,
response: {
success?: boolean;
checkout_url?: string;
},
): void {
if (typeof response.success === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_SUCCESS, response.success);
}
if (response.checkout_url) {
span.setAttribute(SEMATTRS_AUTUMN_CHECKOUT_URL, response.checkout_url);
}
}
function annotateCancelSpan(
span: Span,
params: {
customer_id: string;
product_id: string;
},
): void {
span.setAttributes({
[SEMATTRS_BILLING_SYSTEM]: "autumn",
[SEMATTRS_BILLING_OPERATION]: "cancel",
[SEMATTRS_AUTUMN_RESOURCE]: "products",
[SEMATTRS_AUTUMN_TARGET]: "products.cancel",
});
span.setAttribute(SEMATTRS_AUTUMN_CUSTOMER_ID, params.customer_id);
span.setAttribute(SEMATTRS_AUTUMN_PRODUCT_ID, params.product_id);
}
function annotateCancelResponse(span: Span, response: { success?: boolean }): void {
if (typeof response.success === "boolean") {
span.setAttribute(SEMATTRS_AUTUMN_SUCCESS, response.success);
}
}
export function instrumentAutumn(
client: Autumn,
config?: InstrumentationConfig,
): Autumn {
// Check if already instrumented
if ((client as InstrumentedAutumn)[INSTRUMENTED_FLAG]) {
return client;
}
const tracer = trace.getTracer(DEFAULT_TRACER_NAME);
// Instrument check method
const originalCheck = client.check.bind(client);
const instrumentedCheck = async function instrumentedCheck(
params: Parameters<typeof originalCheck>[0],
): Promise<ReturnType<typeof originalCheck>> {
const span = tracer.startSpan("autumn.check", {
kind: SpanKind.CLIENT,
});
annotateCheckSpan(span, params as {
customer_id: string;
feature_id?: string;
product_id?: string;
entity_id?: string;
required_balance?: number;
});
const activeContext = trace.setSpan(context.active(), span);
try {
const result = await context.with(activeContext, () =>
originalCheck(params),
);
if (result.data) {
annotateCheckResponse(span, result.data as Record<string, unknown>);
}
finalizeSpan(span);
return result;
} catch (error) {
finalizeSpan(span, error);
throw error;
}
};
// Instrument track method
const originalTrack = client.track.bind(client);
const instrumentedTrack = async function instrumentedTrack(
params: Parameters<typeof originalTrack>[0],
): Promise<ReturnType<typeof originalTrack>> {
const span = tracer.startSpan("autumn.track", {
kind: SpanKind.CLIENT,
});
annotateTrackSpan(span, params as {
customer_id: string;
feature_id?: string;
event_name?: string;
value?: number;
entity_id?: string;
idempotency_key?: string;
});
const activeContext = trace.setSpan(context.active(), span);
try {
const result = await context.with(activeContext, () =>
originalTrack(params),
);
if (result.data) {
annotateTrackResponse(span, result.data);
}
finalizeSpan(span);
return result;
} catch (error) {
finalizeSpan(span, error);
throw error;
}
};
// Instrument checkout method
const originalCheckout = client.checkout.bind(client);
const instrumentedCheckout = async function instrumentedCheckout(
params: Parameters<typeof originalCheckout>[0],
): Promise<ReturnType<typeof originalCheckout>> {
const span = tracer.startSpan("autumn.checkout", {
kind: SpanKind.CLIENT,
});
annotateCheckoutSpan(span, params as {
customer_id: string;
product_id?: string;
product_ids?: string[];
entity_id?: string;
force_checkout?: boolean;
invoice?: boolean;
}, config);
const activeContext = trace.setSpan(context.active(), span);
try {
const result = await context.with(activeContext, () =>
originalCheckout(params),
);
if (result.data) {
annotateCheckoutResponse(span, result.data);
}
finalizeSpan(span);
return result;
} catch (error) {
finalizeSpan(span, error);
throw error;
}
};
// Instrument attach method
const originalAttach = client.attach.bind(client);
const instrumentedAttach = async function instrumentedAttach(
params: Parameters<typeof originalAttach>[0],
): Promise<ReturnType<typeof originalAttach>> {
const span = tracer.startSpan("autumn.attach", {
kind: SpanKind.CLIENT,
});
annotateAttachSpan(span, params as {
customer_id: string;
product_id: string;
entity_id?: string;
});
const activeContext = trace.setSpan(context.active(), span);
try {
const result = await context.with(activeContext, () =>
originalAttach(params),
);
if (result.data) {
annotateAttachResponse(span, result.data);
}
finalizeSpan(span);
return result;
} catch (error) {
finalizeSpan(span, error);
throw error;
}
};
// Instrument cancel method
const originalCancel = client.cancel.bind(client);
const instrumentedCancel = async function instrumentedCancel(
params: Parameters<typeof originalCancel>[0],
): Promise<ReturnType<typeof originalCancel>> {
const span = tracer.startSpan("autumn.cancel", {
kind: SpanKind.CLIENT,
});
annotateCancelSpan(span, params as {
customer_id: string;
product_id: string;
});
const activeContext = trace.setSpan(context.active(), span);
try {
const result = await context.with(activeContext, () =>
originalCancel(params),
);
if (result.data) {
annotateCancelResponse(span, result.data);
}
finalizeSpan(span);
return result;
} catch (error) {
finalizeSpan(span, error);
throw error;
}
};
// Replace methods with instrumented versions
client.check = instrumentedCheck;
client.track = instrumentedTrack;
client.checkout = instrumentedCheckout;
client.attach = instrumentedAttach;
client.cancel = instrumentedCancel;
// Mark as instrumented
(client as InstrumentedAutumn)[INSTRUMENTED_FLAG] = true;
return client;
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declarationDir": "dist/types",
"stripInternal": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

397
pnpm-lock.yaml generated
View File

@@ -21,6 +21,30 @@ importers:
specifier: ^1.11.3
version: 1.11.3
packages/otel-autumn:
devDependencies:
'@opentelemetry/api':
specifier: ^1.9.0
version: 1.9.0
'@opentelemetry/sdk-trace-base':
specifier: ^2.1.0
version: 2.1.0(@opentelemetry/api@1.9.0)
'@types/node':
specifier: 18.15.11
version: 18.15.11
autumn-js:
specifier: ^0.1.40
version: 0.1.40(better-auth@1.3.25(react@18.2.0))(better-call@1.0.19)(convex@1.27.4(react@18.2.0))(react@18.2.0)
rimraf:
specifier: 3.0.2
version: 3.0.2
typescript:
specifier: ^5
version: 5.3.3
vitest:
specifier: 0.33.0
version: 0.33.0(less@4.2.0)(sass@1.69.7)(stylus@0.59.0)
packages/otel-better-auth:
devDependencies:
'@opentelemetry/api':
@@ -37,7 +61,7 @@ importers:
version: 18.15.11
better-auth:
specifier: ^1.0.0
version: 1.3.25
version: 1.3.25(react@18.2.0)
rimraf:
specifier: 3.0.2
version: 3.0.2
@@ -217,138 +241,288 @@ packages:
'@changesets/write@0.3.0':
resolution: {integrity: sha512-slGLb21fxZVUYbyea+94uFiD6ntQW0M2hIKNznFizDhZPDgn2c/fv1UzzlW43RVzh1BEDuIqW6hzlJ1OflNmcw==}
'@esbuild/aix-ppc64@0.25.4':
resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.18.20':
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.25.4':
resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.18.20':
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.25.4':
resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.18.20':
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.25.4':
resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.18.20':
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.25.4':
resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.18.20':
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.25.4':
resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.18.20':
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.25.4':
resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.18.20':
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.4':
resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.18.20':
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.25.4':
resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.18.20':
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.25.4':
resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.18.20':
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.25.4':
resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.18.20':
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.25.4':
resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.18.20':
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.25.4':
resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.18.20':
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.25.4':
resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.18.20':
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.25.4':
resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.18.20':
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.25.4':
resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.18.20':
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.25.4':
resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.4':
resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.18.20':
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.4':
resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.4':
resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.18.20':
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.4':
resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.18.20':
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.25.4':
resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.18.20':
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.25.4':
resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.18.20':
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.25.4':
resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.18.20':
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.25.4':
resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
@@ -652,6 +826,21 @@ packages:
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
autumn-js@0.1.40:
resolution: {integrity: sha512-nAmyFJLOQqKosb8MHv09rB2pma8LyOHWsuYtrjXND+2LM51vToco1mweLIYIs/aX33iLAVUxfpXEEt8P3UYoxw==}
peerDependencies:
better-auth: ^1.3.17
better-call: ^1.0.12
convex: ^1.25.4
react: '*'
peerDependenciesMeta:
better-auth:
optional: true
better-call:
optional: true
react:
optional: true
available-typed-arrays@1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
@@ -787,6 +976,22 @@ packages:
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
convex@1.27.4:
resolution: {integrity: sha512-aPP3uxOF5v+K4uftXxRh8GAYepsjsFgU+S9IpAyLVNaFU3Z72WB1rIhaSzPAo4Q0TJWsOKANFGU903IU92QDTA==}
engines: {node: '>=18.0.0', npm: '>=7.0.0'}
hasBin: true
peerDependencies:
'@auth0/auth0-react': ^2.0.1
'@clerk/clerk-react': ^4.12.8 || ^5.0.0
react: ^18.0.0 || ^19.0.0-0 || ^19.0.0
peerDependenciesMeta:
'@auth0/auth0-react':
optional: true
'@clerk/clerk-react':
optional: true
react:
optional: true
copy-anything@2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
@@ -836,6 +1041,10 @@ packages:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decode-uri-component@0.4.1:
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
engines: {node: '>=14.16'}
deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'}
@@ -858,6 +1067,10 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@@ -1028,6 +1241,11 @@ packages:
engines: {node: '>=12'}
hasBin: true
esbuild@0.25.4:
resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==}
engines: {node: '>=18'}
hasBin: true
escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
@@ -1062,6 +1280,10 @@ packages:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
filter-obj@5.1.0:
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
engines: {node: '>=14.16'}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -1713,6 +1935,10 @@ packages:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
query-string@9.3.1:
resolution: {integrity: sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==}
engines: {node: '>=18'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -1801,6 +2027,9 @@ packages:
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
rou3@0.6.3:
resolution: {integrity: sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -1919,6 +2148,10 @@ packages:
spdx-license-ids@3.0.16:
resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==}
split-on-first@3.0.0:
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
engines: {node: '>=12'}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -1984,6 +2217,11 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
swr@2.3.6:
resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
term-size@2.2.1:
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
engines: {node: '>=8'}
@@ -2112,6 +2350,11 @@ packages:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
use-sync-external-store@1.6.0:
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@@ -2471,72 +2714,147 @@ snapshots:
human-id: 1.0.2
prettier: 2.8.8
'@esbuild/aix-ppc64@0.25.4':
optional: true
'@esbuild/android-arm64@0.18.20':
optional: true
'@esbuild/android-arm64@0.25.4':
optional: true
'@esbuild/android-arm@0.18.20':
optional: true
'@esbuild/android-arm@0.25.4':
optional: true
'@esbuild/android-x64@0.18.20':
optional: true
'@esbuild/android-x64@0.25.4':
optional: true
'@esbuild/darwin-arm64@0.18.20':
optional: true
'@esbuild/darwin-arm64@0.25.4':
optional: true
'@esbuild/darwin-x64@0.18.20':
optional: true
'@esbuild/darwin-x64@0.25.4':
optional: true
'@esbuild/freebsd-arm64@0.18.20':
optional: true
'@esbuild/freebsd-arm64@0.25.4':
optional: true
'@esbuild/freebsd-x64@0.18.20':
optional: true
'@esbuild/freebsd-x64@0.25.4':
optional: true
'@esbuild/linux-arm64@0.18.20':
optional: true
'@esbuild/linux-arm64@0.25.4':
optional: true
'@esbuild/linux-arm@0.18.20':
optional: true
'@esbuild/linux-arm@0.25.4':
optional: true
'@esbuild/linux-ia32@0.18.20':
optional: true
'@esbuild/linux-ia32@0.25.4':
optional: true
'@esbuild/linux-loong64@0.18.20':
optional: true
'@esbuild/linux-loong64@0.25.4':
optional: true
'@esbuild/linux-mips64el@0.18.20':
optional: true
'@esbuild/linux-mips64el@0.25.4':
optional: true
'@esbuild/linux-ppc64@0.18.20':
optional: true
'@esbuild/linux-ppc64@0.25.4':
optional: true
'@esbuild/linux-riscv64@0.18.20':
optional: true
'@esbuild/linux-riscv64@0.25.4':
optional: true
'@esbuild/linux-s390x@0.18.20':
optional: true
'@esbuild/linux-s390x@0.25.4':
optional: true
'@esbuild/linux-x64@0.18.20':
optional: true
'@esbuild/linux-x64@0.25.4':
optional: true
'@esbuild/netbsd-arm64@0.25.4':
optional: true
'@esbuild/netbsd-x64@0.18.20':
optional: true
'@esbuild/netbsd-x64@0.25.4':
optional: true
'@esbuild/openbsd-arm64@0.25.4':
optional: true
'@esbuild/openbsd-x64@0.18.20':
optional: true
'@esbuild/openbsd-x64@0.25.4':
optional: true
'@esbuild/sunos-x64@0.18.20':
optional: true
'@esbuild/sunos-x64@0.25.4':
optional: true
'@esbuild/win32-arm64@0.18.20':
optional: true
'@esbuild/win32-arm64@0.25.4':
optional: true
'@esbuild/win32-ia32@0.18.20':
optional: true
'@esbuild/win32-ia32@0.25.4':
optional: true
'@esbuild/win32-x64@0.18.20':
optional: true
'@esbuild/win32-x64@0.25.4':
optional: true
'@hexagon/base64@1.1.28': {}
'@isaacs/cliui@8.0.2':
@@ -2920,11 +3238,23 @@ snapshots:
assertion-error@1.1.0: {}
autumn-js@0.1.40(better-auth@1.3.25(react@18.2.0))(better-call@1.0.19)(convex@1.27.4(react@18.2.0))(react@18.2.0):
dependencies:
convex: 1.27.4(react@18.2.0)
query-string: 9.3.1
rou3: 0.6.3
swr: 2.3.6(react@18.2.0)
zod: 4.1.11
optionalDependencies:
better-auth: 1.3.25(react@18.2.0)
better-call: 1.0.19
react: 18.2.0
available-typed-arrays@1.0.5: {}
balanced-match@1.0.2: {}
better-auth@1.3.25:
better-auth@1.3.25(react@18.2.0):
dependencies:
'@better-auth/core': 1.3.25
'@better-auth/utils': 0.3.0
@@ -2939,6 +3269,8 @@ snapshots:
kysely: 0.28.7
nanostores: 1.0.1
zod: 4.1.11
optionalDependencies:
react: 18.2.0
better-call@1.0.19:
dependencies:
@@ -3065,6 +3397,13 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
convex@1.27.4(react@18.2.0):
dependencies:
esbuild: 0.25.4
prettier: 3.1.1
optionalDependencies:
react: 18.2.0
copy-anything@2.0.6:
dependencies:
is-what: 3.14.1
@@ -3113,6 +3452,8 @@ snapshots:
decamelize@1.2.0: {}
decode-uri-component@0.4.1: {}
deep-eql@4.1.3:
dependencies:
type-detect: 4.0.8
@@ -3137,6 +3478,8 @@ snapshots:
defu@6.1.4: {}
dequal@2.0.3: {}
detect-indent@6.1.0: {}
diff-sequences@29.6.3: {}
@@ -3286,6 +3629,34 @@ snapshots:
'@esbuild/win32-ia32': 0.18.20
'@esbuild/win32-x64': 0.18.20
esbuild@0.25.4:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.4
'@esbuild/android-arm': 0.25.4
'@esbuild/android-arm64': 0.25.4
'@esbuild/android-x64': 0.25.4
'@esbuild/darwin-arm64': 0.25.4
'@esbuild/darwin-x64': 0.25.4
'@esbuild/freebsd-arm64': 0.25.4
'@esbuild/freebsd-x64': 0.25.4
'@esbuild/linux-arm': 0.25.4
'@esbuild/linux-arm64': 0.25.4
'@esbuild/linux-ia32': 0.25.4
'@esbuild/linux-loong64': 0.25.4
'@esbuild/linux-mips64el': 0.25.4
'@esbuild/linux-ppc64': 0.25.4
'@esbuild/linux-riscv64': 0.25.4
'@esbuild/linux-s390x': 0.25.4
'@esbuild/linux-x64': 0.25.4
'@esbuild/netbsd-arm64': 0.25.4
'@esbuild/netbsd-x64': 0.25.4
'@esbuild/openbsd-arm64': 0.25.4
'@esbuild/openbsd-x64': 0.25.4
'@esbuild/sunos-x64': 0.25.4
'@esbuild/win32-arm64': 0.25.4
'@esbuild/win32-ia32': 0.25.4
'@esbuild/win32-x64': 0.25.4
escalade@3.1.1: {}
escape-string-regexp@1.0.5: {}
@@ -3318,6 +3689,8 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
filter-obj@5.1.0: {}
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@@ -3954,6 +4327,12 @@ snapshots:
pvutils@1.1.3: {}
query-string@9.3.1:
dependencies:
decode-uri-component: 0.4.1
filter-obj: 5.1.0
split-on-first: 3.0.0
queue-microtask@1.2.3: {}
quick-lru@4.0.1: {}
@@ -4045,6 +4424,8 @@ snapshots:
rou3@0.5.1: {}
rou3@0.6.3: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -4170,6 +4551,8 @@ snapshots:
spdx-license-ids@3.0.16: {}
split-on-first@3.0.0: {}
sprintf-js@1.0.3: {}
stackback@0.0.2: {}
@@ -4249,6 +4632,12 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
swr@2.3.6(react@18.2.0):
dependencies:
dequal: 2.0.3
react: 18.2.0
use-sync-external-store: 1.6.0(react@18.2.0)
term-size@2.2.1: {}
tinybench@2.5.1: {}
@@ -4364,6 +4753,10 @@ snapshots:
universalify@0.1.2: {}
use-sync-external-store@1.6.0(react@18.2.0):
dependencies:
react: 18.2.0
validate-npm-package-license@3.0.4:
dependencies:
spdx-correct: 3.2.0