From dd700c0522bdaf7c814e61b156d759c707648494 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 19:30:41 +0600 Subject: [PATCH 001/143] feat: add typed environment configuration and @SandboxOnly decorator --- .changeset/fruity-clouds-serve.md | 5 + packages/api/.env-sample | 12 +- packages/api/__tests__/config.ts | 25 +- .../__tests__/integration/authService.test.ts | 9 +- packages/api/__tests__/setup.ts | 2 + .../api/__tests__/unit/authService.test.ts | 19 +- packages/api/__tests__/unit/client.test.ts | 115 +++++++ .../__tests__/unit/customerService.test.ts | 18 +- .../api/__tests__/unit/environment.test.ts | 298 ++++++++++++++++++ packages/api/__tests__/unit/services.test.ts | 80 ++--- packages/api/jest.config.js | 2 + packages/api/src/authManager.ts | 7 +- packages/api/src/client.ts | 15 +- packages/api/src/decorators/index.ts | 1 + packages/api/src/decorators/sandboxOnly.ts | 89 ++++++ packages/api/src/index.ts | 1 + packages/api/src/types/client.ts | 10 +- packages/api/src/types/environment.ts | 42 +++ packages/api/src/types/index.ts | 1 + packages/api/src/utils/errorHandler.ts | 20 +- packages/api/test-sdk.ts | 13 +- packages/api/tsconfig.json | 8 +- 22 files changed, 686 insertions(+), 106 deletions(-) create mode 100644 .changeset/fruity-clouds-serve.md create mode 100644 packages/api/__tests__/setup.ts create mode 100644 packages/api/__tests__/unit/client.test.ts create mode 100644 packages/api/__tests__/unit/environment.test.ts create mode 100644 packages/api/src/decorators/index.ts create mode 100644 packages/api/src/decorators/sandboxOnly.ts create mode 100644 packages/api/src/types/environment.ts diff --git a/.changeset/fruity-clouds-serve.md b/.changeset/fruity-clouds-serve.md new file mode 100644 index 00000000..62d49605 --- /dev/null +++ b/.changeset/fruity-clouds-serve.md @@ -0,0 +1,5 @@ +--- +'@oaknetwork/api': minor +--- + +Add typed environment configuration and @SandboxOnly decorator diff --git a/packages/api/.env-sample b/packages/api/.env-sample index 3e0e9042..cbd90225 100644 --- a/packages/api/.env-sample +++ b/packages/api/.env-sample @@ -1,11 +1,5 @@ -# Crowdsplit SDK Environment Variables -# Copy this file to .env and fill in your actual values - -# Client ID for API authentication CLIENT_ID=your_client_id_here - -# Client Secret for API authentication CLIENT_SECRET=your_client_secret_here - -# Base URL for the API -BASE_URL=https://api.crowdsplit.com +OAK_ENVIRONMENT=sandbox +CROWDSPLIT_SANDBOX_URL=https://api.usecrowdpay.xyz +CROWDSPLIT_PRODUCTION_URL=https://app.usecrowdpay.xyz diff --git a/packages/api/__tests__/config.ts b/packages/api/__tests__/config.ts index 724b74ae..f45a236a 100644 --- a/packages/api/__tests__/config.ts +++ b/packages/api/__tests__/config.ts @@ -1,25 +1,26 @@ -import type { OakClientConfig, RetryOptions } from "../src"; +import type { OakClientConfig, OakEnvironment } from "../src"; import dotenv from "dotenv"; dotenv.config(); -export interface TestClientConfig extends OakClientConfig { - retryOptions?: RetryOptions; -} +export function getConfigFromEnv(): OakClientConfig { + if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) { + throw new Error( + "Missing required environment variables: CLIENT_ID, CLIENT_SECRET" + ); + } + + const environment: OakEnvironment = + (process.env.OAK_ENVIRONMENT as OakEnvironment) || "sandbox"; -export function getConfigFromEnv(): TestClientConfig { - if ( - !process.env.CLIENT_ID || - !process.env.CLIENT_SECRET || - !process.env.BASE_URL - ) { + if (environment !== "sandbox" && environment !== "production") { throw new Error( - "Missing required environment variables: CLIENT_ID, CLIENT_SECRET, BASE_URL" + `Invalid OAK_ENVIRONMENT: ${environment}. Must be 'sandbox' or 'production'.` ); } return { + environment, clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, - baseUrl: process.env.BASE_URL, }; } diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/api/__tests__/integration/authService.test.ts index d4363361..65a4c8d9 100644 --- a/packages/api/__tests__/integration/authService.test.ts +++ b/packages/api/__tests__/integration/authService.test.ts @@ -20,7 +20,7 @@ describe("Auth (Integration)", () => { it("should return the same token if not expired", async () => { const firstToken = await client.getAccessToken(); const secondToken = await client.getAccessToken(); - expect(secondToken).toBe(firstToken); // token cached and reused + expect(secondToken).toBe(firstToken); }); it("should refresh token if expired", async () => { @@ -38,13 +38,10 @@ describe("Auth (Integration)", () => { }); it("should throw SDKError on invalid credentials", async () => { - const badConfig = { + const badClient = createOakClient({ + environment: "sandbox", clientId: "invalid", clientSecret: "invalid", - baseUrl: getConfigFromEnv().baseUrl, - }; - const badClient = createOakClient({ - ...badConfig, retryOptions: { maxNumberOfRetries: 1, delay: 100, diff --git a/packages/api/__tests__/setup.ts b/packages/api/__tests__/setup.ts new file mode 100644 index 00000000..7ff27f7c --- /dev/null +++ b/packages/api/__tests__/setup.ts @@ -0,0 +1,2 @@ +import dotenv from "dotenv"; +dotenv.config(); diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/api/__tests__/unit/authService.test.ts index c70ea112..0a55edd6 100644 --- a/packages/api/__tests__/unit/authService.test.ts +++ b/packages/api/__tests__/unit/authService.test.ts @@ -1,15 +1,18 @@ -// __tests__/unit/authService.test.ts import { createOakClient } from "../../src"; import { httpClient } from "../../src/utils/httpClient"; import { SDKError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils"; -import { getConfigFromEnv } from "../config"; +import type { OakClientConfig } from "../../src/types"; jest.mock("../../src/utils/httpClient"); const mockedHttpClient = httpClient as jest.Mocked; describe("Auth (Unit)", () => { - const config = getConfigFromEnv(); + const config: OakClientConfig = { + environment: "sandbox", + clientId: "test-client-id", + clientSecret: "test-client-secret", + }; const retryOptions: RetryOptions = { maxNumberOfRetries: 1, delay: 100, @@ -37,7 +40,7 @@ describe("Auth (Unit)", () => { const result = await client.grantToken(); expect(mockedHttpClient.post).toHaveBeenCalledWith( - `${config.baseUrl}/api/v1/merchant/token/grant`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/token/grant`, { client_id: config.clientId, client_secret: config.clientSecret, @@ -59,14 +62,11 @@ describe("Auth (Unit)", () => { }; mockedHttpClient.post.mockResolvedValue(mockResponse); - // First call to fetch token const token1 = await client.getAccessToken(); - // Second call should return cached token const token2 = await client.getAccessToken(); expect(token1).toBe("cachedToken"); expect(token2).toBe("cachedToken"); - // httpClient.post should have been called only once expect(mockedHttpClient.post).toHaveBeenCalledTimes(1); }); @@ -75,7 +75,7 @@ describe("Auth (Unit)", () => { access_token: "token1", expires_in: 1, token_type: "bearer", - }; // expires in 1ms + }; const mockResponse2 = { access_token: "token2", expires_in: 3600, @@ -85,11 +85,8 @@ describe("Auth (Unit)", () => { .mockResolvedValueOnce(mockResponse1) .mockResolvedValueOnce(mockResponse2); - // First call const token1 = await client.getAccessToken(); - // wait to expire token await new Promise((r) => setTimeout(r, 10)); - // Second call triggers new token request const token2 = await client.getAccessToken(); expect(token1).toBe("token1"); diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts new file mode 100644 index 00000000..c083f161 --- /dev/null +++ b/packages/api/__tests__/unit/client.test.ts @@ -0,0 +1,115 @@ +import { createOakClient } from "../../src/client"; +import type { OakClientConfig } from "../../src/types/client"; + +jest.mock("../../src/authManager", () => ({ + AuthManager: jest.fn().mockImplementation(() => ({ + getAccessToken: jest.fn().mockResolvedValue("mock-token"), + grantToken: jest.fn().mockResolvedValue({ + access_token: "mock-token", + expires_in: 3600, + }), + })), +})); + +describe("createOakClient", () => { + const baseConfig: Omit = { + clientId: "test-client-id", + clientSecret: "test-client-secret", + }; + + describe("environment URL resolution", () => { + it("should resolve sandbox URL for sandbox environment", () => { + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + }); + + expect(client.config.baseUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + expect(client.config.environment).toBe("sandbox"); + }); + + it("should resolve production URL for production environment", () => { + const client = createOakClient({ + ...baseConfig, + environment: "production", + }); + + expect(client.config.baseUrl).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + expect(client.config.environment).toBe("production"); + }); + + it("should use customUrl when provided", () => { + const customUrl = "http://localhost:3000"; + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + customUrl, + }); + + expect(client.config.baseUrl).toBe(customUrl); + expect(client.config.environment).toBe("sandbox"); + }); + + it("should preserve customUrl with production environment", () => { + const customUrl = "http://localhost:3000"; + const client = createOakClient({ + ...baseConfig, + environment: "production", + customUrl, + }); + + expect(client.config.baseUrl).toBe(customUrl); + expect(client.config.environment).toBe("production"); + }); + }); + + describe("config preservation", () => { + it("should preserve clientId and clientSecret", () => { + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + }); + + expect(client.config.clientId).toBe(baseConfig.clientId); + expect(client.config.clientSecret).toBe(baseConfig.clientSecret); + }); + + it("should preserve custom retry options", () => { + const customRetryOptions = { + maxNumberOfRetries: 5, + delay: 500, + }; + + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + retryOptions: customRetryOptions, + }); + + expect(client.retryOptions.maxNumberOfRetries).toBe(5); + expect(client.retryOptions.delay).toBe(500); + }); + }); + + describe("client methods", () => { + it("should provide getAccessToken method", async () => { + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + }); + + const token = await client.getAccessToken(); + expect(token).toBe("mock-token"); + }); + + it("should provide grantToken method", async () => { + const client = createOakClient({ + ...baseConfig, + environment: "sandbox", + }); + + const response = await client.grantToken(); + expect(response.access_token).toBe("mock-token"); + }); + }); +}); diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index 6359ca62..eb7f3f9a 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -4,7 +4,7 @@ import { httpClient } from "../../src/utils/httpClient"; import { SDKError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; import { - SDKConfig, + OakClientConfig, CreateCustomerRequest, CustomerListQueryParams, } from "../../src/types"; @@ -20,14 +20,14 @@ jest.mock("../../src/utils/httpClient", () => ({ describe("CustomerService - Unit", () => { let customers: ReturnType["customers"]; let client: ReturnType; - let config: SDKConfig; + let config: OakClientConfig; let retryOptions: RetryOptions; beforeEach(() => { config = { - clientId: process.env.CLIENT_ID!, - clientSecret: process.env.CLIENT_SECRET!, - baseUrl: process.env.BASE_URL!, // staging URL + environment: "sandbox", + clientId: process.env.CLIENT_ID || "test-client-id", + clientSecret: process.env.CLIENT_SECRET || "test-client-secret", }; retryOptions = { maxNumberOfRetries: 1, delay: 100, backoffFactor: 2 }; client = createOakClient({ @@ -49,7 +49,7 @@ describe("CustomerService - Unit", () => { expect(client.getAccessToken).toHaveBeenCalled(); expect(httpClient.post).toHaveBeenCalledWith( - `${process.env.BASE_URL}/api/v1/customers`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, request, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, @@ -78,7 +78,7 @@ describe("CustomerService - Unit", () => { const result = await customers.get("123"); expect(httpClient.get).toHaveBeenCalledWith( - `${process.env.BASE_URL}/api/v1/customers/123`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/123`, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), @@ -97,7 +97,7 @@ describe("CustomerService - Unit", () => { const result = await customers.list(params); expect(httpClient.get).toHaveBeenCalledWith( - `${process.env.BASE_URL}/api/v1/customers?limit=10&offset=5`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers?limit=10&offset=5`, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), @@ -116,7 +116,7 @@ describe("CustomerService - Unit", () => { const result = await customers.update("123", updateData); expect(httpClient.put).toHaveBeenCalledWith( - `${process.env.BASE_URL}/api/v1/customers/123`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/123`, updateData, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts new file mode 100644 index 00000000..fe3dfcd6 --- /dev/null +++ b/packages/api/__tests__/unit/environment.test.ts @@ -0,0 +1,298 @@ +import { + resolveBaseUrl, + isTestEnvironment, + getEnvironmentConfigs, + OakEnvironment, +} from "../../src/types/environment"; +import { + EnvironmentViolationError, + SDKError, +} from "../../src/utils/errorHandler"; +import { SandboxOnly, sandboxOnlyFn } from "../../src/decorators/sandboxOnly"; +import type { ResolvedOakClientConfig } from "../../src/types/client"; + +describe("Environment Configuration", () => { + describe("getEnvironmentConfigs", () => { + it("should have sandbox config with test operations allowed", () => { + const configs = getEnvironmentConfigs(); + expect(configs.sandbox).toBeDefined(); + expect(configs.sandbox.allowsTestOperations).toBe(true); + expect(configs.sandbox.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + }); + + it("should have production config with test operations disallowed", () => { + const configs = getEnvironmentConfigs(); + expect(configs.production).toBeDefined(); + expect(configs.production.allowsTestOperations).toBe(false); + expect(configs.production.apiUrl).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + }); + + it("should throw error when env vars are missing", () => { + const originalSandbox = process.env.CROWDSPLIT_SANDBOX_URL; + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + delete process.env.CROWDSPLIT_SANDBOX_URL; + delete process.env.CROWDSPLIT_PRODUCTION_URL; + + expect(() => getEnvironmentConfigs()).toThrow( + "Missing required environment variables" + ); + + process.env.CROWDSPLIT_SANDBOX_URL = originalSandbox; + process.env.CROWDSPLIT_PRODUCTION_URL = originalProd; + }); + }); + + describe("resolveBaseUrl", () => { + it("should return sandbox URL for sandbox environment", () => { + const url = resolveBaseUrl("sandbox"); + expect(url).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + }); + + it("should return production URL for production environment", () => { + const url = resolveBaseUrl("production"); + expect(url).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + }); + + it("should return customUrl when provided", () => { + const customUrl = "http://localhost:3000"; + const url = resolveBaseUrl("sandbox", customUrl); + expect(url).toBe(customUrl); + }); + + it("should use customUrl over environment URL when both provided", () => { + const customUrl = "http://localhost:3000"; + const url = resolveBaseUrl("production", customUrl); + expect(url).toBe(customUrl); + }); + }); + + describe("isTestEnvironment", () => { + it("should return true for sandbox", () => { + expect(isTestEnvironment("sandbox")).toBe(true); + }); + + it("should return false for production", () => { + expect(isTestEnvironment("production")).toBe(false); + }); + }); +}); + +describe("EnvironmentViolationError", () => { + it("should be an instance of SDKError", () => { + const error = new EnvironmentViolationError("testMethod", "production"); + expect(error).toBeInstanceOf(SDKError); + expect(error).toBeInstanceOf(Error); + }); + + it("should have correct name", () => { + const error = new EnvironmentViolationError("testMethod", "production"); + expect(error.name).toBe("EnvironmentViolationError"); + }); + + it("should store method name and environment", () => { + const error = new EnvironmentViolationError("resetAccount", "production"); + expect(error.methodName).toBe("resetAccount"); + expect(error.environment).toBe("production"); + }); + + it("should have descriptive message", () => { + const error = new EnvironmentViolationError("resetAccount", "production"); + expect(error.message).toContain("resetAccount"); + expect(error.message).toContain("sandbox"); + expect(error.message).toContain("production"); + }); +}); + +describe("@SandboxOnly Decorator", () => { + const createMockConfig = ( + environment: OakEnvironment + ): ResolvedOakClientConfig => ({ + environment, + clientId: "test-id", + clientSecret: "test-secret", + baseUrl: environment === "sandbox" + ? process.env.CROWDSPLIT_SANDBOX_URL! + : process.env.CROWDSPLIT_PRODUCTION_URL!, + }); + + describe("with missing descriptor value", () => { + it("should return early if descriptor.value is undefined", () => { + const descriptor: TypedPropertyDescriptor<() => void> = { + value: undefined, + }; + const result = SandboxOnly({}, "testMethod", descriptor); + expect(result).toBeUndefined(); + expect(descriptor.value).toBeUndefined(); + }); + }); + + describe("with class that has config property", () => { + class TestServiceWithConfig { + config: ResolvedOakClientConfig; + + constructor(environment: OakEnvironment) { + this.config = createMockConfig(environment); + } + + @SandboxOnly + async destructiveOperation(): Promise { + return "operation completed"; + } + + @SandboxOnly + syncOperation(): string { + return "sync completed"; + } + } + + it("should allow execution in sandbox environment", async () => { + const service = new TestServiceWithConfig("sandbox"); + const result = await service.destructiveOperation(); + expect(result).toBe("operation completed"); + }); + + it("should allow sync execution in sandbox environment", () => { + const service = new TestServiceWithConfig("sandbox"); + const result = service.syncOperation(); + expect(result).toBe("sync completed"); + }); + + it("should throw EnvironmentViolationError in production", () => { + const service = new TestServiceWithConfig("production"); + expect(() => service.destructiveOperation()).toThrow( + EnvironmentViolationError + ); + }); + + it("should throw with correct method name in error", () => { + const service = new TestServiceWithConfig("production"); + try { + service.destructiveOperation(); + fail("Should have thrown"); + } catch (error) { + expect(error).toBeInstanceOf(EnvironmentViolationError); + expect((error as EnvironmentViolationError).methodName).toBe( + "destructiveOperation" + ); + expect((error as EnvironmentViolationError).environment).toBe( + "production" + ); + } + }); + }); + + describe("with symbol property key", () => { + const symbolMethod = Symbol("symbolMethod"); + + class TestServiceWithSymbol { + config: ResolvedOakClientConfig; + + constructor(environment: OakEnvironment) { + this.config = createMockConfig(environment); + } + + @SandboxOnly + [symbolMethod](): string { + return "symbol method completed"; + } + } + + it("should allow symbol method execution in sandbox", () => { + const service = new TestServiceWithSymbol("sandbox"); + const result = service[symbolMethod](); + expect(result).toBe("symbol method completed"); + }); + + it("should throw with symbol.toString() as method name in production", () => { + const service = new TestServiceWithSymbol("production"); + try { + service[symbolMethod](); + fail("Should have thrown"); + } catch (error) { + expect(error).toBeInstanceOf(EnvironmentViolationError); + expect((error as EnvironmentViolationError).methodName).toContain("Symbol"); + } + }); + }); + + describe("with class that has client property", () => { + class TestServiceWithClient { + client: { config: ResolvedOakClientConfig }; + + constructor(environment: OakEnvironment) { + this.client = { config: createMockConfig(environment) }; + } + + @SandboxOnly + async resetAccount(): Promise {} + } + + it("should allow execution in sandbox environment", async () => { + const service = new TestServiceWithClient("sandbox"); + await expect(service.resetAccount()).resolves.toBeUndefined(); + }); + + it("should throw EnvironmentViolationError in production", () => { + const service = new TestServiceWithClient("production"); + expect(() => service.resetAccount()).toThrow(EnvironmentViolationError); + }); + }); + + describe("with class missing config", () => { + class TestServiceNoConfig { + @SandboxOnly + async noConfigMethod(): Promise {} + } + + it("should throw error when config is not accessible", () => { + const service = new TestServiceNoConfig(); + expect(() => service.noConfigMethod()).toThrow( + "@SandboxOnly decorator requires access to environment configuration" + ); + }); + }); +}); + +describe("sandboxOnlyFn", () => { + it("should allow execution when environment is sandbox", () => { + const fn = jest.fn().mockReturnValue("result"); + const wrapped = sandboxOnlyFn(fn, () => "sandbox", "testFn"); + + const result = wrapped(); + + expect(result).toBe("result"); + expect(fn).toHaveBeenCalled(); + }); + + it("should throw EnvironmentViolationError when environment is production", () => { + const fn = jest.fn().mockReturnValue("result"); + const wrapped = sandboxOnlyFn(fn, () => "production", "testFn"); + + expect(() => wrapped()).toThrow(EnvironmentViolationError); + expect(fn).not.toHaveBeenCalled(); + }); + + it("should pass arguments to wrapped function", () => { + const fn = jest.fn().mockImplementation((a: number, b: number) => a + b); + const wrapped = sandboxOnlyFn(fn, () => "sandbox", "addFn"); + + const result = wrapped(2, 3); + + expect(result).toBe(5); + expect(fn).toHaveBeenCalledWith(2, 3); + }); + + it("should include method name in error", () => { + const fn = jest.fn(); + const wrapped = sandboxOnlyFn(fn, () => "production", "mySpecialMethod"); + + try { + wrapped(); + fail("Should have thrown"); + } catch (error) { + expect((error as EnvironmentViolationError).methodName).toBe( + "mySpecialMethod" + ); + } + }); +}); diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index b3665518..fa4c6d82 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -27,11 +27,15 @@ jest.mock("../../src/utils/httpClient", () => ({ const mockedHttpClient = httpClient as jest.Mocked; -const baseUrl = "https://api.test"; -const retryOptions = { maxNumberOfRetries: 0, delay: 0 }; +const retryOptions = { maxNumberOfRetries: 0, delay: 0, backoffFactor: 2 }; const makeClient = (): OakClient => ({ - config: { baseUrl, clientId: "id", clientSecret: "secret" }, + config: { + environment: "sandbox", + clientId: "id", + clientSecret: "secret", + baseUrl: process.env.CROWDSPLIT_SANDBOX_URL!, + }, retryOptions, getAccessToken: jest.fn().mockResolvedValue("token"), grantToken: jest.fn(), @@ -100,7 +104,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.create({ email: "test@example.com" }), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/customers`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, { email: "test@example.com" }, authConfig, ], @@ -115,7 +119,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("cust-1"), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/customers/cust-1`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1`, authConfig], }); await expectFailure({ call: () => service.get("cust-1"), @@ -127,13 +131,13 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.list({ limit: 10, offset: undefined }), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/customers?limit=10`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers?limit=10`, authConfig], }); await expectSuccess({ client, call: () => service.list({ limit: undefined }), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/customers`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, authConfig], }); await expectFailure({ call: () => service.list({ limit: 10 }), @@ -146,7 +150,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("cust-1", { email: "new@example.com" }), httpMethod: "put", expectedArgs: [ - `${baseUrl}/api/v1/customers/cust-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1`, { email: "new@example.com" }, authConfig, ], @@ -168,7 +172,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.create(payment), httpMethod: "post", - expectedArgs: [`${baseUrl}/api/v1/payments/`, payment, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/`, payment, authConfig], }); await expectFailure({ call: () => service.create(payment), @@ -181,7 +185,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.confirm("pay-1"), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/payments/pay-1/confirm`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/pay-1/confirm`, {}, authConfig, ], @@ -197,7 +201,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.cancel("pay-1"), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/payments/pay-1/cancel`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/pay-1/cancel`, {}, authConfig, ], @@ -215,7 +219,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.add("cust-1", paymentMethod), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/customers/cust-1/payment_methods`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods`, paymentMethod, authConfig, ], @@ -231,7 +235,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.get("cust-1", "pay-1"), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/customers/cust-1/payment_methods/pay-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pay-1`, authConfig, ], }); @@ -250,7 +254,7 @@ describe("Crowdsplit services (Unit)", () => { }), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/customers/cust-1/payment_methods?type=pix`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods?type=pix`, authConfig, ], }); @@ -265,7 +269,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.delete("cust-1", "pm-1"), httpMethod: "delete", expectedArgs: [ - `${baseUrl}/api/v1/customers/cust-1/payment_methods/pm-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pm-1`, authConfig, ], }); @@ -287,7 +291,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getSchema(request), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/provider-registration/schema?provider=stripe`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/schema?provider=stripe`, authConfig, ], }); @@ -302,7 +306,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getRegistrationStatus("cust-1"), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/provider-registration/cust-1/status`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/cust-1/status`, authConfig, ], }); @@ -319,7 +323,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.submitRegistration("cust-1", registration), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/provider-registration/cust-1/submit`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/cust-1/submit`, registration, authConfig, ], @@ -352,7 +356,7 @@ describe("Crowdsplit services (Unit)", () => { service.list({ type_list: "refund", status: undefined }), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/transactions?type_list=refund`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions?type_list=refund`, authConfig, ], }); @@ -366,7 +370,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("txn-1"), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/transactions/txn-1`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions/txn-1`, authConfig], }); await expectFailure({ call: () => service.get("txn-1"), @@ -379,7 +383,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.settle("txn-1", settlement), httpMethod: "patch", expectedArgs: [ - `${baseUrl}/api/v1/transactions/txn-1/settle`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions/txn-1/settle`, settlement, authConfig, ], @@ -403,7 +407,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => transferService.create(transfer), httpMethod: "post", - expectedArgs: [`${baseUrl}/api/v1/transfer`, transfer, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transfer`, transfer, authConfig], }); await expectFailure({ call: () => transferService.create(transfer), @@ -416,7 +420,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => sellService.create(sell), httpMethod: "post", - expectedArgs: [`${baseUrl}/api/v1/sell`, sell, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/sell`, sell, authConfig], }); await expectFailure({ call: () => sellService.create(sell), @@ -429,7 +433,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => buyService.create(buy), httpMethod: "post", - expectedArgs: [`${baseUrl}/api/v1/buy`, buy, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/buy`, buy, authConfig], }); await expectFailure({ call: () => buyService.create(buy), @@ -459,7 +463,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.create(planRequest), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans`, planRequest, authConfig, ], @@ -475,7 +479,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.publish("plan-1"), httpMethod: "patch", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans/plan-1/publish`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1/publish`, undefined, authConfig, ], @@ -491,7 +495,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.details("plan-1"), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans/plan-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, authConfig, ], }); @@ -506,7 +510,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.list({ page_no: 1, per_page: undefined }), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans?page_no=1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans?page_no=1`, authConfig, ], }); @@ -521,7 +525,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("plan-1", planRequest), httpMethod: "patch", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans/plan-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, planRequest, authConfig, ], @@ -537,7 +541,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.delete("plan-1"), httpMethod: "delete", expectedArgs: [ - `${baseUrl}/api/v1/subscription/plans/plan-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, authConfig, ], }); @@ -559,7 +563,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.register(webhook), httpMethod: "post", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks`, webhook, authConfig, ], @@ -581,7 +585,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.list(), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/merchant/webhooks`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks`, authConfig], }); await expectFailure({ call: () => service.list(), @@ -593,7 +597,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("wh-1"), httpMethod: "get", - expectedArgs: [`${baseUrl}/api/v1/merchant/webhooks/wh-1`, authConfig], + expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig], }); await expectFailure({ call: () => service.get("wh-1"), @@ -606,7 +610,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("wh-1", webhook), httpMethod: "put", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks/wh-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, webhook, authConfig, ], @@ -622,7 +626,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.toggle("wh-1"), httpMethod: "patch", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks/wh-1/toggle`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1/toggle`, undefined, authConfig, ], @@ -638,7 +642,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.delete("wh-1"), httpMethod: "delete", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks/wh-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig, ], }); @@ -654,7 +658,7 @@ describe("Crowdsplit services (Unit)", () => { service.listNotifications({ limit: 1, offset: undefined }), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks/notifications?limit=1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/notifications?limit=1`, authConfig, ], }); @@ -669,7 +673,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getNotification("wh-1"), httpMethod: "get", expectedArgs: [ - `${baseUrl}/api/v1/merchant/webhooks/notifications/wh-1`, + `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/notifications/wh-1`, authConfig, ], }); diff --git a/packages/api/jest.config.js b/packages/api/jest.config.js index 27aed3fd..a80de7a8 100644 --- a/packages/api/jest.config.js +++ b/packages/api/jest.config.js @@ -2,9 +2,11 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", testMatch: ["**/__tests__/**/*.test.ts"], + setupFiles: ["/__tests__/setup.ts"], collectCoverageFrom: [ "src/**/*.{ts,tsx}", "!src/**/*.d.ts", + "!src/**/index.ts", ], coverageThreshold: { global: { diff --git a/packages/api/src/authManager.ts b/packages/api/src/authManager.ts index 07c86bce..3f99f497 100644 --- a/packages/api/src/authManager.ts +++ b/packages/api/src/authManager.ts @@ -1,15 +1,15 @@ -import type { OakClientConfig, TokenRequest, TokenResponse } from "./types"; +import type { ResolvedOakClientConfig, TokenRequest, TokenResponse } from "./types"; import { httpClient } from "./utils/httpClient"; import { SDKError } from "./utils/errorHandler"; import { RetryOptions } from "./utils/defaultRetryConfig"; export class AuthManager { - private config: OakClientConfig; + private config: ResolvedOakClientConfig; private accessToken: string | null = null; private tokenExpiration: number | null = null; private retryOptions: RetryOptions; - constructor(config: OakClientConfig, retryOptions: RetryOptions) { + constructor(config: ResolvedOakClientConfig, retryOptions: RetryOptions) { this.config = config; this.retryOptions = retryOptions; } @@ -39,7 +39,6 @@ export class AuthManager { async getAccessToken(): Promise { const currentTime = Date.now(); - // Assume token is invalid if it doesn't exist or is within 60 seconds of expiring if ( !this.accessToken || !this.tokenExpiration || diff --git a/packages/api/src/client.ts b/packages/api/src/client.ts index a9b5ab76..fa0e8379 100644 --- a/packages/api/src/client.ts +++ b/packages/api/src/client.ts @@ -1,26 +1,35 @@ import { AuthManager } from "./authManager"; -import type { OakClient, OakClientConfig } from "./types"; +import type { OakClient, OakClientConfig, ResolvedOakClientConfig } from "./types"; +import { resolveBaseUrl } from "./types/environment"; import { DEFAULT_RETRY_OPTIONS, RetryOptions, } from "./utils/defaultRetryConfig"; export function createOakClient(config: OakClientConfig): OakClient { + const baseUrl = resolveBaseUrl(config.environment, config.customUrl); + + const resolvedConfig: ResolvedOakClientConfig = { + ...config, + baseUrl, + }; + const retryOptions: RetryOptions = { ...DEFAULT_RETRY_OPTIONS, ...config.retryOptions, }; + let authManager: AuthManager | null = null; const getAuthManager = (): AuthManager => { if (!authManager) { - authManager = new AuthManager(config, retryOptions); + authManager = new AuthManager(resolvedConfig, retryOptions); } return authManager; }; return { - config, + config: resolvedConfig, retryOptions, getAccessToken: () => getAuthManager().getAccessToken(), grantToken: () => getAuthManager().grantToken(), diff --git a/packages/api/src/decorators/index.ts b/packages/api/src/decorators/index.ts new file mode 100644 index 00000000..976bd5e9 --- /dev/null +++ b/packages/api/src/decorators/index.ts @@ -0,0 +1 @@ +export { SandboxOnly, sandboxOnlyFn } from "./sandboxOnly"; diff --git a/packages/api/src/decorators/sandboxOnly.ts b/packages/api/src/decorators/sandboxOnly.ts new file mode 100644 index 00000000..9bfd1113 --- /dev/null +++ b/packages/api/src/decorators/sandboxOnly.ts @@ -0,0 +1,89 @@ +import { EnvironmentViolationError } from "../utils/errorHandler"; +import type { ResolvedOakClientConfig } from "../types/client"; + +interface HasConfig { + config: ResolvedOakClientConfig; +} + +interface HasClient { + client: { config: ResolvedOakClientConfig }; +} + +function hasConfig(obj: unknown): obj is HasConfig { + return ( + typeof obj === "object" && + obj !== null && + "config" in obj && + typeof (obj as HasConfig).config === "object" && + (obj as HasConfig).config !== null && + "environment" in (obj as HasConfig).config + ); +} + +function hasClient(obj: unknown): obj is HasClient { + return ( + typeof obj === "object" && + obj !== null && + "client" in obj && + typeof (obj as HasClient).client === "object" && + (obj as HasClient).client !== null && + "config" in (obj as HasClient).client + ); +} + +export function SandboxOnly unknown>( + target: object, + propertyKey: string | symbol, + descriptor: TypedPropertyDescriptor +): TypedPropertyDescriptor | void { + const originalMethod = descriptor.value; + + if (!originalMethod) { + return; + } + + descriptor.value = function (this: unknown, ...args: unknown[]) { + let environment: string | undefined; + + if (hasConfig(this)) { + environment = this.config.environment; + } else if (hasClient(this)) { + environment = this.client.config.environment; + } + + if (!environment) { + throw new Error( + `@SandboxOnly decorator requires access to environment configuration. ` + + `Ensure the class has either a 'config' or 'client.config' property with 'environment' field.` + ); + } + + if (environment === "production") { + const methodName = + typeof propertyKey === "symbol" + ? propertyKey.toString() + : String(propertyKey); + throw new EnvironmentViolationError(methodName, environment); + } + + return originalMethod.apply(this, args); + } as T; + + return descriptor; +} + +export function sandboxOnlyFn unknown>( + fn: T, + getEnvironment: () => string, + methodName: string +): T { + return ((...args: unknown[]) => { + const environment = getEnvironment(); + + if (environment === "production") { + throw new EnvironmentViolationError(methodName, environment); + } + + return fn(...args); + }) as T; +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index a6fd8350..ab9cbdf7 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -2,3 +2,4 @@ export { createOakClient } from "./client"; export * from "./types"; export * from "./services"; export * from "./utils"; +export * from "./decorators"; \ No newline at end of file diff --git a/packages/api/src/types/client.ts b/packages/api/src/types/client.ts index bd6753df..d06a684c 100644 --- a/packages/api/src/types/client.ts +++ b/packages/api/src/types/client.ts @@ -1,15 +1,21 @@ import type { RetryOptions } from "../utils"; import type { TokenResponse } from "./token"; +import type { OakEnvironment } from "./environment"; export interface OakClientConfig { - baseUrl: string; + environment: OakEnvironment; clientId: string; clientSecret: string; + customUrl?: string; retryOptions?: Partial; } +export interface ResolvedOakClientConfig extends OakClientConfig { + baseUrl: string; +} + export interface OakClient { - readonly config: OakClientConfig; + readonly config: ResolvedOakClientConfig; readonly retryOptions: RetryOptions; getAccessToken(): Promise; grantToken(): Promise; diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts new file mode 100644 index 00000000..a8dad0ac --- /dev/null +++ b/packages/api/src/types/environment.ts @@ -0,0 +1,42 @@ +export type OakEnvironment = "sandbox" | "production"; + +export interface EnvironmentConfig { + apiUrl: string; + allowsTestOperations: boolean; +} + +export function getEnvironmentConfigs(): Record { + const sandboxUrl = process.env.CROWDSPLIT_SANDBOX_URL; + const productionUrl = process.env.CROWDSPLIT_PRODUCTION_URL; + + if (!sandboxUrl || !productionUrl) { + throw new Error( + "Missing required environment variables: CROWDSPLIT_SANDBOX_URL, CROWDSPLIT_PRODUCTION_URL" + ); + } + + return { + sandbox: { + apiUrl: sandboxUrl, + allowsTestOperations: true, + }, + production: { + apiUrl: productionUrl, + allowsTestOperations: false, + }, + }; +} + +export function resolveBaseUrl( + environment: OakEnvironment, + customUrl?: string +): string { + if (customUrl) { + return customUrl; + } + return getEnvironmentConfigs()[environment].apiUrl; +} + +export function isTestEnvironment(environment: OakEnvironment): boolean { + return getEnvironmentConfigs()[environment].allowsTestOperations; +} diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index e0a58cdb..034f2125 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -1,5 +1,6 @@ export * from "./config"; export * from "./client"; +export * from "./environment"; export * from "./token"; export * from "./payment"; export * from "./customer"; diff --git a/packages/api/src/utils/errorHandler.ts b/packages/api/src/utils/errorHandler.ts index 708fe030..f067074b 100644 --- a/packages/api/src/utils/errorHandler.ts +++ b/packages/api/src/utils/errorHandler.ts @@ -1,9 +1,25 @@ export class SDKError extends Error { - public cause?: any; + public cause?: unknown; - constructor(message: string, cause?: any) { + constructor(message: string, cause?: unknown) { super(message); this.name = "SDKError"; this.cause = cause; } } + +export class EnvironmentViolationError extends SDKError { + public readonly methodName: string; + public readonly environment: string; + + constructor(methodName: string, environment: string) { + super( + `Method "${methodName}" is only available in sandbox environment. ` + + `Current environment: ${environment}. ` + + `This method cannot be called in production to prevent accidental data corruption.` + ); + this.name = "EnvironmentViolationError"; + this.methodName = methodName; + this.environment = environment; + } +} diff --git a/packages/api/test-sdk.ts b/packages/api/test-sdk.ts index 0a9dce67..00ed2054 100644 --- a/packages/api/test-sdk.ts +++ b/packages/api/test-sdk.ts @@ -8,12 +8,11 @@ import dotenv from "dotenv"; dotenv.config(); async function testSDK() { - console.log(process.env.BASE_URL); // Initialize the SDK with your backend's configuration const client = createOakClient({ - baseUrl: process.env.BASE_URL as string, // Replace with your actual API base URL - clientId: process.env.CLIENT_ID as string, // Replace with your actual client ID - clientSecret: process.env.CLIENT_SECRET as string, // Replace with your actual client secret + environment: (process.env.OAK_ENVIRONMENT as "sandbox" | "production") || "sandbox", + clientId: process.env.CLIENT_ID as string, + clientSecret: process.env.CLIENT_SECRET as string, }); const cs = Crowdsplit(client); @@ -249,10 +248,10 @@ async function testSDK() { // } try { - const response = await cs.transactions.getAllTransactions(); - console.log("Webhook found :", response); + const response = await cs.transactions.list(); + console.log("Transactions:", response); } catch (err) { - console.error("Error getting Webhook:", err); + console.error("Error getting transactions:", err); } } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 309e9cea..1277718f 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -3,14 +3,16 @@ "target": "ES2018", "module": "commonjs", "outDir": "./dist", - "rootDir": "./src", + "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "declaration": true, - "moduleResolution": "node" + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "__tests__/**/*"], "exclude": ["node_modules", "dist"] } From 68aaf84242afccc3668f621712fbec8378fcb198 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 20:51:10 +0600 Subject: [PATCH 002/143] fix: update integration tests for Result pattern --- .../__tests__/integration/authService.test.ts | 36 ++++++++++++------- .../integration/customerService.test.ts | 32 +++++++++++------ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/api/__tests__/integration/authService.test.ts index 65a4c8d9..7c9d3db9 100644 --- a/packages/api/__tests__/integration/authService.test.ts +++ b/packages/api/__tests__/integration/authService.test.ts @@ -13,14 +13,21 @@ describe("Auth (Integration)", () => { it("should get a real access token", async () => { const response = await client.grantToken(); - expect(response.access_token).toBeDefined(); - expect(response.expires_in).toBeGreaterThan(0); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.access_token).toBeDefined(); + expect(response.value.expires_in).toBeGreaterThan(0); + } }); it("should return the same token if not expired", async () => { - const firstToken = await client.getAccessToken(); - const secondToken = await client.getAccessToken(); - expect(secondToken).toBe(firstToken); + const firstResult = await client.getAccessToken(); + const secondResult = await client.getAccessToken(); + expect(firstResult.ok).toBe(true); + expect(secondResult.ok).toBe(true); + if (firstResult.ok && secondResult.ok) { + expect(secondResult.value).toBe(firstResult.value); + } }); it("should refresh token if expired", async () => { @@ -30,14 +37,17 @@ describe("Auth (Integration)", () => { .spyOn(Date, "now") .mockImplementation(() => originalNow() + 86400000); - const newToken = await client.getAccessToken(); + const newTokenResult = await client.getAccessToken(); nowSpy.mockRestore(); - expect(newToken).toBeDefined(); - expect(newToken).not.toBeNull(); + expect(newTokenResult.ok).toBe(true); + if (newTokenResult.ok) { + expect(newTokenResult.value).toBeDefined(); + expect(newTokenResult.value).not.toBeNull(); + } }); - it("should throw SDKError on invalid credentials", async () => { + it("should return error on invalid credentials", async () => { const badClient = createOakClient({ environment: "sandbox", clientId: "invalid", @@ -49,8 +59,10 @@ describe("Auth (Integration)", () => { }, }); - await expect(badClient.grantToken()).rejects.toThrow( - "Failed to grant token" - ); + const result = await badClient.grantToken(); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toContain("Failed to grant token"); + } }); }); diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index 7597a570..e3aa9025 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -1,4 +1,3 @@ -// tests/integration/customerService.integration.test.ts import { createOakClient } from "../../src"; import { Crowdsplit } from "../../src/products/crowdsplit"; import { getConfigFromEnv } from "../config"; @@ -52,32 +51,43 @@ describe("CustomerService - Integration", () => { phone_area_code: "18", phone_number: "998121211", }); - expect(response.data.id).toBeDefined(); - expect(response.data.email).toEqual(email); - createdCustomerId = response.data.id as string; + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.email).toEqual(email); + createdCustomerId = response.value.data.id as string; + } }); it("should get the created customer", async () => { const response = await customers.get(createdCustomerId); - expect(response.data.id).toEqual(createdCustomerId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(createdCustomerId); + } }); it("should update the customer", async () => { const response = await customers.update(createdCustomerId, { first_name: "UpdatedName", }); - expect(response.data.first_name).toEqual("UpdatedName"); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.first_name).toEqual("UpdatedName"); + } }); it("should list customers", async () => { const response = await customers.list({ limit: 5 }); - expect(Array.isArray(response.data.customer_list)).toBe(true); - expect(response.data.customer_list.length).toBeGreaterThan(0); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.customer_list)).toBe(true); + expect(response.value.data.customer_list.length).toBeGreaterThan(0); + } }); it("should handle invalid customer ID gracefully", async () => { - await expect( - customers.get("non-existent-id") - ).rejects.toThrow(); + const response = await customers.get("non-existent-id"); + expect(response.ok).toBe(false); }); }); From ec1f4ef54f134ecc03dfcad34467d419a125cfd1 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 20:54:43 +0600 Subject: [PATCH 003/143] fix: add environment variables to CI workflow --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33a1d272..b2b39182 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,9 @@ jobs: CI: true CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - BASE_URL: ${{ secrets.BASE_URL }} + OAK_ENVIRONMENT: sandbox + CROWDSPLIT_SANDBOX_URL: ${{ secrets.CROWDSPLIT_SANDBOX_URL }} + CROWDSPLIT_PRODUCTION_URL: ${{ secrets.CROWDSPLIT_PRODUCTION_URL }} - name: Upload coverage reports uses: actions/upload-artifact@v4 From 61edf449ea25b3ef99b5ccb27ae087bc93c82359 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 21:41:17 +0600 Subject: [PATCH 004/143] fix: async error handling in SandboxOnly and lazy env URL resolution --- packages/api/__tests__/unit/client.test.ts | 13 ++- .../api/__tests__/unit/environment.test.ts | 102 ++++++++++++------ packages/api/src/decorators/sandboxOnly.ts | 8 +- packages/api/src/types/environment.ts | 33 +++--- 4 files changed, 108 insertions(+), 48 deletions(-) diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts index dc1bfa8c..0ac60673 100644 --- a/packages/api/__tests__/unit/client.test.ts +++ b/packages/api/__tests__/unit/client.test.ts @@ -29,13 +29,24 @@ describe("createOakClient", () => { }); it("should resolve production URL for production environment", () => { + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + const testProdUrl = originalProd || "https://api.production.example.com"; + + if (!originalProd) { + process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; + } + const client = createOakClient({ ...baseConfig, environment: "production", }); - expect(client.config.baseUrl).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + expect(client.config.baseUrl).toBe(testProdUrl); expect(client.config.environment).toBe("production"); + + if (!originalProd) { + delete process.env.CROWDSPLIT_PRODUCTION_URL; + } }); it("should use customUrl when provided", () => { diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts index fe3dfcd6..4f3c0357 100644 --- a/packages/api/__tests__/unit/environment.test.ts +++ b/packages/api/__tests__/unit/environment.test.ts @@ -1,7 +1,7 @@ import { resolveBaseUrl, isTestEnvironment, - getEnvironmentConfigs, + getEnvironmentConfig, OakEnvironment, } from "../../src/types/environment"; import { @@ -12,32 +12,61 @@ import { SandboxOnly, sandboxOnlyFn } from "../../src/decorators/sandboxOnly"; import type { ResolvedOakClientConfig } from "../../src/types/client"; describe("Environment Configuration", () => { - describe("getEnvironmentConfigs", () => { - it("should have sandbox config with test operations allowed", () => { - const configs = getEnvironmentConfigs(); - expect(configs.sandbox).toBeDefined(); - expect(configs.sandbox.allowsTestOperations).toBe(true); - expect(configs.sandbox.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + describe("getEnvironmentConfig", () => { + it("should return sandbox config with test operations allowed", () => { + const config = getEnvironmentConfig("sandbox"); + expect(config).toBeDefined(); + expect(config.allowsTestOperations).toBe(true); + expect(config.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); }); - it("should have production config with test operations disallowed", () => { - const configs = getEnvironmentConfigs(); - expect(configs.production).toBeDefined(); - expect(configs.production.allowsTestOperations).toBe(false); - expect(configs.production.apiUrl).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + it("should return production config with test operations disallowed", () => { + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + const testProdUrl = originalProd || "https://api.production.example.com"; + + if (!originalProd) { + process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; + } + + const config = getEnvironmentConfig("production"); + expect(config).toBeDefined(); + expect(config.allowsTestOperations).toBe(false); + expect(config.apiUrl).toBe(testProdUrl); + + if (!originalProd) { + delete process.env.CROWDSPLIT_PRODUCTION_URL; + } }); - it("should throw error when env vars are missing", () => { + it("should throw error when sandbox env var is missing", () => { const originalSandbox = process.env.CROWDSPLIT_SANDBOX_URL; - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; delete process.env.CROWDSPLIT_SANDBOX_URL; - delete process.env.CROWDSPLIT_PRODUCTION_URL; - expect(() => getEnvironmentConfigs()).toThrow( - "Missing required environment variables" + expect(() => getEnvironmentConfig("sandbox")).toThrow( + "Missing required environment variable: CROWDSPLIT_SANDBOX_URL for sandbox environment" ); process.env.CROWDSPLIT_SANDBOX_URL = originalSandbox; + }); + + it("should throw error when production env var is missing", () => { + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + delete process.env.CROWDSPLIT_PRODUCTION_URL; + + expect(() => getEnvironmentConfig("production")).toThrow( + "Missing required environment variable: CROWDSPLIT_PRODUCTION_URL for production environment" + ); + + process.env.CROWDSPLIT_PRODUCTION_URL = originalProd; + }); + + it("should allow sandbox config when only sandbox URL is set", () => { + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + delete process.env.CROWDSPLIT_PRODUCTION_URL; + + const config = getEnvironmentConfig("sandbox"); + expect(config.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + process.env.CROWDSPLIT_PRODUCTION_URL = originalProd; }); }); @@ -49,8 +78,19 @@ describe("Environment Configuration", () => { }); it("should return production URL for production environment", () => { + const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; + const testProdUrl = originalProd || "https://api.production.example.com"; + + if (!originalProd) { + process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; + } + const url = resolveBaseUrl("production"); - expect(url).toBe(process.env.CROWDSPLIT_PRODUCTION_URL); + expect(url).toBe(testProdUrl); + + if (!originalProd) { + delete process.env.CROWDSPLIT_PRODUCTION_URL; + } }); it("should return customUrl when provided", () => { @@ -157,17 +197,17 @@ describe("@SandboxOnly Decorator", () => { expect(result).toBe("sync completed"); }); - it("should throw EnvironmentViolationError in production", () => { + it("should reject with EnvironmentViolationError in production", async () => { const service = new TestServiceWithConfig("production"); - expect(() => service.destructiveOperation()).toThrow( + await expect(service.destructiveOperation()).rejects.toThrow( EnvironmentViolationError ); }); - it("should throw with correct method name in error", () => { + it("should reject with correct method name in error", async () => { const service = new TestServiceWithConfig("production"); try { - service.destructiveOperation(); + await service.destructiveOperation(); fail("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(EnvironmentViolationError); @@ -203,10 +243,10 @@ describe("@SandboxOnly Decorator", () => { expect(result).toBe("symbol method completed"); }); - it("should throw with symbol.toString() as method name in production", () => { + it("should reject with symbol.toString() as method name in production", async () => { const service = new TestServiceWithSymbol("production"); try { - service[symbolMethod](); + await service[symbolMethod](); fail("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(EnvironmentViolationError); @@ -232,9 +272,11 @@ describe("@SandboxOnly Decorator", () => { await expect(service.resetAccount()).resolves.toBeUndefined(); }); - it("should throw EnvironmentViolationError in production", () => { + it("should reject with EnvironmentViolationError in production", async () => { const service = new TestServiceWithClient("production"); - expect(() => service.resetAccount()).toThrow(EnvironmentViolationError); + await expect(service.resetAccount()).rejects.toThrow( + EnvironmentViolationError + ); }); }); @@ -264,11 +306,11 @@ describe("sandboxOnlyFn", () => { expect(fn).toHaveBeenCalled(); }); - it("should throw EnvironmentViolationError when environment is production", () => { + it("should reject with EnvironmentViolationError when environment is production", async () => { const fn = jest.fn().mockReturnValue("result"); const wrapped = sandboxOnlyFn(fn, () => "production", "testFn"); - expect(() => wrapped()).toThrow(EnvironmentViolationError); + await expect(wrapped()).rejects.toThrow(EnvironmentViolationError); expect(fn).not.toHaveBeenCalled(); }); @@ -282,12 +324,12 @@ describe("sandboxOnlyFn", () => { expect(fn).toHaveBeenCalledWith(2, 3); }); - it("should include method name in error", () => { + it("should include method name in error", async () => { const fn = jest.fn(); const wrapped = sandboxOnlyFn(fn, () => "production", "mySpecialMethod"); try { - wrapped(); + await wrapped(); fail("Should have thrown"); } catch (error) { expect((error as EnvironmentViolationError).methodName).toBe( diff --git a/packages/api/src/decorators/sandboxOnly.ts b/packages/api/src/decorators/sandboxOnly.ts index 9bfd1113..0dd86534 100644 --- a/packages/api/src/decorators/sandboxOnly.ts +++ b/packages/api/src/decorators/sandboxOnly.ts @@ -63,7 +63,9 @@ export function SandboxOnly unknown>( typeof propertyKey === "symbol" ? propertyKey.toString() : String(propertyKey); - throw new EnvironmentViolationError(methodName, environment); + return Promise.reject( + new EnvironmentViolationError(methodName, environment) + ) as ReturnType; } return originalMethod.apply(this, args); @@ -81,7 +83,9 @@ export function sandboxOnlyFn unknown>( const environment = getEnvironment(); if (environment === "production") { - throw new EnvironmentViolationError(methodName, environment); + return Promise.reject( + new EnvironmentViolationError(methodName, environment) + ); } return fn(...args); diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts index a8dad0ac..00c367ad 100644 --- a/packages/api/src/types/environment.ts +++ b/packages/api/src/types/environment.ts @@ -5,25 +5,28 @@ export interface EnvironmentConfig { allowsTestOperations: boolean; } -export function getEnvironmentConfigs(): Record { - const sandboxUrl = process.env.CROWDSPLIT_SANDBOX_URL; - const productionUrl = process.env.CROWDSPLIT_PRODUCTION_URL; +const ENVIRONMENT_VAR_NAMES: Record = { + sandbox: "CROWDSPLIT_SANDBOX_URL", + production: "CROWDSPLIT_PRODUCTION_URL", +}; - if (!sandboxUrl || !productionUrl) { +function getEnvironmentUrl(environment: OakEnvironment): string { + const envVarName = ENVIRONMENT_VAR_NAMES[environment]; + const url = process.env[envVarName]; + + if (!url) { throw new Error( - "Missing required environment variables: CROWDSPLIT_SANDBOX_URL, CROWDSPLIT_PRODUCTION_URL" + `Missing required environment variable: ${envVarName} for ${environment} environment` ); } + return url; +} + +export function getEnvironmentConfig(environment: OakEnvironment): EnvironmentConfig { return { - sandbox: { - apiUrl: sandboxUrl, - allowsTestOperations: true, - }, - production: { - apiUrl: productionUrl, - allowsTestOperations: false, - }, + apiUrl: getEnvironmentUrl(environment), + allowsTestOperations: environment === "sandbox", }; } @@ -34,9 +37,9 @@ export function resolveBaseUrl( if (customUrl) { return customUrl; } - return getEnvironmentConfigs()[environment].apiUrl; + return getEnvironmentUrl(environment); } export function isTestEnvironment(environment: OakEnvironment): boolean { - return getEnvironmentConfigs()[environment].allowsTestOperations; + return environment === "sandbox"; } From 8de703c5a24d55ca757627c94b5ee70ca6746a0e Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 21:48:38 +0600 Subject: [PATCH 005/143] fix: restore tsconfig rootDir to ./src for valid package exports --- packages/api/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 1277718f..9be7cc27 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2018", "module": "commonjs", "outDir": "./dist", - "rootDir": ".", + "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -13,6 +13,6 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true }, - "include": ["src/**/*", "__tests__/**/*"], + "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } From 02537c41c4c532864ce7ad4e5c39827f7813ec1c Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 21:58:20 +0600 Subject: [PATCH 006/143] fix: preserve method contracts in SandboxOnly and fix tsconfig for IDE --- .../api/__tests__/unit/environment.test.ts | 27 ++++++++++++++----- packages/api/package.json | 2 +- packages/api/src/decorators/sandboxOnly.ts | 21 ++++++++++----- packages/api/tsconfig.build.json | 8 ++++++ packages/api/tsconfig.json | 4 +-- 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 packages/api/tsconfig.build.json diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts index 4f3c0357..aeaa9e79 100644 --- a/packages/api/__tests__/unit/environment.test.ts +++ b/packages/api/__tests__/unit/environment.test.ts @@ -243,10 +243,10 @@ describe("@SandboxOnly Decorator", () => { expect(result).toBe("symbol method completed"); }); - it("should reject with symbol.toString() as method name in production", async () => { + it("should throw with symbol.toString() as method name in production", () => { const service = new TestServiceWithSymbol("production"); try { - await service[symbolMethod](); + service[symbolMethod](); fail("Should have thrown"); } catch (error) { expect(error).toBeInstanceOf(EnvironmentViolationError); @@ -306,11 +306,11 @@ describe("sandboxOnlyFn", () => { expect(fn).toHaveBeenCalled(); }); - it("should reject with EnvironmentViolationError when environment is production", async () => { + it("should throw EnvironmentViolationError when environment is production", () => { const fn = jest.fn().mockReturnValue("result"); const wrapped = sandboxOnlyFn(fn, () => "production", "testFn"); - await expect(wrapped()).rejects.toThrow(EnvironmentViolationError); + expect(() => wrapped()).toThrow(EnvironmentViolationError); expect(fn).not.toHaveBeenCalled(); }); @@ -324,12 +324,12 @@ describe("sandboxOnlyFn", () => { expect(fn).toHaveBeenCalledWith(2, 3); }); - it("should include method name in error", async () => { + it("should include method name in error", () => { const fn = jest.fn(); const wrapped = sandboxOnlyFn(fn, () => "production", "mySpecialMethod"); try { - await wrapped(); + wrapped(); fail("Should have thrown"); } catch (error) { expect((error as EnvironmentViolationError).methodName).toBe( @@ -337,4 +337,19 @@ describe("sandboxOnlyFn", () => { ); } }); + + it("should reject with EnvironmentViolationError for async function in production", async () => { + const asyncFn = async () => "async result"; + const wrapped = sandboxOnlyFn(asyncFn, () => "production", "asyncTestFn"); + + await expect(wrapped()).rejects.toThrow(EnvironmentViolationError); + }); + + it("should allow async function execution in sandbox", async () => { + const asyncFn = async () => "async result"; + const wrapped = sandboxOnlyFn(asyncFn, () => "sandbox", "asyncTestFn"); + + const result = await wrapped(); + expect(result).toBe("async result"); + }); }); diff --git a/packages/api/package.json b/packages/api/package.json index fabc3a4f..767e6a22 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -26,7 +26,7 @@ } }, "scripts": { - "build": "tsc", + "build": "tsc -p tsconfig.build.json", "prepublishOnly": "npm run build", "test": "jest __tests__/unit --coverage", "test:unit": "jest __tests__/unit --coverage", diff --git a/packages/api/src/decorators/sandboxOnly.ts b/packages/api/src/decorators/sandboxOnly.ts index 0dd86534..3df9177e 100644 --- a/packages/api/src/decorators/sandboxOnly.ts +++ b/packages/api/src/decorators/sandboxOnly.ts @@ -63,9 +63,14 @@ export function SandboxOnly unknown>( typeof propertyKey === "symbol" ? propertyKey.toString() : String(propertyKey); - return Promise.reject( - new EnvironmentViolationError(methodName, environment) - ) as ReturnType; + const error = new EnvironmentViolationError(methodName, environment); + + const isAsyncFunction = + originalMethod.constructor.name === "AsyncFunction"; + if (isAsyncFunction) { + return Promise.reject(error) as ReturnType; + } + throw error; } return originalMethod.apply(this, args); @@ -83,9 +88,13 @@ export function sandboxOnlyFn unknown>( const environment = getEnvironment(); if (environment === "production") { - return Promise.reject( - new EnvironmentViolationError(methodName, environment) - ); + const error = new EnvironmentViolationError(methodName, environment); + + const isAsyncFunction = fn.constructor.name === "AsyncFunction"; + if (isAsyncFunction) { + return Promise.reject(error); + } + throw error; } return fn(...args); diff --git a/packages/api/tsconfig.build.json b/packages/api/tsconfig.build.json new file mode 100644 index 00000000..7e608008 --- /dev/null +++ b/packages/api/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "__tests__"] +} diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 9be7cc27..1277718f 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2018", "module": "commonjs", "outDir": "./dist", - "rootDir": "./src", + "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -13,6 +13,6 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "__tests__/**/*"], "exclude": ["node_modules", "dist"] } From 35c1ac5bb09fa199d3f609a03db9b67ea770d7aa Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 22:12:23 +0600 Subject: [PATCH 007/143] refactor: hardcode environment URLs as SDK constants --- .github/workflows/ci.yml | 2 - packages/api/.env-sample | 3 - .../api/__tests__/unit/authService.test.ts | 4 +- packages/api/__tests__/unit/client.test.ts | 18 ++--- .../__tests__/unit/customerService.test.ts | 10 ++- .../api/__tests__/unit/environment.test.ts | 69 ++--------------- packages/api/__tests__/unit/services.test.ts | 76 ++++++++++--------- packages/api/src/types/environment.ts | 23 ++---- 8 files changed, 66 insertions(+), 139 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2b39182..a39d45df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,6 @@ jobs: CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} OAK_ENVIRONMENT: sandbox - CROWDSPLIT_SANDBOX_URL: ${{ secrets.CROWDSPLIT_SANDBOX_URL }} - CROWDSPLIT_PRODUCTION_URL: ${{ secrets.CROWDSPLIT_PRODUCTION_URL }} - name: Upload coverage reports uses: actions/upload-artifact@v4 diff --git a/packages/api/.env-sample b/packages/api/.env-sample index cbd90225..17d16646 100644 --- a/packages/api/.env-sample +++ b/packages/api/.env-sample @@ -1,5 +1,2 @@ CLIENT_ID=your_client_id_here CLIENT_SECRET=your_client_secret_here -OAK_ENVIRONMENT=sandbox -CROWDSPLIT_SANDBOX_URL=https://api.usecrowdpay.xyz -CROWDSPLIT_PRODUCTION_URL=https://app.usecrowdpay.xyz diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/api/__tests__/unit/authService.test.ts index d935b20e..3a5a50f4 100644 --- a/packages/api/__tests__/unit/authService.test.ts +++ b/packages/api/__tests__/unit/authService.test.ts @@ -5,6 +5,8 @@ import { RetryOptions } from "../../src/utils"; import type { OakClientConfig } from "../../src/types"; import { ok } from "../../src/types"; +const SANDBOX_URL = "https://api.usecrowdpay.xyz"; + jest.mock("../../src/utils/httpClient"); const mockedHttpClient = httpClient as jest.Mocked; @@ -41,7 +43,7 @@ describe("Auth (Unit)", () => { const result = await client.grantToken(); expect(mockedHttpClient.post).toHaveBeenCalledWith( - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/token/grant`, + `${SANDBOX_URL}/api/v1/merchant/token/grant`, { client_id: config.clientId, client_secret: config.clientSecret, diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts index 0ac60673..77e9a677 100644 --- a/packages/api/__tests__/unit/client.test.ts +++ b/packages/api/__tests__/unit/client.test.ts @@ -1,6 +1,9 @@ import { createOakClient } from "../../src/client"; import type { OakClientConfig } from "../../src/types/client"; +const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; + jest.mock("../../src/authManager", () => ({ AuthManager: jest.fn().mockImplementation(() => ({ getAccessToken: jest.fn().mockResolvedValue({ ok: true, value: "mock-token" }), @@ -24,29 +27,18 @@ describe("createOakClient", () => { environment: "sandbox", }); - expect(client.config.baseUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + expect(client.config.baseUrl).toBe(SANDBOX_URL); expect(client.config.environment).toBe("sandbox"); }); it("should resolve production URL for production environment", () => { - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; - const testProdUrl = originalProd || "https://api.production.example.com"; - - if (!originalProd) { - process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; - } - const client = createOakClient({ ...baseConfig, environment: "production", }); - expect(client.config.baseUrl).toBe(testProdUrl); + expect(client.config.baseUrl).toBe(PRODUCTION_URL); expect(client.config.environment).toBe("production"); - - if (!originalProd) { - delete process.env.CROWDSPLIT_PRODUCTION_URL; - } }); it("should use customUrl when provided", () => { diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index 459f9450..7b329deb 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -10,6 +10,8 @@ import { ok, } from "../../src/types"; +const SANDBOX_URL = "https://api.usecrowdpay.xyz"; + jest.mock("../../src/utils/httpClient", () => ({ httpClient: { post: jest.fn(), @@ -52,7 +54,7 @@ describe("CustomerService - Unit", () => { expect(client.getAccessToken).toHaveBeenCalled(); expect(httpClient.post).toHaveBeenCalledWith( - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, + `${SANDBOX_URL}/api/v1/customers`, request, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, @@ -83,7 +85,7 @@ describe("CustomerService - Unit", () => { const result = await customers.get("123"); expect(httpClient.get).toHaveBeenCalledWith( - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/123`, + `${SANDBOX_URL}/api/v1/customers/123`, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), @@ -102,7 +104,7 @@ describe("CustomerService - Unit", () => { const result = await customers.list(params); expect(httpClient.get).toHaveBeenCalledWith( - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers?limit=10&offset=5`, + `${SANDBOX_URL}/api/v1/customers?limit=10&offset=5`, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), @@ -121,7 +123,7 @@ describe("CustomerService - Unit", () => { const result = await customers.update("123", updateData); expect(httpClient.put).toHaveBeenCalledWith( - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/123`, + `${SANDBOX_URL}/api/v1/customers/123`, updateData, expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts index aeaa9e79..c87dd871 100644 --- a/packages/api/__tests__/unit/environment.test.ts +++ b/packages/api/__tests__/unit/environment.test.ts @@ -11,86 +11,35 @@ import { import { SandboxOnly, sandboxOnlyFn } from "../../src/decorators/sandboxOnly"; import type { ResolvedOakClientConfig } from "../../src/types/client"; +const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; + describe("Environment Configuration", () => { describe("getEnvironmentConfig", () => { it("should return sandbox config with test operations allowed", () => { const config = getEnvironmentConfig("sandbox"); expect(config).toBeDefined(); expect(config.allowsTestOperations).toBe(true); - expect(config.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + expect(config.apiUrl).toBe(SANDBOX_URL); }); it("should return production config with test operations disallowed", () => { - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; - const testProdUrl = originalProd || "https://api.production.example.com"; - - if (!originalProd) { - process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; - } - const config = getEnvironmentConfig("production"); expect(config).toBeDefined(); expect(config.allowsTestOperations).toBe(false); - expect(config.apiUrl).toBe(testProdUrl); - - if (!originalProd) { - delete process.env.CROWDSPLIT_PRODUCTION_URL; - } - }); - - it("should throw error when sandbox env var is missing", () => { - const originalSandbox = process.env.CROWDSPLIT_SANDBOX_URL; - delete process.env.CROWDSPLIT_SANDBOX_URL; - - expect(() => getEnvironmentConfig("sandbox")).toThrow( - "Missing required environment variable: CROWDSPLIT_SANDBOX_URL for sandbox environment" - ); - - process.env.CROWDSPLIT_SANDBOX_URL = originalSandbox; - }); - - it("should throw error when production env var is missing", () => { - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; - delete process.env.CROWDSPLIT_PRODUCTION_URL; - - expect(() => getEnvironmentConfig("production")).toThrow( - "Missing required environment variable: CROWDSPLIT_PRODUCTION_URL for production environment" - ); - - process.env.CROWDSPLIT_PRODUCTION_URL = originalProd; - }); - - it("should allow sandbox config when only sandbox URL is set", () => { - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; - delete process.env.CROWDSPLIT_PRODUCTION_URL; - - const config = getEnvironmentConfig("sandbox"); - expect(config.apiUrl).toBe(process.env.CROWDSPLIT_SANDBOX_URL); - - process.env.CROWDSPLIT_PRODUCTION_URL = originalProd; + expect(config.apiUrl).toBe(PRODUCTION_URL); }); }); describe("resolveBaseUrl", () => { it("should return sandbox URL for sandbox environment", () => { const url = resolveBaseUrl("sandbox"); - expect(url).toBe(process.env.CROWDSPLIT_SANDBOX_URL); + expect(url).toBe(SANDBOX_URL); }); it("should return production URL for production environment", () => { - const originalProd = process.env.CROWDSPLIT_PRODUCTION_URL; - const testProdUrl = originalProd || "https://api.production.example.com"; - - if (!originalProd) { - process.env.CROWDSPLIT_PRODUCTION_URL = testProdUrl; - } - const url = resolveBaseUrl("production"); - expect(url).toBe(testProdUrl); - - if (!originalProd) { - delete process.env.CROWDSPLIT_PRODUCTION_URL; - } + expect(url).toBe(PRODUCTION_URL); }); it("should return customUrl when provided", () => { @@ -150,9 +99,7 @@ describe("@SandboxOnly Decorator", () => { environment, clientId: "test-id", clientSecret: "test-secret", - baseUrl: environment === "sandbox" - ? process.env.CROWDSPLIT_SANDBOX_URL! - : process.env.CROWDSPLIT_PRODUCTION_URL!, + baseUrl: environment === "sandbox" ? SANDBOX_URL : PRODUCTION_URL, }); describe("with missing descriptor value", () => { diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index ba867067..e7b0d11c 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -16,6 +16,8 @@ import { SDKError } from "../../src/utils/errorHandler"; import type { OakClient } from "../../src/types"; import { err, ok } from "../../src/types"; +const SANDBOX_URL = "https://api.usecrowdpay.xyz"; + jest.mock("../../src/utils/httpClient", () => ({ httpClient: { post: jest.fn(), @@ -35,7 +37,7 @@ const makeClient = (): OakClient => ({ environment: "sandbox", clientId: "id", clientSecret: "secret", - baseUrl: process.env.CROWDSPLIT_SANDBOX_URL!, + baseUrl: SANDBOX_URL, }, retryOptions, getAccessToken: jest.fn().mockResolvedValue(ok("token")), @@ -49,7 +51,7 @@ const makeClientWithTokenError = (): OakClient => { environment: "sandbox", clientId: "id", clientSecret: "secret", - baseUrl: process.env.CROWDSPLIT_SANDBOX_URL!, + baseUrl: SANDBOX_URL, }, retryOptions, getAccessToken: jest.fn().mockResolvedValue(err(tokenError)), @@ -138,7 +140,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.create({ email: "test@example.com" }), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, + `${SANDBOX_URL}/api/v1/customers`, { email: "test@example.com" }, authConfig, ], @@ -153,7 +155,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("cust-1"), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/customers/cust-1`, authConfig], }); await expectFailure({ call: () => service.get("cust-1"), @@ -165,13 +167,13 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.list({ limit: 10, offset: undefined }), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers?limit=10`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/customers?limit=10`, authConfig], }); await expectSuccess({ client, call: () => service.list({ limit: undefined }), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/customers`, authConfig], }); await expectFailure({ call: () => service.list({ limit: 10 }), @@ -184,7 +186,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("cust-1", { email: "new@example.com" }), httpMethod: "put", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1`, + `${SANDBOX_URL}/api/v1/customers/cust-1`, { email: "new@example.com" }, authConfig, ], @@ -215,7 +217,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.create(payment), httpMethod: "post", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/`, payment, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/payments/`, payment, authConfig], }); await expectFailure({ call: () => service.create(payment), @@ -228,7 +230,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.confirm("pay-1"), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/pay-1/confirm`, + `${SANDBOX_URL}/api/v1/payments/pay-1/confirm`, {}, authConfig, ], @@ -244,7 +246,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.cancel("pay-1"), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/payments/pay-1/cancel`, + `${SANDBOX_URL}/api/v1/payments/pay-1/cancel`, {}, authConfig, ], @@ -262,7 +264,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.add("cust-1", paymentMethod), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods`, + `${SANDBOX_URL}/api/v1/customers/cust-1/payment_methods`, paymentMethod, authConfig, ], @@ -278,7 +280,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.get("cust-1", "pay-1"), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pay-1`, + `${SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pay-1`, authConfig, ], }); @@ -297,7 +299,7 @@ describe("Crowdsplit services (Unit)", () => { }), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods?type=pix`, + `${SANDBOX_URL}/api/v1/customers/cust-1/payment_methods?type=pix`, authConfig, ], }); @@ -312,7 +314,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => paymentMethodService.delete("cust-1", "pm-1"), httpMethod: "delete", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pm-1`, + `${SANDBOX_URL}/api/v1/customers/cust-1/payment_methods/pm-1`, authConfig, ], }); @@ -353,7 +355,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getSchema(request), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/schema?provider=stripe`, + `${SANDBOX_URL}/api/v1/provider-registration/schema?provider=stripe`, authConfig, ], }); @@ -368,7 +370,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getRegistrationStatus("cust-1"), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/cust-1/status`, + `${SANDBOX_URL}/api/v1/provider-registration/cust-1/status`, authConfig, ], }); @@ -385,7 +387,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.submitRegistration("cust-1", registration), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/provider-registration/cust-1/submit`, + `${SANDBOX_URL}/api/v1/provider-registration/cust-1/submit`, registration, authConfig, ], @@ -434,7 +436,7 @@ describe("Crowdsplit services (Unit)", () => { service.list({ type_list: "refund", status: undefined }), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions?type_list=refund`, + `${SANDBOX_URL}/api/v1/transactions?type_list=refund`, authConfig, ], }); @@ -448,7 +450,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("txn-1"), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions/txn-1`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/transactions/txn-1`, authConfig], }); await expectFailure({ call: () => service.get("txn-1"), @@ -461,7 +463,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.settle("txn-1", settlement), httpMethod: "patch", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transactions/txn-1/settle`, + `${SANDBOX_URL}/api/v1/transactions/txn-1/settle`, settlement, authConfig, ], @@ -495,7 +497,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => transferService.create(transfer), httpMethod: "post", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/transfer`, transfer, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/transfer`, transfer, authConfig], }); await expectFailure({ call: () => transferService.create(transfer), @@ -508,7 +510,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => sellService.create(sell), httpMethod: "post", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/sell`, sell, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/sell`, sell, authConfig], }); await expectFailure({ call: () => sellService.create(sell), @@ -521,7 +523,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => buyService.create(buy), httpMethod: "post", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/buy`, buy, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/buy`, buy, authConfig], }); await expectFailure({ call: () => buyService.create(buy), @@ -559,7 +561,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.create(planRequest), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans`, + `${SANDBOX_URL}/api/v1/subscription/plans`, planRequest, authConfig, ], @@ -575,7 +577,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.publish("plan-1"), httpMethod: "patch", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1/publish`, + `${SANDBOX_URL}/api/v1/subscription/plans/plan-1/publish`, undefined, authConfig, ], @@ -591,7 +593,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.details("plan-1"), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, + `${SANDBOX_URL}/api/v1/subscription/plans/plan-1`, authConfig, ], }); @@ -606,7 +608,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.list({ page_no: 1, per_page: undefined }), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans?page_no=1`, + `${SANDBOX_URL}/api/v1/subscription/plans?page_no=1`, authConfig, ], }); @@ -621,7 +623,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("plan-1", planRequest), httpMethod: "patch", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, + `${SANDBOX_URL}/api/v1/subscription/plans/plan-1`, planRequest, authConfig, ], @@ -637,7 +639,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.delete("plan-1"), httpMethod: "delete", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/subscription/plans/plan-1`, + `${SANDBOX_URL}/api/v1/subscription/plans/plan-1`, authConfig, ], }); @@ -668,7 +670,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.register(webhook), httpMethod: "post", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks`, + `${SANDBOX_URL}/api/v1/merchant/webhooks`, webhook, authConfig, ], @@ -698,7 +700,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.list(), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/merchant/webhooks`, authConfig], }); await expectFailure({ call: () => service.list(), @@ -710,7 +712,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("wh-1"), httpMethod: "get", - expectedArgs: [`${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig], }); await expectFailure({ call: () => service.get("wh-1"), @@ -723,7 +725,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.update("wh-1", webhook), httpMethod: "put", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, + `${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, webhook, authConfig, ], @@ -739,7 +741,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.toggle("wh-1"), httpMethod: "patch", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1/toggle`, + `${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1/toggle`, undefined, authConfig, ], @@ -755,7 +757,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.delete("wh-1"), httpMethod: "delete", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, + `${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig, ], }); @@ -771,7 +773,7 @@ describe("Crowdsplit services (Unit)", () => { service.listNotifications({ limit: 1, offset: undefined }), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/notifications?limit=1`, + `${SANDBOX_URL}/api/v1/merchant/webhooks/notifications?limit=1`, authConfig, ], }); @@ -786,7 +788,7 @@ describe("Crowdsplit services (Unit)", () => { call: () => service.getNotification("wh-1"), httpMethod: "get", expectedArgs: [ - `${process.env.CROWDSPLIT_SANDBOX_URL}/api/v1/merchant/webhooks/notifications/wh-1`, + `${SANDBOX_URL}/api/v1/merchant/webhooks/notifications/wh-1`, authConfig, ], }); diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts index 00c367ad..c154dbbf 100644 --- a/packages/api/src/types/environment.ts +++ b/packages/api/src/types/environment.ts @@ -5,27 +5,14 @@ export interface EnvironmentConfig { allowsTestOperations: boolean; } -const ENVIRONMENT_VAR_NAMES: Record = { - sandbox: "CROWDSPLIT_SANDBOX_URL", - production: "CROWDSPLIT_PRODUCTION_URL", +const ENVIRONMENT_URLS: Record = { + sandbox: "https://api.usecrowdpay.xyz", + production: "https://app.usecrowdpay.xyz", }; -function getEnvironmentUrl(environment: OakEnvironment): string { - const envVarName = ENVIRONMENT_VAR_NAMES[environment]; - const url = process.env[envVarName]; - - if (!url) { - throw new Error( - `Missing required environment variable: ${envVarName} for ${environment} environment` - ); - } - - return url; -} - export function getEnvironmentConfig(environment: OakEnvironment): EnvironmentConfig { return { - apiUrl: getEnvironmentUrl(environment), + apiUrl: ENVIRONMENT_URLS[environment], allowsTestOperations: environment === "sandbox", }; } @@ -37,7 +24,7 @@ export function resolveBaseUrl( if (customUrl) { return customUrl; } - return getEnvironmentUrl(environment); + return ENVIRONMENT_URLS[environment]; } export function isTestEnvironment(environment: OakEnvironment): boolean { From 8e5af5b597b0cf4291bc4304829bf10435dd92a8 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 5 Feb 2026 22:13:43 +0600 Subject: [PATCH 008/143] fix: add the oak environment field to the sample env --- packages/api/.env-sample | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/.env-sample b/packages/api/.env-sample index 17d16646..4c98bf57 100644 --- a/packages/api/.env-sample +++ b/packages/api/.env-sample @@ -1,2 +1,3 @@ CLIENT_ID=your_client_id_here CLIENT_SECRET=your_client_secret_here +OAK_ENVIRONMENT=sandbox From e9337cf75ad1e2e76c700aab3cdb41e3d6929908 Mon Sep 17 00:00:00 2001 From: Lucas Vinhas Date: Thu, 5 Feb 2026 18:08:20 -0300 Subject: [PATCH 009/143] chore: enhance httpClient to include Oak-Version in headers (#18) * docs: update README with work in progress note and launch timeline * chore: enhance release workflow to build and publish packages conditionally Updated the GitHub Actions workflow to include conditional steps for building and publishing packages only when there are no changesets. Added steps for creating GitHub releases and tagging versions based on package.json files. * chore: update release workflow to configure npm registry for OIDC Added a step to set up the npm registry configuration for OIDC in the release workflow, ensuring proper publishing of packages when there are no changesets. * chore: update release workflow to install latest npm version for OIDC support Modified the release workflow to install the latest version of npm and removed the explicit npm registry configuration step, ensuring compatibility with OIDC during package publishing. * chore: modify release workflow to conditionally publish packages based on version Updated the release workflow to check the package version before publishing. If the version includes a pre-release tag, it publishes with the 'dev' tag; otherwise, it publishes normally. This ensures proper version handling during the release process. * chore: add repository metadata to package.json files for api and contracts Included repository information in the package.json files for both the api and contracts packages, specifying the git type, URL, and directory. This enhances package metadata for better integration and visibility. * chore: bump version to 0.1.0-dev.1 in package.json for contracts * chore: add testing changeset for minor version updates in api and contracts * chore: version packages (#17) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * chore: update version to 0.1.0 in package.json for api * chore: refactor release workflow to publish all packages conditionally Updated the release workflow to publish all packages in the 'packages' directory, checking for existing published versions and using the appropriate tag for pre-release versions. This enhances the publishing process by ensuring only new versions are published and improves overall efficiency. * chore: enhance httpClient to include Oak-Version in headers Refactored the httpClient to automatically include the Oak-Version header in all requests, utilizing the version from package.json or an environment variable. Updated unit tests to verify the correct headers are sent, ensuring consistency across API calls. * test: add fallback mechanism for Oak-Version in httpClient tests Implemented a new test case to verify that the httpClient correctly falls back to using 'unknown' for the Oak-Version header when both the environment variable and package.json are unavailable. This enhances the robustness of the httpClient by ensuring it handles versioning gracefully in edge cases. * chore: add changeset for Oak-Version header enhancement in httpClient --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/flat-teams-design.md | 5 + .github/workflows/release.yml | 63 ++++++++++-- README.md | 3 + packages/api/CHANGELOG.md | 8 ++ packages/api/__tests__/unit/utils.test.ts | 113 +++++++++++++++------- packages/api/package.json | 7 +- packages/api/src/utils/httpClient.ts | 13 +++ packages/contracts/CHANGELOG.md | 8 ++ packages/contracts/package.json | 7 +- 9 files changed, 183 insertions(+), 44 deletions(-) create mode 100644 .changeset/flat-teams-design.md diff --git a/.changeset/flat-teams-design.md b/.changeset/flat-teams-design.md new file mode 100644 index 00000000..9f31810d --- /dev/null +++ b/.changeset/flat-teams-design.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/api": patch +--- + +chore: enhance httpClient to include Oak-Version in headers#18 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e0585de..31d58e39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,17 +35,66 @@ jobs: - name: Install deps run: pnpm install --frozen-lockfile - - name: Build - run: pnpm -r build - - - name: Create Release PR or Publish + - name: Create release PR or publish id: changesets uses: changesets/action@v1 with: - version: npx changeset version - publish: npx changeset publish + version: pnpm changeset:version commit: "chore: version packages" title: "Release packages" - createGithubReleases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build packages + if: steps.changesets.outputs.hasChangesets == 'false' + run: pnpm build + + - name: Update npm for OIDC support + if: steps.changesets.outputs.hasChangesets == 'false' + run: | + npm install -g npm@latest + npm --version + + - name: Publish packages + if: steps.changesets.outputs.hasChangesets == 'false' + run: | + for pkg in packages/*/; do + if [ -f "$pkg/package.json" ]; then + NAME=$(node -p "require('./$pkg/package.json').name") + VERSION=$(node -p "require('./$pkg/package.json').version") + + # Check if this version is already published + if npm view "${NAME}@${VERSION}" version 2>/dev/null; then + echo "⏭️ ${NAME}@${VERSION} already published, skipping." + continue + fi + + # Use --tag dev for prerelease versions + if [[ "$VERSION" == *-* ]]; then + TAG="--tag dev" + else + TAG="" + fi + + echo "📦 Publishing ${NAME}@${VERSION}..." + (cd "$pkg" && npm publish --provenance --access public $TAG) + fi + done + + - name: Create GitHub releases + if: steps.changesets.outputs.hasChangesets == 'false' + run: | + for pkg in packages/*/; do + if [ -f "$pkg/package.json" ]; then + name=$(jq -r .name "$pkg/package.json") + version=$(jq -r .version "$pkg/package.json") + tag="${name}@${version}" + if ! git tag -l | grep -q "^${tag}$"; then + git tag "$tag" + git push origin "$tag" || true + gh release create "$tag" --title "$tag" --notes "Release $tag" || true + fi + fi + done env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 68fd3ff1..fd63359d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# WORK IN PROGRESS +- Launch expected by March 2026 + # Crowdsplit SDK Monorepo ## Changesets workflow diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 3dcd1e94..95994936 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,13 @@ # @oaknetwork/api +## 1.1.0 + +### Minor Changes + +### Minor Changes + +- testing changeset for releases + ## 1.0.0 ### Major Changes diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index 27f4bc95..4c4d7bf6 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -5,6 +5,13 @@ import { withRetry } from "../../src/utils/retryHandler"; import { DEFAULT_RETRY_OPTIONS } from "../../src/utils/defaultRetryConfig"; import "../../src/utils"; +const packageVersion = require("../../package.json").version as string; +const expectedHeaders = (headers?: Record) => ({ + "Content-Type": "application/json", + "Oak-Version": packageVersion, + ...(headers ?? {}), +}); + describe("SDKError", () => { it("stores message and cause", () => { const cause = new Error("root"); @@ -78,9 +85,7 @@ describe("httpClient", () => { expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/post", { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), body: JSON.stringify({ data: 1 }), }); }); @@ -99,10 +104,7 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/post", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-Test": "yes", - }, + headers: expectedHeaders({ "X-Test": "yes" }), body: JSON.stringify({ data: 1 }), }); }); @@ -135,9 +137,7 @@ describe("httpClient", () => { expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { method: "GET", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), }); }); @@ -154,10 +154,7 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { method: "GET", - headers: { - "Content-Type": "application/json", - "X-Test": "yes", - }, + headers: expectedHeaders({ "X-Test": "yes" }), }); }); @@ -203,9 +200,7 @@ describe("httpClient", () => { expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/put", { method: "PUT", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), body: JSON.stringify({ data: 2 }), }); }); @@ -224,10 +219,7 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/put", { method: "PUT", - headers: { - "Content-Type": "application/json", - "X-Test": "yes", - }, + headers: expectedHeaders({ "X-Test": "yes" }), body: JSON.stringify({ data: 2 }), }); }); @@ -274,9 +266,7 @@ describe("httpClient", () => { expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), body: undefined, }); }); @@ -291,9 +281,7 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), body: JSON.stringify({ data: 9 }), }); }); @@ -312,10 +300,7 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", - headers: { - "Content-Type": "application/json", - "X-Test": "yes", - }, + headers: expectedHeaders({ "X-Test": "yes" }), body: JSON.stringify({ data: 9 }), }); }); @@ -360,9 +345,7 @@ describe("httpClient", () => { expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/delete", { method: "DELETE", - headers: { - "Content-Type": "application/json", - }, + headers: expectedHeaders(), }); }); @@ -379,11 +362,71 @@ describe("httpClient", () => { expect(fetchMock).toHaveBeenCalledWith("https://api.test/delete", { method: "DELETE", + headers: expectedHeaders({ "X-Test": "yes" }), + }); + }); + + it("falls back to 'unknown' when require and OAK_VERSION both fail", async () => { + const previousVersion = process.env.OAK_VERSION; + delete process.env.OAK_VERSION; + + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ ok: true }), + }); + + let isolatedClient: typeof httpClient; + jest.isolateModules(() => { + jest.mock("../../package.json", () => { + throw new Error("not found"); + }); + isolatedClient = require("../../src/utils/httpClient").httpClient; + }); + + await isolatedClient!.get("https://api.test/get", { retryOptions }); + + expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { + method: "GET", + headers: { + "Content-Type": "application/json", + "Oak-Version": "unknown", + }, + }); + + if (previousVersion !== undefined) { + process.env.OAK_VERSION = previousVersion; + } + }); + + it("uses OAK_VERSION when provided", async () => { + const previousVersion = process.env.OAK_VERSION; + process.env.OAK_VERSION = "9.9.9"; + + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ ok: true }), + }); + + let isolatedClient: typeof httpClient; + jest.isolateModules(() => { + isolatedClient = require("../../src/utils/httpClient").httpClient; + }); + + await isolatedClient!.get("https://api.test/get", { retryOptions }); + + expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { + method: "GET", headers: { "Content-Type": "application/json", - "X-Test": "yes", + "Oak-Version": "9.9.9", }, }); + + if (previousVersion === undefined) { + delete process.env.OAK_VERSION; + } else { + process.env.OAK_VERSION = previousVersion; + } }); it("delete throws error for non-ok response", async () => { diff --git a/packages/api/package.json b/packages/api/package.json index 9e477067..26debe55 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@oaknetwork/api", - "version": "1.0.0", + "version": "0.1.0", "description": "TypeScript SDK for Crowdsplit API", "publishConfig": { "access": "public" @@ -44,6 +44,11 @@ }, "author": "oaknetwork", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/oak-network/sdk", + "directory": "packages/api" + }, "files": [ "dist" ], diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index c2fcfa0c..a8cec512 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -7,8 +7,21 @@ export interface HttpClientConfig { signal?: AbortSignal; } +const getPackageVersion = () => { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const pkg = require("../../package.json") as { version?: string }; + return pkg.version; + } catch { + return undefined; + } +}; + +const oakVersion = process.env.OAK_VERSION ?? getPackageVersion() ?? "unknown"; + const mergeHeaders = (headers?: Record) => ({ "Content-Type": "application/json", + "Oak-Version": oakVersion, ...(headers ?? {}), }); diff --git a/packages/contracts/CHANGELOG.md b/packages/contracts/CHANGELOG.md index 82165b51..e654c612 100644 --- a/packages/contracts/CHANGELOG.md +++ b/packages/contracts/CHANGELOG.md @@ -1,5 +1,13 @@ # @oaknetwork/contracts +## 0.1.0 + +### Minor Changes + +### Minor Changes + +- testing changeset for releases + ## 1.0.0 ### Major Changes diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 5ea1b0b4..f767630b 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@oaknetwork/contracts", - "version": "0.1.0-dev.0", + "version": "0.1.0", "description": "TypeScript SDK for Oak Network smart contracts", "publishConfig": { "access": "public" @@ -23,6 +23,11 @@ }, "author": "oaknetwork", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/oak-network/sdk", + "directory": "packages/contracts" + }, "files": [ "dist" ], From 3d46b6f8f30e4e122af0ddd43b390d56bf10af40 Mon Sep 17 00:00:00 2001 From: Fahad Muntasir Date: Fri, 6 Feb 2026 18:17:11 +0600 Subject: [PATCH 010/143] refactor(api): centralize Result-based errors in httpClient --- .../__tests__/integration/authService.test.ts | 3 +- .../api/__tests__/unit/authService.test.ts | 26 +- .../__tests__/unit/customerService.test.ts | 20 +- packages/api/__tests__/unit/services.test.ts | 77 +++--- packages/api/__tests__/unit/utils.test.ts | 259 +++++++++++++----- packages/api/src/authManager.ts | 41 ++- packages/api/src/services/buyService.ts | 32 +-- packages/api/src/services/customerService.ts | 137 ++++----- .../api/src/services/paymentMethodService.ts | 153 ++++------- packages/api/src/services/paymentService.ts | 109 +++----- packages/api/src/services/planService.ts | 173 +++++------- packages/api/src/services/providerService.ts | 132 ++++----- packages/api/src/services/sellService.ts | 32 +-- .../api/src/services/transactionService.ts | 92 +++---- packages/api/src/services/transferService.ts | 37 +-- packages/api/src/services/webhookService.ts | 244 +++++++---------- packages/api/src/types/result.ts | 4 +- packages/api/src/utils/errorHandler.ts | 47 +++- packages/api/src/utils/httpClient.ts | 167 ++++++----- 19 files changed, 875 insertions(+), 910 deletions(-) diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/api/__tests__/integration/authService.test.ts index 7c9d3db9..dcbad3a9 100644 --- a/packages/api/__tests__/integration/authService.test.ts +++ b/packages/api/__tests__/integration/authService.test.ts @@ -1,5 +1,6 @@ import { createOakClient } from "../../src"; import { getConfigFromEnv } from "../config"; +import { ApiError } from "../../src/utils/errorHandler"; describe("Auth (Integration)", () => { const client = createOakClient({ @@ -62,7 +63,7 @@ describe("Auth (Integration)", () => { const result = await badClient.grantToken(); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.error.message).toContain("Failed to grant token"); + expect(result.error).toBeInstanceOf(ApiError); } }); }); diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/api/__tests__/unit/authService.test.ts index 3a5a50f4..77a210f6 100644 --- a/packages/api/__tests__/unit/authService.test.ts +++ b/packages/api/__tests__/unit/authService.test.ts @@ -1,9 +1,9 @@ import { createOakClient } from "../../src"; import { httpClient } from "../../src/utils/httpClient"; -import { SDKError } from "../../src/utils/errorHandler"; +import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils"; import type { OakClientConfig } from "../../src/types"; -import { ok } from "../../src/types"; +import { err, ok } from "../../src/types"; const SANDBOX_URL = "https://api.usecrowdpay.xyz"; @@ -38,7 +38,7 @@ describe("Auth (Unit)", () => { expires_in: 3300000, token_type: "bearer", }; - mockedHttpClient.post.mockResolvedValue(mockResponse); + mockedHttpClient.post.mockResolvedValue(ok(mockResponse) as never); const result = await client.grantToken(); @@ -63,7 +63,7 @@ describe("Auth (Unit)", () => { expires_in: 3300000, token_type: "bearer", }; - mockedHttpClient.post.mockResolvedValue(mockResponse); + mockedHttpClient.post.mockResolvedValue(ok(mockResponse) as never); const token1 = await client.getAccessToken(); const token2 = await client.getAccessToken(); @@ -74,12 +74,14 @@ describe("Auth (Unit)", () => { }); it("should return error result when grantToken fails in getAccessToken", async () => { - mockedHttpClient.post.mockRejectedValue(new Error("Network Error")); + mockedHttpClient.post.mockResolvedValue( + err(new ApiError("HTTP error", 500, null)) as never + ); const result = await client.getAccessToken(); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.error).toBeInstanceOf(SDKError); + expect(result.error).toBeInstanceOf(ApiError); } }); @@ -95,8 +97,8 @@ describe("Auth (Unit)", () => { token_type: "bearer", }; mockedHttpClient.post - .mockResolvedValueOnce(mockResponse1) - .mockResolvedValueOnce(mockResponse2); + .mockResolvedValueOnce(ok(mockResponse1) as never) + .mockResolvedValueOnce(ok(mockResponse2) as never); const token1 = await client.getAccessToken(); await new Promise((r) => setTimeout(r, 10)); @@ -107,13 +109,15 @@ describe("Auth (Unit)", () => { expect(mockedHttpClient.post).toHaveBeenCalledTimes(2); }); - it("should return SDKError if grantToken fails", async () => { - mockedHttpClient.post.mockRejectedValue(new Error("Network Error")); + it("should return ApiError if grantToken fails", async () => { + mockedHttpClient.post.mockResolvedValue( + err(new ApiError("HTTP error", 500, null)) as never + ); const result = await client.grantToken(); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.error).toBeInstanceOf(SDKError); + expect(result.error).toBeInstanceOf(ApiError); } }); }); diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index 7b329deb..d7092406 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -1,13 +1,14 @@ import { createOakClient } from "../../src"; import { Crowdsplit } from "../../src/products/crowdsplit"; import { httpClient } from "../../src/utils/httpClient"; -import { SDKError } from "../../src/utils/errorHandler"; +import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; import { OakClientConfig, CreateCustomerRequest, CustomerListQueryParams, ok, + err, } from "../../src/types"; const SANDBOX_URL = "https://api.usecrowdpay.xyz"; @@ -48,7 +49,7 @@ describe("CustomerService - Unit", () => { it("should call POST /api/v1/customers with correct payload", async () => { const request: CreateCustomerRequest = { email: "test@example.com" }; const mockResponse = { data: { email: "test@example.com" } }; - (httpClient.post as jest.Mock).mockResolvedValue(mockResponse); + (httpClient.post as jest.Mock).mockResolvedValue(ok(mockResponse)); const result = await customers.create(request); @@ -64,15 +65,14 @@ describe("CustomerService - Unit", () => { expect(result).toEqual(ok(mockResponse)); }); - it("should return SDKError on failure", async () => { - (httpClient.post as jest.Mock).mockRejectedValue( - new Error("Network error") - ); + it("should return ApiError on failure", async () => { + const apiError = new ApiError("HTTP error", 500, { msg: "fail" }); + (httpClient.post as jest.Mock).mockResolvedValue(err(apiError)); const result = await customers.create({ email: "fail@example.com" }); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.error).toBeInstanceOf(SDKError); + expect(result.error).toBeInstanceOf(ApiError); } }); }); @@ -80,7 +80,7 @@ describe("CustomerService - Unit", () => { describe("get", () => { it("should call GET /api/v1/customers/:id", async () => { const mockResponse = { data: { email: "test@example.com" } }; - (httpClient.get as jest.Mock).mockResolvedValue(mockResponse); + (httpClient.get as jest.Mock).mockResolvedValue(ok(mockResponse)); const result = await customers.get("123"); @@ -99,7 +99,7 @@ describe("CustomerService - Unit", () => { it("should call GET /api/v1/customers with query params", async () => { const params: CustomerListQueryParams = { limit: 10, offset: 5 }; const mockResponse = { data: { count: 1, customer_list: [] } }; - (httpClient.get as jest.Mock).mockResolvedValue(mockResponse); + (httpClient.get as jest.Mock).mockResolvedValue(ok(mockResponse)); const result = await customers.list(params); @@ -118,7 +118,7 @@ describe("CustomerService - Unit", () => { it("should call PUT /api/v1/customers/:id", async () => { const updateData = { email: "updated@example.com" }; const mockResponse = { data: { email: "updated@example.com" } }; - (httpClient.put as jest.Mock).mockResolvedValue(mockResponse); + (httpClient.put as jest.Mock).mockResolvedValue(ok(mockResponse)); const result = await customers.update("123", updateData); diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index e7b0d11c..0058fe2d 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -12,7 +12,7 @@ import { createWebhookService, } from "../../src/services"; import { httpClient } from "../../src/utils/httpClient"; -import { SDKError } from "../../src/utils/errorHandler"; +import { ApiError, SDKError } from "../../src/utils/errorHandler"; import type { OakClient } from "../../src/types"; import { err, ok } from "../../src/types"; @@ -72,7 +72,7 @@ const expectSuccess = async (options: { expectedArgs: unknown[]; }) => { const response = { ok: true }; - mockedHttpClient[options.httpMethod].mockResolvedValue(response as never); + mockedHttpClient[options.httpMethod].mockResolvedValue(ok(response) as never); const result = await options.call(); @@ -89,16 +89,21 @@ const expectFailure = async (options: { errorMessage: string; error?: unknown; }) => { - mockedHttpClient[options.httpMethod].mockRejectedValue( - options.error ?? new Error("fail") + const apiError = new ApiError( + options.errorMessage, + 400, + { msg: options.errorMessage }, + undefined, + options.error ); + mockedHttpClient[options.httpMethod].mockResolvedValue(err(apiError) as never); const result = await options.call(); - expect(result).toEqual(err(expect.any(SDKError))); + expect(result).toEqual(err(expect.any(ApiError))); if (result && typeof result === "object" && "ok" in result) { - const typedResult = result as { ok: boolean; error?: SDKError }; + const typedResult = result as { ok: boolean; error?: ApiError }; if (!typedResult.ok && typedResult.error) { - expect(typedResult.error).toBeInstanceOf(SDKError); + expect(typedResult.error).toBeInstanceOf(ApiError); expect(typedResult.error.message).toContain(options.errorMessage); } } @@ -393,24 +398,26 @@ describe("Crowdsplit services (Unit)", () => { ], }); - mockedHttpClient.post.mockRejectedValueOnce({ body: { msg: "Bad" } }); + mockedHttpClient.post.mockResolvedValueOnce( + err(new ApiError("Bad", 400, { msg: "Bad" })) as never + ); const badResult = await service.submitRegistration("cust-1", registration); - expect(badResult).toEqual(err(expect.any(SDKError))); + expect(badResult).toEqual(err(expect.any(ApiError))); if ("ok" in badResult && !badResult.ok) { - const error = (badResult as { error: SDKError }).error; - expect(error.message).toContain( - "Failed to submit provider registration for customer cust-1: Bad" - ); + if (badResult.error instanceof ApiError) { + expect(badResult.error.message).toContain("Bad"); + } } - mockedHttpClient.post.mockRejectedValueOnce("boom"); + mockedHttpClient.post.mockResolvedValueOnce( + err(new ApiError("HTTP error", 500, null)) as never + ); const boomResult = await service.submitRegistration("cust-1", registration); - expect(boomResult).toEqual(err(expect.any(SDKError))); + expect(boomResult).toEqual(err(expect.any(ApiError))); if ("ok" in boomResult && !boomResult.ok) { - const error = (boomResult as { error: SDKError }).error; - expect(error.message).toContain( - "Failed to submit provider registration for customer cust-1: Unknown error" - ); + if (boomResult.error instanceof ApiError) { + expect(boomResult.error.message).toContain("HTTP error"); + } } const tokenErrorClient = makeClientWithTokenError(); @@ -676,24 +683,32 @@ describe("Crowdsplit services (Unit)", () => { ], }); - mockedHttpClient.post.mockRejectedValueOnce({ - body: { msg: "This URL is Already Registered!" }, - }); + mockedHttpClient.post.mockResolvedValueOnce( + err( + new ApiError("This URL is Already Registered!", 409, { + msg: "This URL is Already Registered!", + }) + ) as never + ); const duplicateResult = await service.register(webhook); - expect(duplicateResult).toEqual(err(expect.any(SDKError))); + expect(duplicateResult).toEqual(err(expect.any(ApiError))); if ("ok" in duplicateResult && !duplicateResult.ok) { - const error = (duplicateResult as { error: SDKError }).error; - expect(error.message).toContain( - "Webhook URL is already registered." - ); + if (duplicateResult.error instanceof ApiError) { + expect(duplicateResult.error.message).toContain( + "This URL is Already Registered!" + ); + } } - mockedHttpClient.post.mockRejectedValueOnce(new Error("fail")); + mockedHttpClient.post.mockResolvedValueOnce( + err(new ApiError("HTTP error", 500, null)) as never + ); const failResult = await service.register(webhook); - expect(failResult).toEqual(err(expect.any(SDKError))); + expect(failResult).toEqual(err(expect.any(ApiError))); if ("ok" in failResult && !failResult.ok) { - const error = (failResult as { error: SDKError }).error; - expect(error.message).toContain("Failed to create webhook"); + if (failResult.error instanceof ApiError) { + expect(failResult.error.message).toContain("HTTP error"); + } } await expectSuccess({ diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index 4c4d7bf6..ea950906 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -1,7 +1,12 @@ import { buildQueryString, getErrorBodyMessage } from "../../src/services/helpers"; -import { SDKError } from "../../src/utils/errorHandler"; +import { + ApiError, + NetworkError, + ParseError, + SDKError, +} from "../../src/utils/errorHandler"; import { httpClient } from "../../src/utils/httpClient"; -import { withRetry } from "../../src/utils/retryHandler"; +import * as retryHandler from "../../src/utils/retryHandler"; import { DEFAULT_RETRY_OPTIONS } from "../../src/utils/defaultRetryConfig"; import "../../src/utils"; @@ -82,7 +87,7 @@ describe("httpClient", () => { { retryOptions } ); - expect(result).toEqual({ ok: true }); + expect(result).toEqual({ ok: true, value: { ok: true } }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/post", { method: "POST", headers: expectedHeaders(), @@ -96,12 +101,13 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.post( + const result = await httpClient.post( "https://api.test/post", { data: 1 }, { retryOptions, headers: { "X-Test": "yes" } } ); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/post", { method: "POST", headers: expectedHeaders({ "X-Test": "yes" }), @@ -116,12 +122,33 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ msg: "bad" }), }); - await expect( - httpClient.post("https://api.test/post", { data: 1 }, { retryOptions }) - ).rejects.toMatchObject({ + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(400); + expect((result.error as ApiError).body).toEqual({ msg: "bad" }); + } + }); + + it("post includes response headers in ApiError", async () => { + fetchMock.mockResolvedValue({ + ok: false, status: 400, - body: { msg: "bad" }, + headers: new Headers({ "retry-after": "1" }), + json: jest.fn().mockResolvedValue({ msg: "bad" }), + }); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).headers).toEqual({ "retry-after": "1" }); + } }); it("get returns response body", async () => { @@ -134,7 +161,7 @@ describe("httpClient", () => { retryOptions, }); - expect(result).toEqual({ ok: true }); + expect(result).toEqual({ ok: true, value: { ok: true } }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { method: "GET", headers: expectedHeaders(), @@ -147,11 +174,12 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.get("https://api.test/get", { + const result = await httpClient.get("https://api.test/get", { retryOptions, headers: { "X-Test": "yes" }, }); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/get", { method: "GET", headers: expectedHeaders({ "X-Test": "yes" }), @@ -165,12 +193,15 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ msg: "missing" }), }); - await expect( - httpClient.get("https://api.test/get", { retryOptions }) - ).rejects.toMatchObject({ - status: 404, - body: { msg: "missing" }, + const result = await httpClient.get("https://api.test/get", { + retryOptions, }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(404); + expect((result.error as ApiError).body).toEqual({ msg: "missing" }); + } }); it("get uses fallback error message when missing", async () => { @@ -180,9 +211,14 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue(null), }); - await expect( - httpClient.get("https://api.test/get", { retryOptions }) - ).rejects.toThrow("HTTP error"); + const result = await httpClient.get("https://api.test/get", { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect(result.error.message).toBe("HTTP error"); + } }); it("put returns response body", async () => { @@ -197,7 +233,7 @@ describe("httpClient", () => { { retryOptions } ); - expect(result).toEqual({ ok: true }); + expect(result).toEqual({ ok: true, value: { ok: true } }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/put", { method: "PUT", headers: expectedHeaders(), @@ -211,12 +247,13 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.put( + const result = await httpClient.put( "https://api.test/put", { data: 2 }, { retryOptions, headers: { "X-Test": "yes" } } ); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/put", { method: "PUT", headers: expectedHeaders({ "X-Test": "yes" }), @@ -231,12 +268,15 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ msg: "boom" }), }); - await expect( - httpClient.put("https://api.test/put", { data: 2 }, { retryOptions }) - ).rejects.toMatchObject({ - status: 500, - body: { msg: "boom" }, + const result = await httpClient.put("https://api.test/put", { data: 2 }, { + retryOptions, }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(500); + expect((result.error as ApiError).body).toEqual({ msg: "boom" }); + } }); it("put uses fallback error message when missing", async () => { @@ -246,9 +286,14 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue(null), }); - await expect( - httpClient.put("https://api.test/put", { data: 2 }, { retryOptions }) - ).rejects.toThrow("HTTP error"); + const result = await httpClient.put("https://api.test/put", { data: 2 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect(result.error.message).toBe("HTTP error"); + } }); it("patch returns response body and omits body when undefined", async () => { @@ -263,7 +308,7 @@ describe("httpClient", () => { { retryOptions } ); - expect(result).toEqual({ ok: true }); + expect(result).toEqual({ ok: true, value: { ok: true } }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", headers: expectedHeaders(), @@ -277,8 +322,9 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.patch("https://api.test/patch", { data: 9 }, { retryOptions }); + const result = await httpClient.patch("https://api.test/patch", { data: 9 }, { retryOptions }); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", headers: expectedHeaders(), @@ -292,12 +338,13 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.patch( + const result = await httpClient.patch( "https://api.test/patch", { data: 9 }, { retryOptions, headers: { "X-Test": "yes" } } ); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/patch", { method: "PATCH", headers: expectedHeaders({ "X-Test": "yes" }), @@ -312,12 +359,15 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ msg: "invalid" }), }); - await expect( - httpClient.patch("https://api.test/patch", { data: 3 }, { retryOptions }) - ).rejects.toMatchObject({ - status: 422, - body: { msg: "invalid" }, + const result = await httpClient.patch("https://api.test/patch", { data: 3 }, { + retryOptions, }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(422); + expect((result.error as ApiError).body).toEqual({ msg: "invalid" }); + } }); it("patch uses fallback error message when missing", async () => { @@ -327,9 +377,14 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue(null), }); - await expect( - httpClient.patch("https://api.test/patch", { data: 3 }, { retryOptions }) - ).rejects.toThrow("HTTP error"); + const result = await httpClient.patch("https://api.test/patch", { data: 3 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect(result.error.message).toBe("HTTP error"); + } }); it("delete returns response body", async () => { @@ -342,7 +397,7 @@ describe("httpClient", () => { retryOptions, }); - expect(result).toEqual({ ok: true }); + expect(result).toEqual({ ok: true, value: { ok: true } }); expect(fetchMock).toHaveBeenCalledWith("https://api.test/delete", { method: "DELETE", headers: expectedHeaders(), @@ -355,11 +410,12 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ ok: true }), }); - await httpClient.delete("https://api.test/delete", { + const result = await httpClient.delete("https://api.test/delete", { retryOptions, headers: { "X-Test": "yes" }, }); + expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledWith("https://api.test/delete", { method: "DELETE", headers: expectedHeaders({ "X-Test": "yes" }), @@ -436,12 +492,15 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue({ msg: "unauthorized" }), }); - await expect( - httpClient.delete("https://api.test/delete", { retryOptions }) - ).rejects.toMatchObject({ - status: 401, - body: { msg: "unauthorized" }, + const result = await httpClient.delete("https://api.test/delete", { + retryOptions, }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(401); + expect((result.error as ApiError).body).toEqual({ msg: "unauthorized" }); + } }); it("delete uses fallback error message when missing", async () => { @@ -451,9 +510,14 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue(null), }); - await expect( - httpClient.delete("https://api.test/delete", { retryOptions }) - ).rejects.toThrow("HTTP error"); + const result = await httpClient.delete("https://api.test/delete", { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect(result.error.message).toBe("HTTP error"); + } }); it("post uses fallback error message when missing", async () => { @@ -463,9 +527,71 @@ describe("httpClient", () => { json: jest.fn().mockResolvedValue(null), }); - await expect( - httpClient.post("https://api.test/post", { data: 1 }, { retryOptions }) - ).rejects.toThrow("HTTP error"); + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect(result.error.message).toBe("HTTP error"); + } + }); + + it("post returns ParseError when response body parsing fails", async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: jest.fn().mockRejectedValue(new Error("bad json")), + }); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ParseError); + expect(result.error.message).toBe("Failed to parse response body"); + } + }); + + it("post returns NetworkError when fetch fails", async () => { + fetchMock.mockRejectedValue(new Error("socket hang up")); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions: { maxNumberOfRetries: 0, delay: 0 }, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(NetworkError); + expect(result.error.message).toBe("Network error"); + } + }); + + it("post wraps unexpected errors as OakError", async () => { + const spy = jest + .spyOn(retryHandler, "withRetry") + .mockRejectedValueOnce("boom"); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toBe("Unknown error"); + } + + spy.mockRestore(); + }); + + it("post wraps Error values as OakError", async () => { + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions: { maxNumberOfRetries: -1, delay: 0 }, + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toBe("Retry failed after maximum attempts"); + } }); }); @@ -477,7 +603,10 @@ describe("withRetry", () => { it("returns on first try", async () => { const fn = jest.fn().mockResolvedValue("ok"); - const result = await withRetry(fn, { maxNumberOfRetries: 1, delay: 1 }); + const result = await retryHandler.withRetry(fn, { + maxNumberOfRetries: 1, + delay: 1, + }); expect(result).toBe("ok"); expect(fn).toHaveBeenCalledTimes(1); }); @@ -490,7 +619,7 @@ describe("withRetry", () => { .mockResolvedValueOnce("ok"); const onRetry = jest.fn(); - const promise = withRetry(fn, { + const promise = retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 10, retryOnStatus: [500], @@ -510,7 +639,7 @@ describe("withRetry", () => { .mockRejectedValueOnce({ status: 500 }) .mockResolvedValueOnce("ok"); - const promise = withRetry(fn, { + const promise = retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 100, retryOnStatus: [500], @@ -527,7 +656,7 @@ describe("withRetry", () => { .mockRejectedValueOnce({ isNetworkError: true }) .mockResolvedValueOnce("ok"); - const promise = withRetry(fn, { + const promise = retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 10, retryOnStatus: [], @@ -540,7 +669,7 @@ describe("withRetry", () => { it("does not retry with default retryOnError when no network flag", async () => { const fn = jest.fn().mockRejectedValueOnce({ status: 500 }); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 1, retryOnStatus: [], @@ -552,7 +681,7 @@ describe("withRetry", () => { it("does not retry with default retryOnError when error is undefined", async () => { const fn = jest.fn().mockRejectedValueOnce(undefined); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 0, delay: 1, retryOnStatus: [], @@ -567,7 +696,7 @@ describe("withRetry", () => { .mockRejectedValueOnce({ isNetworkError: true }) .mockResolvedValueOnce("ok"); - const promise = withRetry(fn, { + const promise = retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 10, retryOnStatus: [], @@ -581,7 +710,7 @@ describe("withRetry", () => { it("does not retry when retryOnError returns false", async () => { const fn = jest.fn().mockRejectedValueOnce({ status: 500 }); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 1, retryOnStatus: [], @@ -598,7 +727,7 @@ describe("withRetry", () => { .mockRejectedValueOnce(undefined) .mockResolvedValueOnce("ok"); - const promise = withRetry(fn, { + const promise = retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 10, retryOnStatus: [], @@ -612,7 +741,7 @@ describe("withRetry", () => { it("throws when max retries reached", async () => { const fn = jest.fn().mockRejectedValue({ status: 400 }); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 0, delay: 1, retryOnStatus: [400], @@ -623,7 +752,7 @@ describe("withRetry", () => { it("handles undefined error values", async () => { const fn = jest.fn().mockRejectedValueOnce(undefined); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 0, delay: 1, retryOnStatus: [], @@ -638,7 +767,7 @@ describe("withRetry", () => { const fn = jest.fn().mockResolvedValue("ok"); await expect( - withRetry(fn, { + retryHandler.withRetry(fn, { maxNumberOfRetries: 1, delay: 1, signal: controller.signal, @@ -649,7 +778,7 @@ describe("withRetry", () => { it("throws final error when retries are negative", async () => { await expect( - withRetry(async () => "ok", { + retryHandler.withRetry(async () => "ok", { maxNumberOfRetries: -1, delay: 1, }) diff --git a/packages/api/src/authManager.ts b/packages/api/src/authManager.ts index 8dd7f892..17b950dc 100644 --- a/packages/api/src/authManager.ts +++ b/packages/api/src/authManager.ts @@ -5,7 +5,7 @@ import type { TokenResponse, } from "./types"; import { httpClient } from "./utils/httpClient"; -import { SDKError } from "./utils/errorHandler"; +import { OakError } from "./utils/errorHandler"; import { RetryOptions } from "./utils/defaultRetryConfig"; import { err, ok } from "./types"; @@ -20,30 +20,29 @@ export class AuthManager { this.retryOptions = retryOptions; } - async grantToken(): Promise> { - try { - const payload: TokenRequest = { - client_id: this.config.clientId, - client_secret: this.config.clientSecret, - grant_type: "client_credentials", - }; + async grantToken(): Promise> { + const payload: TokenRequest = { + client_id: this.config.clientId, + client_secret: this.config.clientSecret, + grant_type: "client_credentials", + }; - const response = await httpClient.post( - `${this.config.baseUrl}/api/v1/merchant/token/grant`, - payload, - { - retryOptions: this.retryOptions, - } - ); - this.accessToken = response.access_token; - this.tokenExpiration = Date.now() + response.expires_in; - return ok(response); - } catch (error) { - return err(new SDKError("Failed to grant token", error)); + const response = await httpClient.post( + `${this.config.baseUrl}/api/v1/merchant/token/grant`, + payload, + { + retryOptions: this.retryOptions, + } + ); + if (!response.ok) { + return err(response.error); } + this.accessToken = response.value.access_token; + this.tokenExpiration = Date.now() + response.value.expires_in; + return ok(response.value); } - async getAccessToken(): Promise> { + async getAccessToken(): Promise> { const currentTime = Date.now(); if ( !this.accessToken || diff --git a/packages/api/src/services/buyService.ts b/packages/api/src/services/buyService.ts index 9373d442..baa7d074 100644 --- a/packages/api/src/services/buyService.ts +++ b/packages/api/src/services/buyService.ts @@ -5,8 +5,7 @@ import type { Result, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface BuyService { create(buyRequest: CreateBuyRequest): Promise>; @@ -15,23 +14,18 @@ export interface BuyService { export const createBuyService = (client: OakClient): BuyService => ({ async create( buyRequest: CreateBuyRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/buy`, - buyRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create buy", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.post( + `${client.config.baseUrl}/api/v1/buy`, + buyRequest, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 16cfc7c6..eed6db6b 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -10,9 +10,8 @@ import type { UpdateCustomerResponse, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface CustomerService { create( @@ -31,103 +30,79 @@ export interface CustomerService { export const createCustomerService = (client: OakClient): CustomerService => ({ async create( customer: CreateCustomerRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/customers`, - customer, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/customers`, + customer, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create customer", error)); - } + retryOptions: client.retryOptions, + }, + ); }, - async get(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + async get(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${id}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.get( + `${client.config.baseUrl}/api/v1/customers/${id}`, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to retrieve customer", error)); - } + retryOptions: client.retryOptions, + }, + ); }, async list( params?: CustomerListQueryParams, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const queryString = buildQueryString(params); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } + const queryString = buildQueryString(params); - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/customers${queryString}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.get( + `${client.config.baseUrl}/api/v1/customers${queryString}`, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to list customers", error)); - } + retryOptions: client.retryOptions, + }, + ); }, async update( id: string, customer: UpdateCustomerRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.put( - `${client.config.baseUrl}/api/v1/customers/${id}`, - customer, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.put( + `${client.config.baseUrl}/api/v1/customers/${id}`, + customer, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to update customer", error)); - } + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/paymentMethodService.ts b/packages/api/src/services/paymentMethodService.ts index d7441c69..f3c51f99 100644 --- a/packages/api/src/services/paymentMethodService.ts +++ b/packages/api/src/services/paymentMethodService.ts @@ -9,9 +9,8 @@ import type { Result, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface PaymentMethodService { add( @@ -38,124 +37,80 @@ export const createPaymentMethodService = ( async add( customerId: string, paymentMethod: AddCustomerPaymentMethodRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods`, - paymentMethod, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods`, + paymentMethod, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to add payment method for customer ${customerId}`, - error, - ), - ); - } + retryOptions: client.retryOptions, + }, + ); }, async get( customerId: string, paymentId: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentId}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.get( + `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentId}`, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to get payment method for customer ${customerId}`, - error, - ), - ); - } + retryOptions: client.retryOptions, + }, + ); }, async list( customerId: string, query?: GetAllCustomerPaymentMethodsQuery, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const queryString = buildQueryString(query); - - const response = - await httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to get payment method for customer ${customerId}`, - error, - ), - ); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + const queryString = buildQueryString(query); + + return httpClient.get( + `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods${queryString}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async delete( customerId: string, paymentMethodId: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.delete( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentMethodId}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.delete( + `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentMethodId}`, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to delete payment method ${paymentMethodId} for customer ${customerId}`, - error, - ), - ); - } + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/paymentService.ts b/packages/api/src/services/paymentService.ts index 417d7553..25137353 100644 --- a/packages/api/src/services/paymentService.ts +++ b/packages/api/src/services/paymentService.ts @@ -7,8 +7,7 @@ import type { Result, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface PaymentService { create(payment: CreatePaymentRequest): Promise>; @@ -19,83 +18,61 @@ export interface PaymentService { export const createPaymentService = (client: OakClient): PaymentService => ({ async create( payment: CreatePaymentRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/payments/`, - payment, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/payments/`, + payment, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create payment", error)); - } + retryOptions: client.retryOptions, + }, + ); }, async confirm( paymentId: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/payments/${paymentId}/confirm`, - {}, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/payments/${paymentId}/confirm`, + {}, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError(`Failed to confirm payment with id ${paymentId}`, error), - ); - } + retryOptions: client.retryOptions, + }, + ); }, async cancel( paymentId: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/payments/${paymentId}/cancel`, - {}, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/payments/${paymentId}/cancel`, + {}, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError(`Failed to cancel payment with id ${paymentId}`, error), - ); - } + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/planService.ts b/packages/api/src/services/planService.ts index 1ba61149..83119246 100644 --- a/packages/api/src/services/planService.ts +++ b/packages/api/src/services/planService.ts @@ -12,9 +12,8 @@ import type { UpdatePlanResponse, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface PlanService { create( @@ -33,126 +32,96 @@ export interface PlanService { export const createPlanService = (client: OakClient): PlanService => ({ async create( createPlanRequest: CreatePlanRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/subscription/plans`, - createPlanRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create plan", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.post( + `${client.config.baseUrl}/api/v1/subscription/plans`, + createPlanRequest, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, - async publish(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.patch( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}/publish`, - undefined, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to publish plan", error)); + async publish(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.patch( + `${client.config.baseUrl}/api/v1/subscription/plans/${id}/publish`, + undefined, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, - async details(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get plan details", error)); + async details(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.get( + `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async list( params?: PlansListQueryParams, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const queryString = buildQueryString(params); - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/subscription/plans${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get available plans", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + const queryString = buildQueryString(params); + return httpClient.get( + `${client.config.baseUrl}/api/v1/subscription/plans${queryString}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async update( id: string, updatePlanRequest: UpdatePlanRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.patch( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - updatePlanRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to update plan", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.patch( + `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, + updatePlanRequest, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, - async delete(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.delete( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to delete plan", error)); + async delete(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.delete( + `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/providerService.ts b/packages/api/src/services/providerService.ts index ce6efb4c..76715345 100644 --- a/packages/api/src/services/providerService.ts +++ b/packages/api/src/services/providerService.ts @@ -8,9 +8,7 @@ import type { SubmitProviderRegistrationResponse, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { getErrorBodyMessage } from "./helpers"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface ProviderService { getSchema( @@ -28,100 +26,64 @@ export interface ProviderService { export const createProviderService = (client: OakClient): ProviderService => ({ async getSchema( request: GetProviderSchemaRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.get( - `${ - client.config.baseUrl - }/api/v1/provider-registration/schema?provider=${encodeURIComponent( - request.provider, - )}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.get( + `${ + client.config.baseUrl + }/api/v1/provider-registration/schema?provider=${encodeURIComponent( + request.provider, + )}`, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to retrieve provider schema for ${request.provider}`, - error, - ), - ); - } + retryOptions: client.retryOptions, + }, + ); }, async getRegistrationStatus( customerId: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - const response = - await httpClient.get( - `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/status`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, - }, - ); - - return ok(response); - } catch (error) { - return err( - new SDKError( - `Failed to retrieve provider registration status for customer ${customerId}`, - error, - ), - ); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + + return httpClient.get( + `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/status`, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); }, async submitRegistration( customerId: string, registration: SubmitProviderRegistrationRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - const response = - await httpClient.post( - `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/submit`, - registration, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, - }, - ); - - return ok(response); - } catch (error) { - const msg = getErrorBodyMessage(error) || "Unknown error"; - return err( - new SDKError( - `Failed to submit provider registration for customer ${customerId}: ${msg}`, - error, - ), - ); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + + return httpClient.post( + `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/submit`, + registration, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/sellService.ts b/packages/api/src/services/sellService.ts index b0b6abf6..7964130c 100644 --- a/packages/api/src/services/sellService.ts +++ b/packages/api/src/services/sellService.ts @@ -5,8 +5,7 @@ import type { Result, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface SellService { create(sellRequest: CreateSellRequest): Promise>; @@ -15,23 +14,18 @@ export interface SellService { export const createSellService = (client: OakClient): SellService => ({ async create( sellRequest: CreateSellRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/sell`, - sellRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create sell", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.post( + `${client.config.baseUrl}/api/v1/sell`, + sellRequest, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/transactionService.ts b/packages/api/src/services/transactionService.ts index fcd3782f..3a4922a4 100644 --- a/packages/api/src/services/transactionService.ts +++ b/packages/api/src/services/transactionService.ts @@ -8,9 +8,8 @@ import type { SettlementResponse, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface TransactionService { list(query?: GetAllTransactionsQuery): Promise>; @@ -26,67 +25,52 @@ export const createTransactionService = ( ): TransactionService => ({ async list( query?: GetAllTransactionsQuery, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const queryString = buildQueryString(query); - - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/transactions${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get all transaction", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } - }, + const queryString = buildQueryString(query); - async get(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + return httpClient.get( + `${client.config.baseUrl}/api/v1/transactions${queryString}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); + }, - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/transactions/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get transaction", error)); + async get(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + + return httpClient.get( + `${client.config.baseUrl}/api/v1/transactions/${id}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async settle( id: string, settlementRequest: SettlementRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.patch( - `${client.config.baseUrl}/api/v1/transactions/${id}/settle`, - settlementRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to settle transaction", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.patch( + `${client.config.baseUrl}/api/v1/transactions/${id}/settle`, + settlementRequest, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/transferService.ts b/packages/api/src/services/transferService.ts index eaba1c20..be677e2b 100644 --- a/packages/api/src/services/transferService.ts +++ b/packages/api/src/services/transferService.ts @@ -5,8 +5,7 @@ import type { Result, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { err, ok } from "../types"; +import { err } from "../types"; export interface TransferService { create(transfer: CreateTransferRequest): Promise>; @@ -15,27 +14,21 @@ export interface TransferService { export const createTransferService = (client: OakClient): TransferService => ({ async create( transfer: CreateTransferRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/transfer`, - transfer, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, + return httpClient.post( + `${client.config.baseUrl}/api/v1/transfer`, + transfer, + { + headers: { + Authorization: `Bearer ${token.value}`, }, - ); - - return ok(response); - } catch (error) { - return err(new SDKError("Failed to create transfer", error)); - } + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/services/webhookService.ts b/packages/api/src/services/webhookService.ts index 1f0b5ccf..da5bce84 100644 --- a/packages/api/src/services/webhookService.ts +++ b/packages/api/src/services/webhookService.ts @@ -13,9 +13,8 @@ import type { UpdateWebhookResponse, } from "../types"; import { httpClient } from "../utils/httpClient"; -import { SDKError } from "../utils/errorHandler"; -import { buildQueryString, getErrorBodyMessage } from "./helpers"; -import { err, ok } from "../types"; +import { buildQueryString } from "./helpers"; +import { err } from "../types"; export interface WebhookService { register( @@ -39,176 +38,133 @@ export interface WebhookService { export const createWebhookService = (client: OakClient): WebhookService => ({ async register( webhook: RegisterWebhookRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.post( - `${client.config.baseUrl}/api/v1/merchant/webhooks`, - webhook, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - if (getErrorBodyMessage(error) === "This URL is Already Registered!") { - return err(new SDKError("Webhook URL is already registered.", error)); - } - return err(new SDKError("Failed to create webhook", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.post( + `${client.config.baseUrl}/api/v1/merchant/webhooks`, + webhook, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, - async list(): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get webhook list", error)); + async list(): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.get( + `${client.config.baseUrl}/api/v1/merchant/webhooks`, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); }, - async get(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - { - headers: { - Authorization: `Bearer ${token.value}`, - }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed to get webhook list", error)); + async get(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.get( + `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); }, async update( id: string, webhook: UpdateWebhookRequest, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.put( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - webhook, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed updating webhook ", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.put( + `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, + webhook, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, - async toggle(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - const response = await httpClient.patch( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}/toggle`, - undefined, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed updating webhook ", error)); + async toggle(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } - }, - async delete(id: string): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } + return httpClient.patch( + `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}/toggle`, + undefined, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); + }, - const response = await httpClient.delete( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed deleting webhook ", error)); + async delete(id: string): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + + return httpClient.delete( + `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async listNotifications(params?: { limit?: number; offset?: number; - }): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const queryString = buildQueryString(params); - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed getting webhook notificaiton list ", error)); + }): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + const queryString = buildQueryString(params); + return httpClient.get( + `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications${queryString}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, async getNotification( id: string, - ): Promise> { - try { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - const response = await httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, - ); - return ok(response); - } catch (error) { - return err(new SDKError("Failed getting webhook notificaiton ", error)); + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); } + return httpClient.get( + `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications/${id}`, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); }, }); diff --git a/packages/api/src/types/result.ts b/packages/api/src/types/result.ts index 622a5865..e5711881 100644 --- a/packages/api/src/types/result.ts +++ b/packages/api/src/types/result.ts @@ -1,6 +1,6 @@ -import { SDKError } from "../utils/errorHandler"; +import { OakError } from "../utils/errorHandler"; -export type Result = +export type Result = | { ok: true; value: T } | { ok: false; error: E }; diff --git a/packages/api/src/utils/errorHandler.ts b/packages/api/src/utils/errorHandler.ts index f067074b..d51ba68f 100644 --- a/packages/api/src/utils/errorHandler.ts +++ b/packages/api/src/utils/errorHandler.ts @@ -1,13 +1,56 @@ -export class SDKError extends Error { +export class OakError extends Error { public cause?: unknown; constructor(message: string, cause?: unknown) { super(message); - this.name = "SDKError"; + this.name = "OakError"; this.cause = cause; } } +export class SDKError extends OakError { + constructor(message: string, cause?: unknown) { + super(message, cause); + this.name = "SDKError"; + } +} + +export class ApiError extends OakError { + public readonly status: number; + public readonly body: unknown; + public readonly headers?: Record; + + constructor( + message: string, + status: number, + body: unknown, + headers?: Record, + cause?: unknown + ) { + super(message, cause); + this.name = "ApiError"; + this.status = status; + this.body = body; + this.headers = headers; + } +} + +export class NetworkError extends OakError { + public readonly isNetworkError = true; + + constructor(message: string, cause?: unknown) { + super(message, cause); + this.name = "NetworkError"; + } +} + +export class ParseError extends OakError { + constructor(message: string, cause?: unknown) { + super(message, cause); + this.name = "ParseError"; + } +} + export class EnvironmentViolationError extends SDKError { public readonly methodName: string; public readonly environment: string; diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index a8cec512..0b725663 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -1,5 +1,7 @@ import { RetryOptions } from "./defaultRetryConfig"; import { withRetry } from "./retryHandler"; +import { err, ok, Result } from "../types"; +import { ApiError, NetworkError, OakError, ParseError } from "./errorHandler"; export interface HttpClientConfig { headers?: Record; @@ -30,96 +32,109 @@ const parseResponseBody = async (response: Response) => { return body ?? {}; }; -const toError = (response: Response, responseBody: unknown) => { - const message = (responseBody as { msg?: string }).msg ?? "HTTP error"; - const error: any = new Error(message); - error.status = response.status; - error.body = responseBody; - return error; +const toHeadersRecord = (headers?: Headers): Record => { + const record: Record = {}; + if (!headers) { + return record; + } + headers.forEach((value, key) => { + record[key.toLowerCase()] = value; + }); + return record; }; -export const httpClient = { - async post(url: string, data: any, config: HttpClientConfig): Promise { - return withRetry(async () => { - const response = await fetch(url, { - method: "POST", - headers: mergeHeaders(config.headers), - body: JSON.stringify(data), - }); +const toApiError = (response: Response, responseBody: unknown) => { + const message = (responseBody as { msg?: string }).msg ?? "HTTP error"; + const headers = toHeadersRecord(response.headers); + return new ApiError(message, response.status, responseBody, headers); +}; - const responseBody = await parseResponseBody(response); +const toOakError = (error: unknown): OakError => { + if (error instanceof OakError) { + return error; + } + if (error instanceof Error) { + return new OakError(error.message, error); + } + return new OakError("Unknown error", error); +}; - if (!response.ok) { - throw toError(response, responseBody); +const request = async ( + url: string, + config: HttpClientConfig, + init: RequestInit +): Promise> => { + try { + const responseBody = await withRetry(async () => { + let response: Response; + try { + response = await fetch(url, { + ...init, + headers: mergeHeaders(config.headers), + signal: config.signal, + }); + } catch (error) { + throw new NetworkError("Network error", error); } - return responseBody as T; - }, config.retryOptions); - }, - async get(url: string, config: HttpClientConfig): Promise { - return withRetry(async () => { - const response = await fetch(url, { - method: "GET", - headers: mergeHeaders(config.headers), - }); - - const responseBody = await parseResponseBody(response); - - if (!response.ok) { - throw toError(response, responseBody); + let parsedBody: unknown; + try { + parsedBody = await parseResponseBody(response); + } catch (error) { + throw new ParseError("Failed to parse response body", error); } - return responseBody as T; - }, config.retryOptions); - }, - async delete(url: string, config: HttpClientConfig): Promise { - return withRetry(async () => { - const response = await fetch(url, { - method: "DELETE", - headers: mergeHeaders(config.headers), - }); - - const responseBody = await parseResponseBody(response); - if (!response.ok) { - throw toError(response, responseBody); + throw toApiError(response, parsedBody); } - return responseBody as T; + return parsedBody as T; }, config.retryOptions); - }, - async put(url: string, data: any, config: HttpClientConfig): Promise { - return withRetry(async () => { - const response = await fetch(url, { - method: "PUT", - headers: mergeHeaders(config.headers), - body: JSON.stringify(data), - }); - - const responseBody = await parseResponseBody(response); - if (!response.ok) { - throw toError(response, responseBody); - } + return ok(responseBody); + } catch (error) { + return err(toOakError(error)); + } +}; - return responseBody as T; - }, config.retryOptions); +export const httpClient = { + async post( + url: string, + data: any, + config: HttpClientConfig + ): Promise> { + return request(url, config, { + method: "POST", + body: JSON.stringify(data), + }); }, - async patch(url: string, data: any, config: HttpClientConfig): Promise { - return withRetry(async () => { - const response = await fetch(url, { - method: "PATCH", - headers: mergeHeaders(config.headers), - body: data ? JSON.stringify(data) : undefined, - }); - - const responseBody = await parseResponseBody(response); - - if (!response.ok) { - throw toError(response, responseBody); - } - - return responseBody as T; - }, config.retryOptions); + async get(url: string, config: HttpClientConfig): Promise> { + return request(url, config, { method: "GET" }); + }, + async delete( + url: string, + config: HttpClientConfig + ): Promise> { + return request(url, config, { method: "DELETE" }); + }, + async put( + url: string, + data: any, + config: HttpClientConfig + ): Promise> { + return request(url, config, { + method: "PUT", + body: JSON.stringify(data), + }); + }, + async patch( + url: string, + data: any, + config: HttpClientConfig + ): Promise> { + return request(url, config, { + method: "PATCH", + body: data ? JSON.stringify(data) : undefined, + }); }, }; From 2fa79dcc0462a091e67db73175c14332d397049d Mon Sep 17 00:00:00 2001 From: Fahad Muntasir Date: Fri, 6 Feb 2026 18:32:41 +0600 Subject: [PATCH 011/143] chore: add changeset for Result-based httpClient errors --- .changeset/yellow-snails-divide.md | 5 + package-lock.json | 1817 ++++++++++++++++++++++++++++ 2 files changed, 1822 insertions(+) create mode 100644 .changeset/yellow-snails-divide.md create mode 100644 package-lock.json diff --git a/.changeset/yellow-snails-divide.md b/.changeset/yellow-snails-divide.md new file mode 100644 index 00000000..9d1ba669 --- /dev/null +++ b/.changeset/yellow-snails-divide.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/api": major +--- + +Refactor httpClient to return Result and centralize error handling (breaking change). diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..037edb50 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1817 @@ +{ + "name": "crowdsplit-sdk-monorepo", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "crowdsplit-sdk-monorepo", + "version": "0.0.0", + "devDependencies": { + "@changesets/cli": "^2.29.8", + "@types/jest": "^30.0.0" + }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@changesets/apply-release-plan": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.14.tgz", + "integrity": "sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.2", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", + "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/assemble-release-plan/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0" + } + }, + "node_modules/@changesets/cli": { + "version": "2.29.8", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.8.tgz", + "integrity": "sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^7.0.14", + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.2", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/get-release-plan": "^4.0.14", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.6", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "ci-info": "^3.7.0", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "p-limit": "^2.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/cli/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@changesets/cli/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@changesets/cli/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@changesets/config": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.2.tgz", + "integrity": "sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/logger": "^0.1.1", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" + } + }, + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, + "license": "MIT", + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", + "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/get-dependents-graph/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.14.tgz", + "integrity": "sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/config": "^3.1.2", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.6", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" + } + }, + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.2.tgz", + "integrity": "sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/parse/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@changesets/parse/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.6.tgz", + "integrity": "sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.2", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-id": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", + "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-diff": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.0.1", + "chalk": "^4.1.2", + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-yaml-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", + "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} From 8d9d5a9308200d0bebadfafa8c27b8f9dc8cf48c Mon Sep 17 00:00:00 2001 From: Fahad Muntasir Date: Fri, 6 Feb 2026 18:54:06 +0600 Subject: [PATCH 012/143] fix(api): stop retries after abort signal in httpClient --- packages/api/__tests__/unit/utils.test.ts | 17 + packages/api/package-lock.json | 4872 +++++++++++++++++++++ packages/api/src/utils/errorHandler.ts | 7 + packages/api/src/utils/httpClient.ts | 10 +- 4 files changed, 4904 insertions(+), 2 deletions(-) create mode 100644 packages/api/package-lock.json diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index ea950906..d9b3bd40 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -1,5 +1,6 @@ import { buildQueryString, getErrorBodyMessage } from "../../src/services/helpers"; import { + AbortError, ApiError, NetworkError, ParseError, @@ -566,6 +567,22 @@ describe("httpClient", () => { } }); + it("post returns AbortError when request is aborted", async () => { + const abortError = new Error("aborted"); + abortError.name = "AbortError"; + fetchMock.mockRejectedValue(abortError); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions: { maxNumberOfRetries: 1, delay: 0 }, + signal: new AbortController().signal, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(AbortError); + expect(result.error.message).toBe("Request aborted"); + } + }); + it("post wraps unexpected errors as OakError", async () => { const spy = jest .spyOn(retryHandler, "withRetry") diff --git a/packages/api/package-lock.json b/packages/api/package-lock.json new file mode 100644 index 00000000..79c75417 --- /dev/null +++ b/packages/api/package-lock.json @@ -0,0 +1,4872 @@ +{ + "name": "@oaknetwork/api", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@oaknetwork/api", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "dotenv": "^17.2.1", + "nock": "^14.0.10" + }, + "devDependencies": { + "@types/jest": "^30.0.0", + "@types/node": "^20.14.11", + "jest": "^30.0.5", + "ts-jest": "^29.4.1", + "ts-node": "^10.9.2", + "typescript": "^5.5.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.8.tgz", + "integrity": "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", + "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", + "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nock": { + "version": "14.0.10", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.10.tgz", + "integrity": "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==", + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.39.5", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/api/src/utils/errorHandler.ts b/packages/api/src/utils/errorHandler.ts index d51ba68f..5e444a36 100644 --- a/packages/api/src/utils/errorHandler.ts +++ b/packages/api/src/utils/errorHandler.ts @@ -44,6 +44,13 @@ export class NetworkError extends OakError { } } +export class AbortError extends OakError { + constructor(message: string, cause?: unknown) { + super(message, cause); + this.name = "AbortError"; + } +} + export class ParseError extends OakError { constructor(message: string, cause?: unknown) { super(message, cause); diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index 0b725663..2bff3ef8 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -1,7 +1,7 @@ import { RetryOptions } from "./defaultRetryConfig"; import { withRetry } from "./retryHandler"; import { err, ok, Result } from "../types"; -import { ApiError, NetworkError, OakError, ParseError } from "./errorHandler"; +import { AbortError, ApiError, NetworkError, OakError, ParseError } from "./errorHandler"; export interface HttpClientConfig { headers?: Record; @@ -74,6 +74,12 @@ const request = async ( signal: config.signal, }); } catch (error) { + if ( + config.signal?.aborted || + (error instanceof Error && error.name === "AbortError") + ) { + throw new AbortError("Request aborted", error); + } throw new NetworkError("Network error", error); } @@ -89,7 +95,7 @@ const request = async ( } return parsedBody as T; - }, config.retryOptions); + }, { ...config.retryOptions, signal: config.signal }); return ok(responseBody); } catch (error) { From 3a857c7d7e08d52d19e985735af8d5dab768decc Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Sat, 7 Feb 2026 02:57:30 +0600 Subject: [PATCH 013/143] refactor: refactored req res types --- packages/api/src/services/buyService.ts | 8 +- packages/api/src/services/customerService.ts | 44 +- .../api/src/services/paymentMethodService.ts | 58 +- packages/api/src/services/paymentService.ts | 26 +- packages/api/src/services/planService.ts | 56 +- packages/api/src/services/providerService.ts | 52 +- packages/api/src/services/sellService.ts | 12 +- .../api/src/services/transactionService.ts | 33 +- packages/api/src/services/transferService.ts | 14 +- packages/api/src/services/webhookService.ts | 69 +-- packages/api/src/types/buy.ts | 114 ++-- packages/api/src/types/customer.ts | 142 +++-- packages/api/src/types/index.ts | 1 + packages/api/src/types/payment.ts | 507 ++++++++---------- packages/api/src/types/paymentMethod.ts | 150 ++++++ packages/api/src/types/plan.ts | 130 +++-- packages/api/src/types/provider.ts | 158 +++--- packages/api/src/types/sell.ts | 85 +-- packages/api/src/types/transactions.ts | 119 ++-- packages/api/src/types/transfer.ts | 115 ++-- packages/api/src/types/webhook.ts | 103 ++-- 21 files changed, 1013 insertions(+), 983 deletions(-) create mode 100644 packages/api/src/types/paymentMethod.ts diff --git a/packages/api/src/services/buyService.ts b/packages/api/src/services/buyService.ts index 34ab93c8..ab32aa94 100644 --- a/packages/api/src/services/buyService.ts +++ b/packages/api/src/services/buyService.ts @@ -1,16 +1,16 @@ -import type { CreateBuyRequest, CreateBuyResponse, OakClient } from "../types"; +import type { Buy, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; export interface BuyService { - create(buyRequest: CreateBuyRequest): Promise; + create(buyRequest: Buy.Request): Promise; } export const createBuyService = (client: OakClient): BuyService => ({ - async create(buyRequest: CreateBuyRequest): Promise { + async create(buyRequest: Buy.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/buy`, buyRequest, { diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 7e19c84a..5a5df770 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -1,35 +1,21 @@ -import type { - CreateCustomerRequest, - CreateCustomerResponse, - CustomerListQueryParams, - GetAllCustomerResponse, - GetCustomerResponse, - OakClient, - UpdateCustomerRequest, - UpdateCustomerResponse, -} from "../types"; +import type { Customer, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; export interface CustomerService { - create(customer: CreateCustomerRequest): Promise; - get(id: string): Promise; - list(params?: CustomerListQueryParams): Promise; - update( - id: string, - customer: UpdateCustomerRequest, - ): Promise; + create(customer: Customer.Request): Promise; + get(id: string): Promise; + list(params?: Customer.ListQueryParams): Promise; + update(id: string, customer: Customer.Request): Promise; } export const createCustomerService = (client: OakClient): CustomerService => ({ - async create( - customer: CreateCustomerRequest, - ): Promise { + async create(customer: Customer.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/customers`, customer, { @@ -46,11 +32,11 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ } }, - async get(id: string): Promise { + async get(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/customers/${id}`, { headers: { @@ -67,13 +53,13 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ }, async list( - params?: CustomerListQueryParams, - ): Promise { + params?: Customer.ListQueryParams, + ): Promise { try { const token = await client.getAccessToken(); const queryString = buildQueryString(params); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/customers${queryString}`, { headers: { @@ -91,12 +77,12 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ async update( id: string, - customer: UpdateCustomerRequest, - ): Promise { + customer: Customer.Request, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.put( + const response = await httpClient.put( `${client.config.baseUrl}/api/v1/customers/${id}`, customer, { diff --git a/packages/api/src/services/paymentMethodService.ts b/packages/api/src/services/paymentMethodService.ts index 2c97e7bf..8b48b69d 100644 --- a/packages/api/src/services/paymentMethodService.ts +++ b/packages/api/src/services/paymentMethodService.ts @@ -1,12 +1,4 @@ -import type { - AddCustomerPaymentMethodRequest, - AddCustomerPaymentMethodResponse, - DeletePaymentMethodResponse, - GetAllCustomerPaymentMethodsQuery, - GetAllCustomerPaymentMethodsResponse, - GetCustomerPaymentMethodResponse, - OakClient, -} from "../types"; +import type { PaymentMethod, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; @@ -14,20 +6,17 @@ import { buildQueryString } from "./helpers"; export interface PaymentMethodService { add( customerId: string, - paymentMethod: AddCustomerPaymentMethodRequest, - ): Promise; - get( - customerId: string, - paymentId: string, - ): Promise; + paymentMethod: PaymentMethod.Request, + ): Promise; + get(customerId: string, paymentId: string): Promise; list( customerId: string, - query?: GetAllCustomerPaymentMethodsQuery, - ): Promise; + query?: PaymentMethod.ListQuery, + ): Promise; delete( customerId: string, paymentMethodId: string, - ): Promise; + ): Promise; } export const createPaymentMethodService = ( @@ -35,12 +24,12 @@ export const createPaymentMethodService = ( ): PaymentMethodService => ({ async add( customerId: string, - paymentMethod: AddCustomerPaymentMethodRequest, - ): Promise { + paymentMethod: PaymentMethod.Request, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods`, paymentMethod, { @@ -63,11 +52,11 @@ export const createPaymentMethodService = ( async get( customerId: string, paymentId: string, - ): Promise { + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentId}`, { headers: { @@ -88,20 +77,19 @@ export const createPaymentMethodService = ( async list( customerId: string, - query?: GetAllCustomerPaymentMethodsQuery, - ): Promise { + query?: PaymentMethod.ListQuery, + ): Promise { try { const token = await client.getAccessToken(); const queryString = buildQueryString(query); - const response = - await httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods${queryString}`, - { - headers: { Authorization: `Bearer ${token}` }, - retryOptions: client.retryOptions, - }, - ); + const response = await httpClient.get( + `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods${queryString}`, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ); return response; } catch (error) { throw new SDKError( @@ -114,11 +102,11 @@ export const createPaymentMethodService = ( async delete( customerId: string, paymentMethodId: string, - ): Promise { + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.delete( + const response = await httpClient.delete( `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentMethodId}`, { headers: { diff --git a/packages/api/src/services/paymentService.ts b/packages/api/src/services/paymentService.ts index f9719169..925854c4 100644 --- a/packages/api/src/services/paymentService.ts +++ b/packages/api/src/services/paymentService.ts @@ -1,25 +1,19 @@ -import type { - CancelPaymentResponse, - ConfirmPaymentResponse, - CreatePaymentRequest, - CreatePaymentResponse, - OakClient, -} from "../types"; +import type { Payment, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; export interface PaymentService { - create(payment: CreatePaymentRequest): Promise; - confirm(paymentId: string): Promise; - cancel(paymentId: string): Promise; + create(payment: Payment.Request): Promise; + confirm(paymentId: string): Promise; + cancel(paymentId: string): Promise; } export const createPaymentService = (client: OakClient): PaymentService => ({ - async create(payment: CreatePaymentRequest): Promise { + async create(payment: Payment.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/payments/`, payment, { @@ -36,11 +30,11 @@ export const createPaymentService = (client: OakClient): PaymentService => ({ } }, - async confirm(paymentId: string): Promise { + async confirm(paymentId: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/payments/${paymentId}/confirm`, {}, { @@ -60,11 +54,11 @@ export const createPaymentService = (client: OakClient): PaymentService => ({ } }, - async cancel(paymentId: string): Promise { + async cancel(paymentId: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/payments/${paymentId}/cancel`, {}, { diff --git a/packages/api/src/services/planService.ts b/packages/api/src/services/planService.ts index 94f45f2e..82750f07 100644 --- a/packages/api/src/services/planService.ts +++ b/packages/api/src/services/planService.ts @@ -1,38 +1,22 @@ -import type { - CreatePlanRequest, - CreatePlanResponse, - DeletePlanResponse, - OakClient, - PlanDetailsResponse, - PlansListQueryParams, - PlansListResponse, - PublishPlanResponse, - UpdatePlanRequest, - UpdatePlanResponse, -} from "../types"; +import type { Plan, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; export interface PlanService { - create(createPlanRequest: CreatePlanRequest): Promise; - publish(id: string): Promise; - details(id: string): Promise; - list(params?: PlansListQueryParams): Promise; - update( - id: string, - updatePlanRequest: UpdatePlanRequest, - ): Promise; - delete(id: string): Promise; + create(createPlanRequest: Plan.Request): Promise; + publish(id: string): Promise; + details(id: string): Promise; + list(params?: Plan.ListQuery): Promise; + update(id: string, updatePlanRequest: Plan.Request): Promise; + delete(id: string): Promise; } export const createPlanService = (client: OakClient): PlanService => ({ - async create( - createPlanRequest: CreatePlanRequest, - ): Promise { + async create(createPlanRequest: Plan.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/subscription/plans`, createPlanRequest, { @@ -46,10 +30,10 @@ export const createPlanService = (client: OakClient): PlanService => ({ } }, - async publish(id: string): Promise { + async publish(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.patch( + const response = await httpClient.patch( `${client.config.baseUrl}/api/v1/subscription/plans/${id}/publish`, undefined, { @@ -63,10 +47,10 @@ export const createPlanService = (client: OakClient): PlanService => ({ } }, - async details(id: string): Promise { + async details(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, { headers: { Authorization: `Bearer ${token}` }, @@ -79,11 +63,11 @@ export const createPlanService = (client: OakClient): PlanService => ({ } }, - async list(params?: PlansListQueryParams): Promise { + async list(params?: Plan.ListQuery): Promise { try { const token = await client.getAccessToken(); const queryString = buildQueryString(params); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/subscription/plans${queryString}`, { headers: { Authorization: `Bearer ${token}` }, @@ -98,11 +82,11 @@ export const createPlanService = (client: OakClient): PlanService => ({ async update( id: string, - updatePlanRequest: UpdatePlanRequest, - ): Promise { + updatePlanRequest: Plan.Request, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.patch( + const response = await httpClient.patch( `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, updatePlanRequest, { @@ -116,10 +100,10 @@ export const createPlanService = (client: OakClient): PlanService => ({ } }, - async delete(id: string): Promise { + async delete(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.delete( + const response = await httpClient.delete( `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, { headers: { Authorization: `Bearer ${token}` }, diff --git a/packages/api/src/services/providerService.ts b/packages/api/src/services/providerService.ts index 272ac5f7..0e2a2725 100644 --- a/packages/api/src/services/providerService.ts +++ b/packages/api/src/services/providerService.ts @@ -1,36 +1,29 @@ -import type { - GetProviderRegistrationStatusResponse, - GetProviderSchemaRequest, - GetProviderSchemaResponse, - OakClient, - SubmitProviderRegistrationRequest, - SubmitProviderRegistrationResponse, -} from "../types"; +import type { OakClient, Provider } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { getErrorBodyMessage } from "./helpers"; export interface ProviderService { getSchema( - request: GetProviderSchemaRequest, - ): Promise; + request: Provider.GetSchemaRequest, + ): Promise; getRegistrationStatus( customerId: string, - ): Promise; + ): Promise; submitRegistration( customerId: string, - registration: SubmitProviderRegistrationRequest, - ): Promise; + registration: Provider.Request, + ): Promise; } export const createProviderService = (client: OakClient): ProviderService => ({ async getSchema( - request: GetProviderSchemaRequest, - ): Promise { + request: Provider.GetSchemaRequest, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${ client.config.baseUrl }/api/v1/provider-registration/schema?provider=${encodeURIComponent( @@ -55,12 +48,12 @@ export const createProviderService = (client: OakClient): ProviderService => ({ async getRegistrationStatus( customerId: string, - ): Promise { + ): Promise { try { const token = await client.getAccessToken(); const response = - await httpClient.get( + await httpClient.get( `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/status`, { headers: { @@ -81,22 +74,21 @@ export const createProviderService = (client: OakClient): ProviderService => ({ async submitRegistration( customerId: string, - registration: SubmitProviderRegistrationRequest, - ): Promise { + registration: Provider.Request, + ): Promise { try { const token = await client.getAccessToken(); - const response = - await httpClient.post( - `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/submit`, - registration, - { - headers: { - Authorization: `Bearer ${token}`, - }, - retryOptions: client.retryOptions, + const response = await httpClient.post( + `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/submit`, + registration, + { + headers: { + Authorization: `Bearer ${token}`, }, - ); + retryOptions: client.retryOptions, + }, + ); return response; } catch (error) { diff --git a/packages/api/src/services/sellService.ts b/packages/api/src/services/sellService.ts index 294cbffd..5a9d75af 100644 --- a/packages/api/src/services/sellService.ts +++ b/packages/api/src/services/sellService.ts @@ -1,20 +1,16 @@ -import type { - CreateSellRequest, - CreateSellResponse, - OakClient, -} from "../types"; +import type { Sell, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; export interface SellService { - create(sellRequest: CreateSellRequest): Promise; + create(sellRequest: Sell.Request): Promise; } export const createSellService = (client: OakClient): SellService => ({ - async create(sellRequest: CreateSellRequest): Promise { + async create(sellRequest: Sell.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/sell`, sellRequest, { diff --git a/packages/api/src/services/transactionService.ts b/packages/api/src/services/transactionService.ts index ac91b8a8..a6d93c20 100644 --- a/packages/api/src/services/transactionService.ts +++ b/packages/api/src/services/transactionService.ts @@ -1,35 +1,26 @@ -import type { - GetAllTransactionsQuery, - GetAllTransactionsResponse, - GetTransactionResponse, - OakClient, - SettlementRequest, - SettlementResponse, -} from "../types"; +import type { Transaction, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { buildQueryString } from "./helpers"; export interface TransactionService { - list(query?: GetAllTransactionsQuery): Promise; - get(id: string): Promise; + list(query?: Transaction.ListQuery): Promise; + get(id: string): Promise; settle( id: string, - settlementRequest: SettlementRequest, - ): Promise; + settlementRequest: Transaction.SettlementRequest, + ): Promise; } export const createTransactionService = ( client: OakClient, ): TransactionService => ({ - async list( - query?: GetAllTransactionsQuery, - ): Promise { + async list(query?: Transaction.ListQuery): Promise { try { const token = await client.getAccessToken(); const queryString = buildQueryString(query); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/transactions${queryString}`, { headers: { Authorization: `Bearer ${token}` }, @@ -42,11 +33,11 @@ export const createTransactionService = ( } }, - async get(id: string): Promise { + async get(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/transactions/${id}`, { headers: { Authorization: `Bearer ${token}` }, @@ -61,11 +52,11 @@ export const createTransactionService = ( async settle( id: string, - settlementRequest: SettlementRequest, - ): Promise { + settlementRequest: Transaction.SettlementRequest, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.patch( + const response = await httpClient.patch( `${client.config.baseUrl}/api/v1/transactions/${id}/settle`, settlementRequest, { diff --git a/packages/api/src/services/transferService.ts b/packages/api/src/services/transferService.ts index d001d241..d3caa230 100644 --- a/packages/api/src/services/transferService.ts +++ b/packages/api/src/services/transferService.ts @@ -1,23 +1,17 @@ -import type { - CreateTransferRequest, - CreateTransferResponse, - OakClient, -} from "../types"; +import type { Transfer, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; export interface TransferService { - create(transfer: CreateTransferRequest): Promise; + create(transfer: Transfer.Request): Promise; } export const createTransferService = (client: OakClient): TransferService => ({ - async create( - transfer: CreateTransferRequest, - ): Promise { + async create(transfer: Transfer.Request): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/transfer`, transfer, { diff --git a/packages/api/src/services/webhookService.ts b/packages/api/src/services/webhookService.ts index 841575a0..127b18d9 100644 --- a/packages/api/src/services/webhookService.ts +++ b/packages/api/src/services/webhookService.ts @@ -1,44 +1,27 @@ -import type { - DeleteWebhookResponse, - GetAllWebhookNotificationResponse, - GetAllWebhooksResponse, - GetWebhookNotificationResponse, - GetWebhookResponse, - OakClient, - RegisterWebhookRequest, - RegisterWebhookResponse, - ToggleWebhookResponse, - UpdateWebhookRequest, - UpdateWebhookResponse, -} from "../types"; +import type { Webhook, OakClient } from "../types"; import { httpClient } from "../utils/httpClient"; import { SDKError } from "../utils/errorHandler"; import { buildQueryString, getErrorBodyMessage } from "./helpers"; export interface WebhookService { - register(webhook: RegisterWebhookRequest): Promise; - list(): Promise; - get(id: string): Promise; - update( - id: string, - webhook: UpdateWebhookRequest, - ): Promise; - toggle(id: string): Promise; - delete(id: string): Promise; + register(webhook: Webhook.RegisterRequest): Promise; + list(): Promise; + get(id: string): Promise; + update(id: string, webhook: Webhook.UpdateRequest): Promise; + toggle(id: string): Promise; + delete(id: string): Promise; listNotifications(params?: { limit?: number; offset?: number; - }): Promise; - getNotification(id: string): Promise; + }): Promise; + getNotification(id: string): Promise; } export const createWebhookService = (client: OakClient): WebhookService => ({ - async register( - webhook: RegisterWebhookRequest, - ): Promise { + async register(webhook: Webhook.RegisterRequest): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.post( + const response = await httpClient.post( `${client.config.baseUrl}/api/v1/merchant/webhooks`, webhook, { @@ -55,10 +38,10 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async list(): Promise { + async list(): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/merchant/webhooks`, { headers: { @@ -73,10 +56,10 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async get(id: string): Promise { + async get(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, { headers: { @@ -93,11 +76,11 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ async update( id: string, - webhook: UpdateWebhookRequest, - ): Promise { + webhook: Webhook.UpdateRequest, + ): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.put( + const response = await httpClient.put( `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, webhook, { @@ -111,11 +94,11 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async toggle(id: string): Promise { + async toggle(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.patch( + const response = await httpClient.patch( `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}/toggle`, undefined, { @@ -129,11 +112,11 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async delete(id: string): Promise { + async delete(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.delete( + const response = await httpClient.delete( `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, { headers: { Authorization: `Bearer ${token}` }, @@ -149,11 +132,11 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ async listNotifications(params?: { limit?: number; offset?: number; - }): Promise { + }): Promise { try { const token = await client.getAccessToken(); const queryString = buildQueryString(params); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications${queryString}`, { headers: { Authorization: `Bearer ${token}` }, @@ -166,10 +149,10 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async getNotification(id: string): Promise { + async getNotification(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.get( + const response = await httpClient.get( `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications/${id}`, { headers: { Authorization: `Bearer ${token}` }, diff --git a/packages/api/src/types/buy.ts b/packages/api/src/types/buy.ts index cc626e47..a2ec514e 100644 --- a/packages/api/src/types/buy.ts +++ b/packages/api/src/types/buy.ts @@ -1,61 +1,59 @@ import { ApiResponse } from "./common"; -// ----- Common Types ----- -export interface WalletDetails { - address: string; +export namespace Buy { + export interface WalletDetails { + address: string; + } + + export interface PaymentMethod { + wallet_details: WalletDetails; + } + + export interface CurrencyInfo { + currency: string; + } + + export interface Source extends CurrencyInfo { + customer_id: string; + } + + export interface Destination extends CurrencyInfo { + payment_method: PaymentMethod; + } + + export interface ProviderData { + destination_payment_rail: string; // e.g., "polygon" + } + + export interface ProviderResponse { + currency: string; + bank_name: string; + bank_address: string; + bank_routing_number: string; + bank_account_number: string; + bank_beneficiary_name: string; + bank_beneficiary_address: string; + payment_rails: string[]; // e.g., ["ach_push", "wire"] + deposit_message: string; + } + + export interface Transaction { + id: string; + status: string; // e.g., "captured" + type: string; // e.g., "buy" + source: Source; + provider: string; + destination: Destination; + provider_data: ProviderData; + provider_response: ProviderResponse; + } + + export interface Request { + provider: string; // e.g., "bridge" + source: Source; + destination: Destination; + provider_data: ProviderData; + } + + export type Response = ApiResponse; } - -export interface PaymentMethod { - wallet_details: WalletDetails; -} - -export interface CurrencyInfo { - currency: string; -} - -export interface BuySource extends CurrencyInfo { - customer_id: string; -} - -export interface BuyDestination extends CurrencyInfo { - payment_method: PaymentMethod; -} - -export interface ProviderData { - destination_payment_rail: string; // e.g., "polygon" -} - -export interface BuyProviderResponse { - currency: string; - bank_name: string; - bank_address: string; - bank_routing_number: string; - bank_account_number: string; - bank_beneficiary_name: string; - bank_beneficiary_address: string; - payment_rails: string[]; // e.g., ["ach_push", "wire"] - deposit_message: string; -} - -// ----- Requests ----- -export interface CreateBuyRequest { - provider: string; // e.g., "bridge" - source: BuySource; - destination: BuyDestination; - provider_data: ProviderData; -} - -// ----- Transactions ----- -export interface BuyTransaction { - id: string; - status: string; // e.g., "captured" - type: string; // e.g., "buy" - source: BuySource; - provider: string; - destination: BuyDestination; - provider_data: ProviderData; - provider_response: BuyProviderResponse; -} - -// ----- Responses ----- -export type CreateBuyResponse = ApiResponse; diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 56e178c0..05c4dee7 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -1,85 +1,77 @@ import { ApiResponse } from "./common"; -// ----- Common Types ----- -export type DocumentType = "personal_tax_id" | "company_tax_id" | "cpf"; +export namespace Customer { + export type DocumentType = "personal_tax_id" | "company_tax_id"; -export interface CustomerBase { - email: string; - document_number?: string; - document_type?: DocumentType; - first_name?: string; - last_name?: string; - dob?: string; - phone_country_code?: string; - phone_area_code?: string; - phone_number?: string; - country_code?: string; - company_name?: string; -} - -// ----- Response Data Shape ----- -export interface CustomerData { - id?: string; - customer_id?: string; - document_number?: string | null; - document_type?: string | null; - email: string; - first_name?: string | null; - last_name?: string | null; - house_number?: string | null; - street_number?: string | null; - street_name?: string | null; - postal_code?: string | null; - city?: string | null; - state?: string | null; - country_code?: string | null; - subdivision?: string | null; - phone_country_code?: string | null; - phone_area_code?: string | null; - phone_number?: string | null; - dob?: string | null; - mother_name?: string | null; - monthly_net_income?: number | null; - gender?: string | null; - owner_legal_name?: string | null; - owner_document_number?: string | null; - owner_document_type?: string | null; - company_name?: string | null; - company_start_date?: string | null; - social_name?: string | null; - tax_id?: string | null; - neighborhood?: string | null; - customer_wallet?: string | null; - trading_wallet?: string | null; - account_type?: string | null; -} + export interface Base { + email: string; + document_number?: string; + document_type?: DocumentType; + first_name?: string; + last_name?: string; + dob?: string; + phone_country_code?: string; + phone_area_code?: string; + phone_number?: string; + country_code?: string; + company_name?: string; + } -// ----- Requests ----- -export interface CreateCustomerRequest extends CustomerBase {} -export interface UpdateCustomerRequest extends Partial {} + export interface Data { + id?: string; + customer_id?: string; + document_number?: string | null; + document_type?: string | null; + email: string; + first_name?: string | null; + last_name?: string | null; + house_number?: string | null; + street_number?: string | null; + street_name?: string | null; + postal_code?: string | null; + city?: string | null; + state?: string | null; + country_code?: string | null; + subdivision?: string | null; + phone_country_code?: string | null; + phone_area_code?: string | null; + phone_number?: string | null; + dob?: string | null; + mother_name?: string | null; + monthly_net_income?: number | null; + gender?: string | null; + owner_legal_name?: string | null; + owner_document_number?: string | null; + owner_document_type?: string | null; + company_name?: string | null; + company_start_date?: string | null; + social_name?: string | null; + tax_id?: string | null; + neighborhood?: string | null; + customer_wallet?: string | null; + trading_wallet?: string | null; + account_type?: string | null; + } -// ----- Responses ----- -export type CreateCustomerResponse = ApiResponse; -export type GetCustomerResponse = ApiResponse; -export type UpdateCustomerResponse = ApiResponse; + export interface Request extends Partial {} -export interface GetAllCustomerResponse - extends ApiResponse<{ + export type Response = ApiResponse; + export interface ListResponse extends ApiResponse<{ count: number; - customer_list: CustomerData[]; + customer_list: Data[]; }> {} -// ----- Query Params ----- -export interface CustomerListQueryParams { - limit?: number; - offset?: number; - customer_id?: string; - type_list?: string; - status?: string; - payment_method?: string; - dateFrom?: string; - dateTo?: string; - source_currency?: string; - destination_currency?: string; - country_code?: string; + export interface ListQueryParams { + limit?: number; + offset?: number; + customer_id?: string; + type_list?: string; + status?: string; + payment_method?: string; + dateFrom?: string; + dateTo?: string; + source_currency?: string; + destination_currency?: string; + country_code?: string; + } } diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index e0a58cdb..af349716 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -2,6 +2,7 @@ export * from "./config"; export * from "./client"; export * from "./token"; export * from "./payment"; +export * from "./paymentMethod"; export * from "./customer"; export * from "./provider"; export * from "./transactions"; diff --git a/packages/api/src/types/payment.ts b/packages/api/src/types/payment.ts index 0c9d7e23..589684fe 100644 --- a/packages/api/src/types/payment.ts +++ b/packages/api/src/types/payment.ts @@ -1,273 +1,242 @@ -// ---------------------------------------- -// Common Base Types - import { ApiResponse } from "./common"; -// ---------------------------------------- -export interface Customer { - id: string; -} - -export interface Metadata { - [key: string]: string; -} - -export interface BillingAddress { - house_number?: string; - street_number?: string; - street_name?: string; - postal_code?: string; - city?: string; - state?: string; - country_code?: string; - address_line1?: string; - address_line2?: string; - zip_code?: string; -} - -export interface FraudCheckData { - last_four_digits?: string; - card_expiration_date?: string; - card_holder_name?: string; -} - -export interface FraudCheckConfig { - threshold?: "low" | "medium" | "high"; - sequence?: "fraud_before_auth" | "fraud_after_auth"; - action_on_fail?: "reject" | "review"; -} - -export interface FraudCheck { - enabled: boolean; - provider?: string; - config?: FraudCheckConfig; - data?: FraudCheckData; -} - -// ---------------------------------------- -// Payment Methods -// ---------------------------------------- -export interface BasePaymentMethod { - id?: string; - type: - | "bank" - | "card" - | "pix" - | "customer_wallet" - | "virtual_account" - | "liquidation_address" - | "plaid"; - status?: string; - provider?: string; - metadata?: Metadata; -} - -// Variants -export interface BankPaymentMethod extends BasePaymentMethod { - type: "bank"; - bank_name?: string; - bank_account_name?: string; - bank_account_number?: string; - bank_branch_code?: string; - bank_swift_code?: string; - bank_account_type?: string; - bank_routing_number?: string; -} - -export interface CardPaymentMethod extends BasePaymentMethod { - type: "card"; - card_token?: string; - billing_address?: BillingAddress; - provider_response?: ProviderResponse; -} - -export interface PixPaymentMethod extends BasePaymentMethod { - type: "pix"; - pix_string?: string; -} - -export interface CustomerWalletPaymentMethod extends BasePaymentMethod { - type: "customer_wallet"; - evm_address?: string; - chain?: string; - currency?: string; -} - -export interface VirtualAccountPaymentMethod extends BasePaymentMethod { - type: "virtual_account"; - source_currency?: string; - destination_currency?: string; - chain?: string; - provider_response?: { - source_deposit_instructions?: Record; - }; - provider_data?: Record; - destination_payment_method_id?: string; -} - -export interface LiquidationAddressPaymentMethod extends BasePaymentMethod { - type: "liquidation_address"; - source_currency?: string; - destination_currency?: string; - liquidation_address?: string; - provider_data?: Record; - destination_payment_method_id?: string; -} - -export interface PlaidPaymentMethod extends BasePaymentMethod { - type: "plaid"; - link_token?: string; - callback_url?: string; - link_token_expires_at?: string; -} - -// Unified type for responses -export type PaymentMethodResponseData = - | BankPaymentMethod - | CardPaymentMethod - | PixPaymentMethod - | CustomerWalletPaymentMethod - | VirtualAccountPaymentMethod - | LiquidationAddressPaymentMethod - | PlaidPaymentMethod; - -// ---------------------------------------- -// Source/Destination Shapes -// ---------------------------------------- - -export interface PaymentSource { - amount: number; - currency: string; - customer?: Customer; - payment_method: PaymentMethodResponseData; - installments?: number; - float_rate?: number; - capture_method?: "automatic" | "manual"; - fraud_check?: FraudCheck; -} - -export interface PaymentDestination { - amount?: number; - currency?: string; - customer?: Customer; -} - -// ---------------------------------------- -// Provider Response -// ---------------------------------------- -export interface ProviderResponse { - qr_code?: string; - qr_code_url?: string; - [key: string]: any; -} - -// ---------------------------------------- -// Request Types -// ---------------------------------------- -export interface MercadoPagoPaymentRequest { - provider: "mercado_pago"; - source: PaymentSource & { - currency: "COP"; - customer: Customer; - payment_method: { type: "card"; card_token: string }; - capture_method: "automatic"; - }; - confirm?: boolean; - metadata?: Metadata; -} - -export interface PagarMePaymentRequest { - provider: "pagar_me"; - source: PaymentSource & { - currency: "brl"; - customer: Customer; - payment_method: { - type: "card" | "pix"; - card_token?: string; - expiry_date?: string; +export namespace Payment { + // ---------------------------------------- + // Common + // ---------------------------------------- + export interface CustomerRef { + id: string; + } + + export interface Metadata { + [key: string]: string; + } + + export interface BillingAddress { + house_number?: string; + street_number?: string; + street_name?: string; + postal_code?: string; + city?: string; + state?: string; + country_code?: string; + address_line1?: string; + address_line2?: string; + zip_code?: string; + } + + export interface FraudCheckData { + last_four_digits?: string; + card_expiration_date?: string; + card_holder_name?: string; + } + + export interface FraudCheckConfig { + threshold?: "low" | "medium" | "high"; + sequence?: "fraud_before_auth" | "fraud_after_auth"; + action_on_fail?: "reject" | "review"; + } + + export interface FraudCheck { + enabled: boolean; + provider?: string; + config?: FraudCheckConfig; + data?: FraudCheckData; + } + + export interface ProviderResponse { + qr_code?: string; + qr_code_url?: string; + [key: string]: any; + } + + // ---------------------------------------- + // Payment methods + // ---------------------------------------- + export interface MethodBase { + id?: string; + type: + | "bank" + | "card" + | "pix" + | "customer_wallet" + | "virtual_account" + | "liquidation_address" + | "plaid"; + status?: string; + provider?: string; + metadata?: Metadata; + } + + export interface BankMethod extends MethodBase { + type: "bank"; + bank_name?: string; + bank_account_name?: string; + bank_account_number?: string; + bank_branch_code?: string; + bank_swift_code?: string; + bank_account_type?: string; + bank_routing_number?: string; + } + + export interface CardMethod extends MethodBase { + type: "card"; + card_token?: string; + billing_address?: BillingAddress; + provider_response?: ProviderResponse; + } + + export interface PixMethod extends MethodBase { + type: "pix"; + pix_string?: string; + } + + export interface CustomerWalletMethod extends MethodBase { + type: "customer_wallet"; + evm_address?: string; + chain?: string; + currency?: string; + } + + export interface VirtualAccountMethod extends MethodBase { + type: "virtual_account"; + source_currency?: string; + destination_currency?: string; + chain?: string; + provider_response?: { source_deposit_instructions?: Record }; + provider_data?: Record; + destination_payment_method_id?: string; + } + + export interface LiquidationAddressMethod extends MethodBase { + type: "liquidation_address"; + source_currency?: string; + destination_currency?: string; + liquidation_address?: string; + provider_data?: Record; + destination_payment_method_id?: string; + } + + export interface PlaidMethod extends MethodBase { + type: "plaid"; + link_token?: string; + callback_url?: string; + link_token_expires_at?: string; + } + + export type MethodData = + | BankMethod + | CardMethod + | PixMethod + | CustomerWalletMethod + | VirtualAccountMethod + | LiquidationAddressMethod + | PlaidMethod; + + // ---------------------------------------- + // Source / destination + // ---------------------------------------- + export interface Source { + amount: number; + currency: string; + customer?: CustomerRef; + payment_method: MethodData; + installments?: number; + float_rate?: number; + capture_method?: "automatic" | "manual"; + fraud_check?: FraudCheck; + } + + export interface Destination { + amount?: number; + currency?: string; + customer?: CustomerRef; + } + + // ---------------------------------------- + // Create payment (provider-specific requests) + // ---------------------------------------- + export interface MercadoPagoRequest { + provider: "mercado_pago"; + source: Source & { + currency: "COP"; + customer: CustomerRef; + payment_method: { type: "card"; card_token: string }; + capture_method: "automatic"; }; - capture_method: "automatic" | "manual"; - fraud_check: FraudCheck & { provider?: "konduto" }; - }; - confirm?: boolean; - metadata?: Metadata; -} - -export interface StripePaymentRequest { - provider: "stripe"; - source: PaymentSource & { - payment_method: { type: "card"; id?: string }; - capture_method: "automatic"; - fraud_check?: { enabled: false }; - }; - destination?: PaymentDestination; - confirm?: boolean; - metadata?: Metadata; -} - -export type CreatePaymentRequest = - | MercadoPagoPaymentRequest - | PagarMePaymentRequest - | StripePaymentRequest; - -// ---------------------------------------- -// Response Types -// ---------------------------------------- -export type CreatePaymentResponse = ApiResponse<{ - id: string; - status: string; - type: string; - source: PaymentSource; - confirm: boolean; - metadata?: Metadata; - provider: string; -}>; - -export type ConfirmPaymentResponse = ApiResponse<{ - id: string; - status: string; - type: string; - source: PaymentSource; - confirm: boolean; - metadata?: Metadata; - provider: string; - provider_response?: ProviderResponse; -}>; - -export type CancelPaymentResponse = ApiResponse<{ - id: string; - status: string; - type: string; - source: PaymentSource; - confirm: boolean; - metadata?: Metadata; - provider: string; - provider_response?: ProviderResponse; -}>; - -export type AddCustomerPaymentMethodRequest = - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit; - -export type AddCustomerPaymentMethodResponse = - ApiResponse; -export type GetCustomerPaymentMethodResponse = - ApiResponse; -export type GetAllCustomerPaymentMethodsResponse = ApiResponse< - PaymentMethodResponseData[] ->; - -export interface GetAllCustomerPaymentMethodsQuery { - type?: string; - status?: string; - platform?: string; -} - -export interface DeletePaymentMethodResponse { - msg: string; + confirm?: boolean; + metadata?: Metadata; + } + + export interface PagarMeRequest { + provider: "pagar_me"; + source: Source & { + currency: "brl"; + customer: CustomerRef; + payment_method: { + type: "card" | "pix"; + card_token?: string; + expiry_date?: string; + }; + capture_method: "automatic" | "manual"; + fraud_check: FraudCheck & { provider?: "konduto" }; + }; + confirm?: boolean; + metadata?: Metadata; + } + + export interface StripeRequest { + provider: "stripe"; + source: Source & { + payment_method: { type: "card"; id?: string }; + capture_method: "automatic"; + fraud_check?: { enabled: false }; + }; + destination?: Destination; + confirm?: boolean; + metadata?: Metadata; + } + + export type Request = MercadoPagoRequest | PagarMeRequest | StripeRequest; + + // ---------------------------------------- + // Payment responses (create / confirm / cancel) + // ---------------------------------------- + export interface TransactionPayload { + id: string; + status: string; + type: string; + source: Source; + confirm: boolean; + metadata?: Metadata; + provider: string; + provider_response?: ProviderResponse; + } + + export type Response = ApiResponse; + + // ---------------------------------------- + // Customer payment methods (add / get / list / delete) + // ---------------------------------------- + export type AddMethodRequest = + | Omit + | Omit + | Omit + | Omit + | Omit + | Omit + | Omit; + + export type MethodResponse = ApiResponse; + export type ListMethodsResponse = ApiResponse; + + export interface ListMethodsQuery { + type?: string; + status?: string; + platform?: string; + } + + export interface DeleteMethodResponse { + msg: string; + } } diff --git a/packages/api/src/types/paymentMethod.ts b/packages/api/src/types/paymentMethod.ts new file mode 100644 index 00000000..59409d44 --- /dev/null +++ b/packages/api/src/types/paymentMethod.ts @@ -0,0 +1,150 @@ +import { ApiResponse } from "./common"; + +export namespace PaymentMethod { + export interface Metadata { + [key: string]: string; + } + + export interface BillingAddress { + house_number?: string; + street_number?: string; + street_name?: string; + postal_code?: string; + city?: string; + state?: string; + country_code?: string; + address_line1?: string; + address_line2?: string; + zip_code?: string; + } + + export interface FraudCheckData { + last_four_digits?: string; + card_expiration_date?: string; + card_holder_name?: string; + } + + export interface FraudCheckConfig { + threshold?: "low" | "medium" | "high"; + sequence?: "fraud_before_auth" | "fraud_after_auth"; + action_on_fail?: "reject" | "review"; + } + + export interface FraudCheck { + enabled: boolean; + provider?: string; + config?: FraudCheckConfig; + data?: FraudCheckData; + } + + export interface ProviderResponse { + qr_code?: string; + qr_code_url?: string; + [key: string]: any; + } + + // ---------------------------------------- + // Payment methods + // ---------------------------------------- + export interface MethodBase { + id?: string; + type: + | "bank" + | "card" + | "pix" + | "customer_wallet" + | "virtual_account" + | "liquidation_address" + | "plaid"; + status?: string; + provider?: string; + metadata?: Metadata; + } + + export interface BankMethod extends MethodBase { + type: "bank"; + bank_name?: string; + bank_account_name?: string; + bank_account_number?: string; + bank_branch_code?: string; + bank_swift_code?: string; + bank_account_type?: string; + bank_routing_number?: string; + } + + export interface CardMethod extends MethodBase { + type: "card"; + card_token?: string; + billing_address?: BillingAddress; + provider_response?: ProviderResponse; + } + + export interface PixMethod extends MethodBase { + type: "pix"; + pix_string?: string; + } + + export interface CustomerWalletMethod extends MethodBase { + type: "customer_wallet"; + evm_address?: string; + chain?: string; + currency?: string; + } + + export interface VirtualAccountMethod extends MethodBase { + type: "virtual_account"; + source_currency?: string; + destination_currency?: string; + chain?: string; + provider_response?: { source_deposit_instructions?: Record }; + provider_data?: Record; + destination_payment_method_id?: string; + } + + export interface LiquidationAddressMethod extends MethodBase { + type: "liquidation_address"; + source_currency?: string; + destination_currency?: string; + liquidation_address?: string; + provider_data?: Record; + destination_payment_method_id?: string; + } + + export interface PlaidMethod extends MethodBase { + type: "plaid"; + link_token?: string; + callback_url?: string; + link_token_expires_at?: string; + } + + export type MethodData = + | BankMethod + | CardMethod + | PixMethod + | CustomerWalletMethod + | VirtualAccountMethod + | LiquidationAddressMethod + | PlaidMethod; + + export type Request = + | Omit + | Omit + | Omit + | Omit + | Omit + | Omit + | Omit; + + export type Response = ApiResponse; + export type ListResponse = ApiResponse; + + export interface ListQuery { + type?: string; + status?: string; + platform?: string; + } + + export interface DeleteResponse { + msg: string; + } +} diff --git a/packages/api/src/types/plan.ts b/packages/api/src/types/plan.ts index f70d0709..93d24529 100644 --- a/packages/api/src/types/plan.ts +++ b/packages/api/src/types/plan.ts @@ -1,76 +1,72 @@ -// ---------------------- -// Shared Request Fields - import { ApiResponse } from "./common"; -// ---------------------- -export interface PlanBaseRequest { - name: string; - description: string; - frequency: number; // in days - price: number; - start_date: string; // ISO date format (YYYY-MM-DD) - end_date?: string; // Optional ISO date format - is_auto_renewable: boolean; - currency: string; // e.g. "BRL" - allow_amount_override: boolean; - created_by: string; -} +export namespace Plan { + // ---------------------- + // Request + // ---------------------- + export interface Base { + name: string; + description: string; + frequency: number; // in days + price: number; + start_date: string; // ISO date format (YYYY-MM-DD) + end_date?: string; // Optional ISO date format + is_auto_renewable: boolean; + currency: string; // e.g. "BRL" + allow_amount_override: boolean; + created_by: string; + } -// Requests -export interface CreatePlanRequest extends PlanBaseRequest {} -export interface UpdatePlanRequest extends PlanBaseRequest {} + /** Use for both create and update. */ + export interface Request extends Base {} -// ---------------------- -// Plan Data Structure -// ---------------------- -export interface PlanDetails { - hash_id: string; - name: string; - description: string; - frequency: number; // in days - price: number; - is_active: boolean; - start_time: string; // ISO datetime - end_time: string; // ISO datetime - is_auto_renewable: boolean; - created_by: string; - updated_by: string; - currency: string; // lowercase like "brl" - allow_amount_override: boolean; - created_at: string; // ISO datetime - updated_at: string; // ISO datetime - deleted_at: string | null; -} + // ---------------------- + // Data + // ---------------------- + export interface Details { + hash_id: string; + name: string; + description: string; + frequency: number; // in days + price: number; + is_active: boolean; + start_time: string; // ISO datetime + end_time: string; // ISO datetime + is_auto_renewable: boolean; + created_by: string; + updated_by: string; + currency: string; // lowercase like "brl" + allow_amount_override: boolean; + created_at: string; // ISO datetime + updated_at: string; // ISO datetime + deleted_at: string | null; + } -// ---------------------- -// Pagination -// ---------------------- -export interface Pagination { - per_page: number; - page_no: number; - total: number; -} + export interface Pagination { + per_page: number; + page_no: number; + total: number; + } -export interface PlansListData { - data: PlanDetails[]; - pagination: Pagination; -} + export interface ListData { + data: Details[]; + pagination: Pagination; + } + + // ---------------------- + // Responses + // ---------------------- + /** create, update, publish, delete */ + export type Response = ApiResponse; -// ---------------------- -// API Responses -// ---------------------- -export type CreatePlanResponse = ApiResponse; -export type UpdatePlanResponse = ApiResponse; -export type PublishPlanResponse = ApiResponse; -export type DeletePlanResponse = ApiResponse; -export type PlanDetailsResponse = ApiResponse; -export type PlansListResponse = ApiResponse; + export type DetailsResponse = ApiResponse
; + export type ListResponse = ApiResponse; -// ---------------------- -// Query Params -// ---------------------- -export interface PlansListQueryParams { - page_no?: number; - per_page?: number; + // ---------------------- + // Query + // ---------------------- + export interface ListQuery { + page_no?: number; + per_page?: number; + } } diff --git a/packages/api/src/types/provider.ts b/packages/api/src/types/provider.ts index 99a6bf46..95894a1e 100644 --- a/packages/api/src/types/provider.ts +++ b/packages/api/src/types/provider.ts @@ -1,93 +1,91 @@ -// ---------------------- -// Shared Enums - import { ApiResponse } from "./common"; -// ---------------------- -export type ProviderName = - | "avenia" - | "mercado_pago" - | "bridge" - | "stripe" - | "pagar_me"; +export namespace Provider { + // ---------------------- + // Enums / literals + // ---------------------- + export type Name = + | "avenia" + | "mercado_pago" + | "bridge" + | "stripe" + | "pagar_me"; -export type TargetRole = "subaccount" | "customer" | "connected_account"; + export type TargetRole = "subaccount" | "customer" | "connected_account"; -// ---------------------- -// Provider Schema Types -// ---------------------- -export interface ProviderSchemaCondition { - if: { properties: { document_type: { const: string } } }; - then: { - not?: { anyOf?: Array<{ required: string[] }> }; - properties?: Record; - errorMessage?: { not?: string }; - }; -} + // ---------------------- + // Schema + // ---------------------- + export interface SchemaCondition { + if: { properties: { document_type: { const: string } } }; + then: { + not?: { anyOf?: Array<{ required: string[] }> }; + properties?: Record; + errorMessage?: { not?: string }; + }; + } -export interface ProviderSchema { - type: string; - allOf?: ProviderSchemaCondition[]; - $async?: boolean; - required: string[]; - properties: Record< - string, - { - type?: string; - format?: string; - enum?: string[]; - nullable?: boolean; - minLength?: number; - validateCountry?: boolean; - } - >; -} + export interface Schema { + type: string; + allOf?: SchemaCondition[]; + $async?: boolean; + required: string[]; + properties: Record< + string, + { + type?: string; + format?: string; + enum?: string[]; + nullable?: boolean; + minLength?: number; + validateCountry?: boolean; + } + >; + } -export type GetProviderSchemaRequest = { - provider: ProviderName; -}; + export interface GetSchemaRequest { + provider: Name; + } -export type GetProviderSchemaResponse = ApiResponse; + export type GetSchemaResponse = ApiResponse; -// ---------------------- -// Provider Registration Status -// ---------------------- -export interface ProviderRegistrationStatus { - provider: string; - status: string; - target_role: string | null; - provider_response: any | null; - rejection_reason: string | null; -} + // ---------------------- + // Registration status + // ---------------------- + export interface RegistrationStatus { + provider: string; + status: string; + target_role: string | null; + provider_response: any | null; + rejection_reason: string | null; + } -export type GetProviderRegistrationStatusResponse = ApiResponse< - ProviderRegistrationStatus[] ->; + export type GetRegistrationStatusResponse = ApiResponse; -// ---------------------- -// Provider Registration Submission -// ---------------------- -export interface ProviderRegistrationData { - callback_url?: string; - account_type?: string; - transfers_requested?: boolean; - card_payments_requested?: boolean; - tax_reporting_us_1099_k_requested?: boolean; - payouts_debit_negative_balances?: boolean; - external_account_collection_requested?: boolean; -} + // ---------------------- + // Registration submission + // ---------------------- + export interface RegistrationData { + callback_url?: string; + account_type?: string; + transfers_requested?: boolean; + card_payments_requested?: boolean; + tax_reporting_us_1099_k_requested?: boolean; + payouts_debit_negative_balances?: boolean; + external_account_collection_requested?: boolean; + } -export interface SubmitProviderRegistrationRequest { - provider: ProviderName; - target_role: TargetRole; - provider_data?: ProviderRegistrationData; -} + export interface Request { + provider: Name; + target_role: TargetRole; + provider_data?: RegistrationData; + } -export interface SubmitProviderRegistrationResult { - status: string; - provider: string; - target_role: string; -} + export interface SubmitResponse { + status: string; + provider: string; + target_role: string; + } -export type SubmitProviderRegistrationResponse = - ApiResponse; + export type Response = ApiResponse; +} diff --git a/packages/api/src/types/sell.ts b/packages/api/src/types/sell.ts index f2d34e08..c4a0fef0 100644 --- a/packages/api/src/types/sell.ts +++ b/packages/api/src/types/sell.ts @@ -1,44 +1,44 @@ -// ---------------------- -// Sell Payment Method - import { ApiResponse } from "./common"; -// ---------------------- -export type SellPaymentMethod = - | { type: "pix"; id: string } // For saved payment method - | { type: "pix"; pix_string: string }; // For direct PIX string - -// ---------------------- -// Create Sell Request -// ---------------------- -export interface CreateSellRequest { - provider: "avenia"; - source: { +export namespace Sell { + // ---------------------- + // Payment method + // ---------------------- + export type PaymentMethod = + | { type: "pix"; id: string } // saved payment method + | { type: "pix"; pix_string: string }; // direct PIX string + + // ---------------------- + // Request + // ---------------------- + export interface Source { customer?: { id: string }; - currency: string; // e.g., "brla" + currency: string; // e.g. "brla" amount: number; - }; - destination: { + } + + export interface Destination { customer: { id: string }; - currency: string; // e.g., "brl" - payment_method: SellPaymentMethod; - }; -} + currency: string; // e.g. "brl" + payment_method: PaymentMethod; + } + + export interface Request { + provider: "avenia"; + source: Source; + destination: Destination; + } -// ---------------------- -// Sell Transaction -// ---------------------- -export interface SellTransaction { - id: string; - status: "created" | string; // could extend later - type: "sell"; - source: { + // ---------------------- + // Transaction (response payload) + // ---------------------- + export interface TransactionSource { amount: string; currency: string; customer?: { id: string }; - }; - provider: "avenia"; - destination: { + } + + export interface TransactionDestination { currency: string; customer: { id: string }; payment_method: { @@ -46,10 +46,19 @@ export interface SellTransaction { id?: string; pix_string?: string; }; - }; -} + } -// ---------------------- -// Create Sell Response -// ---------------------- -export type CreateSellResponse = ApiResponse; + export interface Transaction { + id: string; + status: "created" | string; + type: "sell"; + source: TransactionSource; + provider: "avenia"; + destination: TransactionDestination; + } + + // ---------------------- + // Response + // ---------------------- + export type Response = ApiResponse; +} diff --git a/packages/api/src/types/transactions.ts b/packages/api/src/types/transactions.ts index 69caa661..26b7b989 100644 --- a/packages/api/src/types/transactions.ts +++ b/packages/api/src/types/transactions.ts @@ -1,65 +1,68 @@ import { ApiResponse } from "./common"; -import { Metadata, PaymentSource } from "./payment"; +import { Payment } from "./payment"; -// ---------------------- -// Query Params -// ---------------------- -export interface GetAllTransactionsQuery { - limit?: number; - offset?: number; - customer_id?: string; // UUID - type_list?: string; // e.g. "installment_payment" - status?: string; // comma-separated, e.g. "created,processing" - payment_method?: string; // e.g. "pix" - dateFrom?: string; // e.g. "2025-07-02" - dateTo?: string; // e.g. "2025-07-02" - source_currency?: string; // e.g. "brla" - destination_currency?: string; // e.g. "brl" -} +export namespace Transaction { + // ---------------------- + // Query + // ---------------------- + export interface ListQuery { + limit?: number; + offset?: number; + customer_id?: string; // UUID + type_list?: string; // e.g. "installment_payment" + status?: string; // comma-separated, e.g. "created,processing" + payment_method?: string; // e.g. "pix" + dateFrom?: string; // e.g. "2025-07-02" + dateTo?: string; // e.g. "2025-07-02" + source_currency?: string; // e.g. "brla" + destination_currency?: string; // e.g. "brl" + } -// ---------------------- -// Transaction Status Enum -// ---------------------- -export type TransactionStatus = - | "INITIATED" - | "PENDING" - | "COMPLETED" - | "SETTLED" - | "FAILED" - | "CANCELED_AFTER_COMPLETION"; + // ---------------------- + // Status + // ---------------------- + export type Status = + | "INITIATED" + | "PENDING" + | "COMPLETED" + | "SETTLED" + | "FAILED" + | "CANCELED_AFTER_COMPLETION"; -// ---------------------- -// Transaction Model -// ---------------------- -export interface Transaction { - id: string; - status: TransactionStatus | string; - type: string; - source: PaymentSource; - confirm: boolean; - metadata?: Metadata; - provider: string; -} + // ---------------------- + // Model + // ---------------------- + export interface Item { + id: string; + status: Status | string; + type: string; + source: Payment.Source; + confirm: boolean; + metadata?: Payment.Metadata; + provider: string; + } -// ---------------------- -// Paginated Response -// ---------------------- -export interface TransactionList { - count: number; - transaction_list: Transaction[]; -} + // ---------------------- + // List payload + // ---------------------- + export interface ListData { + count: number; + transaction_list: Item[]; + } + + // ---------------------- + // Settlement + // ---------------------- + export interface SettlementRequest { + charge_id: string; + amount: number; + status: Status; + } -// ---------------------- -// Settlement -// ---------------------- -export interface SettlementRequest { - charge_id: string; - amount: number; - status: TransactionStatus; + // ---------------------- + // Responses + // ---------------------- + export type ListResponse = ApiResponse; + export type GetResponse = ApiResponse; + export type SettlementResponse = ApiResponse; } -// ---------------------- -// API Responses -// ---------------------- -export type GetAllTransactionsResponse = ApiResponse; -export type GetTransactionResponse = ApiResponse; -export type SettlementResponse = ApiResponse; diff --git a/packages/api/src/types/transfer.ts b/packages/api/src/types/transfer.ts index 65721abd..0af593e7 100644 --- a/packages/api/src/types/transfer.ts +++ b/packages/api/src/types/transfer.ts @@ -1,71 +1,68 @@ -// ---------------------- -// Common Types - import { ApiResponse } from "./common"; -// ---------------------- -interface Customer { - id: string; -} - -interface Source { - amount: number; - currency: string; - customer?: Customer; -} +export namespace Transfer { + // ---------------------- + // Common + // ---------------------- + export interface CustomerRef { + id: string; + } -interface PaymentMethod { - id?: string; - type: string; - chain?: string; - evm_address?: string; -} + export interface Source { + amount: number; + currency: string; + customer?: CustomerRef; + } -interface Destination { - customer?: Customer; - payment_method?: PaymentMethod; -} + export interface PaymentMethod { + id?: string; + type: string; + chain?: string; + evm_address?: string; + } -type Metadata = Record; + export interface Destination { + customer?: CustomerRef; + payment_method?: PaymentMethod; + } -// ---------------------- -// Provider-specific Requests -// ---------------------- -export interface BrlaTransferRequest { - provider: "brla"; - source: Source & { currency: "brla" }; - destination: Destination; - metadata?: Metadata; -} + export type Metadata = Record; -export interface StripeTransferRequest { - provider: "stripe"; - source: Source & { currency: "usd" }; - destination: Destination & { - customer: Customer; - payment_method: { id: string; type: "bank" }; - }; - metadata?: Metadata; - provider_data?: { statement_descriptor?: string }; -} + // ---------------------- + // Provider-specific requests + // ---------------------- + export interface BrlaRequest { + provider: "brla"; + source: Source & { currency: "brla" }; + destination: Destination; + metadata?: Metadata; + } -// ---------------------- -// Union Request Type -// ---------------------- -export type CreateTransferRequest = BrlaTransferRequest | StripeTransferRequest; + export interface StripeRequest { + provider: "stripe"; + source: Source & { currency: "usd" }; + destination: Destination & { + customer: CustomerRef; + payment_method: { id: string; type: "bank" }; + }; + metadata?: Metadata; + provider_data?: { statement_descriptor?: string }; + } -// ---------------------- -// API Response -// ---------------------- + export type Request = BrlaRequest | StripeRequest; -export type CreateTransferResponse = ApiResponse; + // ---------------------- + // Response + // ---------------------- + export interface Data { + id: string; + status: string; // e.g. "created" + type: "transfer"; + source: Source; + destination: Destination; + metadata?: Metadata; + provider: string; + } -export interface TransferData { - id: string; - status: string; // e.g., "created" - type: "transfer"; - source: Source; - destination: Destination; - metadata?: Metadata; - provider: string; + export type Response = ApiResponse; } diff --git a/packages/api/src/types/webhook.ts b/packages/api/src/types/webhook.ts index d60874cd..8e77a780 100644 --- a/packages/api/src/types/webhook.ts +++ b/packages/api/src/types/webhook.ts @@ -1,54 +1,63 @@ -// ---------------------- -// Core Webhook Types - import { ApiResponse } from "./common"; -// ---------------------- -export interface WebhookData { - id: string; - url: string; - description?: string; - secret: string; - is_active: boolean; -} +export namespace Webhook { + // ---------------------- + // Data + // ---------------------- + export interface Data { + id: string; + url: string; + description?: string; + secret: string; + is_active: boolean; + } -export type PublicWebhookData = Omit; + export type PublicData = Omit; -// ---------------------- -// Requests -// ---------------------- -export interface RegisterWebhookRequest { - url: string; - description?: string; -} + // ---------------------- + // Requests + // ---------------------- + export interface RegisterRequest { + url: string; + description?: string; + } -export interface UpdateWebhookRequest { - url?: string; - description?: string; -} + export interface UpdateRequest { + url?: string; + description?: string; + } -// ---------------------- -// Notifications -// ---------------------- -export interface WebhookNotification { - id: string; - is_acknowledged: boolean; - event: string | null; - category: string | null; - data: any; -} + // ---------------------- + // Notifications + // ---------------------- + export interface Notification { + id: string; + is_acknowledged: boolean; + event: string | null; + category: string | null; + data: any; + } -// ---------------------- -// Responses -// ---------------------- -export type RegisterWebhookResponse = ApiResponse; -export type GetAllWebhooksResponse = ApiResponse; -export type GetWebhookNotificationResponse = ApiResponse; -export type ToggleWebhookResponse = ApiResponse; -export type GetAllWebhookNotificationResponse = ApiResponse<{ - count: number; - transaction_list: WebhookNotification[]; -}>; -export type GetWebhookResponse = ApiResponse; -export type UpdateWebhookResponse = ApiResponse; -export type DeleteWebhookResponse = ApiResponse<{ success: boolean }>; + export interface ListNotificationsQuery { + limit?: number; + offset?: number; + } + + export interface ListNotificationsData { + count: number; + transaction_list: Notification[]; + } + + // ---------------------- + // Responses + // ---------------------- + /** register, toggle, update (full webhook data) */ + export type Response = ApiResponse; + + export type GetResponse = ApiResponse; + export type ListResponse = ApiResponse; + export type DeleteResponse = ApiResponse<{ success: boolean }>; + + export type GetNotificationResponse = ApiResponse; + export type ListNotificationsResponse = ApiResponse; +} From edac706bdb45942ec13acfea51d95e72d2eb4d01 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Mon, 9 Feb 2026 18:15:49 +0600 Subject: [PATCH 014/143] feat: updated typescript types according to API --- packages/api/src/types/buy.ts | 52 ++--- packages/api/src/types/payment.ts | 231 +++++++------------ packages/api/src/types/paymentMethod.ts | 288 +++++++++++++----------- packages/api/src/types/provider.ts | 12 +- packages/api/src/types/sell.ts | 6 +- packages/api/src/types/transactions.ts | 2 + packages/api/src/types/transfer.ts | 100 ++++---- 7 files changed, 334 insertions(+), 357 deletions(-) diff --git a/packages/api/src/types/buy.ts b/packages/api/src/types/buy.ts index a2ec514e..d947870d 100644 --- a/packages/api/src/types/buy.ts +++ b/packages/api/src/types/buy.ts @@ -1,23 +1,22 @@ import { ApiResponse } from "./common"; export namespace Buy { - export interface WalletDetails { - address: string; - } - export interface PaymentMethod { - wallet_details: WalletDetails; - } - - export interface CurrencyInfo { - currency: string; + type: "customer_wallet"; + chain?: "ethereum" | "polygon" | "arbitrum" | "solana"; + evm_address: string; } - export interface Source extends CurrencyInfo { - customer_id: string; + export interface Source { + currency: "usd"; + amount?: number; } - export interface Destination extends CurrencyInfo { + export interface Destination { + currency: "usdc" | "usdt" | "usdb"; + customer: { + id: string; + }; payment_method: PaymentMethod; } @@ -26,34 +25,33 @@ export namespace Buy { } export interface ProviderResponse { - currency: string; - bank_name: string; - bank_address: string; - bank_routing_number: string; - bank_account_number: string; - bank_beneficiary_name: string; - bank_beneficiary_address: string; - payment_rails: string[]; // e.g., ["ach_push", "wire"] - deposit_message: string; + [key: string]: any; } export interface Transaction { id: string; status: string; // e.g., "captured" - type: string; // e.g., "buy" + type: "buy"; source: Source; - provider: string; + provider: "bridge" | "brla"; destination: Destination; - provider_data: ProviderData; provider_response: ProviderResponse; + created_at: string; + updated_at: string; } - export interface Request { - provider: string; // e.g., "bridge" + export interface Metadata { + [key: string]: any; + } + + export interface Bridge { + provider: "bridge"; source: Source; destination: Destination; - provider_data: ProviderData; + metadata?: Metadata; } + export type Request = Bridge; + export type Response = ApiResponse; } diff --git a/packages/api/src/types/payment.ts b/packages/api/src/types/payment.ts index 589684fe..889a2db4 100644 --- a/packages/api/src/types/payment.ts +++ b/packages/api/src/types/payment.ts @@ -1,4 +1,5 @@ import { ApiResponse } from "./common"; +import { PaymentMethod } from "./paymentMethod"; export namespace Payment { // ---------------------------------------- @@ -9,20 +10,7 @@ export namespace Payment { } export interface Metadata { - [key: string]: string; - } - - export interface BillingAddress { - house_number?: string; - street_number?: string; - street_name?: string; - postal_code?: string; - city?: string; - state?: string; - country_code?: string; - address_line1?: string; - address_line2?: string; - zip_code?: string; + [key: string]: any; } export interface FraudCheckData { @@ -45,156 +33,122 @@ export namespace Payment { } export interface ProviderResponse { - qr_code?: string; - qr_code_url?: string; [key: string]: any; } - // ---------------------------------------- - // Payment methods - // ---------------------------------------- - export interface MethodBase { - id?: string; - type: - | "bank" - | "card" - | "pix" - | "customer_wallet" - | "virtual_account" - | "liquidation_address" - | "plaid"; - status?: string; - provider?: string; - metadata?: Metadata; - } - - export interface BankMethod extends MethodBase { - type: "bank"; - bank_name?: string; - bank_account_name?: string; - bank_account_number?: string; - bank_branch_code?: string; - bank_swift_code?: string; - bank_account_type?: string; - bank_routing_number?: string; - } - - export interface CardMethod extends MethodBase { + export interface PaymentMethod { type: "card"; - card_token?: string; - billing_address?: BillingAddress; - provider_response?: ProviderResponse; - } - - export interface PixMethod extends MethodBase { - type: "pix"; - pix_string?: string; - } - - export interface CustomerWalletMethod extends MethodBase { - type: "customer_wallet"; - evm_address?: string; - chain?: string; - currency?: string; - } - - export interface VirtualAccountMethod extends MethodBase { - type: "virtual_account"; - source_currency?: string; - destination_currency?: string; - chain?: string; - provider_response?: { source_deposit_instructions?: Record }; - provider_data?: Record; - destination_payment_method_id?: string; - } - - export interface LiquidationAddressMethod extends MethodBase { - type: "liquidation_address"; - source_currency?: string; - destination_currency?: string; - liquidation_address?: string; - provider_data?: Record; - destination_payment_method_id?: string; - } - - export interface PlaidMethod extends MethodBase { - type: "plaid"; - link_token?: string; - callback_url?: string; - link_token_expires_at?: string; + id?: string; } - export type MethodData = - | BankMethod - | CardMethod - | PixMethod - | CustomerWalletMethod - | VirtualAccountMethod - | LiquidationAddressMethod - | PlaidMethod; - - // ---------------------------------------- - // Source / destination - // ---------------------------------------- export interface Source { amount: number; currency: string; customer?: CustomerRef; - payment_method: MethodData; + payment_method: PaymentMethod; installments?: number; float_rate?: number; - capture_method?: "automatic" | "manual"; + capture_method: "automatic"; fraud_check?: FraudCheck; } - export interface Destination { - amount?: number; - currency?: string; - customer?: CustomerRef; - } - // ---------------------------------------- // Create payment (provider-specific requests) // ---------------------------------------- export interface MercadoPagoRequest { provider: "mercado_pago"; - source: Source & { + source: { + amount: number; currency: "COP"; - customer: CustomerRef; - payment_method: { type: "card"; card_token: string }; + customer: { + id: string; // UUID + }; + payment_method: { + type: "card"; + card_token: string; + }; capture_method: "automatic"; }; confirm?: boolean; - metadata?: Metadata; + metadata?: Record; } export interface PagarMeRequest { provider: "pagar_me"; - source: Source & { - currency: "brl"; - customer: CustomerRef; + source: { + amount: number; + currency: "BRL"; + customer: { + id: string; // UUID + }; payment_method: { - type: "card" | "pix"; - card_token?: string; - expiry_date?: string; + type: "card"; + id?: string; // if present, card_token and billing_address are forbidden + card_token?: string; // required when id is absent + billing_address?: { + house_number: string; + street_number: string; + street_name: string; + postal_code: string; + city: string; + state: string; + country_code: string; + }; // required when id is absent + }; + capture_method: "automatic" | "manual"; // from CARD_CAPTURE_METHOD + fraud_check: { + enabled: boolean; + provider?: "konduto"; // required when enabled=true + config?: { + sequence: string; // from FRAUD_SEQUENCE keys + threshold: string; + }; // required when enabled=true + data?: { + last_four_digits: string; // length 4 + card_expiration_date: string; // pattern MM/YYYY + card_holder_name: string; + }; // required when enabled=true }; - capture_method: "automatic" | "manual"; - fraud_check: FraudCheck & { provider?: "konduto" }; }; + total_installments?: number; // integer, min 1 confirm?: boolean; - metadata?: Metadata; + metadata?: Record; } export interface StripeRequest { provider: "stripe"; - source: Source & { - payment_method: { type: "card"; id?: string }; + source: { + amount: number; + currency: string; + customer?: { id?: string }; + payment_method: { + type: "card"; + id?: string; + }; + installments?: number; + float_rate?: number; capture_method: "automatic"; fraud_check?: { enabled: false }; }; - destination?: Destination; + destination?: { + amount?: number; + currency?: "usd"; + customer?: { id?: string }; + }; + fee?: { + bearer: "platform" | "connected_account"; + }; + flow?: "platform" | "destination"; + allocations?: Array<{ + type?: string; + receiver: { + type?: "platform" | "connected_account"; + id?: string; + }; + amount: number; + }>; confirm?: boolean; - metadata?: Metadata; + metadata?: Record; } export type Request = MercadoPagoRequest | PagarMeRequest | StripeRequest; @@ -202,33 +156,16 @@ export namespace Payment { // ---------------------------------------- // Payment responses (create / confirm / cancel) // ---------------------------------------- - export interface TransactionPayload { + export type Transaction = Request & { id: string; status: string; - type: string; - source: Source; - confirm: boolean; - metadata?: Metadata; - provider: string; + type: "payment"; + created_at: string; + updated_at: string; provider_response?: ProviderResponse; - } - - export type Response = ApiResponse; + }; - // ---------------------------------------- - // Customer payment methods (add / get / list / delete) - // ---------------------------------------- - export type AddMethodRequest = - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit; - - export type MethodResponse = ApiResponse; - export type ListMethodsResponse = ApiResponse; + export type Response = ApiResponse; export interface ListMethodsQuery { type?: string; diff --git a/packages/api/src/types/paymentMethod.ts b/packages/api/src/types/paymentMethod.ts index 59409d44..32bf7898 100644 --- a/packages/api/src/types/paymentMethod.ts +++ b/packages/api/src/types/paymentMethod.ts @@ -1,142 +1,162 @@ import { ApiResponse } from "./common"; export namespace PaymentMethod { - export interface Metadata { - [key: string]: string; - } - - export interface BillingAddress { - house_number?: string; - street_number?: string; - street_name?: string; - postal_code?: string; - city?: string; - state?: string; - country_code?: string; - address_line1?: string; - address_line2?: string; - zip_code?: string; - } - - export interface FraudCheckData { - last_four_digits?: string; - card_expiration_date?: string; - card_holder_name?: string; - } - - export interface FraudCheckConfig { - threshold?: "low" | "medium" | "high"; - sequence?: "fraud_before_auth" | "fraud_after_auth"; - action_on_fail?: "reject" | "review"; - } - - export interface FraudCheck { - enabled: boolean; - provider?: string; - config?: FraudCheckConfig; - data?: FraudCheckData; - } - - export interface ProviderResponse { - qr_code?: string; - qr_code_url?: string; - [key: string]: any; - } - - // ---------------------------------------- - // Payment methods - // ---------------------------------------- - export interface MethodBase { - id?: string; - type: - | "bank" - | "card" - | "pix" - | "customer_wallet" - | "virtual_account" - | "liquidation_address" - | "plaid"; - status?: string; - provider?: string; - metadata?: Metadata; - } - - export interface BankMethod extends MethodBase { - type: "bank"; - bank_name?: string; - bank_account_name?: string; - bank_account_number?: string; - bank_branch_code?: string; - bank_swift_code?: string; - bank_account_type?: string; - bank_routing_number?: string; - } - - export interface CardMethod extends MethodBase { - type: "card"; - card_token?: string; - billing_address?: BillingAddress; - provider_response?: ProviderResponse; - } - - export interface PixMethod extends MethodBase { - type: "pix"; - pix_string?: string; - } - - export interface CustomerWalletMethod extends MethodBase { - type: "customer_wallet"; - evm_address?: string; - chain?: string; - currency?: string; - } - - export interface VirtualAccountMethod extends MethodBase { - type: "virtual_account"; - source_currency?: string; - destination_currency?: string; - chain?: string; - provider_response?: { source_deposit_instructions?: Record }; - provider_data?: Record; - destination_payment_method_id?: string; - } - - export interface LiquidationAddressMethod extends MethodBase { - type: "liquidation_address"; - source_currency?: string; - destination_currency?: string; - liquidation_address?: string; - provider_data?: Record; - destination_payment_method_id?: string; - } - - export interface PlaidMethod extends MethodBase { - type: "plaid"; - link_token?: string; - callback_url?: string; - link_token_expires_at?: string; - } - - export type MethodData = - | BankMethod - | CardMethod - | PixMethod - | CustomerWalletMethod - | VirtualAccountMethod - | LiquidationAddressMethod - | PlaidMethod; + export interface BridgeBankAccount { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider?: string; // from PLATFORMS keys + currency?: string; // from CURRENCY keys (lowercase) + bank_name: string; + bank_account_number: string; // pattern: digits only + bank_routing_number: string; // pattern: digits only + bank_account_type: string; + bank_account_name: string; + billing_address: { + street_line_1: string; + street_line_2?: string; + city: string; + state: string; + postal_code: string; + country: string; + }; + metadata?: Record; + } + + export interface CrowdSplitBankAccount { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider?: string; // from PLATFORMS keys + bank_branch_code: string; + bank_account_number: string; // pattern: digits only + bank_account_name: string; + bank_account_type: string; // from SUBJECT_BANK_ACCOUNT_TYPE keys + bank_name: string; + bank_swift_code: string; + metadata?: Record; + } + + export interface StripeBankAccount { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider?: string; // from PLATFORMS keys + currency?: string; // from CURRENCY keys + bank_name: string; + bank_account_number: string; // pattern: digits only + bank_routing_number: string; // pattern: digits only + bank_account_type: string; + bank_account_name: string; + bank_metadata?: Record; + metadata?: Record; + } + + export interface MercadoPagoCard { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + card_details: { + card_token: string; + }; + metadata?: Record; + } + + export interface PagarMeCard { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + card_token: string; + billing_address: { + house_number: string; + street_number: string; + street_name: string; + postal_code: string; + city: string; + state: string; + country_code: string; // validated externally + }; + metadata?: Record; + } + + export interface StripeCard { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + metadata?: Record; + } + + export interface CrowdSplitCustomerWallet { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider?: string; // from PLATFORMS keys + evm_address: string; // validated as checksummed Ethereum address + chain: string; // from WALLET_CHAIN keys + currency: string; // from ASSET_TYPE keys + metadata?: Record; + } + + export interface BridgeLiquidationAddress { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + source_currency: string; // from ASSET_TYPE keys + destination_currency: string; // from CURRENCY keys + destination_payment_method_id: string; + provider_data?: { + destination_wire_message?: string; + destination_payment_rail: string; + chain: string; + }; + metadata?: Record; + } + + export interface CrowdSplitPix { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider?: string; // from PLATFORMS keys + pix_string: string; + metadata?: Record; + } + + export interface BridgePlaid { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + metadata?: Record; + } + + export interface BridgeVirtualAccount { + type: string; // from SUBJECT_PAYMENT_METHOD_TYPE keys + provider: string; // from PLATFORMS keys + source_currency: string; // from CURRENCY keys + destination_currency: string; // from ASSET_TYPE keys + provider_data?: { + chain: string; + evm_address: string; + }; + destination_payment_method_id?: string; // UUID v4 + metadata?: Record; + } + + // export type MethodData = + // | BankMethod + // | CardMethod + // | PixMethod + // | CustomerWalletMethod + // | VirtualAccountMethod + // | LiquidationAddressMethod + // | PlaidMethod; export type Request = - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit; - - export type Response = ApiResponse; - export type ListResponse = ApiResponse; + | BridgeBankAccount + | CrowdSplitBankAccount + | StripeBankAccount + | MercadoPagoCard + | PagarMeCard + | StripeCard + | CrowdSplitCustomerWallet + | BridgeLiquidationAddress + | CrowdSplitPix + | BridgePlaid + | BridgeVirtualAccount; + + export type ResponseData = Request & { + id: string; + status: string; + created_at: string; + updated_at: string; + }; + + export type Response = ApiResponse; + export type ListResponse = ApiResponse; export interface ListQuery { type?: string; diff --git a/packages/api/src/types/provider.ts b/packages/api/src/types/provider.ts index 95894a1e..757c11c7 100644 --- a/packages/api/src/types/provider.ts +++ b/packages/api/src/types/provider.ts @@ -54,10 +54,13 @@ export namespace Provider { // ---------------------- export interface RegistrationStatus { provider: string; - status: string; + status: string; // e.g., "created" target_role: string | null; provider_response: any | null; rejection_reason: string | null; + readiness: any | null; + created_at: string; + updated_at: string; } export type GetRegistrationStatusResponse = ApiResponse; @@ -85,7 +88,12 @@ export namespace Provider { status: string; provider: string; target_role: string; + provider_response: any | null; + rejection_reason: string | null; + readiness: any | null; + created_at: string; + updated_at: string; } - export type Response = ApiResponse; + export type Response = ApiResponse; } diff --git a/packages/api/src/types/sell.ts b/packages/api/src/types/sell.ts index c4a0fef0..7083e6e7 100644 --- a/packages/api/src/types/sell.ts +++ b/packages/api/src/types/sell.ts @@ -50,11 +50,13 @@ export namespace Sell { export interface Transaction { id: string; - status: "created" | string; + status: string; // e.g., "created" type: "sell"; source: TransactionSource; - provider: "avenia"; + provider: string; destination: TransactionDestination; + created_at: string; + updated_at: string; } // ---------------------- diff --git a/packages/api/src/types/transactions.ts b/packages/api/src/types/transactions.ts index 26b7b989..65547d59 100644 --- a/packages/api/src/types/transactions.ts +++ b/packages/api/src/types/transactions.ts @@ -40,6 +40,8 @@ export namespace Transaction { confirm: boolean; metadata?: Payment.Metadata; provider: string; + created_at: string; + updated_at: string; } // ---------------------- diff --git a/packages/api/src/types/transfer.ts b/packages/api/src/types/transfer.ts index 0af593e7..401efcec 100644 --- a/packages/api/src/types/transfer.ts +++ b/packages/api/src/types/transfer.ts @@ -1,68 +1,78 @@ import { ApiResponse } from "./common"; export namespace Transfer { - // ---------------------- - // Common - // ---------------------- - export interface CustomerRef { - id: string; - } - - export interface Source { - amount: number; - currency: string; - customer?: CustomerRef; - } - - export interface PaymentMethod { - id?: string; - type: string; - chain?: string; - evm_address?: string; - } - - export interface Destination { - customer?: CustomerRef; - payment_method?: PaymentMethod; - } - - export type Metadata = Record; - - // ---------------------- - // Provider-specific requests - // ---------------------- export interface BrlaRequest { provider: "brla"; - source: Source & { currency: "brla" }; - destination: Destination; - metadata?: Metadata; + source: { + amount: number; // integer, positive + currency: "brla"; // from ASSET_TYPE.BRLA + customer?: { + id: string; + }; + }; + destination: { + customer?: { + id: string; // required if payment_method.id is provided + }; + payment_method?: { + id?: string; // if present, chain and evm_address are forbidden + type: string; // from TRANSFER_PAYMENT_METHOD_TYPE keys + chain?: string; // from WALLET_CHAIN values, required when id is absent + evm_address?: string; // required when id is absent, validated as checksummed address + }; + }; + metadata?: Record; + provider_data?: { + wallet_memo?: string; // max 50 characters + }; + } + + export interface PagarMeRequest { + provider: "pagar_me"; + source: { + amount: number; // integer, positive + currency: "brl"; // from CURRENCY.BRL + }; + metadata?: Record; } export interface StripeRequest { provider: "stripe"; - source: Source & { currency: "usd" }; - destination: Destination & { - customer: CustomerRef; - payment_method: { id: string; type: "bank" }; + source: { + amount: number; // integer, positive + currency: "usd"; // from CURRENCY.USD + customer: { + id: string; // must equal destination.customer.id + }; + }; + destination: { + customer: { + id: string; // must equal source.customer.id + }; + payment_method: { + id: string; + type: "BANK"; + }; + }; + metadata?: Record; + provider_data?: { + statement_descriptor?: string; }; - metadata?: Metadata; - provider_data?: { statement_descriptor?: string }; } - export type Request = BrlaRequest | StripeRequest; + export type Request = BrlaRequest | PagarMeRequest | StripeRequest; // ---------------------- // Response // ---------------------- - export interface Data { + export type Data = Request & { id: string; status: string; // e.g. "created" type: "transfer"; - source: Source; - destination: Destination; - metadata?: Metadata; provider: string; - } + created_at: string; + updated_at: string; + }; export type Response = ApiResponse; } From 7b504bb49d36f08f2beb5fdeeb79a24f58a3530e Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Mon, 9 Feb 2026 18:29:38 +0600 Subject: [PATCH 015/143] fix: fixed webhook delete function type --- packages/api/src/services/webhookService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/services/webhookService.ts b/packages/api/src/services/webhookService.ts index 127b18d9..292d58a1 100644 --- a/packages/api/src/services/webhookService.ts +++ b/packages/api/src/services/webhookService.ts @@ -9,7 +9,7 @@ export interface WebhookService { get(id: string): Promise; update(id: string, webhook: Webhook.UpdateRequest): Promise; toggle(id: string): Promise; - delete(id: string): Promise; + delete(id: string): Promise; listNotifications(params?: { limit?: number; offset?: number; @@ -112,11 +112,11 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ } }, - async delete(id: string): Promise { + async delete(id: string): Promise { try { const token = await client.getAccessToken(); - const response = await httpClient.delete( + const response = await httpClient.delete( `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, { headers: { Authorization: `Bearer ${token}` }, From 13965ac322240349094c5b182e1f163e3e3fd42b Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Mon, 9 Feb 2026 23:08:32 +0600 Subject: [PATCH 016/143] added changeset --- .changeset/soft-plums-say.md | 5 +++++ packages/api/src/services/buyService.ts | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/soft-plums-say.md diff --git a/.changeset/soft-plums-say.md b/.changeset/soft-plums-say.md new file mode 100644 index 00000000..6f3a0641 --- /dev/null +++ b/.changeset/soft-plums-say.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/api": major +--- + +Updated types of all request response diff --git a/packages/api/src/services/buyService.ts b/packages/api/src/services/buyService.ts index 03e2bfa4..6e910e9a 100644 --- a/packages/api/src/services/buyService.ts +++ b/packages/api/src/services/buyService.ts @@ -1,7 +1,6 @@ import type { Buy, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { err } from "../types"; -import { OakError } from "../utils/errorHandler"; export interface BuyService { create(buyRequest: Buy.Request): Promise>; From 8227b2075e4f33b667816a81b365c3bcdf586671 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Tue, 10 Feb 2026 17:50:17 +0600 Subject: [PATCH 017/143] feat: added refund service --- packages/api/src/services/index.ts | 3 ++ packages/api/src/services/providerService.ts | 3 +- packages/api/src/services/refundService.ts | 29 ++++++++++++++++++++ packages/api/src/types/index.ts | 1 + packages/api/src/types/refund.ts | 18 ++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/api/src/services/refundService.ts create mode 100644 packages/api/src/types/refund.ts diff --git a/packages/api/src/services/index.ts b/packages/api/src/services/index.ts index a730602e..7c2873b9 100644 --- a/packages/api/src/services/index.ts +++ b/packages/api/src/services/index.ts @@ -30,3 +30,6 @@ export type { BuyService } from "./buyService"; export { createWebhookService } from "./webhookService"; export type { WebhookService } from "./webhookService"; + +export { createRefundService } from "./refundService"; +export type { RefundService } from "./refundService"; diff --git a/packages/api/src/services/providerService.ts b/packages/api/src/services/providerService.ts index 89e55c8e..24f9be9c 100644 --- a/packages/api/src/services/providerService.ts +++ b/packages/api/src/services/providerService.ts @@ -1,6 +1,5 @@ -import type { OakClient, Provider, Result } from "../types"; +import { err, OakClient, Provider, Result } from "../types"; import { httpClient } from "../utils/httpClient"; -import { err } from "../types"; export interface ProviderService { getSchema( diff --git a/packages/api/src/services/refundService.ts b/packages/api/src/services/refundService.ts new file mode 100644 index 00000000..9e3a72ff --- /dev/null +++ b/packages/api/src/services/refundService.ts @@ -0,0 +1,29 @@ +import { err, OakClient, Refund, Result } from "../types"; +import { httpClient } from "../utils/httpClient"; + +export interface RefundService { + create( + paymentId: string, + refund: Refund.Request, + ): Promise>; +} + +export const createRefundService = (client: OakClient): RefundService => ({ + async create( + paymentId: string, + refund: Refund.Request, + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } + return httpClient.post( + `${client.config.baseUrl}/api/v1/payments/${paymentId}/refund`, + refund, + { + headers: { Authorization: `Bearer ${token.value}` }, + retryOptions: client.retryOptions, + }, + ); + }, +}); diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index e1d36679..d6a49558 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -13,3 +13,4 @@ export * from "./sell"; export * from "./plan"; export * from "./buy"; export * from "./result"; +export * from "./refund"; diff --git a/packages/api/src/types/refund.ts b/packages/api/src/types/refund.ts new file mode 100644 index 00000000..8f3e3c65 --- /dev/null +++ b/packages/api/src/types/refund.ts @@ -0,0 +1,18 @@ +import { ApiResponse } from "./common"; + +export namespace Refund { + export interface Request { + amount?: number; + metadata?: Record; + } + + interface Data { + id: string; + status: string; // e.g., "created" + type: "refund"; + amount?: number; + provider?: string; + } + + export type Response = ApiResponse; +} From e3d3946e91be340318d7c645873479d369b4863c Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Tue, 10 Feb 2026 21:46:01 +0600 Subject: [PATCH 018/143] feat: updated unit test for refund service --- .../__tests__/unit/customerService.test.ts | 24 ++--- packages/api/__tests__/unit/services.test.ts | 96 +++++++++++++------ 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index d7092406..dab3fe6f 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -3,13 +3,7 @@ import { Crowdsplit } from "../../src/products/crowdsplit"; import { httpClient } from "../../src/utils/httpClient"; import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; -import { - OakClientConfig, - CreateCustomerRequest, - CustomerListQueryParams, - ok, - err, -} from "../../src/types"; +import { OakClientConfig, Customer, ok, err } from "../../src/types"; const SANDBOX_URL = "https://api.usecrowdpay.xyz"; @@ -38,16 +32,14 @@ describe("CustomerService - Unit", () => { ...config, retryOptions, }); - jest - .spyOn(client, "getAccessToken") - .mockResolvedValue(ok("fake-token")); + jest.spyOn(client, "getAccessToken").mockResolvedValue(ok("fake-token")); customers = Crowdsplit(client).customers; jest.clearAllMocks(); }); describe("create", () => { it("should call POST /api/v1/customers with correct payload", async () => { - const request: CreateCustomerRequest = { email: "test@example.com" }; + const request: Customer.Request = { email: "test@example.com" }; const mockResponse = { data: { email: "test@example.com" } }; (httpClient.post as jest.Mock).mockResolvedValue(ok(mockResponse)); @@ -60,7 +52,7 @@ describe("CustomerService - Unit", () => { expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), - }) + }), ); expect(result).toEqual(ok(mockResponse)); }); @@ -89,7 +81,7 @@ describe("CustomerService - Unit", () => { expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), - }) + }), ); expect(result).toEqual(ok(mockResponse)); }); @@ -97,7 +89,7 @@ describe("CustomerService - Unit", () => { describe("list", () => { it("should call GET /api/v1/customers with query params", async () => { - const params: CustomerListQueryParams = { limit: 10, offset: 5 }; + const params: Customer.ListQueryParams = { limit: 10, offset: 5 }; const mockResponse = { data: { count: 1, customer_list: [] } }; (httpClient.get as jest.Mock).mockResolvedValue(ok(mockResponse)); @@ -108,7 +100,7 @@ describe("CustomerService - Unit", () => { expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), - }) + }), ); expect(result).toEqual(ok(mockResponse)); }); @@ -128,7 +120,7 @@ describe("CustomerService - Unit", () => { expect.objectContaining({ headers: { Authorization: "Bearer fake-token" }, retryOptions: expect.objectContaining(retryOptions), - }) + }), ); expect(result).toEqual(ok(mockResponse)); }); diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index 0058fe2d..c29d673e 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -6,6 +6,7 @@ import { createPaymentService, createPlanService, createProviderService, + createRefundService, createSellService, createTransactionService, createTransferService, @@ -78,7 +79,7 @@ const expectSuccess = async (options: { expect(result).toEqual(ok(response)); expect(mockedHttpClient[options.httpMethod]).toHaveBeenCalledWith( - ...options.expectedArgs + ...options.expectedArgs, ); expect(options.client.getAccessToken).toHaveBeenCalled(); }; @@ -94,9 +95,11 @@ const expectFailure = async (options: { 400, { msg: options.errorMessage }, undefined, - options.error + options.error, + ); + mockedHttpClient[options.httpMethod].mockResolvedValue( + err(apiError) as never, ); - mockedHttpClient[options.httpMethod].mockResolvedValue(err(apiError) as never); const result = await options.call(); expect(result).toEqual(err(expect.any(ApiError))); @@ -204,11 +207,13 @@ describe("Crowdsplit services (Unit)", () => { const tokenErrorClient = makeClientWithTokenError(); const tokenErrorService = createCustomerService(tokenErrorClient); - await expectTokenFailure(() => tokenErrorService.create({ email: "t@t.com" })); + await expectTokenFailure(() => + tokenErrorService.create({ email: "t@t.com" }), + ); await expectTokenFailure(() => tokenErrorService.get("cust-1")); await expectTokenFailure(() => tokenErrorService.list({ limit: 1 })); await expectTokenFailure(() => - tokenErrorService.update("cust-1", { email: "t@t.com" }) + tokenErrorService.update("cust-1", { email: "t@t.com" }), ); }); @@ -331,21 +336,22 @@ describe("Crowdsplit services (Unit)", () => { const tokenErrorClient = makeClientWithTokenError(); const tokenPaymentService = createPaymentService(tokenErrorClient); - const tokenPaymentMethodService = createPaymentMethodService(tokenErrorClient); + const tokenPaymentMethodService = + createPaymentMethodService(tokenErrorClient); await expectTokenFailure(() => tokenPaymentService.create(payment)); await expectTokenFailure(() => tokenPaymentService.confirm("pay-1")); await expectTokenFailure(() => tokenPaymentService.cancel("pay-1")); await expectTokenFailure(() => - tokenPaymentMethodService.add("cust-1", paymentMethod) + tokenPaymentMethodService.add("cust-1", paymentMethod), ); await expectTokenFailure(() => - tokenPaymentMethodService.get("cust-1", "pay-1") + tokenPaymentMethodService.get("cust-1", "pay-1"), ); await expectTokenFailure(() => - tokenPaymentMethodService.list("cust-1", { type: "pix" }) + tokenPaymentMethodService.list("cust-1", { type: "pix" }), ); await expectTokenFailure(() => - tokenPaymentMethodService.delete("cust-1", "pm-1") + tokenPaymentMethodService.delete("cust-1", "pm-1"), ); }); @@ -399,7 +405,7 @@ describe("Crowdsplit services (Unit)", () => { }); mockedHttpClient.post.mockResolvedValueOnce( - err(new ApiError("Bad", 400, { msg: "Bad" })) as never + err(new ApiError("Bad", 400, { msg: "Bad" })) as never, ); const badResult = await service.submitRegistration("cust-1", registration); expect(badResult).toEqual(err(expect.any(ApiError))); @@ -410,7 +416,7 @@ describe("Crowdsplit services (Unit)", () => { } mockedHttpClient.post.mockResolvedValueOnce( - err(new ApiError("HTTP error", 500, null)) as never + err(new ApiError("HTTP error", 500, null)) as never, ); const boomResult = await service.submitRegistration("cust-1", registration); expect(boomResult).toEqual(err(expect.any(ApiError))); @@ -424,10 +430,10 @@ describe("Crowdsplit services (Unit)", () => { const tokenErrorService = createProviderService(tokenErrorClient); await expectTokenFailure(() => tokenErrorService.getSchema(request)); await expectTokenFailure(() => - tokenErrorService.getRegistrationStatus("cust-1") + tokenErrorService.getRegistrationStatus("cust-1"), ); await expectTokenFailure(() => - tokenErrorService.submitRegistration("cust-1", registration) + tokenErrorService.submitRegistration("cust-1", registration), ); }); @@ -435,12 +441,15 @@ describe("Crowdsplit services (Unit)", () => { const client = makeClient(); const service = createTransactionService(client); const authConfig = getAuthConfig(client); - const settlement = { charge_id: "ch_1", amount: 10, status: "SETTLED" } as any; + const settlement = { + charge_id: "ch_1", + amount: 10, + status: "SETTLED", + } as any; await expectSuccess({ client, - call: () => - service.list({ type_list: "refund", status: undefined }), + call: () => service.list({ type_list: "refund", status: undefined }), httpMethod: "get", expectedArgs: [ `${SANDBOX_URL}/api/v1/transactions?type_list=refund`, @@ -484,11 +493,11 @@ describe("Crowdsplit services (Unit)", () => { const tokenErrorClient = makeClientWithTokenError(); const tokenErrorService = createTransactionService(tokenErrorClient); await expectTokenFailure(() => - tokenErrorService.list({ type_list: "refund" }) + tokenErrorService.list({ type_list: "refund" }), ); await expectTokenFailure(() => tokenErrorService.get("txn-1")); await expectTokenFailure(() => - tokenErrorService.settle("txn-1", settlement) + tokenErrorService.settle("txn-1", settlement), ); }); @@ -662,7 +671,9 @@ describe("Crowdsplit services (Unit)", () => { await expectTokenFailure(() => tokenErrorService.publish("plan-1")); await expectTokenFailure(() => tokenErrorService.details("plan-1")); await expectTokenFailure(() => tokenErrorService.list({ page_no: 1 })); - await expectTokenFailure(() => tokenErrorService.update("plan-1", planRequest)); + await expectTokenFailure(() => + tokenErrorService.update("plan-1", planRequest), + ); await expectTokenFailure(() => tokenErrorService.delete("plan-1")); }); @@ -687,21 +698,21 @@ describe("Crowdsplit services (Unit)", () => { err( new ApiError("This URL is Already Registered!", 409, { msg: "This URL is Already Registered!", - }) - ) as never + }), + ) as never, ); const duplicateResult = await service.register(webhook); expect(duplicateResult).toEqual(err(expect.any(ApiError))); if ("ok" in duplicateResult && !duplicateResult.ok) { if (duplicateResult.error instanceof ApiError) { expect(duplicateResult.error.message).toContain( - "This URL is Already Registered!" + "This URL is Already Registered!", ); } } mockedHttpClient.post.mockResolvedValueOnce( - err(new ApiError("HTTP error", 500, null)) as never + err(new ApiError("HTTP error", 500, null)) as never, ); const failResult = await service.register(webhook); expect(failResult).toEqual(err(expect.any(ApiError))); @@ -727,7 +738,10 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.get("wh-1"), httpMethod: "get", - expectedArgs: [`${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, authConfig], + expectedArgs: [ + `${SANDBOX_URL}/api/v1/merchant/webhooks/wh-1`, + authConfig, + ], }); await expectFailure({ call: () => service.get("wh-1"), @@ -784,8 +798,7 @@ describe("Crowdsplit services (Unit)", () => { await expectSuccess({ client, - call: () => - service.listNotifications({ limit: 1, offset: undefined }), + call: () => service.listNotifications({ limit: 1, offset: undefined }), httpMethod: "get", expectedArgs: [ `${SANDBOX_URL}/api/v1/merchant/webhooks/notifications?limit=1`, @@ -822,8 +835,35 @@ describe("Crowdsplit services (Unit)", () => { await expectTokenFailure(() => tokenErrorService.toggle("wh-1")); await expectTokenFailure(() => tokenErrorService.delete("wh-1")); await expectTokenFailure(() => - tokenErrorService.listNotifications({ limit: 1 }) + tokenErrorService.listNotifications({ limit: 1 }), ); await expectTokenFailure(() => tokenErrorService.getNotification("wh-1")); }); + + it("refund service methods", async () => { + const client = makeClient(); + const service = createRefundService(client); + const authConfig = getAuthConfig(client); + const refund = { amount: 1 } as any; + + await expectSuccess({ + client, + call: () => service.create("pay-1", refund), + httpMethod: "post", + expectedArgs: [ + `${SANDBOX_URL}/api/v1/payments/pay-1/refund`, + refund, + authConfig, + ], + }); + await expectFailure({ + call: () => service.create("pay-1", refund), + httpMethod: "post", + errorMessage: "Failed to create refund", + }); + + const tokenErrorClient = makeClientWithTokenError(); + const tokenErrorService = createRefundService(tokenErrorClient); + await expectTokenFailure(() => tokenErrorService.create("pay-1", refund)); + }); }); From 98027237b32249e6bf82162aad452ed1c0844482 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 13 Feb 2026 19:27:10 +0600 Subject: [PATCH 019/143] fix: handle non-JSON error responses in httpClient --- packages/api/__tests__/unit/utils.test.ts | 183 +++++++++------------- packages/api/src/utils/httpClient.ts | 32 ++-- 2 files changed, 98 insertions(+), 117 deletions(-) diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index d9b3bd40..31d96ca6 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -71,16 +71,27 @@ describe("httpClient", () => { const retryOptions = { maxNumberOfRetries: 0, delay: 0 }; let fetchMock: jest.Mock; + const mockResponse = (options: { + ok: boolean; + status?: number; + body?: unknown; + headers?: Headers; + }) => ({ + ok: options.ok, + status: options.status ?? (options.ok ? 200 : 400), + headers: options.headers, + text: jest.fn().mockResolvedValue( + options.body !== undefined ? JSON.stringify(options.body) : "" + ), + }); + beforeEach(() => { fetchMock = jest.fn(); (globalThis as any).fetch = fetchMock; }); it("post returns response body", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.post( "https://api.test/post", @@ -97,10 +108,7 @@ describe("httpClient", () => { }); it("post merges custom headers", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.post( "https://api.test/post", @@ -117,11 +125,7 @@ describe("httpClient", () => { }); it("post throws error for non-ok response", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 400, - json: jest.fn().mockResolvedValue({ msg: "bad" }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 400, body: { msg: "bad" } })); const result = await httpClient.post("https://api.test/post", { data: 1 }, { retryOptions, @@ -135,12 +139,12 @@ describe("httpClient", () => { }); it("post includes response headers in ApiError", async () => { - fetchMock.mockResolvedValue({ + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 400, + body: { msg: "bad" }, headers: new Headers({ "retry-after": "1" }), - json: jest.fn().mockResolvedValue({ msg: "bad" }), - }); + })); const result = await httpClient.post("https://api.test/post", { data: 1 }, { retryOptions, @@ -153,10 +157,7 @@ describe("httpClient", () => { }); it("get returns response body", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.get("https://api.test/get", { retryOptions, @@ -170,10 +171,7 @@ describe("httpClient", () => { }); it("get merges custom headers", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.get("https://api.test/get", { retryOptions, @@ -188,11 +186,7 @@ describe("httpClient", () => { }); it("get throws error for non-ok response", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 404, - json: jest.fn().mockResolvedValue({ msg: "missing" }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 404, body: { msg: "missing" } })); const result = await httpClient.get("https://api.test/get", { retryOptions, @@ -206,11 +200,7 @@ describe("httpClient", () => { }); it("get uses fallback error message when missing", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 500, - json: jest.fn().mockResolvedValue(null), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 500, body: {} })); const result = await httpClient.get("https://api.test/get", { retryOptions, @@ -223,10 +213,7 @@ describe("httpClient", () => { }); it("put returns response body", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.put( "https://api.test/put", @@ -243,10 +230,7 @@ describe("httpClient", () => { }); it("put merges custom headers", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.put( "https://api.test/put", @@ -263,11 +247,7 @@ describe("httpClient", () => { }); it("put throws error for non-ok response", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 500, - json: jest.fn().mockResolvedValue({ msg: "boom" }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 500, body: { msg: "boom" } })); const result = await httpClient.put("https://api.test/put", { data: 2 }, { retryOptions, @@ -281,11 +261,7 @@ describe("httpClient", () => { }); it("put uses fallback error message when missing", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 500, - json: jest.fn().mockResolvedValue(null), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 500, body: {} })); const result = await httpClient.put("https://api.test/put", { data: 2 }, { retryOptions, @@ -298,10 +274,7 @@ describe("httpClient", () => { }); it("patch returns response body and omits body when undefined", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.patch( "https://api.test/patch", @@ -318,10 +291,7 @@ describe("httpClient", () => { }); it("patch sends body when provided", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.patch("https://api.test/patch", { data: 9 }, { retryOptions }); @@ -334,10 +304,7 @@ describe("httpClient", () => { }); it("patch merges custom headers", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.patch( "https://api.test/patch", @@ -354,11 +321,7 @@ describe("httpClient", () => { }); it("patch throws error for non-ok response", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 422, - json: jest.fn().mockResolvedValue({ msg: "invalid" }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 422, body: { msg: "invalid" } })); const result = await httpClient.patch("https://api.test/patch", { data: 3 }, { retryOptions, @@ -372,11 +335,7 @@ describe("httpClient", () => { }); it("patch uses fallback error message when missing", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 422, - json: jest.fn().mockResolvedValue(null), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 422, body: {} })); const result = await httpClient.patch("https://api.test/patch", { data: 3 }, { retryOptions, @@ -389,10 +348,7 @@ describe("httpClient", () => { }); it("delete returns response body", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.delete("https://api.test/delete", { retryOptions, @@ -406,10 +362,7 @@ describe("httpClient", () => { }); it("delete merges custom headers", async () => { - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); const result = await httpClient.delete("https://api.test/delete", { retryOptions, @@ -427,10 +380,7 @@ describe("httpClient", () => { const previousVersion = process.env.OAK_VERSION; delete process.env.OAK_VERSION; - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); let isolatedClient: typeof httpClient; jest.isolateModules(() => { @@ -459,10 +409,7 @@ describe("httpClient", () => { const previousVersion = process.env.OAK_VERSION; process.env.OAK_VERSION = "9.9.9"; - fetchMock.mockResolvedValue({ - ok: true, - json: jest.fn().mockResolvedValue({ ok: true }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: true, body: { ok: true } })); let isolatedClient: typeof httpClient; jest.isolateModules(() => { @@ -487,11 +434,7 @@ describe("httpClient", () => { }); it("delete throws error for non-ok response", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 401, - json: jest.fn().mockResolvedValue({ msg: "unauthorized" }), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 401, body: { msg: "unauthorized" } })); const result = await httpClient.delete("https://api.test/delete", { retryOptions, @@ -505,11 +448,7 @@ describe("httpClient", () => { }); it("delete uses fallback error message when missing", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 401, - json: jest.fn().mockResolvedValue(null), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 401, body: {} })); const result = await httpClient.delete("https://api.test/delete", { retryOptions, @@ -522,11 +461,7 @@ describe("httpClient", () => { }); it("post uses fallback error message when missing", async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 400, - json: jest.fn().mockResolvedValue(null), - }); + fetchMock.mockResolvedValue(mockResponse({ ok: false, status: 400, body: {} })); const result = await httpClient.post("https://api.test/post", { data: 1 }, { retryOptions, @@ -541,7 +476,8 @@ describe("httpClient", () => { it("post returns ParseError when response body parsing fails", async () => { fetchMock.mockResolvedValue({ ok: true, - json: jest.fn().mockRejectedValue(new Error("bad json")), + status: 200, + text: jest.fn().mockResolvedValue("not valid json {{{"), }); const result = await httpClient.post("https://api.test/post", { data: 1 }, { @@ -610,6 +546,41 @@ describe("httpClient", () => { expect(result.error.message).toBe("Retry failed after maximum attempts"); } }); + + it("handles empty response body", async () => { + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + text: jest.fn().mockResolvedValue(""), + }); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value).toEqual({}); + } + }); + + it("returns ApiError with rawText when error response has invalid JSON", async () => { + fetchMock.mockResolvedValue({ + ok: false, + status: 500, + headers: new Headers(), + text: jest.fn().mockResolvedValue("Internal Server Error"), + }); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(500); + expect((result.error as ApiError).body).toEqual({ rawText: "Internal Server Error" }); + } + }); }); describe("withRetry", () => { diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index 2bff3ef8..170ad8f3 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -27,9 +27,18 @@ const mergeHeaders = (headers?: Record) => ({ ...(headers ?? {}), }); -const parseResponseBody = async (response: Response) => { - const body = await response.json(); - return body ?? {}; +type ParseResult = + | { success: true; data: unknown; error?: undefined } + | { success: false; data?: undefined; error: Error }; + +const parseJsonSafe = (text: string): ParseResult => { + try { + return { success: true, data: JSON.parse(text) }; + } catch (error) { + /* istanbul ignore next -- defensive: JSON.parse always throws Error */ + const err = error instanceof Error ? error : new Error(String(error)); + return { success: false, error: err }; + } }; const toHeadersRecord = (headers?: Headers): Record => { @@ -83,18 +92,19 @@ const request = async ( throw new NetworkError("Network error", error); } - let parsedBody: unknown; - try { - parsedBody = await parseResponseBody(response); - } catch (error) { - throw new ParseError("Failed to parse response body", error); - } + const text = await response.text(); + const parseResult: ParseResult = text ? parseJsonSafe(text) : { success: true, data: {} }; if (!response.ok) { - throw toApiError(response, parsedBody); + const body = parseResult.success ? parseResult.data : { rawText: text }; + throw toApiError(response, body); + } + + if (!parseResult.success) { + throw new ParseError("Failed to parse response body", parseResult.error); } - return parsedBody as T; + return parseResult.data as T; }, { ...config.retryOptions, signal: config.signal }); return ok(responseBody); From c1f1d5e440e14332369f9ad08632590c74edd70c Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 13 Feb 2026 19:32:25 +0600 Subject: [PATCH 020/143] fix: increase timeout for customerService integration tests --- .../__tests__/integration/authService.test.ts | 16 +++++++----- .../integration/customerService.test.ts | 26 +++++++++++++------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/api/__tests__/integration/authService.test.ts index dcbad3a9..ed8994be 100644 --- a/packages/api/__tests__/integration/authService.test.ts +++ b/packages/api/__tests__/integration/authService.test.ts @@ -2,13 +2,15 @@ import { createOakClient } from "../../src"; import { getConfigFromEnv } from "../config"; import { ApiError } from "../../src/utils/errorHandler"; +const INTEGRATION_TEST_TIMEOUT = 30000; + describe("Auth (Integration)", () => { const client = createOakClient({ ...getConfigFromEnv(), retryOptions: { - maxNumberOfRetries: 1, - delay: 100, - backoffFactor: 1, + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, }, }); @@ -19,7 +21,7 @@ describe("Auth (Integration)", () => { expect(response.value.access_token).toBeDefined(); expect(response.value.expires_in).toBeGreaterThan(0); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should return the same token if not expired", async () => { const firstResult = await client.getAccessToken(); @@ -29,7 +31,7 @@ describe("Auth (Integration)", () => { if (firstResult.ok && secondResult.ok) { expect(secondResult.value).toBe(firstResult.value); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should refresh token if expired", async () => { const originalNow = Date.now; @@ -46,7 +48,7 @@ describe("Auth (Integration)", () => { expect(newTokenResult.value).toBeDefined(); expect(newTokenResult.value).not.toBeNull(); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should return error on invalid credentials", async () => { const badClient = createOakClient({ @@ -65,5 +67,5 @@ describe("Auth (Integration)", () => { if (!result.ok) { expect(result.error).toBeInstanceOf(ApiError); } - }); + }, INTEGRATION_TEST_TIMEOUT); }); diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index e3aa9025..d922c90f 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -2,6 +2,8 @@ import { createOakClient } from "../../src"; import { Crowdsplit } from "../../src/products/crowdsplit"; import { getConfigFromEnv } from "../config"; +const INTEGRATION_TEST_TIMEOUT = 30000; + const generateCpf = (): string => { const digits = Array.from({ length: 9 }, () => Math.floor(Math.random() * 10) @@ -26,15 +28,15 @@ describe("CustomerService - Integration", () => { const client = createOakClient({ ...getConfigFromEnv(), retryOptions: { - maxNumberOfRetries: 1, - delay: 200, + maxNumberOfRetries: 2, + delay: 500, backoffFactor: 2, }, }); customers = Crowdsplit(client).customers; }); - let createdCustomerId: string; + let createdCustomerId: string | undefined; it("should create a customer", async () => { const document_number = generateCpf(); @@ -57,17 +59,25 @@ describe("CustomerService - Integration", () => { expect(response.value.data.email).toEqual(email); createdCustomerId = response.value.data.id as string; } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should get the created customer", async () => { + if (!createdCustomerId) { + console.warn("Skipping: createdCustomerId not available from previous test"); + return; + } const response = await customers.get(createdCustomerId); expect(response.ok).toBe(true); if (response.ok) { expect(response.value.data.id).toEqual(createdCustomerId); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should update the customer", async () => { + if (!createdCustomerId) { + console.warn("Skipping: createdCustomerId not available from previous test"); + return; + } const response = await customers.update(createdCustomerId, { first_name: "UpdatedName", }); @@ -75,7 +85,7 @@ describe("CustomerService - Integration", () => { if (response.ok) { expect(response.value.data.first_name).toEqual("UpdatedName"); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should list customers", async () => { const response = await customers.list({ limit: 5 }); @@ -84,10 +94,10 @@ describe("CustomerService - Integration", () => { expect(Array.isArray(response.value.data.customer_list)).toBe(true); expect(response.value.data.customer_list.length).toBeGreaterThan(0); } - }); + }, INTEGRATION_TEST_TIMEOUT); it("should handle invalid customer ID gracefully", async () => { const response = await customers.get("non-existent-id"); expect(response.ok).toBe(false); - }); + }, INTEGRATION_TEST_TIMEOUT); }); From 3315c34ca122a2788e7247265252bff5c06e5b29 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 13 Feb 2026 19:37:00 +0600 Subject: [PATCH 021/143] chore: add changeset for httpClient fix and test timeout improvements --- .changeset/quick-eyes-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quick-eyes-rest.md diff --git a/.changeset/quick-eyes-rest.md b/.changeset/quick-eyes-rest.md new file mode 100644 index 00000000..47c1aa71 --- /dev/null +++ b/.changeset/quick-eyes-rest.md @@ -0,0 +1,5 @@ +--- +'@oaknetwork/api': minor +--- + +Fix httpClient to return ApiError for non-JSON API error responses From 9efbb077619509bfbd298fa02190dbe4f14d96f4 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 13 Feb 2026 19:52:40 +0600 Subject: [PATCH 022/143] fix(http): normalize null JSON body before building ApiError --- packages/api/__tests__/unit/utils.test.ts | 20 ++++++++++++++++++++ packages/api/src/utils/httpClient.ts | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index 31d96ca6..721a80f6 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -581,6 +581,26 @@ describe("httpClient", () => { expect((result.error as ApiError).body).toEqual({ rawText: "Internal Server Error" }); } }); + + it("returns ApiError with empty body when error response is JSON null", async () => { + fetchMock.mockResolvedValue({ + ok: false, + status: 400, + headers: new Headers(), + text: jest.fn().mockResolvedValue("null"), + }); + + const result = await httpClient.post("https://api.test/post", { data: 1 }, { + retryOptions, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + expect((result.error as ApiError).status).toBe(400); + expect((result.error as ApiError).body).toEqual({}); + expect(result.error.message).toBe("HTTP error"); + } + }); }); describe("withRetry", () => { diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index 170ad8f3..a6c7254e 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -96,7 +96,7 @@ const request = async ( const parseResult: ParseResult = text ? parseJsonSafe(text) : { success: true, data: {} }; if (!response.ok) { - const body = parseResult.success ? parseResult.data : { rawText: text }; + const body = parseResult.success ? (parseResult.data ?? {}) : { rawText: text }; throw toApiError(response, body); } From cb6dd8e9217f7a1b0c48cc712ad057e9510c582d Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Sat, 14 Feb 2026 00:28:54 +0600 Subject: [PATCH 023/143] feat: update the stage url --- packages/api/__tests__/unit/authService.test.ts | 2 +- packages/api/__tests__/unit/client.test.ts | 2 +- packages/api/__tests__/unit/customerService.test.ts | 2 +- packages/api/__tests__/unit/environment.test.ts | 2 +- packages/api/__tests__/unit/services.test.ts | 2 +- packages/api/src/types/environment.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/api/__tests__/unit/authService.test.ts index 77a210f6..75e1e24b 100644 --- a/packages/api/__tests__/unit/authService.test.ts +++ b/packages/api/__tests__/unit/authService.test.ts @@ -5,7 +5,7 @@ import { RetryOptions } from "../../src/utils"; import type { OakClientConfig } from "../../src/types"; import { err, ok } from "../../src/types"; -const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; jest.mock("../../src/utils/httpClient"); const mockedHttpClient = httpClient as jest.Mocked; diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts index 77e9a677..30021b8c 100644 --- a/packages/api/__tests__/unit/client.test.ts +++ b/packages/api/__tests__/unit/client.test.ts @@ -1,7 +1,7 @@ import { createOakClient } from "../../src/client"; import type { OakClientConfig } from "../../src/types/client"; -const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; jest.mock("../../src/authManager", () => ({ diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index dab3fe6f..d1662041 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -5,7 +5,7 @@ import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; import { OakClientConfig, Customer, ok, err } from "../../src/types"; -const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; jest.mock("../../src/utils/httpClient", () => ({ httpClient: { diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts index c87dd871..1d8b3333 100644 --- a/packages/api/__tests__/unit/environment.test.ts +++ b/packages/api/__tests__/unit/environment.test.ts @@ -11,7 +11,7 @@ import { import { SandboxOnly, sandboxOnlyFn } from "../../src/decorators/sandboxOnly"; import type { ResolvedOakClientConfig } from "../../src/types/client"; -const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; describe("Environment Configuration", () => { diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index c29d673e..460718ed 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -17,7 +17,7 @@ import { ApiError, SDKError } from "../../src/utils/errorHandler"; import type { OakClient } from "../../src/types"; import { err, ok } from "../../src/types"; -const SANDBOX_URL = "https://api.usecrowdpay.xyz"; +const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; jest.mock("../../src/utils/httpClient", () => ({ httpClient: { diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts index c154dbbf..32b2be4e 100644 --- a/packages/api/src/types/environment.ts +++ b/packages/api/src/types/environment.ts @@ -6,7 +6,7 @@ export interface EnvironmentConfig { } const ENVIRONMENT_URLS: Record = { - sandbox: "https://api.usecrowdpay.xyz", + sandbox: "https://api-stage.usecrowdpay.xyz", production: "https://app.usecrowdpay.xyz", }; From 604c5308699b2abe2759d2351bcda6b8469a74d3 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Mon, 16 Feb 2026 19:50:40 +0600 Subject: [PATCH 024/143] refactor: centralize environment URLs in single exported constant --- packages/api/__tests__/unit/authService.test.ts | 3 ++- packages/api/__tests__/unit/client.test.ts | 5 +++-- packages/api/__tests__/unit/customerService.test.ts | 3 ++- packages/api/__tests__/unit/environment.test.ts | 5 +++-- packages/api/__tests__/unit/services.test.ts | 3 ++- packages/api/src/types/environment.ts | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/api/__tests__/unit/authService.test.ts b/packages/api/__tests__/unit/authService.test.ts index 75e1e24b..4d7c5e6e 100644 --- a/packages/api/__tests__/unit/authService.test.ts +++ b/packages/api/__tests__/unit/authService.test.ts @@ -4,8 +4,9 @@ import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils"; import type { OakClientConfig } from "../../src/types"; import { err, ok } from "../../src/types"; +import { ENVIRONMENT_URLS } from "../../src/types/environment"; -const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; +const SANDBOX_URL = ENVIRONMENT_URLS.sandbox; jest.mock("../../src/utils/httpClient"); const mockedHttpClient = httpClient as jest.Mocked; diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts index 30021b8c..e709f5d3 100644 --- a/packages/api/__tests__/unit/client.test.ts +++ b/packages/api/__tests__/unit/client.test.ts @@ -1,8 +1,9 @@ import { createOakClient } from "../../src/client"; import type { OakClientConfig } from "../../src/types/client"; +import { ENVIRONMENT_URLS } from "../../src/types/environment"; -const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; -const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; +const SANDBOX_URL = ENVIRONMENT_URLS.sandbox; +const PRODUCTION_URL = ENVIRONMENT_URLS.production; jest.mock("../../src/authManager", () => ({ AuthManager: jest.fn().mockImplementation(() => ({ diff --git a/packages/api/__tests__/unit/customerService.test.ts b/packages/api/__tests__/unit/customerService.test.ts index d1662041..fbc67b7d 100644 --- a/packages/api/__tests__/unit/customerService.test.ts +++ b/packages/api/__tests__/unit/customerService.test.ts @@ -4,8 +4,9 @@ import { httpClient } from "../../src/utils/httpClient"; import { ApiError } from "../../src/utils/errorHandler"; import { RetryOptions } from "../../src/utils/defaultRetryConfig"; import { OakClientConfig, Customer, ok, err } from "../../src/types"; +import { ENVIRONMENT_URLS } from "../../src/types/environment"; -const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; +const SANDBOX_URL = ENVIRONMENT_URLS.sandbox; jest.mock("../../src/utils/httpClient", () => ({ httpClient: { diff --git a/packages/api/__tests__/unit/environment.test.ts b/packages/api/__tests__/unit/environment.test.ts index 1d8b3333..dd2a0d85 100644 --- a/packages/api/__tests__/unit/environment.test.ts +++ b/packages/api/__tests__/unit/environment.test.ts @@ -3,6 +3,7 @@ import { isTestEnvironment, getEnvironmentConfig, OakEnvironment, + ENVIRONMENT_URLS, } from "../../src/types/environment"; import { EnvironmentViolationError, @@ -11,8 +12,8 @@ import { import { SandboxOnly, sandboxOnlyFn } from "../../src/decorators/sandboxOnly"; import type { ResolvedOakClientConfig } from "../../src/types/client"; -const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; -const PRODUCTION_URL = "https://app.usecrowdpay.xyz"; +const SANDBOX_URL = ENVIRONMENT_URLS.sandbox; +const PRODUCTION_URL = ENVIRONMENT_URLS.production; describe("Environment Configuration", () => { describe("getEnvironmentConfig", () => { diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index 460718ed..261e1678 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -16,8 +16,9 @@ import { httpClient } from "../../src/utils/httpClient"; import { ApiError, SDKError } from "../../src/utils/errorHandler"; import type { OakClient } from "../../src/types"; import { err, ok } from "../../src/types"; +import { ENVIRONMENT_URLS } from "../../src/types/environment"; -const SANDBOX_URL = "https://api-stage.usecrowdpay.xyz"; +const SANDBOX_URL = ENVIRONMENT_URLS.sandbox; jest.mock("../../src/utils/httpClient", () => ({ httpClient: { diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts index 32b2be4e..8a3274ac 100644 --- a/packages/api/src/types/environment.ts +++ b/packages/api/src/types/environment.ts @@ -5,7 +5,7 @@ export interface EnvironmentConfig { allowsTestOperations: boolean; } -const ENVIRONMENT_URLS: Record = { +export const ENVIRONMENT_URLS: Record = { sandbox: "https://api-stage.usecrowdpay.xyz", production: "https://app.usecrowdpay.xyz", }; From 7584f115eddd54974b13c03ce230c1b23c6daa03 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Tue, 17 Feb 2026 16:49:23 +0600 Subject: [PATCH 025/143] test: updated integration test of customer for us clients --- package.json | 4 + .../__tests__/integration/authService.test.ts | 112 +++++++----- .../integration/customerService.test.ts | 167 ++++++++++-------- 3 files changed, 161 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index 544d0e53..b43d7cd6 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,10 @@ "build": "pnpm -r build", "test": "pnpm -r test --coverage", "test:ci": "pnpm -r test --coverage", + "test:integration": "pnpm -r test:integration", + "test:unit": "pnpm -r test:unit --coverage", + "test:all": "pnpm -r test --coverage", + "test:watch": "pnpm -r test:watch", "lint": "pnpm -r lint", "clean": "pnpm -r clean", "changeset": "changeset", diff --git a/packages/api/__tests__/integration/authService.test.ts b/packages/api/__tests__/integration/authService.test.ts index ed8994be..9b2fcf34 100644 --- a/packages/api/__tests__/integration/authService.test.ts +++ b/packages/api/__tests__/integration/authService.test.ts @@ -14,58 +14,74 @@ describe("Auth (Integration)", () => { }, }); - it("should get a real access token", async () => { - const response = await client.grantToken(); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.access_token).toBeDefined(); - expect(response.value.expires_in).toBeGreaterThan(0); - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should get a real access token", + async () => { + const response = await client.grantToken(); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.access_token).toBeDefined(); + expect(response.value.expires_in).toBeGreaterThan(0); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should return the same token if not expired", async () => { - const firstResult = await client.getAccessToken(); - const secondResult = await client.getAccessToken(); - expect(firstResult.ok).toBe(true); - expect(secondResult.ok).toBe(true); - if (firstResult.ok && secondResult.ok) { - expect(secondResult.value).toBe(firstResult.value); - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should return the same token if not expired", + async () => { + const firstResult = await client.getAccessToken(); + const secondResult = await client.getAccessToken(); + expect(firstResult.ok).toBe(true); + expect(secondResult.ok).toBe(true); + if (firstResult.ok && secondResult.ok) { + expect(secondResult.value).toBe(firstResult.value); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should refresh token if expired", async () => { - const originalNow = Date.now; - await client.getAccessToken(); - const nowSpy = jest - .spyOn(Date, "now") - .mockImplementation(() => originalNow() + 86400000); + it( + "should refresh token if expired", + async () => { + const originalNow = Date.now; + await client.getAccessToken(); + const nowSpy = jest + .spyOn(Date, "now") + .mockImplementation(() => originalNow() + 86400000); - const newTokenResult = await client.getAccessToken(); + const newTokenResult = await client.getAccessToken(); - nowSpy.mockRestore(); - expect(newTokenResult.ok).toBe(true); - if (newTokenResult.ok) { - expect(newTokenResult.value).toBeDefined(); - expect(newTokenResult.value).not.toBeNull(); - } - }, INTEGRATION_TEST_TIMEOUT); + nowSpy.mockRestore(); + expect(newTokenResult.ok).toBe(true); + if (newTokenResult.ok) { + expect(newTokenResult.value).toBeDefined(); + expect(newTokenResult.value).not.toBeNull(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should return error on invalid credentials", async () => { - const badClient = createOakClient({ - environment: "sandbox", - clientId: "invalid", - clientSecret: "invalid", - retryOptions: { - maxNumberOfRetries: 1, - delay: 100, - backoffFactor: 1, - }, - }); + it( + "should return error on invalid credentials", + async () => { + const badClient = createOakClient({ + environment: "sandbox", + clientId: "invalid", + clientSecret: "invalid", + retryOptions: { + maxNumberOfRetries: 1, + delay: 100, + backoffFactor: 1, + }, + }); - const result = await badClient.grantToken(); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBeInstanceOf(ApiError); - } - }, INTEGRATION_TEST_TIMEOUT); + const result = await badClient.grantToken(); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBeInstanceOf(ApiError); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); }); diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index d922c90f..e4bd8083 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -4,23 +4,6 @@ import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; -const generateCpf = (): string => { - const digits = Array.from({ length: 9 }, () => - Math.floor(Math.random() * 10) - ); - const calcDigit = (numbers: number[], factor: number): number => { - const sum = numbers.reduce( - (total, num, idx) => total + num * (factor - idx), - 0 - ); - const remainder = (sum * 10) % 11; - return remainder === 10 ? 0 : remainder; - }; - const first = calcDigit(digits, 10); - const second = calcDigit([...digits, first], 11); - return [...digits, first, second].join(""); -}; - describe("CustomerService - Integration", () => { let customers: ReturnType["customers"]; @@ -38,66 +21,102 @@ describe("CustomerService - Integration", () => { let createdCustomerId: string | undefined; - it("should create a customer", async () => { - const document_number = generateCpf(); + it( + "should create a stripe customer", + async () => { + const email = `test_${Date.now()}@example.com`; + const response = await customers.create({ + email, + }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.email).toEqual(email); + createdCustomerId = response.value.data.id as string; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - const email = `test_${Date.now()}@example.com`; - const response = await customers.create({ - document_number, - document_type: "personal_tax_id", - email, - first_name: "Adr", - last_name: "Cius", - dob: "1997-04-01", - phone_country_code: "55", - phone_area_code: "18", - phone_number: "998121211", - }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.id).toBeDefined(); - expect(response.value.data.email).toEqual(email); - createdCustomerId = response.value.data.id as string; - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should create a stripe connected account", + async () => { + const email = `test_${Date.now()}@example.com`; + const country_code = "US"; + const response = await customers.create({ + email, + country_code, + }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.email).toEqual(email); + expect(response.value.data.country_code).toEqual( + country_code.toLowerCase(), + ); + createdCustomerId = response.value.data.id as string; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should get the created customer", async () => { - if (!createdCustomerId) { - console.warn("Skipping: createdCustomerId not available from previous test"); - return; - } - const response = await customers.get(createdCustomerId); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.id).toEqual(createdCustomerId); - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should get the created customer", + async () => { + if (!createdCustomerId) { + console.warn( + "Skipping: createdCustomerId not available from previous test", + ); + return; + } + const response = await customers.get(createdCustomerId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(createdCustomerId); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should update the customer", async () => { - if (!createdCustomerId) { - console.warn("Skipping: createdCustomerId not available from previous test"); - return; - } - const response = await customers.update(createdCustomerId, { - first_name: "UpdatedName", - }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.first_name).toEqual("UpdatedName"); - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should update the customer", + async () => { + if (!createdCustomerId) { + console.warn( + "Skipping: createdCustomerId not available from previous test", + ); + return; + } + const response = await customers.update(createdCustomerId, { + first_name: "UpdatedName", + }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.first_name).toEqual("UpdatedName"); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should list customers", async () => { - const response = await customers.list({ limit: 5 }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(Array.isArray(response.value.data.customer_list)).toBe(true); - expect(response.value.data.customer_list.length).toBeGreaterThan(0); - } - }, INTEGRATION_TEST_TIMEOUT); + it( + "should list customers", + async () => { + const response = await customers.list({ limit: 5 }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.customer_list)).toBe(true); + expect(response.value.data.customer_list.length).toBeGreaterThan(0); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); - it("should handle invalid customer ID gracefully", async () => { - const response = await customers.get("non-existent-id"); - expect(response.ok).toBe(false); - }, INTEGRATION_TEST_TIMEOUT); + it( + "should handle invalid customer ID gracefully", + async () => { + const response = await customers.get("non-existent-id"); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); }); From 4b360a91f4515a2afc960968238310b973e0b1c4 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Tue, 17 Feb 2026 19:45:41 +0600 Subject: [PATCH 026/143] test: added stripe manual payout integration test --- .../integration/transferService.test.ts | 86 +++++++++++++++++++ packages/api/src/types/customer.ts | 22 +++-- packages/api/src/types/transfer.ts | 2 +- 3 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 packages/api/__tests__/integration/transferService.test.ts diff --git a/packages/api/__tests__/integration/transferService.test.ts b/packages/api/__tests__/integration/transferService.test.ts new file mode 100644 index 00000000..c8a548d9 --- /dev/null +++ b/packages/api/__tests__/integration/transferService.test.ts @@ -0,0 +1,86 @@ +import { createOakClient } from "../../src"; +import { Crowdsplit } from "../../src/products/crowdsplit"; +import { getConfigFromEnv } from "../config"; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +describe("TransferService - Integration", () => { + let transfers: ReturnType["transfers"]; + let customers: ReturnType["customers"]; + let paymentMethods: ReturnType["paymentMethods"]; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + transfers = Crowdsplit(client).transfers; + customers = Crowdsplit(client).customers; + paymentMethods = Crowdsplit(client).paymentMethods; + }); + + let customerId: string | undefined; + let paymentMethodId: string | undefined; + + it( + "should create a transfer (Stripe: Manual Payout)", + async () => { + const customerList = await customers.list({ + target_role: "customer", + provider_registration_status: "approved", + provider: "stripe", + }); + expect(customerList.ok).toBe(true); + if (customerList.ok) { + expect(customerList.value.data.customer_list.length).toBeGreaterThan(0); + + for (const customer of customerList.value.data.customer_list) { + const paymentMethod = await paymentMethods.list( + customer.id as string, + { + type: "bank", + status: "active", + platform: "stripe", + }, + ); + if (paymentMethod.ok && paymentMethod.value.data.length > 0) { + customerId = customer.id as string; + paymentMethodId = paymentMethod.value.data[0].id as string; + break; + } + } + } + expect(customerId).toBeDefined(); + expect(paymentMethodId).toBeDefined(); + const transfer = await transfers.create({ + provider: "stripe", + source: { + amount: 1, + currency: "usd", + customer: { + id: customerId as string, + }, + }, + destination: { + customer: { + id: customerId as string, + }, + payment_method: { + type: "bank", + id: paymentMethodId as string, + }, + }, // required + metadata: { + reference_id: `payout_testing_in_sdk_${Date.now()}`, + campaign_id: "crowdfund_xyz", + }, + }); + expect(transfer.ok).toBe(true); + }, + INTEGRATION_TEST_TIMEOUT, + ); +}); diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 05c4dee7..7fc2a005 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -56,22 +56,20 @@ export namespace Customer { export interface Request extends Partial {} export type Response = ApiResponse; - export interface ListResponse extends ApiResponse<{ - count: number; - customer_list: Data[]; - }> {} + export interface ListResponse + extends ApiResponse<{ + count: number; + customer_list: Data[]; + }> {} export interface ListQueryParams { limit?: number; offset?: number; - customer_id?: string; - type_list?: string; - status?: string; - payment_method?: string; - dateFrom?: string; - dateTo?: string; - source_currency?: string; - destination_currency?: string; + target_role?: string; + provider_registration_status?: string; + provider?: string; + email?: string; + document_type?: string; country_code?: string; } } diff --git a/packages/api/src/types/transfer.ts b/packages/api/src/types/transfer.ts index 401efcec..5fecfdd8 100644 --- a/packages/api/src/types/transfer.ts +++ b/packages/api/src/types/transfer.ts @@ -51,7 +51,7 @@ export namespace Transfer { }; payment_method: { id: string; - type: "BANK"; + type: "bank"; }; }; metadata?: Record; From 96805ef9eeb2a6882acdd935dd247a68233bbf64 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Tue, 17 Feb 2026 23:46:16 +0600 Subject: [PATCH 027/143] feat: add integration tests for webhook service --- .changeset/hot-lions-bow.md | 5 + .../integration/webhookService.test.ts | 196 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 .changeset/hot-lions-bow.md create mode 100644 packages/api/__tests__/integration/webhookService.test.ts diff --git a/.changeset/hot-lions-bow.md b/.changeset/hot-lions-bow.md new file mode 100644 index 00000000..c552d50f --- /dev/null +++ b/.changeset/hot-lions-bow.md @@ -0,0 +1,5 @@ +--- +'@oaknetwork/api': minor +--- + +Add integration tests for WebhookService covering CRUD operations, toggle, and notifications endpoints diff --git a/packages/api/__tests__/integration/webhookService.test.ts b/packages/api/__tests__/integration/webhookService.test.ts new file mode 100644 index 00000000..782f3a6d --- /dev/null +++ b/packages/api/__tests__/integration/webhookService.test.ts @@ -0,0 +1,196 @@ +import { createOakClient } from "../../src"; +import { Crowdsplit } from "../../src/products/crowdsplit"; +import { getConfigFromEnv } from "../config"; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +describe("WebhookService - Integration", () => { + let webhooks: ReturnType["webhooks"]; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + webhooks = Crowdsplit(client).webhooks; + }); + + let createdWebhookId: string | undefined; + const testWebhookUrl = `https://webhook.site/test-${Date.now()}`; + + describe("register", () => { + it("should register a new webhook", async () => { + const response = await webhooks.register({ + url: testWebhookUrl, + description: "Integration test webhook", + }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.url).toEqual(testWebhookUrl); + expect(response.value.data.description).toEqual("Integration test webhook"); + expect(response.value.data.is_active).toBe(true); + expect(response.value.data.secret).toBeDefined(); + createdWebhookId = response.value.data.id; + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should handle duplicate URL registration", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.register({ + url: testWebhookUrl, + description: "Duplicate webhook", + }); + + expect(response.ok).toBe(false); + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("list", () => { + it("should list all webhooks", async () => { + const response = await webhooks.list(); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + expect(response.value.data.length).toBeGreaterThan(0); + } + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("get", () => { + it("should get the created webhook", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.get(createdWebhookId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(createdWebhookId); + expect(response.value.data.url).toEqual(testWebhookUrl); + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should handle invalid webhook ID gracefully", async () => { + const response = await webhooks.get("non-existent-webhook-id"); + + expect(response.ok).toBe(false); + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("update", () => { + it("should update the webhook", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.update(createdWebhookId, { + description: "Updated integration test webhook", + }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.description).toEqual("Updated integration test webhook"); + } + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("toggle", () => { + it("should toggle webhook status to inactive", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.toggle(createdWebhookId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.is_active).toBe(false); + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should toggle webhook status back to active", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.toggle(createdWebhookId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.is_active).toBe(true); + } + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("notifications", () => { + it("should list notifications with pagination", async () => { + const response = await webhooks.listNotifications({ limit: 10, offset: 0 }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.count).toBeDefined(); + // API may return transaction_list or transactionList depending on version + const list = response.value.data.transaction_list ?? (response.value.data as any).transactionList; + expect(list === undefined || Array.isArray(list)).toBe(true); + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should list notifications without params", async () => { + const response = await webhooks.listNotifications(); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.count).toBeDefined(); + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should handle invalid notification ID gracefully", async () => { + const response = await webhooks.getNotification("non-existent-notification-id"); + + expect(response.ok).toBe(false); + }, INTEGRATION_TEST_TIMEOUT); + }); + + describe("delete", () => { + it("should delete the webhook", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.delete(createdWebhookId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.success).toBe(true); + } + }, INTEGRATION_TEST_TIMEOUT); + + it("should verify webhook is deleted", async () => { + if (!createdWebhookId) { + console.warn("Skipping: createdWebhookId not available from previous test"); + return; + } + + const response = await webhooks.get(createdWebhookId); + + expect(response.ok).toBe(false); + }, INTEGRATION_TEST_TIMEOUT); + }); +}); From ea7bc0c906e248b776e17902bb7c8b13fc862e6d Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Tue, 17 Feb 2026 23:56:59 +0600 Subject: [PATCH 028/143] chore: added changeset --- .changeset/solid-rules-run.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/solid-rules-run.md diff --git a/.changeset/solid-rules-run.md b/.changeset/solid-rules-run.md new file mode 100644 index 00000000..c7add307 --- /dev/null +++ b/.changeset/solid-rules-run.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/api": major +--- + +updated customer test for US clients, added test for transfer From 2e02fcf9eb4b3036d5789bbc01c840e9b16cae3c Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Wed, 18 Feb 2026 17:31:30 +0600 Subject: [PATCH 029/143] fix: rename transaction_list to notification_list in webhook types --- packages/api/__tests__/integration/webhookService.test.ts | 4 +--- packages/api/src/types/webhook.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/api/__tests__/integration/webhookService.test.ts b/packages/api/__tests__/integration/webhookService.test.ts index 782f3a6d..3e57b253 100644 --- a/packages/api/__tests__/integration/webhookService.test.ts +++ b/packages/api/__tests__/integration/webhookService.test.ts @@ -145,9 +145,7 @@ describe("WebhookService - Integration", () => { expect(response.ok).toBe(true); if (response.ok) { expect(response.value.data.count).toBeDefined(); - // API may return transaction_list or transactionList depending on version - const list = response.value.data.transaction_list ?? (response.value.data as any).transactionList; - expect(list === undefined || Array.isArray(list)).toBe(true); + expect(Array.isArray(response.value.data.notification_list)).toBe(true); } }, INTEGRATION_TEST_TIMEOUT); diff --git a/packages/api/src/types/webhook.ts b/packages/api/src/types/webhook.ts index 8e77a780..2decb502 100644 --- a/packages/api/src/types/webhook.ts +++ b/packages/api/src/types/webhook.ts @@ -45,7 +45,7 @@ export namespace Webhook { export interface ListNotificationsData { count: number; - transaction_list: Notification[]; + notification_list: Notification[]; } // ---------------------- From e254cc6d879b709c97033bb459fb5a333dec1c17 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Wed, 18 Feb 2026 18:30:08 +0600 Subject: [PATCH 030/143] chore: added guard if no customer found --- .../__tests__/integration/transferService.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/api/__tests__/integration/transferService.test.ts b/packages/api/__tests__/integration/transferService.test.ts index c8a548d9..41b3f071 100644 --- a/packages/api/__tests__/integration/transferService.test.ts +++ b/packages/api/__tests__/integration/transferService.test.ts @@ -35,6 +35,13 @@ describe("TransferService - Integration", () => { provider: "stripe", }); expect(customerList.ok).toBe(true); + if ( + customerList.ok && + customerList.value.data.customer_list.length === 0 + ) { + console.warn("Skipping: no customers found"); + return; + } if (customerList.ok) { expect(customerList.value.data.customer_list.length).toBeGreaterThan(0); @@ -54,6 +61,12 @@ describe("TransferService - Integration", () => { } } } + + if (!customerId || !paymentMethodId) { + console.warn("Skipping: no customer or payment method found"); + return; + } + expect(customerId).toBeDefined(); expect(paymentMethodId).toBeDefined(); const transfer = await transfers.create({ From 94bb17521496f75d8eb4a1384c161c045f74bf7e Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Thu, 19 Feb 2026 21:14:40 +0600 Subject: [PATCH 031/143] feat: add integration tests for payment method service --- .changeset/some-bottles-sleep.md | 5 + .../integration/paymentMethodService.test.ts | 284 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 .changeset/some-bottles-sleep.md create mode 100644 packages/api/__tests__/integration/paymentMethodService.test.ts diff --git a/.changeset/some-bottles-sleep.md b/.changeset/some-bottles-sleep.md new file mode 100644 index 00000000..e616cd83 --- /dev/null +++ b/.changeset/some-bottles-sleep.md @@ -0,0 +1,5 @@ +--- +'@oaknetwork/api': minor +--- + +Add Payment Method Service Integration Tests diff --git a/packages/api/__tests__/integration/paymentMethodService.test.ts b/packages/api/__tests__/integration/paymentMethodService.test.ts new file mode 100644 index 00000000..0c2649df --- /dev/null +++ b/packages/api/__tests__/integration/paymentMethodService.test.ts @@ -0,0 +1,284 @@ +import { createOakClient } from "../../src"; +import { Crowdsplit } from "../../src/products/crowdsplit"; +import { getConfigFromEnv } from "../config"; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +describe("PaymentMethodService - Integration", () => { + let paymentMethods: ReturnType["paymentMethods"]; + let customers: ReturnType["customers"]; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + const crowdsplit = Crowdsplit(client); + paymentMethods = crowdsplit.paymentMethods; + customers = crowdsplit.customers; + }); + + let testCustomerId: string | undefined; + let createdPaymentMethodId: string | undefined; + + describe("setup", () => { + it( + "should find or create a test customer", + async () => { + const listResponse = await customers.list({ limit: 1 }); + if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { + testCustomerId = listResponse.value.data.customer_list[0].id as string; + } else { + const email = `pm_test_${Date.now()}@example.com`; + const createResponse = await customers.create({ email }); + if (createResponse.ok) { + testCustomerId = createResponse.value.data.id as string; + } + } + expect(testCustomerId).toBeDefined(); + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("add", () => { + it( + "should add a PIX payment method", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.add(testCustomerId, { + type: "pix", + pix_string: `pix_test_${Date.now()}@example.com`, + metadata: { + test: true, + created_by: "integration_test", + }, + }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.type).toBe("pix"); + createdPaymentMethodId = response.value.data.id; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should add a bank account payment method (Stripe)", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.add(testCustomerId, { + type: "bank", + provider: "stripe", + currency: "usd", + bank_name: "Test Bank", + bank_account_number: "000123456789", + bank_routing_number: "110000000", + bank_account_type: "CHECKING", + bank_account_name: "Integration Test Account", + metadata: { + test: true, + created_by: "integration_test", + }, + }); + + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.type).toBe("bank"); + if (!createdPaymentMethodId) { + createdPaymentMethodId = response.value.data.id; + } + } else { + console.warn("Bank account creation failed - may require Stripe connected account"); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("get", () => { + it( + "should get the created payment method", + async () => { + if (!testCustomerId || !createdPaymentMethodId) { + console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); + return; + } + + const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(createdPaymentMethodId); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should handle invalid payment method ID gracefully", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.get(testCustomerId, "non-existent-pm-id"); + + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should handle invalid customer ID gracefully", + async () => { + const response = await paymentMethods.get("non-existent-customer-id", "non-existent-pm-id"); + + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("list", () => { + it( + "should list all payment methods for customer", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.list(testCustomerId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should list payment methods with type filter", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.list(testCustomerId, { type: "pix" }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + response.value.data.forEach((pm) => { + expect(pm.type).toBe("pix"); + }); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should list payment methods with status filter", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.list(testCustomerId, { status: "active" }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should list payment methods with platform filter", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.list(testCustomerId, { platform: "stripe" }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("delete", () => { + it( + "should delete the payment method", + async () => { + if (!testCustomerId || !createdPaymentMethodId) { + console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); + return; + } + + const response = await paymentMethods.delete(testCustomerId, createdPaymentMethodId); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.msg).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should handle deleting non-existent payment method", + async () => { + if (!testCustomerId) { + console.warn("Skipping: testCustomerId not available"); + return; + } + + const response = await paymentMethods.delete(testCustomerId, "non-existent-pm-id"); + + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should verify payment method is deleted", + async () => { + if (!testCustomerId || !createdPaymentMethodId) { + console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); + return; + } + + const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); + + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); +}); From f2f31cd30865839b9ff4cc27cea47658f572ecea Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 16:25:01 +0600 Subject: [PATCH 032/143] feat: add TSDoc to core types and client factory --- packages/api/src/authManager.ts | 12 ++++++++++++ packages/api/src/client.ts | 14 ++++++++++++++ packages/api/src/types/environment.ts | 13 +++++++++++++ packages/api/src/types/result.ts | 14 ++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/packages/api/src/authManager.ts b/packages/api/src/authManager.ts index 17b950dc..4a197cbd 100644 --- a/packages/api/src/authManager.ts +++ b/packages/api/src/authManager.ts @@ -15,11 +15,19 @@ export class AuthManager { private tokenExpiration: number | null = null; private retryOptions: RetryOptions; + /** + * @param config - Resolved client configuration + * @param retryOptions - Retry options for token requests + */ constructor(config: ResolvedOakClientConfig, retryOptions: RetryOptions) { this.config = config; this.retryOptions = retryOptions; } + /** + * Requests a new OAuth token from the API. + * @returns Result containing TokenResponse or error + */ async grantToken(): Promise> { const payload: TokenRequest = { client_id: this.config.clientId, @@ -42,6 +50,10 @@ export class AuthManager { return ok(response.value); } + /** + * Gets a valid access token, refreshing if expired. + * @returns Result containing the access token string or error + */ async getAccessToken(): Promise> { const currentTime = Date.now(); if ( diff --git a/packages/api/src/client.ts b/packages/api/src/client.ts index fa0e8379..e54ec3ab 100644 --- a/packages/api/src/client.ts +++ b/packages/api/src/client.ts @@ -6,6 +6,20 @@ import { RetryOptions, } from "./utils/defaultRetryConfig"; +/** + * Creates a new Oak SDK client instance. + * @param config - Client configuration including credentials and environment + * @returns Configured OakClient instance + * + * @example + * ```typescript + * const client = createOakClient({ + * environment: "sandbox", + * clientId: "your-client-id", + * clientSecret: "your-client-secret", + * }); + * ``` + */ export function createOakClient(config: OakClientConfig): OakClient { const baseUrl = resolveBaseUrl(config.environment, config.customUrl); diff --git a/packages/api/src/types/environment.ts b/packages/api/src/types/environment.ts index 8a3274ac..8a5178a1 100644 --- a/packages/api/src/types/environment.ts +++ b/packages/api/src/types/environment.ts @@ -10,6 +10,10 @@ export const ENVIRONMENT_URLS: Record = { production: "https://app.usecrowdpay.xyz", }; +/** + * @param environment - Target environment + * @returns Configuration for the specified environment + */ export function getEnvironmentConfig(environment: OakEnvironment): EnvironmentConfig { return { apiUrl: ENVIRONMENT_URLS[environment], @@ -17,6 +21,11 @@ export function getEnvironmentConfig(environment: OakEnvironment): EnvironmentCo }; } +/** + * @param environment - Target environment + * @param customUrl - Optional custom URL override + * @returns The resolved API base URL + */ export function resolveBaseUrl( environment: OakEnvironment, customUrl?: string @@ -27,6 +36,10 @@ export function resolveBaseUrl( return ENVIRONMENT_URLS[environment]; } +/** + * @param environment - Environment to check + * @returns True if environment allows test operations + */ export function isTestEnvironment(environment: OakEnvironment): boolean { return environment === "sandbox"; } diff --git a/packages/api/src/types/result.ts b/packages/api/src/types/result.ts index e5711881..104abf20 100644 --- a/packages/api/src/types/result.ts +++ b/packages/api/src/types/result.ts @@ -1,8 +1,22 @@ import { OakError } from "../utils/errorHandler"; +/** + * Discriminated union representing success or failure. + * @typeParam T - Success value type + * @typeParam E - Error type (defaults to OakError) + */ export type Result = | { ok: true; value: T } | { ok: false; error: E }; +/** + * @param value - The success value to wrap + * @returns A Result with ok: true + */ export const ok = (value: T): Result => ({ ok: true, value }); + +/** + * @param error - The error to wrap + * @returns A Result with ok: false + */ export const err = (error: E): Result => ({ ok: false, error }); From 1504f77d04de02c438a40a669e94844890b2e65f Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Fri, 20 Feb 2026 16:36:15 +0600 Subject: [PATCH 033/143] feat: added sync API in customer service --- packages/api/src/services/customerService.ts | 23 ++++++++++++++++++++ packages/api/src/types/customer.ts | 16 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 4a0f71d6..65696c62 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -13,6 +13,8 @@ export interface CustomerService { id: string, customer: Customer.Request, ): Promise>; + + sync(id: string, sync: Customer.Sync): Promise>; } export const createCustomerService = (client: OakClient): CustomerService => ({ @@ -91,4 +93,25 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ }, ); }, + + async sync( + id: string, + sync: Customer.Sync, + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } + + return httpClient.post( + `${client.config.baseUrl}/api/v1/customers/${id}/sync`, + sync, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); + }, }); diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 7fc2a005..f9d7e03c 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -53,6 +53,22 @@ export namespace Customer { account_type?: string | null; } + export interface Sync { + // provider is an array of providers to sync the customer with and length must be 1 + providers: + | "stripe" + | "bridge" + | "pagar_me" + | "brla" + | "avenia" + | "mercado_pago"[]; + + // fields is an array of fields to sync the customer + fields: "shipping" | "email" | "first_name" | "last_name"[]; + } + + export type SyncResponse = ApiResponse; + export interface Request extends Partial {} export type Response = ApiResponse; From 3a026190c697ff839cce45ae0baf5d4a0ed55019 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 16:51:51 +0600 Subject: [PATCH 034/143] docs: add TSDoc to error classes and SandboxOnly decorator --- packages/api/src/decorators/sandboxOnly.ts | 22 +++++++++++++++ packages/api/src/utils/errorHandler.ts | 31 ++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/api/src/decorators/sandboxOnly.ts b/packages/api/src/decorators/sandboxOnly.ts index 3df9177e..db035223 100644 --- a/packages/api/src/decorators/sandboxOnly.ts +++ b/packages/api/src/decorators/sandboxOnly.ts @@ -9,6 +9,10 @@ interface HasClient { client: { config: ResolvedOakClientConfig }; } +/** + * @param obj - Object to check + * @returns True if object has a config property with environment + */ function hasConfig(obj: unknown): obj is HasConfig { return ( typeof obj === "object" && @@ -20,6 +24,10 @@ function hasConfig(obj: unknown): obj is HasConfig { ); } +/** + * @param obj - Object to check + * @returns True if object has a client.config property + */ function hasClient(obj: unknown): obj is HasClient { return ( typeof obj === "object" && @@ -31,6 +39,13 @@ function hasClient(obj: unknown): obj is HasClient { ); } +/** + * @typeParam T - Method signature type + * @param target - Class prototype + * @param propertyKey - Method name + * @param descriptor - Property descriptor + * @returns Modified descriptor or void + */ export function SandboxOnly unknown>( target: object, propertyKey: string | symbol, @@ -79,6 +94,13 @@ export function SandboxOnly unknown>( return descriptor; } +/** + * @typeParam T - Function signature type + * @param fn - Function to wrap + * @param getEnvironment - Function that returns current environment + * @param methodName - Name for error messages + * @returns Wrapped function that throws in production + */ export function sandboxOnlyFn unknown>( fn: T, getEnvironment: () => string, diff --git a/packages/api/src/utils/errorHandler.ts b/packages/api/src/utils/errorHandler.ts index 5e444a36..c5ca312c 100644 --- a/packages/api/src/utils/errorHandler.ts +++ b/packages/api/src/utils/errorHandler.ts @@ -1,6 +1,10 @@ export class OakError extends Error { public cause?: unknown; + /** + * @param message - Error description + * @param cause - Original error that caused this error + */ constructor(message: string, cause?: unknown) { super(message); this.name = "OakError"; @@ -9,6 +13,10 @@ export class OakError extends Error { } export class SDKError extends OakError { + /** + * @param message - Error description + * @param cause - Original error that caused this error + */ constructor(message: string, cause?: unknown) { super(message, cause); this.name = "SDKError"; @@ -20,6 +28,13 @@ export class ApiError extends OakError { public readonly body: unknown; public readonly headers?: Record; + /** + * @param message - Error description + * @param status - HTTP status code + * @param body - Parsed response body + * @param headers - Response headers + * @param cause - Original error that caused this error + */ constructor( message: string, status: number, @@ -38,6 +53,10 @@ export class ApiError extends OakError { export class NetworkError extends OakError { public readonly isNetworkError = true; + /** + * @param message - Error description + * @param cause - Original error that caused this error + */ constructor(message: string, cause?: unknown) { super(message, cause); this.name = "NetworkError"; @@ -45,6 +64,10 @@ export class NetworkError extends OakError { } export class AbortError extends OakError { + /** + * @param message - Error description + * @param cause - Original error that caused this error + */ constructor(message: string, cause?: unknown) { super(message, cause); this.name = "AbortError"; @@ -52,6 +75,10 @@ export class AbortError extends OakError { } export class ParseError extends OakError { + /** + * @param message - Error description + * @param cause - Original error that caused this error + */ constructor(message: string, cause?: unknown) { super(message, cause); this.name = "ParseError"; @@ -62,6 +89,10 @@ export class EnvironmentViolationError extends SDKError { public readonly methodName: string; public readonly environment: string; + /** + * @param methodName - Name of the restricted method + * @param environment - Current environment + */ constructor(methodName: string, environment: string) { super( `Method "${methodName}" is only available in sandbox environment. ` + From e08a0f7299934194665bcdabd9dd5799eb99c925 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 16:57:02 +0600 Subject: [PATCH 035/143] docs: add TSDoc to HTTP client and retry utilities --- packages/api/src/utils/httpClient.ts | 57 ++++++++++++++++++++++++++ packages/api/src/utils/retryHandler.ts | 7 ++++ 2 files changed, 64 insertions(+) diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index a6c7254e..f938ab02 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -31,6 +31,10 @@ type ParseResult = | { success: true; data: unknown; error?: undefined } | { success: false; data?: undefined; error: Error }; +/** + * @param text - JSON string to parse + * @returns ParseResult with parsed data or error + */ const parseJsonSafe = (text: string): ParseResult => { try { return { success: true, data: JSON.parse(text) }; @@ -41,6 +45,10 @@ const parseJsonSafe = (text: string): ParseResult => { } }; +/** + * @param headers - Fetch API Headers object + * @returns Plain object with lowercase header keys + */ const toHeadersRecord = (headers?: Headers): Record => { const record: Record = {}; if (!headers) { @@ -52,12 +60,21 @@ const toHeadersRecord = (headers?: Headers): Record => { return record; }; +/** + * @param response - Fetch API Response object + * @param responseBody - Parsed response body + * @returns ApiError with status, body, and headers + */ const toApiError = (response: Response, responseBody: unknown) => { const message = (responseBody as { msg?: string }).msg ?? "HTTP error"; const headers = toHeadersRecord(response.headers); return new ApiError(message, response.status, responseBody, headers); }; +/** + * @param error - Any thrown error + * @returns Normalized OakError instance + */ const toOakError = (error: unknown): OakError => { if (error instanceof OakError) { return error; @@ -68,6 +85,13 @@ const toOakError = (error: unknown): OakError => { return new OakError("Unknown error", error); }; +/** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param config - HTTP client configuration + * @param init - Fetch RequestInit options + * @returns Result containing parsed response or error + */ const request = async ( url: string, config: HttpClientConfig, @@ -114,6 +138,13 @@ const request = async ( }; export const httpClient = { + /** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param data - Request body data + * @param config - HTTP client configuration + * @returns Result containing parsed response or error + */ async post( url: string, data: any, @@ -124,15 +155,34 @@ export const httpClient = { body: JSON.stringify(data), }); }, + /** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param config - HTTP client configuration + * @returns Result containing parsed response or error + */ async get(url: string, config: HttpClientConfig): Promise> { return request(url, config, { method: "GET" }); }, + /** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param config - HTTP client configuration + * @returns Result containing parsed response or error + */ async delete( url: string, config: HttpClientConfig ): Promise> { return request(url, config, { method: "DELETE" }); }, + /** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param data - Request body data + * @param config - HTTP client configuration + * @returns Result containing parsed response or error + */ async put( url: string, data: any, @@ -143,6 +193,13 @@ export const httpClient = { body: JSON.stringify(data), }); }, + /** + * @typeParam T - Expected response body type + * @param url - Request URL + * @param data - Request body data + * @param config - HTTP client configuration + * @returns Result containing parsed response or error + */ async patch( url: string, data: any, diff --git a/packages/api/src/utils/retryHandler.ts b/packages/api/src/utils/retryHandler.ts index 5b40beee..3e5494e4 100644 --- a/packages/api/src/utils/retryHandler.ts +++ b/packages/api/src/utils/retryHandler.ts @@ -1,5 +1,12 @@ import { RetryOptions } from "./defaultRetryConfig"; +/** + * @typeParam T - Return type of the function + * @param fn - Async function to execute + * @param options - Retry configuration + * @returns Promise resolving to function result + * @throws Last error if all retries exhausted + */ export async function withRetry( fn: () => Promise, options: RetryOptions From 19c41a0b26e885ff5d01f1254f45c8d47a59ffe1 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 17:07:36 +0600 Subject: [PATCH 036/143] docs: add TSDoc to service factory functions --- packages/api/src/services/authService.ts | 4 ++++ packages/api/src/services/buyService.ts | 4 ++++ packages/api/src/services/customerService.ts | 4 ++++ packages/api/src/services/helpers.ts | 9 +++++++++ packages/api/src/services/paymentMethodService.ts | 4 ++++ packages/api/src/services/paymentService.ts | 4 ++++ packages/api/src/services/planService.ts | 4 ++++ packages/api/src/services/providerService.ts | 4 ++++ packages/api/src/services/refundService.ts | 4 ++++ packages/api/src/services/sellService.ts | 4 ++++ packages/api/src/services/transactionService.ts | 4 ++++ packages/api/src/services/transferService.ts | 4 ++++ packages/api/src/services/webhookService.ts | 4 ++++ 13 files changed, 57 insertions(+) diff --git a/packages/api/src/services/authService.ts b/packages/api/src/services/authService.ts index f8b76440..dd9f0648 100644 --- a/packages/api/src/services/authService.ts +++ b/packages/api/src/services/authService.ts @@ -5,6 +5,10 @@ export interface AuthService { getAccessToken(): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns AuthService instance + */ export const createAuthService = (client: OakClient): AuthService => ({ grantToken: () => client.grantToken(), getAccessToken: () => client.getAccessToken(), diff --git a/packages/api/src/services/buyService.ts b/packages/api/src/services/buyService.ts index 6e910e9a..248bdc0c 100644 --- a/packages/api/src/services/buyService.ts +++ b/packages/api/src/services/buyService.ts @@ -6,6 +6,10 @@ export interface BuyService { create(buyRequest: Buy.Request): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns BuyService instance + */ export const createBuyService = (client: OakClient): BuyService => ({ async create(buyRequest: Buy.Request): Promise> { const token = await client.getAccessToken(); diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 4a0f71d6..bedd7215 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -15,6 +15,10 @@ export interface CustomerService { ): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns CustomerService instance + */ export const createCustomerService = (client: OakClient): CustomerService => ({ async create(customer: Customer.Request): Promise> { const token = await client.getAccessToken(); diff --git a/packages/api/src/services/helpers.ts b/packages/api/src/services/helpers.ts index e751f782..b3aaf949 100644 --- a/packages/api/src/services/helpers.ts +++ b/packages/api/src/services/helpers.ts @@ -1,3 +1,8 @@ +/** + * @typeParam T - Query parameters object type + * @param params - Optional query parameters + * @returns URL query string or empty string + */ export const buildQueryString = (params?: T): string => { if (!params) { return ""; @@ -16,6 +21,10 @@ export const buildQueryString = (params?: T): string => { .join("&")}`; }; +/** + * @param error - Error object to extract message from + * @returns Error message from body.msg or undefined + */ export const getErrorBodyMessage = (error: unknown): string | undefined => { if (typeof error !== "object" || error === null) { return undefined; diff --git a/packages/api/src/services/paymentMethodService.ts b/packages/api/src/services/paymentMethodService.ts index c461244e..920a22d2 100644 --- a/packages/api/src/services/paymentMethodService.ts +++ b/packages/api/src/services/paymentMethodService.ts @@ -22,6 +22,10 @@ export interface PaymentMethodService { ): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns PaymentMethodService instance + */ export const createPaymentMethodService = ( client: OakClient, ): PaymentMethodService => ({ diff --git a/packages/api/src/services/paymentService.ts b/packages/api/src/services/paymentService.ts index 32212efd..09ad1ae2 100644 --- a/packages/api/src/services/paymentService.ts +++ b/packages/api/src/services/paymentService.ts @@ -8,6 +8,10 @@ export interface PaymentService { cancel(paymentId: string): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns PaymentService instance + */ export const createPaymentService = (client: OakClient): PaymentService => ({ async create(payment: Payment.Request): Promise> { const token = await client.getAccessToken(); diff --git a/packages/api/src/services/planService.ts b/packages/api/src/services/planService.ts index d2cf6a67..3a17f553 100644 --- a/packages/api/src/services/planService.ts +++ b/packages/api/src/services/planService.ts @@ -15,6 +15,10 @@ export interface PlanService { delete(id: string): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns PlanService instance + */ export const createPlanService = (client: OakClient): PlanService => ({ async create( createPlanRequest: Plan.Request, diff --git a/packages/api/src/services/providerService.ts b/packages/api/src/services/providerService.ts index 24f9be9c..e85c7e05 100644 --- a/packages/api/src/services/providerService.ts +++ b/packages/api/src/services/providerService.ts @@ -14,6 +14,10 @@ export interface ProviderService { ): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns ProviderService instance + */ export const createProviderService = (client: OakClient): ProviderService => ({ async getSchema( request: Provider.GetSchemaRequest, diff --git a/packages/api/src/services/refundService.ts b/packages/api/src/services/refundService.ts index 9e3a72ff..47d06937 100644 --- a/packages/api/src/services/refundService.ts +++ b/packages/api/src/services/refundService.ts @@ -8,6 +8,10 @@ export interface RefundService { ): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns RefundService instance + */ export const createRefundService = (client: OakClient): RefundService => ({ async create( paymentId: string, diff --git a/packages/api/src/services/sellService.ts b/packages/api/src/services/sellService.ts index 79a55ec1..16e149a5 100644 --- a/packages/api/src/services/sellService.ts +++ b/packages/api/src/services/sellService.ts @@ -6,6 +6,10 @@ export interface SellService { create(sellRequest: Sell.Request): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns SellService instance + */ export const createSellService = (client: OakClient): SellService => ({ async create(sellRequest: Sell.Request): Promise> { const token = await client.getAccessToken(); diff --git a/packages/api/src/services/transactionService.ts b/packages/api/src/services/transactionService.ts index ccbb729c..639f58ec 100644 --- a/packages/api/src/services/transactionService.ts +++ b/packages/api/src/services/transactionService.ts @@ -14,6 +14,10 @@ export interface TransactionService { ): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns TransactionService instance + */ export const createTransactionService = ( client: OakClient, ): TransactionService => ({ diff --git a/packages/api/src/services/transferService.ts b/packages/api/src/services/transferService.ts index 08c2ee30..fd40b625 100644 --- a/packages/api/src/services/transferService.ts +++ b/packages/api/src/services/transferService.ts @@ -6,6 +6,10 @@ export interface TransferService { create(transfer: Transfer.Request): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns TransferService instance + */ export const createTransferService = (client: OakClient): TransferService => ({ async create(transfer: Transfer.Request): Promise> { const token = await client.getAccessToken(); diff --git a/packages/api/src/services/webhookService.ts b/packages/api/src/services/webhookService.ts index 9332e624..83e247db 100644 --- a/packages/api/src/services/webhookService.ts +++ b/packages/api/src/services/webhookService.ts @@ -20,6 +20,10 @@ export interface WebhookService { getNotification(id: string): Promise>; } +/** + * @param client - Configured OakClient instance + * @returns WebhookService instance + */ export const createWebhookService = (client: OakClient): WebhookService => ({ async register( webhook: Webhook.RegisterRequest, From b6bd00f1a7065660dc1dffc4863e9314782a109e Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 17:15:14 +0600 Subject: [PATCH 037/143] docs: complete TSDoc coverage for remaining functions --- packages/api/src/products/crowdsplit/index.ts | 4 ++++ packages/api/src/types/plan.ts | 2 -- packages/api/src/types/webhook.ts | 1 - packages/api/src/utils/httpClient.ts | 7 +++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/api/src/products/crowdsplit/index.ts b/packages/api/src/products/crowdsplit/index.ts index 5be15100..13fda538 100644 --- a/packages/api/src/products/crowdsplit/index.ts +++ b/packages/api/src/products/crowdsplit/index.ts @@ -25,6 +25,10 @@ export interface CrowdsplitProduct { buy: ReturnType; } +/** + * @param client - Configured OakClient instance + * @returns Object containing all Crowdsplit service instances + */ export const Crowdsplit = (client: OakClient): CrowdsplitProduct => ({ customers: createCustomerService(client), payments: createPaymentService(client), diff --git a/packages/api/src/types/plan.ts b/packages/api/src/types/plan.ts index 93d24529..a3aea98b 100644 --- a/packages/api/src/types/plan.ts +++ b/packages/api/src/types/plan.ts @@ -17,7 +17,6 @@ export namespace Plan { created_by: string; } - /** Use for both create and update. */ export interface Request extends Base {} // ---------------------- @@ -56,7 +55,6 @@ export namespace Plan { // ---------------------- // Responses // ---------------------- - /** create, update, publish, delete */ export type Response = ApiResponse; export type DetailsResponse = ApiResponse
; diff --git a/packages/api/src/types/webhook.ts b/packages/api/src/types/webhook.ts index 2decb502..4ad44c49 100644 --- a/packages/api/src/types/webhook.ts +++ b/packages/api/src/types/webhook.ts @@ -51,7 +51,6 @@ export namespace Webhook { // ---------------------- // Responses // ---------------------- - /** register, toggle, update (full webhook data) */ export type Response = ApiResponse; export type GetResponse = ApiResponse; diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index f938ab02..1b07248c 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -9,6 +9,9 @@ export interface HttpClientConfig { signal?: AbortSignal; } +/** + * @returns Package version string or undefined + */ const getPackageVersion = () => { try { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -21,6 +24,10 @@ const getPackageVersion = () => { const oakVersion = process.env.OAK_VERSION ?? getPackageVersion() ?? "unknown"; +/** + * @param headers - Optional custom headers to merge + * @returns Merged headers with defaults + */ const mergeHeaders = (headers?: Record) => ({ "Content-Type": "application/json", "Oak-Version": oakVersion, From 8b5e9f8d4114a1da65da85e1744675314a581c97 Mon Sep 17 00:00:00 2001 From: fahmidareem3 Date: Fri, 20 Feb 2026 17:23:40 +0600 Subject: [PATCH 038/143] chore: add changeset for TSDoc documentation --- .changeset/heavy-eggs-sell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-eggs-sell.md diff --git a/.changeset/heavy-eggs-sell.md b/.changeset/heavy-eggs-sell.md new file mode 100644 index 00000000..b58541e9 --- /dev/null +++ b/.changeset/heavy-eggs-sell.md @@ -0,0 +1,5 @@ +--- +'@oaknetwork/api': patch +--- + +Add TSDoc documentation to SDK public API From c86a0b8c9b215a41c3cd0f5e97544851280c1ac5 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Fri, 20 Feb 2026 22:34:21 +0600 Subject: [PATCH 039/143] feat: integrated sync and balance api in customer service --- packages/api/src/services/customerService.ts | 27 +++++++++++++++++ packages/api/src/types/customer.ts | 31 ++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 65696c62..a549408e 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -15,6 +15,11 @@ export interface CustomerService { ): Promise>; sync(id: string, sync: Customer.Sync): Promise>; + + balance( + customer_id: string, + filter: Customer.BalanceFilter, + ): Promise>; } export const createCustomerService = (client: OakClient): CustomerService => ({ @@ -114,4 +119,26 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ }, ); }, + + async balance( + customer_id: string, + filter: Customer.BalanceFilter, + ): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } + + const queryString = buildQueryString(filter); + + return httpClient.get( + `${client.config.baseUrl}/api/v1/customers/${customer_id}/balance${queryString}`, + { + headers: { + Authorization: `Bearer ${token.value}`, + }, + retryOptions: client.retryOptions, + }, + ); + }, }); diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index f9d7e03c..12baeec8 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -88,4 +88,35 @@ export namespace Customer { document_type?: string; country_code?: string; } + export interface BalanceFilter { + provider: string; + role: string; + } + + export interface BalanceResponse + extends ApiResponse<{ + as_of: string; + filters: { + customer_id: string; + provider?: string; + role?: string; + }; + + balances: { + account_id: string; + provider: string; + customer: { + id: string; + role: string; + }; + as_of: string; + totals: { + currency: string; + amount: number; + pending: number; + reserved: number; + instant_payouts: number; + }[]; + }[]; + }> {} } From 44cee69fefee780d0fc3ed3613fb668a51dc254e Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Fri, 20 Feb 2026 22:43:03 +0600 Subject: [PATCH 040/143] added change set --- .changeset/seven-hornets-shout.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/seven-hornets-shout.md diff --git a/.changeset/seven-hornets-shout.md b/.changeset/seven-hornets-shout.md new file mode 100644 index 00000000..2fc0fb2e --- /dev/null +++ b/.changeset/seven-hornets-shout.md @@ -0,0 +1,5 @@ +--- +"@oaknetwork/api": minor +--- + +added sync and balance API From d498a7ae95ab0976216858e8d3f8a846919a2032 Mon Sep 17 00:00:00 2001 From: Lucas Vinhas Date: Fri, 20 Feb 2026 16:06:57 -0300 Subject: [PATCH 041/143] Sdk 52 * feat(api): enhance Jest configuration and add payment service integration tests - Updated VSCode settings to support on-demand Jest runs with virtual folders for API and contracts. - Expanded the sample environment file to include payment-related configuration variables. - Implemented integration tests for the payment service, covering both PagarMe and Stripe payment requests. - Added a script to ensure exact test runs with Jest, improving test execution accuracy. * refactor(api): clean up payment service tests and update environment configuration - Removed obsolete payment-related variables from the sample environment file. - Simplified payment service integration tests by eliminating unnecessary code and focusing on essential functionality. * test(api): enhance payment service integration tests with improved customer retrieval and timeout settings - Added a timeout constant for integration tests to ensure consistent execution duration. - Updated the logic for finding an approved customer, simplifying the retrieval process and improving test reliability. - Enhanced error handling and logging for scenarios where no approved customers are found. - Refactored test cases for clarity and consistency, ensuring they adhere to the new timeout settings. --- .vscode/settings.json | 17 +- packages/api/__tests__/config.ts | 9 +- .../integration/paymentService.test.ts | 244 ++++++++++++++++++ packages/api/scripts/jest-run-exact.js | 46 ++++ 4 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 packages/api/__tests__/integration/paymentService.test.ts create mode 100644 packages/api/scripts/jest-run-exact.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 75c4673a..6722d814 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,20 @@ "**/.trunk/*plugins/": true, "**/.git/**": true, "**/.vscode/**": true - } + }, + "jest.runMode": "on-demand", + "jest.virtualFolders": [ + { + "name": "api", + "rootPath": "packages/api", + "jestCommandLine": "node packages/api/scripts/jest-run-exact.js --", + "pathToConfig": "jest.config.js" + }, + { + "name": "contracts", + "rootPath": "packages/contracts", + "jestCommandLine": "pnpm --filter @oaknetwork/contracts test --", + "pathToConfig": "jest.config.js" + } + ] } \ No newline at end of file diff --git a/packages/api/__tests__/config.ts b/packages/api/__tests__/config.ts index 0993fe07..919c16d0 100644 --- a/packages/api/__tests__/config.ts +++ b/packages/api/__tests__/config.ts @@ -1,7 +1,14 @@ import type { OakClientConfig, OakEnvironment } from "../src"; import type { RetryOptions } from "../src/utils"; import dotenv from "dotenv"; -dotenv.config(); +import path from "path"; + +// Always load env vars from the API package's .env, +// regardless of the current working directory. +dotenv.config({ + path: path.resolve(__dirname, "../.env"), +}); + export interface TestClientConfig extends OakClientConfig { retryOptions?: RetryOptions; diff --git a/packages/api/__tests__/integration/paymentService.test.ts b/packages/api/__tests__/integration/paymentService.test.ts new file mode 100644 index 00000000..0cf564c7 --- /dev/null +++ b/packages/api/__tests__/integration/paymentService.test.ts @@ -0,0 +1,244 @@ +import { createOakClient } from '../../src'; +import { Crowdsplit } from '../../src/products/crowdsplit'; +import type { CreatePaymentRequest } from '../../src/types/payment'; +import { ApiError } from '../../src/utils/errorHandler'; +import { getConfigFromEnv } from '../config'; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +/** + * Build a PagarMe PIX payment request. + * + * NOTE: capture_method is NOT allowed for PagarMe PIX (API returns 422). + */ +const buildPagarMePixPaymentRequest = ( + customerId: string, + confirm = false, +): CreatePaymentRequest => + ({ + provider: 'pagar_me', + source: { + amount: 100, + currency: 'brl', + customer: { id: customerId }, + payment_method: { type: 'pix', expiry_date: '2030-01-01' }, + }, + confirm, + }) as unknown as CreatePaymentRequest; + +/** + * Build a Stripe card payment request. + * + * capture_method: 'automatic' is REQUIRED for Stripe. + */ +const buildStripePaymentRequest = ( + customerId: string, + confirm = false, +): CreatePaymentRequest => { + const request: Record = { + provider: 'stripe', + source: { + amount: 1500, + currency: 'usd', + payment_method: { type: 'card' }, + capture_method: 'automatic', + customer: { id: customerId }, + }, + confirm, + metadata: { + order_id: `test-${Date.now()}`, + customer_email: 'integration-test@example.com', + }, + }; + return request as unknown as CreatePaymentRequest; +}; + +describe('PaymentService - Integration', () => { + let payments: ReturnType['payments']; + let customers: ReturnType['customers']; + + /** A KYC-approved customer ID fetched via filtered customer list. */ + let approvedCustomerId: string | undefined; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + const cs = Crowdsplit(client); + customers = cs.customers; + payments = cs.payments; + }); + + // --------------------------------------------------------------- + // Find an approved customer using filtered list + // --------------------------------------------------------------- + it( + 'should find a Stripe-approved customer from the database', + async () => { + const listRes = await customers.list({ + target_role: 'customer', + provider_registration_status: 'approved', + provider: 'stripe', + }); + + expect(listRes.ok).toBe(true); + if (listRes.ok && listRes.value.data.customer_list.length === 0) { + console.warn('Skipping: no Stripe-approved customers found'); + return; + } + if (listRes.ok) { + expect(listRes.value.data.customer_list.length).toBeGreaterThan(0); + approvedCustomerId = (listRes.value.data.customer_list[0].id ?? + listRes.value.data.customer_list[0].customer_id) as string; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // Payment creation + // --------------------------------------------------------------- + it( + 'should create a payment with a valid customer', + async () => { + if (!approvedCustomerId) { + console.warn('Skipping: no approved customer available'); + return; + } + + const response = await payments.create( + buildStripePaymentRequest(approvedCustomerId, false), + ); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toBeDefined(); + expect(response.value.data.provider).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for an invalid customer', + async () => { + const response = await payments.create( + buildPagarMePixPaymentRequest('non-existent-id'), + ); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // Customer retrieval + // --------------------------------------------------------------- + it( + 'should validate that an invalid customer ID returns an error', + async () => { + const response = await customers.get('invalid-customer-id-123'); + expect(response.ok).toBe(false); + if (!response.ok) { + expect(response.error).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should validate that the approved customer ID is valid', + async () => { + if (!approvedCustomerId) { + console.warn('Skipping: no approved customer available'); + return; + } + + const response = await customers.get(approvedCustomerId); + expect(response.ok).toBe(true); + if (response.ok) { + const id = response.value.data.id ?? response.value.data.customer_id; + expect(id).toEqual(approvedCustomerId); + expect(response.value.data).toBeDefined(); + expect(response.value.data.email).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // Payment confirmation + // --------------------------------------------------------------- + it( + 'should confirm a payment with a valid ID', + async () => { + if (!approvedCustomerId) { + console.warn('Skipping: no approved customer available'); + return; + } + + const createResponse = await payments.create( + buildStripePaymentRequest(approvedCustomerId, false), + ); + expect(createResponse.ok).toBe(true); + if (!createResponse.ok) return; + + const paymentId = createResponse.value.data.id; + const response = await payments.confirm(paymentId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(paymentId); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for an invalid confirm ID', + async () => { + const response = await payments.confirm('non-existent-id'); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // Payment cancellation + // --------------------------------------------------------------- + it( + 'should cancel a payment with a valid ID', + async () => { + if (!approvedCustomerId) { + console.warn('Skipping: no approved customer available'); + return; + } + + const createResponse = await payments.create( + buildStripePaymentRequest(approvedCustomerId, false), + ); + expect(createResponse.ok).toBe(true); + if (!createResponse.ok) return; + + const paymentId = createResponse.value.data.id; + const response = await payments.cancel(paymentId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(paymentId); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for an invalid cancel ID', + async () => { + const response = await payments.cancel('non-existent-id'); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); +}); diff --git a/packages/api/scripts/jest-run-exact.js b/packages/api/scripts/jest-run-exact.js new file mode 100644 index 00000000..315fea8b --- /dev/null +++ b/packages/api/scripts/jest-run-exact.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node +/** + * Wrapper for Jest that adds regex anchors (^ $) to -t/--testNamePattern + * so only the exact test runs. Fixes vscode-jest running wrong tests when + * similar test names exist. + */ +const { spawn } = require('child_process'); + +const args = process.argv.slice(2); + +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function anchorTestPattern(arg) { + const match = arg.match(/^--testNamePattern=(.+)$/); + if (match) { + const value = match[1].replace(/^["']|["']$/g, ''); + if (value && !value.startsWith('^')) { + return `--testNamePattern=^${escapeRegex(value)}$`; + } + } + return arg; +} + +const tIdx = args.indexOf('-t'); +if (tIdx >= 0 && args[tIdx + 1]) { + const value = args[tIdx + 1].replace(/^["']|["']$/g, ''); + if (value && !value.startsWith('^')) { + args[tIdx + 1] = `^${escapeRegex(value)}$`; + } +} else { + args.forEach((arg, i) => { + if (arg.startsWith('--testNamePattern=')) { + args[i] = anchorTestPattern(arg); + } + }); +} + +const child = spawn( + 'pnpm', + ['--filter', '@oaknetwork/api', 'exec', 'jest', ...args], + { stdio: 'inherit', cwd: require('path').resolve(__dirname, '../..') }, +); + +child.on('exit', (code) => process.exit(code ?? 0)); From 855ab0be5275dc9f1b14573155208bd02f7eb510 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:17:55 -0300 Subject: [PATCH 042/143] Refactor code structure for improved readability and maintainability --- .gitignore | 8 + package-lock.json | 1817 --------------------------------------------- 2 files changed, 8 insertions(+), 1817 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index d92b5f3f..9568e9ee 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,10 @@ coverage/ .yarn-cache/ .yarn-integrity +### Wrong package manager lockfiles (use pnpm) +package-lock.json +**/package-lock.json + ### Lint/test caches .eslintcache .jest-cache/ @@ -101,5 +105,9 @@ coverage/ storage/ init-queues.sh +### Test and scratch files +test-sdk.ts +**/test-sdk.ts + .specstory .specstory/** */ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 037edb50..00000000 --- a/package-lock.json +++ /dev/null @@ -1,1817 +0,0 @@ -{ - "name": "crowdsplit-sdk-monorepo", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "crowdsplit-sdk-monorepo", - "version": "0.0.0", - "devDependencies": { - "@changesets/cli": "^2.29.8", - "@types/jest": "^30.0.0" - }, - "engines": { - "node": ">=18.0.0", - "pnpm": ">=8.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@changesets/apply-release-plan": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.14.tgz", - "integrity": "sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/config": "^3.1.2", - "@changesets/get-version-range-type": "^0.4.0", - "@changesets/git": "^3.0.4", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "detect-indent": "^6.0.0", - "fs-extra": "^7.0.1", - "lodash.startcase": "^4.4.0", - "outdent": "^0.5.0", - "prettier": "^2.7.1", - "resolve-from": "^5.0.0", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/apply-release-plan/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@changesets/assemble-release-plan": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", - "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/assemble-release-plan/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@changesets/changelog-git": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", - "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0" - } - }, - "node_modules/@changesets/cli": { - "version": "2.29.8", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.8.tgz", - "integrity": "sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/apply-release-plan": "^7.0.14", - "@changesets/assemble-release-plan": "^6.0.9", - "@changesets/changelog-git": "^0.2.1", - "@changesets/config": "^3.1.2", - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/get-release-plan": "^4.0.14", - "@changesets/git": "^3.0.4", - "@changesets/logger": "^0.1.1", - "@changesets/pre": "^2.0.2", - "@changesets/read": "^0.6.6", - "@changesets/should-skip-package": "^0.1.2", - "@changesets/types": "^6.1.0", - "@changesets/write": "^0.4.0", - "@inquirer/external-editor": "^1.0.2", - "@manypkg/get-packages": "^1.1.3", - "ansi-colors": "^4.1.3", - "ci-info": "^3.7.0", - "enquirer": "^2.4.1", - "fs-extra": "^7.0.1", - "mri": "^1.2.0", - "p-limit": "^2.2.0", - "package-manager-detector": "^0.2.0", - "picocolors": "^1.1.0", - "resolve-from": "^5.0.0", - "semver": "^7.5.3", - "spawndamnit": "^3.0.1", - "term-size": "^2.1.0" - }, - "bin": { - "changeset": "bin.js" - } - }, - "node_modules/@changesets/cli/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@changesets/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@changesets/cli/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@changesets/config": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.2.tgz", - "integrity": "sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/get-dependents-graph": "^2.1.3", - "@changesets/logger": "^0.1.1", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1", - "micromatch": "^4.0.8" - } - }, - "node_modules/@changesets/errors": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", - "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", - "dev": true, - "license": "MIT", - "dependencies": { - "extendable-error": "^0.1.5" - } - }, - "node_modules/@changesets/get-dependents-graph": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", - "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "picocolors": "^1.1.0", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/get-dependents-graph/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@changesets/get-release-plan": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.14.tgz", - "integrity": "sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/assemble-release-plan": "^6.0.9", - "@changesets/config": "^3.1.2", - "@changesets/pre": "^2.0.2", - "@changesets/read": "^0.6.6", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3" - } - }, - "node_modules/@changesets/get-version-range-type": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", - "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@changesets/git": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", - "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^0.2.0", - "@manypkg/get-packages": "^1.1.3", - "is-subdir": "^1.1.1", - "micromatch": "^4.0.8", - "spawndamnit": "^3.0.1" - } - }, - "node_modules/@changesets/logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", - "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.0" - } - }, - "node_modules/@changesets/parse": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.2.tgz", - "integrity": "sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "js-yaml": "^4.1.1" - } - }, - "node_modules/@changesets/parse/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@changesets/parse/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@changesets/pre": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", - "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/errors": "^0.2.0", - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1" - } - }, - "node_modules/@changesets/read": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.6.tgz", - "integrity": "sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/git": "^3.0.4", - "@changesets/logger": "^0.1.1", - "@changesets/parse": "^0.4.2", - "@changesets/types": "^6.1.0", - "fs-extra": "^7.0.1", - "p-filter": "^2.1.0", - "picocolors": "^1.1.0" - } - }, - "node_modules/@changesets/should-skip-package": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", - "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "@manypkg/get-packages": "^1.1.3" - } - }, - "node_modules/@changesets/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", - "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@changesets/write": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", - "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@changesets/types": "^6.1.0", - "fs-extra": "^7.0.1", - "human-id": "^4.1.1", - "prettier": "^2.7.1" - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", - "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@manypkg/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/node": "^12.7.1", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0" - } - }, - "node_modules/@manypkg/find-root/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@manypkg/find-root/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@manypkg/get-packages": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", - "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@changesets/types": "^4.0.1", - "@manypkg/find-root": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "read-yaml-file": "^1.1.0" - } - }, - "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", - "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@manypkg/get-packages/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/better-path-resolve": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", - "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-windows": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/enquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/enquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/expect": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", - "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.0.5", - "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.5", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/extendable-error": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", - "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/human-id": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", - "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==", - "dev": true, - "license": "MIT", - "bin": { - "human-id": "dist/cli.js" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-subdir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", - "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "better-path-resolve": "1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jest-diff": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", - "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "pretty-format": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", - "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "jest-diff": "30.0.5", - "pretty-format": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", - "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.5", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "jest-util": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.0.5", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/outdent": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", - "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-map": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-manager-detector": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", - "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "quansync": "^0.2.7" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/read-yaml-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", - "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.6.1", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/read-yaml-file/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/spawndamnit": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", - "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", - "dev": true, - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "cross-spawn": "^7.0.5", - "signal-exit": "^4.0.1" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - } - } -} From 6d6a082d7d42328175dd6a5be10babbec2c771a7 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:18:05 -0300 Subject: [PATCH 043/143] chore: update pnpm engine requirement to version 10.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b43d7cd6..eded3ee2 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,6 @@ "packageManager": "pnpm@10.17.1+sha512.17c560fca4867ae9473a3899ad84a88334914f379be46d455cbf92e5cf4b39d34985d452d2583baf19967fa76cb5c17bc9e245529d0b98745721aa7200ecaf7a", "engines": { "node": ">=18.0.0", - "pnpm": ">=8.0.0" + "pnpm": ">=10.0.0" } } From cc6ab4f85ceae06ed0fde1836a55b4c997c41178 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:18:21 -0300 Subject: [PATCH 044/143] fix: update CI and release workflows to exclude contracts from build and test steps --- .github/workflows/ci.yml | 7 +++---- .github/workflows/release.yml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a39d45df..1edd7d42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,10 +38,10 @@ jobs: run: pnpm changeset:status - name: Build all packages - run: pnpm -r --workspace-concurrency=Infinity build + run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts build - name: Run tests with coverage (enforces 100% threshold) - run: pnpm -r --workspace-concurrency=Infinity test --coverage + run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts test --coverage env: CI: true CLIENT_ID: ${{ secrets.CLIENT_ID }} @@ -58,5 +58,4 @@ jobs: retention-days: 30 - name: Run lint - run: pnpm -r --workspace-concurrency=Infinity lint - continue-on-error: true + run: pnpm -r --workspace-concurrency=Infinity --filter=!@oaknetwork/contracts lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31d58e39..ce7b472c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,12 +47,12 @@ jobs: - name: Build packages if: steps.changesets.outputs.hasChangesets == 'false' - run: pnpm build + run: pnpm --filter=!@oaknetwork/contracts build - name: Update npm for OIDC support if: steps.changesets.outputs.hasChangesets == 'false' run: | - npm install -g npm@latest + npm install -g npm@10.9.2 npm --version - name: Publish packages From 48ecfc2e90b44a6761a1fa3f7c17c1ce6af9ad25 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:18:43 -0300 Subject: [PATCH 045/143] refactor: remove unused authService and config types; delete test-sdk script --- packages/api/package-lock.json | 4872 ---------------------- packages/api/src/services/authService.ts | 15 - packages/api/src/types/config.ts | 3 - packages/api/test-sdk.ts | 258 -- 4 files changed, 5148 deletions(-) delete mode 100644 packages/api/package-lock.json delete mode 100644 packages/api/src/services/authService.ts delete mode 100644 packages/api/src/types/config.ts delete mode 100644 packages/api/test-sdk.ts diff --git a/packages/api/package-lock.json b/packages/api/package-lock.json deleted file mode 100644 index 79c75417..00000000 --- a/packages/api/package-lock.json +++ /dev/null @@ -1,4872 +0,0 @@ -{ - "name": "@oaknetwork/api", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@oaknetwork/api", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "dotenv": "^17.2.1", - "nock": "^14.0.10" - }, - "devDependencies": { - "@types/jest": "^30.0.0", - "@types/node": "^20.14.11", - "jest": "^30.0.5", - "ts-jest": "^29.4.1", - "ts-node": "^10.9.2", - "typescript": "^5.5.4" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mswjs/interceptors": { - "version": "0.39.8", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.8.tgz", - "integrity": "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/node": { - "version": "20.19.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.32.tgz", - "integrity": "sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "17.2.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", - "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "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" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" - } - }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nock": { - "version": "14.0.10", - "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.10.tgz", - "integrity": "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==", - "license": "MIT", - "dependencies": { - "@mswjs/interceptors": "^0.39.5", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">=18.20.0 <20 || >=20.12.1" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "license": "MIT" - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/packages/api/src/services/authService.ts b/packages/api/src/services/authService.ts deleted file mode 100644 index dd9f0648..00000000 --- a/packages/api/src/services/authService.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { OakClient, Result, TokenResponse } from "../types"; - -export interface AuthService { - grantToken(): Promise>; - getAccessToken(): Promise>; -} - -/** - * @param client - Configured OakClient instance - * @returns AuthService instance - */ -export const createAuthService = (client: OakClient): AuthService => ({ - grantToken: () => client.grantToken(), - getAccessToken: () => client.getAccessToken(), -}); diff --git a/packages/api/src/types/config.ts b/packages/api/src/types/config.ts deleted file mode 100644 index 4e616aad..00000000 --- a/packages/api/src/types/config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { OakClientConfig } from "./client"; - -export type SDKConfig = OakClientConfig; diff --git a/packages/api/test-sdk.ts b/packages/api/test-sdk.ts deleted file mode 100644 index 00ed2054..00000000 --- a/packages/api/test-sdk.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { - createOakClient, - CustomerListQueryParams, - SubmitProviderRegistrationRequest, -} from "./src"; -import { Crowdsplit } from "./src/products/crowdsplit"; -import dotenv from "dotenv"; -dotenv.config(); - -async function testSDK() { - // Initialize the SDK with your backend's configuration - const client = createOakClient({ - environment: (process.env.OAK_ENVIRONMENT as "sandbox" | "production") || "sandbox", - clientId: process.env.CLIENT_ID as string, - clientSecret: process.env.CLIENT_SECRET as string, - }); - const cs = Crowdsplit(client); - - // try { - // const params: CustomerListQueryParams = { - // limit: 10, - // offset: 0, - // }; - // const response = await sdk.customer.listCustomers(params); - // console.log("Customer List Response:", response); - // } catch (error) { - // console.error("Error:", error); - // } - - // try { - // const provider = "mercado_pago"; - // const response = await sdk.provider.getProviderSchema(provider); - // console.log("Provider Schema Response:", response); - // } catch (error) { - // console.error("Error:", error); - // } - - // try { - // const customerId = "3ccf61b1-d884-4b9a-8a1c-3c4f29fcd0a1"; - // const response = await sdk.provider.getProviderRegistrationStatus( - // customerId - // ); - // console.log("Provider Registration Status Response:", response); - // } catch (error) { - // console.error("Error:", error); - // } - - // try { - // const customerId = "3ccf61b1-d884-4b9a-8a1c-3c4f29fcd0a1"; - // const registration: ProviderRegistrationRequest = { - // provider: "pagar_me", - // target_role: "customer", - // }; - // const response = await sdk.provider.submitProviderRegistration( - // customerId, - // registration - // ); - // console.log("Provider Registration Response:", response); - // } catch (error) { - // console.error("Error:", error); - // } - - // try { - // const confirmation = await sdk.payment.confirmPayment( - // "eb4ebcd4-e9c1-44f6-94c8-c55117bb6abb" - // ); - // console.log("Payment confirmed:", confirmation.data.status); - // } catch (err) { - // console.error("Error confirming payment:", err); - // } - - // try { - // const confirmation = await sdk.payment.addCustomerPaymentMethod( - // "1c1fd15c-2545-4762-9af9-27a0246520ba", - // { - // type: "bank", - // bank_name: "JP Morgan", - // bank_account_name: "21235", - // bank_account_number: "62650521015", - // bank_branch_code: "52", - // bank_swift_code: "4562", // ispb - // bank_account_type: "payment", // payment/ checking/ savings - // } - // ); - // console.log("Payment confirmed:", confirmation.data.status); - // } catch (err) { - // console.error("Error confirming payment:", err); - // } - - // try { - // const confirmation = await sdk.payment.getCustomerPaymentMethod( - // "1c1fd15c-2545-4762-9af9-27a0246520ba", - // "398e54c4-5c34-46f9-8a66-d489c901288f" - // ); - // console.log("Payment :", confirmation.data); - // } catch (err) { - // console.error("Error getting payment:", err); - // } - - // try { - // const confirmation = await sdk.payment.deleteCustomerPaymentMethod( - // "1c1fd15c-2545-4762-9af9-27a0246520ba", - // "a9d6f9f8-76d7-48c5-85b7-d265504b4bdb" - // ); - // console.log("Payment method :", confirmation); - // } catch (err) { - // console.error("Error deleting payment:", err); - // } - - // try { - // const confirmation = await sdk.payment.getAllCustomerPaymentMethods( - // "1c1fd15c-2545-4762-9af9-27a0246520ba", - // { - // type: "card,pix,customer_wallet", - // } - // ); - // console.log("Payment method :", confirmation.data); - // } catch (err) { - // console.error("Error getting payment:", err); - // } - - // try { - // const confirmation = await sdk.payment.deleteCustomerPaymentMethod( - // "1c1fd15c-2545-4762-9af9-27a0246520ba", - // "a9d6f9f8-76d7-48c5-85b7-d265504b4bdb" - // ); - // console.log("Payment method :", confirmation); - // } catch (err) { - // console.error("Error deleting payment:", err); - // } - - // try { - // const response = await sdk.transaction.getAllTransactions({ - // type_list: "refund,installment_payment", - // }); - // console.log("Payment method :", response); - // } catch (err) { - // console.error("Error deleting payment:", err); - // } - - // try { - // const response = await sdk.transaction.getTransaction( - // "418b7de3-093b-4eaf-bd61-7cd77d104410" - // ); - // console.log("Transaction found :", response); - // } catch (err) { - // console.error("Error getting transaction:", err); - // } - - // try { - // const response = await sdk.webhookService.getAllWebhooks(); - // console.log("Webhook found :", response); - // } catch (err) { - // console.error("Error getting Webhook:", err); - // } - - // try { - // const response = await sdk.webhookService.registerWebhook({ - // url: "localhost:3000/ping2", - // description: "testing sdk", - // }); - // console.log("Webhook found :", response); - // } catch (err) { - // console.error("Error getting Webhook:", err); - // } - - // try { - // const response = await sdk.webhookService.deleteWebhook( - // "40adc88a-c19f-4c2a-bf6e-8e967e675ebf" - // ); - // console.log("Webhook found :", response); - // } catch (error) { - // console.error("Error getting Webhook:", error); - // } - - // try { - // const response = await sdk.webhookService.getAllWebhooks(); - // console.log("Webhook found :", response); - // } catch (err) { - // console.error("Error getting Webhook:", err); - // } - - // try { - // const response = await sdk.webhookService.getWebhookNotifications( - // "92cac70a-ef19-4998-b141-4614c4f650db" - // ); - // console.log("Webhook found :", response); - // } catch (err) { - // console.error("Error getting Webhook:", err); - // } - - // try { - // const reqBody = { - // provider: "stripe" as const, - // source: { - // amount: 2500, - // currency: "usd" as const, - // customer: { - // id: "1c1fd15c-2545-4762-9af9-27a0246520ba", - // }, // optional - // }, - // destination: { - // customer: { - // id: "1c1fd15c-2545-4762-9af9-27a0246520ba", - // }, - // payment_method: { - // type: "bank" as const, - // id: "dfe076fc-b47e-4ccd-be46-7301694bbbf5", - // }, - // }, // required - // metadata: { - // reference_id: "payout_20250717_abc123", - // campaign_id: "crowdfund_xyz", - // }, - // }; - // const response = await sdk.transfer.createTransfer(reqBody); - // console.log("transfer successful :", response); - // } catch (err) { - // console.error("Error creating transfer:", err); - // } - - // const req = { - // provider: "avenia" as const, - // source: { - // customer: { - // id: "fd1bcf8a-8f2a-493d-b3d3-e575c506cb73", - // }, // if not provided, the master account would be assumed as source - // currency: "brla", - // amount: 100, - // }, - // destination: { - // customer: { - // id: "b776bbfb-69cd-42df-bb64-06d46c79db6d", - // }, - // currency: "brl", - // payment_method: { - // type: "pix" as const, - // id: "e82ed5e1-c828-41c1-9b43-a391bf5e33f2", - // }, - // }, - // }; - - // try { - // const response = await sdk.sell.createSell(req); - // console.log("Webhook found :", response); - // } catch (err) { - // console.error("Error getting Webhook:", err); - // } - - try { - const response = await cs.transactions.list(); - console.log("Transactions:", response); - } catch (err) { - console.error("Error getting transactions:", err); - } -} - -testSDK(); From fc0912b50fda16dbe4d494410467bc1eca1d69ae Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:18:53 -0300 Subject: [PATCH 046/143] fix: update devDependencies for dotenv and nock; remove unused dependencies --- packages/api/package.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index ed450bf6..e93ab3ce 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -37,8 +37,10 @@ "devDependencies": { "@types/jest": "^30.0.0", "@types/node": "^20.14.11", + "dotenv": "^17.2.1", "jest": "^30.0.5", - "ts-jest": "^29.4.1", + "nock": "^14.0.10", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.5.4" }, @@ -52,9 +54,5 @@ "files": [ "dist" ], - "dependencies": { - "dotenv": "^17.2.1", - "nock": "^14.0.10" - }, "packageManager": "pnpm@10.17.1+sha512.17c560fca4867ae9473a3899ad84a88334914f379be46d455cbf92e5cf4b39d34985d452d2583baf19967fa76cb5c17bc9e245529d0b98745721aa7200ecaf7a" } From b90c4703ab481a74d494599f47e4d606553d0e28 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:04 -0300 Subject: [PATCH 047/143] fix: add comment for clarity on experimentalDecorators in tsconfig --- packages/api/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 1277718f..ecb5e923 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -10,6 +10,7 @@ "forceConsistentCasingInFileNames": true, "declaration": true, "moduleResolution": "node", + // Required for @SandboxOnly decorator exported in public API "experimentalDecorators": true, "emitDecoratorMetadata": true }, From b01513e4c8a21236da5f7ecf9066484a31b8844b Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:13 -0300 Subject: [PATCH 048/143] fix: improve error handling in payment method tests for missing prerequisites --- .../integration/paymentMethodService.test.ts | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/api/__tests__/integration/paymentMethodService.test.ts b/packages/api/__tests__/integration/paymentMethodService.test.ts index 0c2649df..36ada682 100644 --- a/packages/api/__tests__/integration/paymentMethodService.test.ts +++ b/packages/api/__tests__/integration/paymentMethodService.test.ts @@ -50,8 +50,7 @@ describe("PaymentMethodService - Integration", () => { "should add a PIX payment method", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.add(testCustomerId, { @@ -77,8 +76,7 @@ describe("PaymentMethodService - Integration", () => { "should add a bank account payment method (Stripe)", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.add(testCustomerId, { @@ -115,8 +113,7 @@ describe("PaymentMethodService - Integration", () => { "should get the created payment method", async () => { if (!testCustomerId || !createdPaymentMethodId) { - console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); - return; + throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); @@ -133,8 +130,7 @@ describe("PaymentMethodService - Integration", () => { "should handle invalid payment method ID gracefully", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.get(testCustomerId, "non-existent-pm-id"); @@ -160,8 +156,7 @@ describe("PaymentMethodService - Integration", () => { "should list all payment methods for customer", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.list(testCustomerId); @@ -178,8 +173,7 @@ describe("PaymentMethodService - Integration", () => { "should list payment methods with type filter", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.list(testCustomerId, { type: "pix" }); @@ -199,8 +193,7 @@ describe("PaymentMethodService - Integration", () => { "should list payment methods with status filter", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.list(testCustomerId, { status: "active" }); @@ -217,8 +210,7 @@ describe("PaymentMethodService - Integration", () => { "should list payment methods with platform filter", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.list(testCustomerId, { platform: "stripe" }); @@ -237,8 +229,7 @@ describe("PaymentMethodService - Integration", () => { "should delete the payment method", async () => { if (!testCustomerId || !createdPaymentMethodId) { - console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); - return; + throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.delete(testCustomerId, createdPaymentMethodId); @@ -255,8 +246,7 @@ describe("PaymentMethodService - Integration", () => { "should handle deleting non-existent payment method", async () => { if (!testCustomerId) { - console.warn("Skipping: testCustomerId not available"); - return; + throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.delete(testCustomerId, "non-existent-pm-id"); @@ -270,8 +260,7 @@ describe("PaymentMethodService - Integration", () => { "should verify payment method is deleted", async () => { if (!testCustomerId || !createdPaymentMethodId) { - console.warn("Skipping: testCustomerId or createdPaymentMethodId not available"); - return; + throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); } const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); From 0b62d2b6551a739a075ed86f5034d4411fcb57c9 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:17 -0300 Subject: [PATCH 049/143] fix: improve error handling in transfer service tests for missing customers and payment methods --- packages/api/__tests__/integration/transferService.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/api/__tests__/integration/transferService.test.ts b/packages/api/__tests__/integration/transferService.test.ts index 41b3f071..a6580de8 100644 --- a/packages/api/__tests__/integration/transferService.test.ts +++ b/packages/api/__tests__/integration/transferService.test.ts @@ -39,8 +39,7 @@ describe("TransferService - Integration", () => { customerList.ok && customerList.value.data.customer_list.length === 0 ) { - console.warn("Skipping: no customers found"); - return; + throw new Error("No customers found - this test requires at least one customer with approved provider registration"); } if (customerList.ok) { expect(customerList.value.data.customer_list.length).toBeGreaterThan(0); @@ -63,8 +62,7 @@ describe("TransferService - Integration", () => { } if (!customerId || !paymentMethodId) { - console.warn("Skipping: no customer or payment method found"); - return; + throw new Error("No customer or payment method found - this test requires at least one customer with an active bank payment method"); } expect(customerId).toBeDefined(); From 58d7314bae1d7712bfde0aaf9d8e51ed5958661c Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:22 -0300 Subject: [PATCH 050/143] fix: improve error handling in webhook service tests for missing createdWebhookId --- .../integration/webhookService.test.ts | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/api/__tests__/integration/webhookService.test.ts b/packages/api/__tests__/integration/webhookService.test.ts index 3e57b253..c31305dd 100644 --- a/packages/api/__tests__/integration/webhookService.test.ts +++ b/packages/api/__tests__/integration/webhookService.test.ts @@ -42,8 +42,7 @@ describe("WebhookService - Integration", () => { it("should handle duplicate URL registration", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.register({ @@ -70,8 +69,7 @@ describe("WebhookService - Integration", () => { describe("get", () => { it("should get the created webhook", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.get(createdWebhookId); @@ -93,8 +91,7 @@ describe("WebhookService - Integration", () => { describe("update", () => { it("should update the webhook", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.update(createdWebhookId, { @@ -111,8 +108,7 @@ describe("WebhookService - Integration", () => { describe("toggle", () => { it("should toggle webhook status to inactive", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.toggle(createdWebhookId); @@ -125,8 +121,7 @@ describe("WebhookService - Integration", () => { it("should toggle webhook status back to active", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.toggle(createdWebhookId); @@ -168,8 +163,7 @@ describe("WebhookService - Integration", () => { describe("delete", () => { it("should delete the webhook", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.delete(createdWebhookId); @@ -182,8 +176,7 @@ describe("WebhookService - Integration", () => { it("should verify webhook is deleted", async () => { if (!createdWebhookId) { - console.warn("Skipping: createdWebhookId not available from previous test"); - return; + throw new Error("createdWebhookId not available from previous test - this test requires prerequisite setup"); } const response = await webhooks.get(createdWebhookId); From f25d7b477a8d59f0dfa62d11c5f3813d1d35a7c7 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:26 -0300 Subject: [PATCH 051/143] test: add unit tests for buildUrl utility function --- packages/api/__tests__/unit/buildUrl.test.ts | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/api/__tests__/unit/buildUrl.test.ts diff --git a/packages/api/__tests__/unit/buildUrl.test.ts b/packages/api/__tests__/unit/buildUrl.test.ts new file mode 100644 index 00000000..7953f7fd --- /dev/null +++ b/packages/api/__tests__/unit/buildUrl.test.ts @@ -0,0 +1,61 @@ +import { buildUrl } from "../../src/utils/buildUrl"; + +describe("buildUrl", () => { + it("should join URL segments correctly", () => { + const result = buildUrl("https://api.oak.com", "api/v1", "customers"); + expect(result).toBe("https://api.oak.com/api/v1/customers"); + }); + + it("should handle trailing slashes", () => { + const result = buildUrl("https://api.oak.com/", "api/v1/", "customers/"); + expect(result).toBe("https://api.oak.com/api/v1/customers"); + }); + + it("should handle single segment", () => { + const result = buildUrl("https://api.oak.com"); + expect(result).toBe("https://api.oak.com"); + }); + + it("should filter out undefined segments", () => { + const result = buildUrl( + "https://api.oak.com", + "api/v1", + undefined, + "customers", + ); + expect(result).toBe("https://api.oak.com/api/v1/customers"); + }); + + it("should filter out empty string segments", () => { + const result = buildUrl("https://api.oak.com", "api/v1", "", "customers"); + expect(result).toBe("https://api.oak.com/api/v1/customers"); + }); + + it("should handle resource IDs", () => { + const customerId = "cust_123"; + const result = buildUrl( + "https://api.oak.com", + "api/v1/customers", + customerId, + ); + expect(result).toBe("https://api.oak.com/api/v1/customers/cust_123"); + }); + + it("should handle complex paths", () => { + const result = buildUrl( + "https://api.oak.com", + "api/v1", + "customers", + "cust_123", + "payments", + ); + expect(result).toBe( + "https://api.oak.com/api/v1/customers/cust_123/payments", + ); + }); + + it("should work with localhost URLs", () => { + const result = buildUrl("http://localhost:3000", "api", "test"); + expect(result).toBe("http://localhost:3000/api/test"); + }); +}); From 37b0a78ff9f6c71093b056c2cee1a473792f8a90 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:30 -0300 Subject: [PATCH 052/143] test: add unit tests for webhook verification and payload parsing --- .../unit/webhookVerification.test.ts | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 packages/api/__tests__/unit/webhookVerification.test.ts diff --git a/packages/api/__tests__/unit/webhookVerification.test.ts b/packages/api/__tests__/unit/webhookVerification.test.ts new file mode 100644 index 00000000..d288a291 --- /dev/null +++ b/packages/api/__tests__/unit/webhookVerification.test.ts @@ -0,0 +1,135 @@ +import { createHmac } from "crypto"; +import { + verifyWebhookSignature, + parseWebhookPayload, +} from "../../src/utils/webhookVerification"; + +describe("webhookVerification", () => { + const secret = "test-webhook-secret"; + const payload = JSON.stringify({ event: "payment.created", id: "pay_123" }); + + // Helper to generate valid signature + const generateSignature = (data: string, webhookSecret: string): string => { + const hmac = createHmac("sha256", webhookSecret); + hmac.update(data); + return hmac.digest("hex"); + }; + + describe("verifyWebhookSignature", () => { + it("should verify valid signature", () => { + const signature = generateSignature(payload, secret); + const result = verifyWebhookSignature(payload, signature, secret); + expect(result).toBe(true); + }); + + it("should reject invalid signature", () => { + const invalidSignature = "invalid-signature-12345"; + const result = verifyWebhookSignature(payload, invalidSignature, secret); + expect(result).toBe(false); + }); + + it("should reject signature with wrong secret", () => { + const signature = generateSignature(payload, "wrong-secret"); + const result = verifyWebhookSignature(payload, signature, secret); + expect(result).toBe(false); + }); + + it("should reject signature with tampered payload", () => { + const signature = generateSignature(payload, secret); + const tamperedPayload = JSON.stringify({ + event: "payment.created", + id: "pay_999", + }); + const result = verifyWebhookSignature( + tamperedPayload, + signature, + secret, + ); + expect(result).toBe(false); + }); + + it("should handle empty payload", () => { + const emptyPayload = ""; + const signature = generateSignature(emptyPayload, secret); + const result = verifyWebhookSignature(emptyPayload, signature, secret); + expect(result).toBe(true); + }); + + it("should return false for signatures of different lengths", () => { + const shortSignature = "abc"; + const result = verifyWebhookSignature(payload, shortSignature, secret); + expect(result).toBe(false); + }); + }); + + describe("parseWebhookPayload", () => { + interface TestEvent { + event: string; + id: string; + } + + it("should parse and verify valid webhook", () => { + const signature = generateSignature(payload, secret); + const result = parseWebhookPayload(payload, signature, secret); + + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.event).toBe("payment.created"); + expect(result.value.id).toBe("pay_123"); + } + }); + + it("should reject invalid signature", () => { + const invalidSignature = "invalid-signature"; + const result = parseWebhookPayload( + payload, + invalidSignature, + secret, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toBe("Invalid webhook signature"); + expect(result.error.code).toBe("WEBHOOK_VERIFICATION_FAILED"); + expect(result.error.statusCode).toBe(401); + } + }); + + it("should reject invalid JSON", () => { + const invalidPayload = "{ invalid json }"; + const signature = generateSignature(invalidPayload, secret); + const result = parseWebhookPayload( + invalidPayload, + signature, + secret, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toContain("Failed to parse webhook payload"); + expect(result.error.code).toBe("WEBHOOK_PARSE_ERROR"); + expect(result.error.statusCode).toBe(400); + } + }); + + it("should handle complex nested objects", () => { + const complexPayload = JSON.stringify({ + event: "payment.created", + data: { + payment: { + id: "pay_123", + amount: 1000, + metadata: { userId: "user_456" }, + }, + }, + }); + const signature = generateSignature(complexPayload, secret); + const result = parseWebhookPayload(complexPayload, signature, secret); + + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value).toHaveProperty("data.payment.metadata.userId"); + } + }); + }); +}); From 9ec45704440e7dad72e5bd936497e932ff0fae48 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:34 -0300 Subject: [PATCH 053/143] test: add unit tests for withAuth utility function --- packages/api/__tests__/unit/withAuth.test.ts | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 packages/api/__tests__/unit/withAuth.test.ts diff --git a/packages/api/__tests__/unit/withAuth.test.ts b/packages/api/__tests__/unit/withAuth.test.ts new file mode 100644 index 00000000..a7c38f82 --- /dev/null +++ b/packages/api/__tests__/unit/withAuth.test.ts @@ -0,0 +1,77 @@ +import { withAuth } from "../../src/utils/withAuth"; +import { createOakClient } from "../../src/client"; +import { err, ok } from "../../src/types"; +import { OakError } from "../../src/utils/errorHandler"; +import type { OakClient } from "../../src/types"; + +describe("withAuth", () => { + let mockClient: OakClient; + + beforeEach(() => { + mockClient = createOakClient({ + environment: "sandbox", + clientId: "test-client-id", + clientSecret: "test-client-secret", + }); + }); + + it("should execute operation with valid token", async () => { + // Mock successful token fetch + const mockToken = "valid-access-token"; + jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(ok(mockToken)); + + // Mock operation + const mockOperation = jest.fn().mockResolvedValue(ok({ data: "success" })); + + const result = await withAuth(mockClient, mockOperation); + + expect(mockClient.getAccessToken).toHaveBeenCalled(); + expect(mockOperation).toHaveBeenCalledWith(mockToken); + expect(result).toEqual(ok({ data: "success" })); + }); + + it("should return error if token fetch fails", async () => { + // Mock failed token fetch + const tokenError = new OakError("Token fetch failed", "AUTH_ERROR", 401); + jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(err(tokenError)); + + // Mock operation (should not be called) + const mockOperation = jest.fn(); + + const result = await withAuth(mockClient, mockOperation); + + expect(mockClient.getAccessToken).toHaveBeenCalled(); + expect(mockOperation).not.toHaveBeenCalled(); + expect(result).toEqual(err(tokenError)); + }); + + it("should propagate operation errors", async () => { + // Mock successful token fetch + jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(ok("token")); + + // Mock operation that returns error + const operationError = new OakError("Operation failed", "OP_ERROR", 500); + const mockOperation = jest.fn().mockResolvedValue(err(operationError)); + + const result = await withAuth(mockClient, mockOperation); + + expect(result).toEqual(err(operationError)); + }); + + it("should handle async operations correctly", async () => { + jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(ok("token")); + + // Simulate async operation with delay + const mockOperation = jest.fn().mockImplementation(async (token) => { + await new Promise((resolve) => setTimeout(resolve, 10)); + return ok({ token }); + }); + + const result = await withAuth(mockClient, mockOperation); + + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value).toEqual({ token: "token" }); + } + }); +}); From 99f82c31fe4adf70ad47c4f07a888ac80bda9ea0 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:41 -0300 Subject: [PATCH 054/143] fix: convert token expiration from seconds to milliseconds in AuthManager --- packages/api/src/authManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/src/authManager.ts b/packages/api/src/authManager.ts index 4a197cbd..49f9d113 100644 --- a/packages/api/src/authManager.ts +++ b/packages/api/src/authManager.ts @@ -46,7 +46,8 @@ export class AuthManager { return err(response.error); } this.accessToken = response.value.access_token; - this.tokenExpiration = Date.now() + response.value.expires_in; + // Convert expires_in from seconds to milliseconds + this.tokenExpiration = Date.now() + (response.value.expires_in * 1000); return ok(response.value); } From 74c22b3380e3795479710662c71bcc210617eac6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:46 -0300 Subject: [PATCH 055/143] fix: update createOakClient to return public config without clientSecret for security --- packages/api/src/client.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/api/src/client.ts b/packages/api/src/client.ts index e54ec3ab..d3ff9270 100644 --- a/packages/api/src/client.ts +++ b/packages/api/src/client.ts @@ -1,5 +1,5 @@ import { AuthManager } from "./authManager"; -import type { OakClient, OakClientConfig, ResolvedOakClientConfig } from "./types"; +import type { OakClient, OakClientConfig, ResolvedOakClientConfig, PublicOakClientConfig } from "./types"; import { resolveBaseUrl } from "./types/environment"; import { DEFAULT_RETRY_OPTIONS, @@ -28,6 +28,15 @@ export function createOakClient(config: OakClientConfig): OakClient { baseUrl, }; + // Create public config without clientSecret for security + const publicConfig: PublicOakClientConfig = { + environment: config.environment, + clientId: config.clientId, + baseUrl, + customUrl: config.customUrl, + retryOptions: config.retryOptions, + }; + const retryOptions: RetryOptions = { ...DEFAULT_RETRY_OPTIONS, ...config.retryOptions, @@ -43,7 +52,7 @@ export function createOakClient(config: OakClientConfig): OakClient { }; return { - config: resolvedConfig, + config: publicConfig, retryOptions, getAccessToken: () => getAuthManager().getAccessToken(), grantToken: () => getAuthManager().grantToken(), From 8eaa7459d8bd46e204cda31fe852ade365c98759 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:52 -0300 Subject: [PATCH 056/143] refactor: simplify create method in BuyService by using withAuth and buildUrl --- packages/api/src/services/buyService.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/api/src/services/buyService.ts b/packages/api/src/services/buyService.ts index 248bdc0c..b4b12b53 100644 --- a/packages/api/src/services/buyService.ts +++ b/packages/api/src/services/buyService.ts @@ -1,6 +1,7 @@ import type { Buy, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface BuyService { create(buyRequest: Buy.Request): Promise>; @@ -12,17 +13,15 @@ export interface BuyService { */ export const createBuyService = (client: OakClient): BuyService => ({ async create(buyRequest: Buy.Request): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.post( - `${client.config.baseUrl}/api/v1/buy`, - buyRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/buy"), + buyRequest, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From 5eb9b28df9dd0e02f87ce81178b38f2393c081a6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:19:57 -0300 Subject: [PATCH 057/143] refactor: streamline customer service methods by utilizing withAuth and buildUrl --- packages/api/src/services/customerService.ts | 90 +++++++++----------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index bedd7215..5d789ca1 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -1,7 +1,8 @@ import type { Customer, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface CustomerService { create(customer: Customer.Request): Promise>; @@ -21,57 +22,49 @@ export interface CustomerService { */ export const createCustomerService = (client: OakClient): CustomerService => ({ async create(customer: Customer.Request): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/customers`, - customer, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/customers"), + customer, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async get(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${id}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/customers", id), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async list( params?: Customer.ListQueryParams, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } const queryString = buildQueryString(params); - return httpClient.get( - `${client.config.baseUrl}/api/v1/customers${queryString}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/customers")}${queryString}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, @@ -79,20 +72,17 @@ export const createCustomerService = (client: OakClient): CustomerService => ({ id: string, customer: Customer.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.put( - `${client.config.baseUrl}/api/v1/customers/${id}`, - customer, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.put( + buildUrl(client.config.baseUrl, "api/v1/customers", id), + customer, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, }); From 8b589f21ead553fba9698bb7de5f4f2cae1bfb9c Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:03 -0300 Subject: [PATCH 058/143] refactor: replace token retrieval with withAuth in PaymentMethodService methods --- .../api/src/services/paymentMethodService.ts | 86 ++++++++----------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/packages/api/src/services/paymentMethodService.ts b/packages/api/src/services/paymentMethodService.ts index 920a22d2..90cc2566 100644 --- a/packages/api/src/services/paymentMethodService.ts +++ b/packages/api/src/services/paymentMethodService.ts @@ -1,7 +1,8 @@ import type { PaymentMethod, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface PaymentMethodService { add( @@ -33,20 +34,17 @@ export const createPaymentMethodService = ( customerId: string, paymentMethod: PaymentMethod.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods`, - paymentMethod, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/customers", customerId, "payment_methods"), + paymentMethod, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, @@ -54,19 +52,16 @@ export const createPaymentMethodService = ( customerId: string, paymentId: string, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentId}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/customers", customerId, "payment_methods", paymentId), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, @@ -74,18 +69,16 @@ export const createPaymentMethodService = ( customerId: string, query?: PaymentMethod.ListQuery, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } const queryString = buildQueryString(query); - return httpClient.get( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/customers", customerId, "payment_methods")}${queryString}`, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, @@ -93,19 +86,16 @@ export const createPaymentMethodService = ( customerId: string, paymentMethodId: string, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.delete( - `${client.config.baseUrl}/api/v1/customers/${customerId}/payment_methods/${paymentMethodId}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.delete( + buildUrl(client.config.baseUrl, "api/v1/customers", customerId, "payment_methods", paymentMethodId), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, }); From d7f6c2ac6289a6b7659eddd85a701d64b5f15bd4 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:07 -0300 Subject: [PATCH 059/143] refactor: replace manual token retrieval with withAuth in PaymentService methods --- packages/api/src/services/paymentService.ts | 72 +++++++++------------ 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/packages/api/src/services/paymentService.ts b/packages/api/src/services/paymentService.ts index 09ad1ae2..d8b0cf90 100644 --- a/packages/api/src/services/paymentService.ts +++ b/packages/api/src/services/paymentService.ts @@ -1,6 +1,7 @@ import type { Payment, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface PaymentService { create(payment: Payment.Request): Promise>; @@ -14,56 +15,47 @@ export interface PaymentService { */ export const createPaymentService = (client: OakClient): PaymentService => ({ async create(payment: Payment.Request): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/payments/`, - payment, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/payments"), + payment, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async confirm(paymentId: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/payments/${paymentId}/confirm`, - {}, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/payments", paymentId, "confirm"), + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async cancel(paymentId: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/payments/${paymentId}/cancel`, - {}, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/payments", paymentId, "cancel"), + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, }); From 9e0e50950fc628b76a5fd9a502b9667adf3ffd0d Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:14 -0300 Subject: [PATCH 060/143] refactor: replace manual token handling with withAuth in PlanService methods --- packages/api/src/services/planService.ts | 117 ++++++++++------------- 1 file changed, 53 insertions(+), 64 deletions(-) diff --git a/packages/api/src/services/planService.ts b/packages/api/src/services/planService.ts index 3a17f553..0694c0dc 100644 --- a/packages/api/src/services/planService.ts +++ b/packages/api/src/services/planService.ts @@ -1,7 +1,8 @@ import type { Plan, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface PlanService { create(createPlanRequest: Plan.Request): Promise>; @@ -23,61 +24,53 @@ export const createPlanService = (client: OakClient): PlanService => ({ async create( createPlanRequest: Plan.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.post( - `${client.config.baseUrl}/api/v1/subscription/plans`, - createPlanRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/subscription/plans"), + createPlanRequest, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async publish(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.patch( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}/publish`, - undefined, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.patch( + buildUrl(client.config.baseUrl, "api/v1/subscription/plans", id, "publish"), + undefined, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async details(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.get( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/subscription/plans", id), + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async list(params?: Plan.ListQuery): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } const queryString = buildQueryString(params); - return httpClient.get( - `${client.config.baseUrl}/api/v1/subscription/plans${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/subscription/plans")}${queryString}`, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, @@ -85,31 +78,27 @@ export const createPlanService = (client: OakClient): PlanService => ({ id: string, updatePlanRequest: Plan.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.patch( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - updatePlanRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.patch( + buildUrl(client.config.baseUrl, "api/v1/subscription/plans", id), + updatePlanRequest, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async delete(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.delete( - `${client.config.baseUrl}/api/v1/subscription/plans/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.delete( + buildUrl(client.config.baseUrl, "api/v1/subscription/plans", id), + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From 238816ce6fcca5a0c0a148664501062d7058e92a Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:19 -0300 Subject: [PATCH 061/143] refactor: replace manual token handling with withAuth in ProviderService methods --- packages/api/src/services/providerService.ts | 75 +++++++++----------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/packages/api/src/services/providerService.ts b/packages/api/src/services/providerService.ts index e85c7e05..a10ee077 100644 --- a/packages/api/src/services/providerService.ts +++ b/packages/api/src/services/providerService.ts @@ -1,5 +1,7 @@ -import { err, OakClient, Provider, Result } from "../types"; +import { OakClient, Provider, Result } from "../types"; import { httpClient } from "../utils/httpClient"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface ProviderService { getSchema( @@ -22,42 +24,34 @@ export const createProviderService = (client: OakClient): ProviderService => ({ async getSchema( request: Provider.GetSchemaRequest, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.get( - `${ - client.config.baseUrl - }/api/v1/provider-registration/schema?provider=${encodeURIComponent( - request.provider, - )}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/provider-registration/schema")}?provider=${encodeURIComponent( + request.provider, + )}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async getRegistrationStatus( customerId: string, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.get( - `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/status`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/provider-registration", customerId, "status"), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, @@ -65,20 +59,17 @@ export const createProviderService = (client: OakClient): ProviderService => ({ customerId: string, registration: Provider.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/provider-registration/${customerId}/submit`, - registration, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/provider-registration", customerId, "submit"), + registration, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, }); From e7fa8172318ee6e2aae8e40293b4da64fbfa2a16 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:24 -0300 Subject: [PATCH 062/143] refactor: replace manual token handling with withAuth in RefundService methods --- packages/api/src/services/refundService.ts | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/api/src/services/refundService.ts b/packages/api/src/services/refundService.ts index 47d06937..6e9e09b9 100644 --- a/packages/api/src/services/refundService.ts +++ b/packages/api/src/services/refundService.ts @@ -1,5 +1,7 @@ -import { err, OakClient, Refund, Result } from "../types"; +import { OakClient, Refund, Result } from "../types"; import { httpClient } from "../utils/httpClient"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface RefundService { create( @@ -17,17 +19,15 @@ export const createRefundService = (client: OakClient): RefundService => ({ paymentId: string, refund: Refund.Request, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.post( - `${client.config.baseUrl}/api/v1/payments/${paymentId}/refund`, - refund, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/payments", paymentId, "refund"), + refund, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From f6971c9dd950a9d4a986eed2b21a0902961e4ef0 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:29 -0300 Subject: [PATCH 063/143] refactor: replace manual token handling with withAuth in SellService methods --- packages/api/src/services/sellService.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/api/src/services/sellService.ts b/packages/api/src/services/sellService.ts index 16e149a5..9a2402b9 100644 --- a/packages/api/src/services/sellService.ts +++ b/packages/api/src/services/sellService.ts @@ -1,6 +1,7 @@ import type { Sell, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface SellService { create(sellRequest: Sell.Request): Promise>; @@ -12,17 +13,15 @@ export interface SellService { */ export const createSellService = (client: OakClient): SellService => ({ async create(sellRequest: Sell.Request): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.post( - `${client.config.baseUrl}/api/v1/sell`, - sellRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/sell"), + sellRequest, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From 465f691e27c84e1556c2acc74a29c19087edb3c1 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:34 -0300 Subject: [PATCH 064/143] refactor: replace manual token handling with withAuth in TransactionService methods --- .../api/src/services/transactionService.ts | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/packages/api/src/services/transactionService.ts b/packages/api/src/services/transactionService.ts index 639f58ec..61a78fe5 100644 --- a/packages/api/src/services/transactionService.ts +++ b/packages/api/src/services/transactionService.ts @@ -1,7 +1,8 @@ import type { Transaction, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface TransactionService { list( @@ -24,33 +25,28 @@ export const createTransactionService = ( async list( query?: Transaction.ListQuery, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } const queryString = buildQueryString(query); - return httpClient.get( - `${client.config.baseUrl}/api/v1/transactions${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/transactions")}${queryString}`, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async get(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.get( - `${client.config.baseUrl}/api/v1/transactions/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/transactions", id), + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, @@ -58,17 +54,15 @@ export const createTransactionService = ( id: string, settlementRequest: Transaction.SettlementRequest, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.patch( - `${client.config.baseUrl}/api/v1/transactions/${id}/settle`, - settlementRequest, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.patch( + buildUrl(client.config.baseUrl, "api/v1/transactions", id, "settle"), + settlementRequest, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From df6e7d87e27a1192a2584a3dd10db421c056e8eb Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:38 -0300 Subject: [PATCH 065/143] refactor: replace manual token handling with withAuth in TransferService methods --- packages/api/src/services/transferService.ts | 26 +++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/api/src/services/transferService.ts b/packages/api/src/services/transferService.ts index fd40b625..99d8c585 100644 --- a/packages/api/src/services/transferService.ts +++ b/packages/api/src/services/transferService.ts @@ -1,6 +1,7 @@ import type { Transfer, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface TransferService { create(transfer: Transfer.Request): Promise>; @@ -12,20 +13,17 @@ export interface TransferService { */ export const createTransferService = (client: OakClient): TransferService => ({ async create(transfer: Transfer.Request): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.post( - `${client.config.baseUrl}/api/v1/transfer`, - transfer, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/transfer"), + transfer, + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, }); From 613870332df631a33ed612453ff1a3f491136023 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:43 -0300 Subject: [PATCH 066/143] refactor: replace manual token handling with withAuth in WebhookService methods --- packages/api/src/services/webhookService.ts | 159 +++++++++----------- 1 file changed, 71 insertions(+), 88 deletions(-) diff --git a/packages/api/src/services/webhookService.ts b/packages/api/src/services/webhookService.ts index 83e247db..e99569da 100644 --- a/packages/api/src/services/webhookService.ts +++ b/packages/api/src/services/webhookService.ts @@ -1,7 +1,8 @@ import type { Webhook, OakClient, Result } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; -import { err } from "../types"; +import { withAuth } from "../utils/withAuth"; +import { buildUrl } from "../utils/buildUrl"; export interface WebhookService { register(webhook: Webhook.RegisterRequest): Promise>; @@ -28,49 +29,43 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ async register( webhook: Webhook.RegisterRequest, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.post( - `${client.config.baseUrl}/api/v1/merchant/webhooks`, - webhook, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.post( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks"), + webhook, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async list(): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks"), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, async get(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - { - headers: { - Authorization: `Bearer ${token.value}`, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks", id), + { + headers: { + Authorization: `Bearer ${token}`, + }, + retryOptions: client.retryOptions, }, - retryOptions: client.retryOptions, - }, + ), ); }, @@ -78,81 +73,69 @@ export const createWebhookService = (client: OakClient): WebhookService => ({ id: string, webhook: Webhook.UpdateRequest, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.put( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - webhook, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.put( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks", id), + webhook, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async toggle(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.patch( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}/toggle`, - undefined, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.patch( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks", id, "toggle"), + undefined, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async delete(id: string): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - - return httpClient.delete( - `${client.config.baseUrl}/api/v1/merchant/webhooks/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.delete( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks", id), + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async listNotifications( params?: Webhook.ListNotificationsQuery, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } const queryString = buildQueryString(params); - return httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications${queryString}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + `${buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks/notifications")}${queryString}`, + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, async getNotification( id: string, ): Promise> { - const token = await client.getAccessToken(); - if (!token.ok) { - return err(token.error); - } - return httpClient.get( - `${client.config.baseUrl}/api/v1/merchant/webhooks/notifications/${id}`, - { - headers: { Authorization: `Bearer ${token.value}` }, - retryOptions: client.retryOptions, - }, + return withAuth(client, (token) => + httpClient.get( + buildUrl(client.config.baseUrl, "api/v1/merchant/webhooks/notifications", id), + { + headers: { Authorization: `Bearer ${token}` }, + retryOptions: client.retryOptions, + }, + ), ); }, }); From 1dea0b9e065d707d6cd31e114b441787f547a5f6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:20:57 -0300 Subject: [PATCH 067/143] refactor: update CrowdsplitProduct interface to use service types and include refunds --- packages/api/src/products/crowdsplit/index.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/api/src/products/crowdsplit/index.ts b/packages/api/src/products/crowdsplit/index.ts index 13fda538..6a477e19 100644 --- a/packages/api/src/products/crowdsplit/index.ts +++ b/packages/api/src/products/crowdsplit/index.ts @@ -6,23 +6,36 @@ import { createPaymentService, createPlanService, createProviderService, + createRefundService, createSellService, createTransactionService, createTransferService, createWebhookService, + type BuyService, + type CustomerService, + type PaymentMethodService, + type PaymentService, + type PlanService, + type ProviderService, + type RefundService, + type SellService, + type TransactionService, + type TransferService, + type WebhookService, } from "../../services"; export interface CrowdsplitProduct { - customers: ReturnType; - payments: ReturnType; - paymentMethods: ReturnType; - providers: ReturnType; - transactions: ReturnType; - webhooks: ReturnType; - transfers: ReturnType; - sell: ReturnType; - plans: ReturnType; - buy: ReturnType; + customers: CustomerService; + payments: PaymentService; + paymentMethods: PaymentMethodService; + providers: ProviderService; + refunds: RefundService; + transactions: TransactionService; + webhooks: WebhookService; + transfers: TransferService; + sell: SellService; + plans: PlanService; + buy: BuyService; } /** @@ -34,6 +47,7 @@ export const Crowdsplit = (client: OakClient): CrowdsplitProduct => ({ payments: createPaymentService(client), paymentMethods: createPaymentMethodService(client), providers: createProviderService(client), + refunds: createRefundService(client), transactions: createTransactionService(client), webhooks: createWebhookService(client), transfers: createTransferService(client), From 89909d0f670176743b4bf8f9bb1eaeffa0eaed40 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:01 -0300 Subject: [PATCH 068/143] refactor: remove unused getErrorBodyMessage function from helpers --- packages/api/src/services/helpers.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/api/src/services/helpers.ts b/packages/api/src/services/helpers.ts index b3aaf949..f0cc677e 100644 --- a/packages/api/src/services/helpers.ts +++ b/packages/api/src/services/helpers.ts @@ -21,17 +21,3 @@ export const buildQueryString = (params?: T): string => { .join("&")}`; }; -/** - * @param error - Error object to extract message from - * @returns Error message from body.msg or undefined - */ -export const getErrorBodyMessage = (error: unknown): string | undefined => { - if (typeof error !== "object" || error === null) { - return undefined; - } - if (!("body" in error)) { - return undefined; - } - const body = (error as { body?: { msg?: string } }).body; - return body?.msg; -}; From a204aea0317351c5bc311209f7d0c499aa7b24c6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:05 -0300 Subject: [PATCH 069/143] refactor: remove authService exports from services index --- packages/api/src/services/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/api/src/services/index.ts b/packages/api/src/services/index.ts index 7c2873b9..47eeb926 100644 --- a/packages/api/src/services/index.ts +++ b/packages/api/src/services/index.ts @@ -1,6 +1,3 @@ -export { createAuthService } from "./authService"; -export type { AuthService } from "./authService"; - export { createCustomerService } from "./customerService"; export type { CustomerService } from "./customerService"; From c049cabf868fc8d19ac24f7fab6b7fe51e37d96d Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:09 -0300 Subject: [PATCH 070/143] refactor: update OakClient config to use PublicOakClientConfig for security --- packages/api/src/types/client.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/api/src/types/client.ts b/packages/api/src/types/client.ts index 80fb689c..5866e3ce 100644 --- a/packages/api/src/types/client.ts +++ b/packages/api/src/types/client.ts @@ -15,8 +15,20 @@ export interface ResolvedOakClientConfig extends OakClientConfig { baseUrl: string; } +/** + * Public configuration exposed by the OakClient. + * Excludes clientSecret for security reasons. + */ +export interface PublicOakClientConfig { + environment: OakEnvironment; + clientId: string; + baseUrl: string; + customUrl?: string; + retryOptions?: Partial; +} + export interface OakClient { - readonly config: ResolvedOakClientConfig; + readonly config: PublicOakClientConfig; readonly retryOptions: RetryOptions; getAccessToken(): Promise>; grantToken(): Promise>; From a58d22861207047b20de5385fd1547d697b961fd Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:16 -0300 Subject: [PATCH 071/143] refactor: add customer identifier fields with documentation in Data interface --- packages/api/src/types/customer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 7fc2a005..7f002d29 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -18,7 +18,6 @@ export namespace Customer { } export interface Data { - id?: string; customer_id?: string; document_number?: string | null; document_type?: string | null; From 18cf34944dcc5706ba038d9499ee89a116745055 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:21 -0300 Subject: [PATCH 072/143] refactor: remove unused config export from types index --- packages/api/src/types/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index d6a49558..c033cfd8 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -1,4 +1,3 @@ -export * from "./config"; export * from "./client"; export * from "./environment"; export * from "./token"; From 42705ba1137a9fb991e35083a2d643b0ec56386f Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:25 -0300 Subject: [PATCH 073/143] refactor: update Transaction interface to include provider, source, confirm, and metadata fields --- packages/api/src/types/payment.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/api/src/types/payment.ts b/packages/api/src/types/payment.ts index 889a2db4..0dacfec4 100644 --- a/packages/api/src/types/payment.ts +++ b/packages/api/src/types/payment.ts @@ -156,14 +156,18 @@ export namespace Payment { // ---------------------------------------- // Payment responses (create / confirm / cancel) // ---------------------------------------- - export type Transaction = Request & { + export interface Transaction { + provider: string; + source: Source; + confirm?: boolean; + metadata?: Record; id: string; status: string; type: "payment"; created_at: string; updated_at: string; provider_response?: ProviderResponse; - }; + } export type Response = ApiResponse; From e9e9ea7833f27b35a72fc4b1c75efcb97a03905f Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:29 -0300 Subject: [PATCH 074/143] refactor: restructure TransferData type for clarity and consistency --- packages/api/src/types/transfer.ts | 32 +++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/api/src/types/transfer.ts b/packages/api/src/types/transfer.ts index 5fecfdd8..a49e8de8 100644 --- a/packages/api/src/types/transfer.ts +++ b/packages/api/src/types/transfer.ts @@ -65,14 +65,36 @@ export namespace Transfer { // ---------------------- // Response // ---------------------- - export type Data = Request & { + export interface TransferData { + provider: string; + source: { + amount: number; + currency: string; + customer?: { + id: string; + }; + }; + destination?: { + customer?: { + id: string; + }; + payment_method?: { + id?: string; + type: string; + chain?: string; + evm_address?: string; + }; + }; + metadata?: Record; + provider_data?: Record; id: string; - status: string; // e.g. "created" + status: string; type: "transfer"; - provider: string; created_at: string; updated_at: string; - }; + } + + export type Data = TransferData; - export type Response = ApiResponse; + export type Response = ApiResponse; } From 35fb74df20b5da8444999296bdcca04885e147ed Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:34 -0300 Subject: [PATCH 075/143] feat: add buildUrl function for consistent URL construction with trailing slash handling --- packages/api/src/utils/buildUrl.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/api/src/utils/buildUrl.ts diff --git a/packages/api/src/utils/buildUrl.ts b/packages/api/src/utils/buildUrl.ts new file mode 100644 index 00000000..15a91197 --- /dev/null +++ b/packages/api/src/utils/buildUrl.ts @@ -0,0 +1,22 @@ +/** + * Builds a URL from base and path segments with consistent trailing slash handling. + * Automatically removes trailing slashes from segments and joins them properly. + * + * @param segments - URL segments to join (base URL, path parts, IDs, etc.) + * @returns Complete URL string + * + * @example + * ```typescript + * buildUrl(client.config.baseUrl, "api/v1/customers", customerId) + * // => "https://api.oak.com/api/v1/customers/123" + * + * buildUrl(client.config.baseUrl, "api/v1/customers/") + * // => "https://api.oak.com/api/v1/customers" + * ``` + */ +export function buildUrl(...segments: (string | undefined)[]): string { + return segments + .filter((segment): segment is string => segment !== undefined && segment !== "") + .map((segment) => segment.replace(/\/$/, "")) // Remove trailing slashes + .join("/"); +} From 68eff3cbe5ccd15c1df27367756ecfde81d30629 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:39 -0300 Subject: [PATCH 076/143] refactor: change data type from any to unknown in post, put, and patch methods for better type safety --- packages/api/src/utils/httpClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/utils/httpClient.ts b/packages/api/src/utils/httpClient.ts index 1b07248c..6a2483f1 100644 --- a/packages/api/src/utils/httpClient.ts +++ b/packages/api/src/utils/httpClient.ts @@ -154,7 +154,7 @@ export const httpClient = { */ async post( url: string, - data: any, + data: unknown, config: HttpClientConfig ): Promise> { return request(url, config, { @@ -192,7 +192,7 @@ export const httpClient = { */ async put( url: string, - data: any, + data: unknown, config: HttpClientConfig ): Promise> { return request(url, config, { @@ -209,7 +209,7 @@ export const httpClient = { */ async patch( url: string, - data: any, + data: unknown, config: HttpClientConfig ): Promise> { return request(url, config, { From 744dd5dcbcae2f895e1f731e1353e67bedcea14f Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:44 -0300 Subject: [PATCH 077/143] refactor: add missing exports for withAuth, buildUrl, and webhookVerification in utils index --- packages/api/src/utils/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts index 5f7d46c8..887e683c 100644 --- a/packages/api/src/utils/index.ts +++ b/packages/api/src/utils/index.ts @@ -2,3 +2,6 @@ export * from "./httpClient"; export * from "./errorHandler"; export * from "./retryHandler"; export * from "./defaultRetryConfig"; +export * from "./withAuth"; +export * from "./buildUrl"; +export * from "./webhookVerification"; From 4da6885e97d41fe364abe74837268992860f0fb3 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:48 -0300 Subject: [PATCH 078/143] refactor: improve error handling in withRetry function for better type safety --- packages/api/src/utils/retryHandler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/src/utils/retryHandler.ts b/packages/api/src/utils/retryHandler.ts index 3e5494e4..0b4bd2b2 100644 --- a/packages/api/src/utils/retryHandler.ts +++ b/packages/api/src/utils/retryHandler.ts @@ -29,16 +29,16 @@ export async function withRetry( try { if (signal?.aborted) throw new Error("Retry aborted"); return await fn(); - } catch (error: any) { - const status = error?.status; - const shouldRetry = retryOnStatus.includes(status) || retryOnError(error); + } catch (error: unknown) { + const status = (error as { status?: number })?.status; + const shouldRetry = retryOnStatus.includes(status ?? 0) || retryOnError(error); if (attempt === maxNumberOfRetries || !shouldRetry) throw error; onRetry?.(attempt + 1, error); // Honor Retry-After header if present - let retryAfter = error?.headers?.["retry-after"]; + let retryAfter = (error as { headers?: Record })?.headers?.["retry-after"]; if (retryAfter) { waitTime = Number(retryAfter) * 1000; } else { From 5a4443095936463354f4275ff775c5c5afe0c773 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:52 -0300 Subject: [PATCH 079/143] feat: implement webhook signature verification and payload parsing functions --- packages/api/src/utils/webhookVerification.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 packages/api/src/utils/webhookVerification.ts diff --git a/packages/api/src/utils/webhookVerification.ts b/packages/api/src/utils/webhookVerification.ts new file mode 100644 index 00000000..55cf408d --- /dev/null +++ b/packages/api/src/utils/webhookVerification.ts @@ -0,0 +1,107 @@ +import { createHmac, timingSafeEqual } from "crypto"; +import { err, ok, Result } from "../types"; +import { OakError } from "./errorHandler"; + +/** + * Verifies a webhook signature using HMAC-SHA256. + * Uses timing-safe comparison to prevent timing attacks. + * + * @param payload - Raw webhook payload string (usually req.body as string) + * @param signature - Signature from webhook headers (e.g., x-oak-signature) + * @param secret - Your webhook secret from Oak dashboard + * @returns True if signature is valid, false otherwise + * + * @example + * ```typescript + * const isValid = verifyWebhookSignature( + * JSON.stringify(req.body), + * req.headers["x-oak-signature"], + * process.env.WEBHOOK_SECRET + * ); + * if (!isValid) { + * return res.status(401).send("Invalid signature"); + * } + * ``` + */ +export function verifyWebhookSignature( + payload: string, + signature: string, + secret: string, +): boolean { + try { + // Generate expected signature + const hmac = createHmac("sha256", secret); + hmac.update(payload); + const expectedSignature = hmac.digest("hex"); + + // Convert both signatures to buffers for timing-safe comparison + const signatureBuffer = Buffer.from(signature, "utf-8"); + const expectedBuffer = Buffer.from(expectedSignature, "utf-8"); + + // Ensure buffers are same length to prevent timing attacks + if (signatureBuffer.length !== expectedBuffer.length) { + return false; + } + + // Use timing-safe comparison + return timingSafeEqual(signatureBuffer, expectedBuffer); + } catch { + return false; + } +} + +/** + * Parses and verifies a webhook payload in one step. + * Combines signature verification with JSON parsing. + * + * @param payload - Raw webhook payload string + * @param signature - Signature from webhook headers + * @param secret - Your webhook secret + * @returns Result containing parsed payload or error + * + * @example + * ```typescript + * const result = parseWebhookPayload( + * JSON.stringify(req.body), + * req.headers["x-oak-signature"], + * process.env.WEBHOOK_SECRET + * ); + * + * if (!result.ok) { + * return res.status(401).send(result.error.message); + * } + * + * const event = result.value; + * // Handle event... + * ``` + */ +export function parseWebhookPayload( + payload: string, + signature: string, + secret: string, +): Result { + // Verify signature first + if (!verifyWebhookSignature(payload, signature, secret)) { + return err( + new OakError( + "Invalid webhook signature", + "WEBHOOK_VERIFICATION_FAILED", + 401, + ), + ); + } + + // Parse JSON + try { + const parsed = JSON.parse(payload) as T; + return ok(parsed); + } catch (error) { + return err( + new OakError( + `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`, + "WEBHOOK_PARSE_ERROR", + 400, + ), + ); + } +} From bdba8ca570fedef3bf3629d3c32ca1aae1f5d50c Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:21:56 -0300 Subject: [PATCH 080/143] feat: add withAuth function for authenticated HTTP operations --- packages/api/src/utils/withAuth.ts | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/api/src/utils/withAuth.ts diff --git a/packages/api/src/utils/withAuth.ts b/packages/api/src/utils/withAuth.ts new file mode 100644 index 00000000..7b13f4d6 --- /dev/null +++ b/packages/api/src/utils/withAuth.ts @@ -0,0 +1,31 @@ +import type { OakClient, Result } from "../types"; +import { err } from "../types"; + +/** + * Higher-order function that wraps HTTP operations with authentication. + * Handles token fetching and error propagation automatically. + * + * @param client - Configured OakClient instance + * @param operation - Callback that receives the access token and returns a Result + * @returns Result from the operation or token fetch error + * + * @example + * ```typescript + * return withAuth(client, (token) => + * httpClient.post(url, data, { + * headers: { Authorization: `Bearer ${token}` }, + * retryOptions: client.retryOptions, + * }) + * ); + * ``` + */ +export async function withAuth( + client: OakClient, + operation: (token: string) => Promise>, +): Promise> { + const tokenResult = await client.getAccessToken(); + if (!tokenResult.ok) { + return err(tokenResult.error); + } + return operation(tokenResult.value); +} From b34deadc48aa6abcba78f4a9b4aca7bace0ad1bf Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:22:03 -0300 Subject: [PATCH 081/143] docs: update README with comprehensive SDK usage examples and security best practices --- README.md | 628 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 612 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fd63359d..26a61702 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,621 @@ -# WORK IN PROGRESS -- Launch expected by March 2026 +# Oak SDK Monorepo -# Crowdsplit SDK Monorepo +> **Status**: Pre-launch development (Expected launch: March 2026) -## Changesets workflow +TypeScript SDK for the Oak Network Crowdsplit API. Build secure payment applications with type-safe interfaces, comprehensive error handling, and OAuth 2.0 authentication. -We use Changesets to record version intent and compute the next versions in CI. +--- -### For developers +## 📦 Packages -1. After making a change that should affect a package version, run: - - `npx changeset` -2. Select the impact (Major/Minor/Patch) for each affected package. -3. Commit the generated file in `.changeset/` alongside your code changes. +- **[@oaknetwork/api](./packages/api)** - Core SDK for Crowdsplit API +- **@oaknetwork/contracts** - Smart contracts (placeholder, not in active development) -### What CI does +--- -- CI runs `pnpm changeset:status` to calculate the next version numbers from all changesets. -- This prevents manual version bumps and keeps versions consistent. +## 🚀 Quick Start -### Release flow (when ready) +### Installation -1. Run `pnpm changeset:version` to apply version bumps and generate changelogs. -2. Publish the packages using your normal release process. +```bash +pnpm add @oaknetwork/api +# or +npm install @oaknetwork/api +# or +yarn add @oaknetwork/api +``` + +### Basic Usage + +```typescript +import { createOakClient } from "@oaknetwork/api"; +import { Crowdsplit } from "@oaknetwork/api/products/crowdsplit"; + +// Create client +const client = createOakClient({ + environment: "sandbox", // or "production" + clientId: process.env.OAK_CLIENT_ID, + clientSecret: process.env.OAK_CLIENT_SECRET, +}); + +// Initialize Crowdsplit product +const crowdsplit = Crowdsplit(client); + +// Create a customer +const customerResult = await crowdsplit.customers.create({ + email: "user@example.com", + first_name: "John", + last_name: "Doe", +}); + +if (customerResult.ok) { + console.log("Customer created:", customerResult.value.data.customer_id); +} else { + console.error("Error:", customerResult.error.message); +} +``` + +--- + +## 🔐 Authentication + +The SDK uses OAuth 2.0 client credentials flow with automatic token management. + +```typescript +// Tokens are automatically fetched and cached +const result = await crowdsplit.customers.list(); + +// Manual token operations (rarely needed) +const tokenResult = await client.getAccessToken(); +if (tokenResult.ok) { + console.log("Token:", tokenResult.value); +} +``` + +**Security Best Practices:** + +- ✅ Store credentials in environment variables +- ✅ Never commit `.env` files +- ✅ Use different credentials for sandbox and production +- ❌ Never log `clientSecret` or access tokens + +--- + +## 📡 Available Services + +### Customers + +```typescript +// Create customer +await crowdsplit.customers.create({ + email: "user@example.com", + first_name: "John", + document_type: "personal_tax_id", + document_number: "123456789", +}); + +// Get customer +await crowdsplit.customers.get("customer_id"); + +// List customers +await crowdsplit.customers.list({ limit: 10, offset: 0 }); + +// Update customer +await crowdsplit.customers.update("customer_id", { + email: "newemail@example.com", +}); +``` + +### Payments + +```typescript +// Create payment +await crowdsplit.payments.create({ + provider: "stripe", + source: { + amount: 1000, // Amount in cents + currency: "usd", + customer: { id: "customer_id" }, + payment_method: { type: "card", id: "pm_123" }, + capture_method: "automatic", + }, + confirm: true, +}); + +// Confirm payment +await crowdsplit.payments.confirm("payment_id"); + +// Cancel payment +await crowdsplit.payments.cancel("payment_id"); +``` + +### Payment Methods + +```typescript +// Create payment method +await crowdsplit.paymentMethods.create("customer_id", { + type: "card", + provider: "stripe", + provider_data: { + token: "tok_visa", + }, +}); + +// List customer payment methods +await crowdsplit.paymentMethods.list("customer_id"); + +// Delete payment method +await crowdsplit.paymentMethods.delete("customer_id", "pm_id"); +``` + +### Refunds + +```typescript +// Create refund +await crowdsplit.refunds.create({ + transaction_id: "txn_123", + amount: 500, // Partial refund + reason: "customer_request", +}); +``` + +### Transfers + +```typescript +// Create transfer +await crowdsplit.transfers.create({ + provider: "stripe", + source: { + amount: 1000, + currency: "usd", + customer: { id: "customer_id" }, + }, + destination: { + customer: { id: "customer_id" }, + payment_method: { id: "pm_123", type: "bank" }, + }, +}); +``` + +### Webhooks + +```typescript +// Register webhook +await crowdsplit.webhooks.register({ + url: "https://your-app.com/webhooks/oak", + events: ["payment.created", "payment.succeeded"], +}); + +// List webhooks +await crowdsplit.webhooks.list(); + +// Update webhook +await crowdsplit.webhooks.update("webhook_id", { + url: "https://your-app.com/webhooks/oak-v2", +}); + +// Toggle webhook status +await crowdsplit.webhooks.toggleStatus("webhook_id", "inactive"); + +// Delete webhook +await crowdsplit.webhooks.delete("webhook_id"); +``` + +### Providers + +```typescript +// List available providers +await crowdsplit.providers.list(); + +// Get provider details +await crowdsplit.providers.get("stripe"); +``` + +### Plans + +```typescript +// List plans +await crowdsplit.plans.list(); + +// Get plan details +await crowdsplit.plans.get("plan_id"); +``` + +### Transactions + +```typescript +// List transactions +await crowdsplit.transactions.list({ + limit: 20, + offset: 0, +}); + +// Get transaction details +await crowdsplit.transactions.get("txn_id"); +``` + +--- + +## 🔔 Webhook Verification + +**New in v0.2.0**: Secure webhook signature verification using HMAC-SHA256 with timing-safe comparison. + +### Express.js Example + +```typescript +import express from "express"; +import { verifyWebhookSignature, parseWebhookPayload } from "@oaknetwork/api"; + +const app = express(); +app.use(express.json()); + +app.post("/webhooks/oak", async (req, res) => { + const signature = req.headers["x-oak-signature"] as string; + const payload = JSON.stringify(req.body); + + // Option 1: Verify signature only + const isValid = verifyWebhookSignature( + payload, + signature, + process.env.WEBHOOK_SECRET!, + ); + + if (!isValid) { + return res.status(401).send("Invalid signature"); + } + + const event = req.body; + console.log("Webhook event:", event.type); + + // Option 2: Verify and parse in one step (preferred) + const result = parseWebhookPayload<{ + type: string; + data: unknown; + }>(payload, signature, process.env.WEBHOOK_SECRET!); + + if (!result.ok) { + console.error("Webhook verification failed:", result.error.message); + return res.status(401).send(result.error.message); + } + + // Handle verified event + const verifiedEvent = result.value; + switch (verifiedEvent.type) { + case "payment.created": + // Handle payment created + break; + case "payment.succeeded": + // Handle payment succeeded + break; + default: + console.log("Unhandled event:", verifiedEvent.type); + } + + res.sendStatus(200); +}); +``` + +### Next.js API Route Example + +```typescript +import type { NextApiRequest, NextApiResponse } from "next"; +import { parseWebhookPayload } from "@oaknetwork/api"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method !== "POST") { + return res.status(405).end(); + } + + const signature = req.headers["x-oak-signature"] as string; + const payload = JSON.stringify(req.body); + + const result = parseWebhookPayload( + payload, + signature, + process.env.WEBHOOK_SECRET!, + ); + + if (!result.ok) { + return res.status(401).json({ error: result.error.message }); + } + + // Process verified webhook + const event = result.value; + console.log("Received:", event); + + res.status(200).json({ received: true }); +} +``` + +**Security Notes:** + +- Always verify signatures before processing webhooks +- Use timing-safe comparison (built into SDK) +- Store webhook secret securely (environment variables) +- Never expose webhook endpoints without verification + +--- + +## 🎯 Error Handling + +The SDK uses a `Result` type pattern for predictable error handling: + +```typescript +const result = await crowdsplit.customers.create(customerData); + +if (result.ok) { + // Success - result.value contains the response + const customer = result.value.data; + console.log("Created:", customer.customer_id); +} else { + // Error - result.error contains the OakError + console.error("Failed:", result.error.message); + console.error("Status:", result.error.statusCode); + console.error("Code:", result.error.code); +} +``` + +### Error Types + +- `ApiError` - HTTP errors from the API (4xx, 5xx) +- `NetworkError` - Network failures, timeouts +- `ParseError` - Invalid JSON responses +- `AbortError` - Request aborted +- `OakError` - Base error class + +--- + +## ⚙️ Configuration + +### Environment Options + +```typescript +type OakEnvironment = "sandbox" | "production" | "custom"; + +createOakClient({ + environment: "sandbox", // Use sandbox for testing + clientId: "your_client_id", + clientSecret: "your_client_secret", + + // Optional: Custom URL for development + customUrl: "http://localhost:3000", + + // Optional: Retry configuration + retryOptions: { + maxNumberOfRetries: 3, + delay: 1000, + backoffFactor: 2, + maxDelay: 30000, + }, +}); +``` + +### Retry Configuration + +The SDK automatically retries failed requests with exponential backoff: + +- **Retry on**: 408, 429, 500, 502, 503, 504 +- **Max retries**: 3 (configurable) +- **Backoff**: Exponential with jitter to prevent thundering herd + +```typescript +retryOptions: { + maxNumberOfRetries: 3, // Number of retry attempts + delay: 1000, // Initial delay in ms + backoffFactor: 2, // Multiplier for each retry + maxDelay: 30000, // Maximum delay cap + retryOnStatus: [408, 429, 500, 502, 503, 504], + retryOnError: (error) => error.isNetworkError, +} +``` + +--- + +## 📝 TypeScript Support + +The SDK is written in TypeScript with full type definitions: + +```typescript +import type { + Customer, + Payment, + PaymentMethod, + Transaction, + Transfer, + Result, +} from "@oaknetwork/api"; + +// Type-safe customer creation +const customerData: Customer.Request = { + email: "user@example.com", + first_name: "John", +}; + +// Type-safe result handling +const result: Result = await crowdsplit.customers.create( + customerData, +); + +if (result.ok) { + const customer: Customer.Data = result.value.data; +} +``` + +--- + +## 🔄 Migration Guide (v0.1 → v0.2) + +### Breaking Changes + +#### 1. `clientSecret` No Longer Exposed + +**Before (v0.1):** + +```typescript +console.log(client.config.clientSecret); // ✅ Works in v0.1 +``` + +**After (v0.2):** + +```typescript +console.log(client.config.clientSecret); // ❌ undefined in v0.2 +// Store separately if needed: +const secret = process.env.CLIENT_SECRET; +``` + +#### 2. `createAuthService()` Removed + +**Before (v0.1):** + +```typescript +import { createAuthService } from "@oaknetwork/api"; +const auth = createAuthService(client); +await auth.getAccessToken(); +``` + +**After (v0.2):** + +```typescript +// Use client directly +await client.getAccessToken(); +``` + +See [CHANGELOG.md](./CHANGELOG.md) for full migration guide. + +--- + +## 🛠️ Development + +### Package Manager + +This project uses **pnpm** exclusively: + +```bash +pnpm install # Install dependencies +pnpm build # Build all packages +pnpm test # Run tests +pnpm lint # Lint code +``` + +**DO NOT** use npm or yarn. The repository enforces pnpm >= 10.0.0. + +### Changesets Workflow + +We use Changesets to manage versions and changelogs: + +1. **After making changes**, run: + + ```bash + pnpm changeset + ``` + +2. **Select impact** (Major/Minor/Patch) for affected packages + +3. **Commit** the generated file in `.changeset/` + +4. **CI automatically**: + - Calculates next versions + - Generates changelogs + - Creates release PR + +### Running Tests + +```bash +# Unit tests +pnpm test:unit + +# Integration tests (requires credentials) +pnpm test:integration + +# All tests with coverage +pnpm test:all + +# Watch mode +pnpm test:watch +``` + +### Environment Variables for Testing + +Create `.env` file in `packages/api`: + +```env +CLIENT_ID=your_sandbox_client_id +CLIENT_SECRET=your_sandbox_client_secret +OAK_ENVIRONMENT=sandbox +``` + +--- + +## 📖 Documentation + +- **API Reference**: See [packages/api/README.md](./packages/api/README.md) +- **Type Definitions**: Included with package, supports IDE autocomplete +- **Examples**: See [examples/](./examples/) directory (coming soon) +- **Changelog**: See [CHANGELOG.md](./CHANGELOG.md) + +--- + +### Development Guidelines + +See [CLAUDE.md](./CLAUDE.md) for comprehensive coding standards including: + +- Architecture principles (Result types, factory pattern) +- Security rules (never expose secrets, timing-safe comparisons) +- Testing requirements (no silent skips, >90% coverage) +- Type system rules (use `unknown`, named interfaces) +- Anti-patterns to avoid + +### Code Review Checklist + +Before submitting PR: + +- [ ] Run `pnpm build` successfully +- [ ] Run `pnpm test` with >90% coverage +- [ ] Run `pnpm lint` without errors +- [ ] Create changeset with `pnpm changeset` +- [ ] Update documentation if needed +- [ ] Follow patterns in [CLAUDE.md](./CLAUDE.md) + +--- + +## 📄 License + +MIT + +--- + +## 🔗 Links + +- [Oak Network Website](https://oaknetwork.org) +- [API Documentation](https://www.oaknetwork.org/docs/intro) +- [GitHub Repository](https://github.com/oak-network/sdk) +- [Issue Tracker](https://github.com/oak-network/sdk/issues) +- [npm Package](https://www.npmjs.com/package/@oaknetwork/api) + +--- + +## 🎯 Roadmap + +**Pre-Launch (Current → March 2026)** + +- ✅ Core API services implemented +- ✅ Comprehensive type safety +- ✅ Webhook verification utilities +- ✅ Full test coverage +- ⏳ Production hardening +- ⏳ Performance optimization +- ⏳ Example applications + +**Post-Launch** + +- Advanced retry strategies +- Request/response middleware +- CLI tools +- TBD (Being evaluated based on user feedback) + +--- + +**Questions?** Open an issue or contact support@oaknetwork.org From a4ce548c9440b7ff613c377bfefb3c6155ae22ed Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:22:08 -0300 Subject: [PATCH 082/143] docs: add comprehensive AI development guidelines for Oak SDK --- CLAUDE.md | 745 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..c91d57d3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,745 @@ +# Oak SDK - AI Development Guidelines + +**Last Updated:** February 2026 +**Status:** Pre-launch SDK (March 2026) + +This document provides strict rules and standards for AI assistants (Claude Code, Cursor, etc.) working on the Oak SDK codebase. Following these guidelines is **mandatory** to maintain code quality, security, and architectural consistency. + +--- + +## Table of Contents + +1. [Architecture Principles](#architecture-principles) +2. [Code Standards](#code-standards) +3. [Security Rules](#security-rules) +4. [Testing Requirements](#testing-requirements) +5. [Anti-Patterns](#anti-patterns) +6. [Refactoring Guidelines](#refactoring-guidelines) +7. [Git Workflow](#git-workflow) +8. [Performance](#performance) +9. [Type System Rules](#type-system-rules) +10. [Documentation](#documentation) + +--- + +## Architecture Principles + +### Core Patterns (DO NOT BREAK) + +#### 1. Result Type Pattern + +**ALWAYS** use the `Result` type for operations that can fail. + +```typescript +// ✅ CORRECT +async function getCustomer(id: string): Promise> { + return withAuth(client, (token) => + httpClient.get(url, config), + ); +} + +// ❌ WRONG - Never throw errors from service methods +async function getCustomer(id: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(); // NO! + return response.json(); +} +``` + +#### 2. Factory Pattern for Services + +All services use factory functions, not classes: + +```typescript +// ✅ CORRECT +export const createCustomerService = (client: OakClient): CustomerService => ({ + create: (data) => { + /* ... */ + }, + get: (id) => { + /* ... */ + }, +}); + +// ❌ WRONG - Don't use classes for services +export class CustomerService { + constructor(private client: OakClient) {} +} +``` + +#### 3. Clean Separation of Concerns + +- **Services** (`src/services/`): Business logic, API calls +- **HTTP Client** (`src/utils/httpClient.ts`): Low-level HTTP operations +- **Auth Manager** (`src/authManager.ts`): Token management, OAuth flow +- **Types** (`src/types/`): Type definitions only, no logic +- **Utils** (`src/utils/`): Pure helper functions + +**NEVER** mix concerns (e.g., don't put HTTP logic in services, don't put business logic in HTTP client). + +--- + +## Code Standards + +### TypeScript Strict Mode + +- **ALWAYS** use TypeScript strict mode +- **NEVER** use `any` type - use `unknown` instead +- **ALWAYS** provide explicit return types for exported functions +- **ALWAYS** use named interfaces instead of `ReturnType` + +```typescript +// ✅ CORRECT +function processData(data: unknown): Result { + // Type guard to narrow unknown + if (!isValidData(data)) { + return err(new ValidationError()); + } + return ok(transformData(data)); +} + +// ❌ WRONG +function processData(data: any): any { + return transformData(data); +} +``` + +### Type System Best Practices + +1. **Use `unknown` over `any`**: + + ```typescript + // ✅ CORRECT + catch (error: unknown) { + const status = (error as { status?: number })?.status; + } + + // ❌ WRONG + catch (error: any) { + const status = error.status; + } + ``` + +2. **Named Interfaces over Intersection Types**: + + ```typescript + // ✅ CORRECT + export interface Transaction { + provider: string; + source: Source; + id: string; + status: string; + created_at: string; + } + + // ❌ WRONG + export type Transaction = Request & { + id: string; + status: string; + }; + ``` + +3. **Direct Interface Imports over ReturnType**: + + ```typescript + // ✅ CORRECT + export interface CrowdsplitProduct { + customers: CustomerService; + payments: PaymentService; + } + + // ❌ WRONG + export interface CrowdsplitProduct { + customers: ReturnType; + } + ``` + +### JSDoc Requirements + +- **ALWAYS** add JSDoc to exported functions and types +- **ALWAYS** document parameters with `@param` +- **ALWAYS** document return types with `@returns` +- **ALWAYS** provide usage examples for complex utilities + +````typescript +/** + * Creates a new customer in the system. + * + * @param customer - Customer data to create + * @returns Result containing created customer or error + * + * @example + * ```typescript + * const result = await customerService.create({ + * email: "user@example.com", + * first_name: "John", + * }); + * if (result.ok) { + * console.log(result.value.customer_id); + * } + * ``` + */ +create(customer: Customer.Request): Promise>; +```` + +--- + +## Security Rules + +### Critical Security Requirements + +1. **NEVER expose secrets in public API**: + + ```typescript + // ✅ CORRECT - clientSecret only in private config + export interface PublicOakClientConfig { + environment: OakEnvironment; + clientId: string; + baseUrl: string; + // NO clientSecret here + } + + // ❌ WRONG - Exposes secret through logging + export interface OakClient { + config: { clientSecret: string }; // Dangerous! + } + ``` + +2. **ALWAYS validate inputs at boundaries**: + + ```typescript + // ✅ CORRECT + if (!isValidEmail(customer.email)) { + return err(new ValidationError("Invalid email")); + } + ``` + +3. **ALWAYS use timing-safe comparisons for secrets**: + + ```typescript + import { timingSafeEqual } from "crypto"; + + // ✅ CORRECT + return timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature), + ); + + // ❌ WRONG - Vulnerable to timing attacks + return signature === expectedSignature; + ``` + +4. **NEVER log sensitive data**: + + - No passwords, tokens, or API keys in logs + - Sanitize error messages before logging + - Use structured logging with sensitive field filtering + +5. **ALWAYS handle token expiration correctly**: + + ```typescript + // ✅ CORRECT - OAuth expires_in is in SECONDS + this.tokenExpiration = Date.now() + response.value.expires_in * 1000; + + // ❌ WRONG - Date.now() is milliseconds, expires_in is seconds + this.tokenExpiration = Date.now() + response.value.expires_in; + ``` + +--- + +## Testing Requirements + +### Unit Tests + +**ALWAYS** write unit tests for: + +- New utility functions +- Complex business logic +- Error handling paths +- Type guards and validators + +```typescript +describe("withAuth", () => { + it("should execute operation with valid token", async () => { + // Arrange + const mockClient = createTestClient(); + jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(ok("token")); + + // Act + const result = await withAuth(mockClient, (token) => + ok({ data: "success" }), + ); + + // Assert + expect(result.ok).toBe(true); + }); +}); +``` + +### Integration Tests + +- **NEVER** use silent test skips +- **ALWAYS** throw explicit errors when prerequisites are missing + +```typescript +// ✅ CORRECT +it("should get customer", async () => { + if (!customerId) { + throw new Error("customerId not available - prerequisite test failed"); + } + const result = await customerService.get(customerId); + expect(result.ok).toBe(true); +}); + +// ❌ WRONG - Test passes silently even when skipped +it("should get customer", async () => { + if (!customerId) { + console.warn("Skipping test"); + return; // Test shows as passed! + } +}); +``` + +### Test Coverage + +- Maintain **>90% code coverage** +- **100% coverage** for critical paths (auth, payment processing) +- Test both success and error paths + +--- + +## Anti-Patterns + +### What NOT to Do + +#### 1. Token Fetch Duplication ❌ + +**BEFORE (Bad)**: + +```typescript +async create(data: Request): Promise> { + const token = await client.getAccessToken(); + if (!token.ok) { + return err(token.error); + } + return httpClient.post(url, data, { + headers: { Authorization: `Bearer ${token.value}` }, + }); +} +``` + +**AFTER (Good)**: + +```typescript +async create(data: Request): Promise> { + return withAuth(client, (token) => + httpClient.post(url, data, { + headers: { Authorization: `Bearer ${token}` }, + }) + ); +} +``` + +#### 2. Hardcoded URLs ❌ + +**BEFORE (Bad)**: + +```typescript +const url = `${client.config.baseUrl}/api/v1/customers/${id}`; +``` + +**AFTER (Good)**: + +```typescript +const url = buildUrl(client.config.baseUrl, "api/v1/customers", id); +``` + +#### 3. Dead Code ❌ + +- **NEVER** keep unused functions +- **ALWAYS** delete commented-out code +- **ALWAYS** remove unused imports and types + +#### 4. Production Dependencies on Test Tools ❌ + +```json +// ❌ WRONG - Test tools in dependencies +{ + "dependencies": { + "nock": "^14.0.0", + "dotenv": "^17.0.0" + } +} + +// ✅ CORRECT - Test tools in devDependencies +{ + "devDependencies": { + "nock": "^14.0.0", + "dotenv": "^17.0.0" + } +} +``` + +#### 5. Zero-Value Wrappers ❌ + +Don't create wrapper functions that add no value: + +```typescript +// ❌ WRONG - Useless wrapper +export const createAuthService = (client: OakClient) => ({ + getAccessToken: () => client.getAccessToken(), + grantToken: () => client.grantToken(), +}); + +// ✅ CORRECT - Use client directly +await client.getAccessToken(); +``` + +--- + +## Refactoring Guidelines + +### When to Create Helper Functions + +Create a helper when: + +1. **Code is duplicated 3+ times** across files +2. **Logic is complex** and deserves a name +3. **Concerns can be separated** (e.g., auth from business logic) + +**Example**: The `withAuth` helper was created because token-fetching appeared 35+ times across services. + +### How to Structure Refactors + +1. **Read before modifying**: Always read existing code first +2. **Create utilities first**: Build helpers before refactoring services +3. **Test helpers**: Unit test utilities before using them +4. **Refactor incrementally**: Update services one at a time +5. **Verify after each change**: Run tests after each file update + +### Breaking Changes + +Since this is **pre-launch** (March 2026), breaking changes are acceptable: + +- Document all breaking changes in CHANGELOG +- Add migration guide for developers +- Update version per semver (0.1.0 → 0.2.0 for breaking) + +--- + +## Git Workflow + +### Commit Messages + +Follow conventional commits: + +``` +(): + + + +Co-Authored-By: Claude Sonnet 4.5 +``` + +Types: + +- `feat`: New feature +- `fix`: Bug fix +- `refactor`: Code change without behavior change +- `docs`: Documentation only +- `test`: Adding or updating tests +- `chore`: Build, deps, or config changes + +Example: + +``` +fix(auth): correct token expiration calculation + +OAuth expires_in is in seconds, but Date.now() returns milliseconds. +Multiply by 1000 to convert seconds to milliseconds. + +Co-Authored-By: Claude Sonnet 4.5 +``` + +### Changesets + +**ALWAYS** create a changeset for user-facing changes: + +```bash +pnpm changeset +``` + +Choose: + +- **patch**: Bug fixes, internal improvements +- **minor**: New features, non-breaking additions +- **major**: Breaking changes (rare in pre-launch) + +### Pull Requests + +1. **NEVER** push directly to `main` +2. **ALWAYS** create feature branch +3. **ALWAYS** run full test suite before PR +4. **ALWAYS** update README for new features + +--- + +## Performance + +### Caching Strategies + +1. **Token Caching**: AuthManager caches tokens with 60s buffer before expiry + + ```typescript + // Check if token is valid (with 60s buffer) + if (currentTime >= this.tokenExpiration - 60000) { + await this.grantToken(); + } + ``` + +2. **HTTP Client**: Uses retry with exponential backoff + ```typescript + { + maxNumberOfRetries: 3, + delay: 1000, + backoffFactor: 2, + maxDelay: 30000, + } + ``` + +### Retry Logic + +**ALWAYS** retry on: + +- 408 (Timeout) +- 429 (Rate Limited) +- 500, 502, 503, 504 (Server Errors) + +**NEVER** retry on: + +- 4xx client errors (except 408, 429) +- 401 (Unauthorized) + +```typescript +retryOnStatus: [408, 429, 500, 502, 503, 504]; +``` + +### Exponential Backoff + +```typescript +waitTime = Math.min(waitTime * backoffFactor, maxDelay); +waitTime = waitTime * (0.8 + Math.random() * 0.4); // Add jitter +``` + +**Jitter prevents thundering herd** when multiple clients retry simultaneously. + +--- + +## Type System Rules + +### 1. Use `unknown` over `any` + +```typescript +// ✅ CORRECT +async function post(url: string, data: unknown): Promise> { + return request(url, { body: JSON.stringify(data) }); +} + +// ❌ WRONG +async function post(url: string, data: any): Promise> { + return request(url, { body: JSON.stringify(data) }); +} +``` + +### 2. Named Interfaces over `ReturnType` + +```typescript +// ✅ CORRECT +import { CustomerService } from "../../services"; +export interface Product { + customers: CustomerService; +} + +// ❌ WRONG +export interface Product { + customers: ReturnType; +} +``` + +### 3. Standalone Interfaces over Intersections + +```typescript +// ✅ CORRECT +export interface Transaction { + provider: string; + source: Source; + id: string; + status: string; + type: "payment"; + created_at: string; + updated_at: string; +} + +// ❌ WRONG +export type Transaction = Request & { + id: string; + status: string; + type: "payment"; +}; +``` + +### 4. Type Guards for `unknown` + +```typescript +function isCustomerData(data: unknown): data is Customer.Data { + return ( + typeof data === "object" && + data !== null && + "email" in data && + typeof (data as Customer.Data).email === "string" + ); +} +``` + +--- + +## Documentation + +### README Requirements + +**ALWAYS** include: + +1. Installation instructions +2. Quick start example +3. Authentication setup +4. API reference links +5. Error handling examples +6. Webhook verification examples (if applicable) + +### Migration Guides + +When introducing breaking changes: + +```markdown +## Breaking Changes in v0.2.0 + +### Security Improvements + +- `client.config.clientSecret` is no longer accessible +- Store credentials separately, pass to `createOakClient()` only + +### Migration Steps + +1. Remove references to `client.config.clientSecret` +2. Store secret in environment variables +3. Update to latest `@oaknetwork/api` version +``` + +### TSDoc Examples + +````typescript +/** + * Verifies a webhook signature using HMAC-SHA256. + * Uses timing-safe comparison to prevent timing attacks. + * + * @param payload - Raw webhook payload string + * @param signature - Signature from webhook headers + * @param secret - Your webhook secret from Oak dashboard + * @returns True if signature is valid, false otherwise + * + * @example + * ```typescript + * const isValid = verifyWebhookSignature( + * JSON.stringify(req.body), + * req.headers["x-oak-signature"], + * process.env.WEBHOOK_SECRET + * ); + * if (!isValid) { + * return res.status(401).send("Invalid signature"); + * } + * ``` + */ +export function verifyWebhookSignature( + payload: string, + signature: string, + secret: string, +): boolean { + // Implementation... +} +```` + +--- + +## Package Management + +### Use pnpm Only + +- **NEVER** use npm or yarn +- **ALWAYS** use `pnpm` for all operations +- Engine requirement: `pnpm >= 10.0.0` + +```json +{ + "packageManager": "pnpm@10.17.1", + "engines": { + "pnpm": ">=10.0.0" + } +} +``` + +### Lockfiles + +- `package-lock.json` is **forbidden** (gitignored) +- Only `pnpm-lock.yaml` should exist + +--- + +## CI/CD Requirements + +### CI Workflow + +1. **Build**: `pnpm build` must pass +2. **Lint**: `pnpm lint` must pass (no `continue-on-error`) +3. **Tests**: `pnpm test` must pass with >80% coverage +4. **Type Check**: `tsc --noEmit` must pass + +### Release Workflow + +1. Changesets gather changes +2. Version bump via `pnpm changeset:version` +3. Build packages: `pnpm --filter=!@oaknetwork/contracts build` +4. Publish to npm with provenance +5. Create GitHub releases + +--- + +## Common Mistakes to Avoid + +1. ❌ Using `any` instead of `unknown` +2. ❌ Not multiplying OAuth `expires_in` by 1000 +3. ❌ Silent test skips with `console.warn` + `return` +4. ❌ Exposing `clientSecret` in public config +5. ❌ Hardcoding URLs instead of using `buildUrl` +6. ❌ Duplicating token-fetch logic instead of using `withAuth` +7. ❌ Putting test tools in `dependencies` instead of `devDependencies` +8. ❌ Creating zero-value wrapper functions +9. ❌ Using `ReturnType` instead of named interfaces +10. ❌ Not handling both success and error paths in tests + +--- + +## Questions? + +If you're an AI assistant and unsure about something: + +1. Check this document first +2. Look at existing code patterns in the same directory +3. Read the comprehensive plan at the repository root +4. When in doubt, choose the **more type-safe** option +5. When in doubt, choose the **more explicit** option + +**Remember**: This is a financial API SDK. Security, correctness, and type safety are paramount. + +--- + +**End of Guidelines** From fc90e269b9f7cf4c7bed9baf8f1995b86121db83 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 17:22:13 -0300 Subject: [PATCH 083/143] chore: add comprehensive changelog documenting notable changes, features, fixes, and migration guide --- CHANGELOG.md | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cba5f12f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,223 @@ +# Changelog + +All notable changes to the Oak SDK will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- **Webhook Verification Utilities**: New `verifyWebhookSignature()` and `parseWebhookPayload()` functions for secure webhook handling using HMAC-SHA256 with timing-safe comparison +- **RefundService**: Added to Crowdsplit product facade, exposing refund functionality that was previously available but not exposed +- **Helper Utilities**: + - `withAuth()`: Higher-order function for wrapping HTTP operations with authentication (eliminates 35+ duplications) + - `buildUrl()`: Centralized URL construction with consistent trailing slash handling (standardizes 36+ URL constructions) +- **Comprehensive Unit Tests**: Added test coverage for all new utility functions +- **AI Development Guidelines**: Created `CLAUDE.md` with comprehensive coding standards and best practices + +### Fixed + +- **Critical Token Expiration Bug**: Fixed OAuth token expiration calculation - `expires_in` is in seconds but `Date.now()` returns milliseconds. Now correctly multiplies by 1000 +- **Integration Test Silent Skips**: Replaced 20+ silent test skips (console.warn + return) with explicit error throws for accurate test reporting +- **Payment URL Inconsistency**: Removed inconsistent trailing slash from payment service URL construction + +### Changed + +- **BREAKING**: `client.config.clientSecret` is no longer accessible for security reasons. Store credentials separately and only pass to `createOakClient()` +- **BREAKING**: Removed `createAuthService()` wrapper - use `client.getAccessToken()` and `client.grantToken()` directly +- **Type System Improvements**: + - Replaced `any` with `unknown` in httpClient methods (`post`, `put`, `patch`) and retryHandler for better type safety + - Converted `ReturnType` to direct interface imports in Crowdsplit facade + - Converted intersection types to standalone interfaces in Payment and Transfer types + - Added JSDoc to clarify `customer.id` (legacy) vs `customer.customer_id` (preferred) +- **Dependency Updates**: + - Moved `nock` and `dotenv` from dependencies to devDependencies (reduces production bundle size) + - Updated `ts-jest` from `^29.4.1` to `^29.4.6` + - Engine requirement updated: `pnpm >= 10.0.0` (was `>= 8.0.0`) +- **CI/CD Improvements**: + - Removed `continue-on-error` from lint step - lint failures now block PRs + - Excluded `@oaknetwork/contracts` placeholder package from CI builds + - Pinned npm version in release workflow to `10.9.2` for deterministic builds + - Added `package-lock.json` to .gitignore (enforces pnpm as canonical package manager) + +### Removed + +- **Dead Code**: Deleted unused `getErrorBodyMessage()` function (14 lines) +- **Unused Types**: Deleted unused `SDKConfig` type and `src/types/config.ts` +- **Scratch Files**: Deleted `test-sdk.ts` (200+ lines with hardcoded UUIDs) and added to .gitignore +- **Lockfiles**: Removed npm lockfiles from root and api package + +### Internal + +- **Service Refactoring**: All 11 service files refactored to use new `withAuth` and `buildUrl` helpers + - Net reduction: 75 lines of code + - Eliminated ~300 lines of duplicated token-fetch code + - Standardized URL construction across all services +- **TypeScript Config**: Added comment explaining `experimentalDecorators` requirement for `@SandboxOnly` decorator + +## Migration Guide + +### Breaking Changes in v0.2.0 + +#### 1. `clientSecret` No Longer Accessible + +**Before:** +```typescript +const client = createOakClient({ + environment: "sandbox", + clientId: "your-client-id", + clientSecret: "your-client-secret", +}); + +// This no longer works: +console.log(client.config.clientSecret); // ❌ undefined +``` + +**After:** +```typescript +// Store secret separately if needed for logging/debugging +const clientSecret = process.env.CLIENT_SECRET; + +const client = createOakClient({ + environment: "sandbox", + clientId: process.env.CLIENT_ID, + clientSecret, // Pass it in, but don't access it later +}); + +// Secret is NOT exposed on client.config for security +``` + +**Why**: Prevents accidental secret exposure through logging, serialization, or error messages. + +#### 2. `createAuthService()` Removed + +**Before:** +```typescript +import { createAuthService } from "@oaknetwork/api"; + +const auth = createAuthService(client); +const token = await auth.getAccessToken(); +``` + +**After:** +```typescript +// Use client methods directly +const token = await client.getAccessToken(); +const tokenResponse = await client.grantToken(); +``` + +**Why**: Zero-value wrapper that added no functionality. + +#### 3. Stricter Type Checking + +**Before:** +```typescript +// Any type accepted +httpClient.post(url, anyData, config); +``` + +**After:** +```typescript +// Unknown type requires explicit typing +httpClient.post(url, requestData as RequestType, config); +``` + +**Why**: Better type safety prevents runtime errors. + +### New Features + +#### Webhook Verification + +```typescript +import { verifyWebhookSignature, parseWebhookPayload } from "@oaknetwork/api"; + +// Option 1: Verify signature only +app.post("/webhook", (req, res) => { + const isValid = verifyWebhookSignature( + JSON.stringify(req.body), + req.headers["x-oak-signature"] as string, + process.env.WEBHOOK_SECRET + ); + + if (!isValid) { + return res.status(401).send("Invalid signature"); + } + + // Process webhook... +}); + +// Option 2: Verify and parse in one step +app.post("/webhook", (req, res) => { + const result = parseWebhookPayload( + JSON.stringify(req.body), + req.headers["x-oak-signature"] as string, + process.env.WEBHOOK_SECRET + ); + + if (!result.ok) { + return res.status(401).send(result.error.message); + } + + const event = result.value; + // Handle event... +}); +``` + +#### RefundService Now Available + +```typescript +import { Crowdsplit } from "@oaknetwork/api/products/crowdsplit"; + +const crowdsplit = Crowdsplit(client); + +// Refund service is now exposed +const result = await crowdsplit.refunds.create({ + transaction_id: "txn_123", + amount: 1000, +}); +``` + +### Upgrade Steps + +1. **Update Package**: + ```bash + pnpm update @oaknetwork/api@latest + ``` + +2. **Remove `clientSecret` Access**: + - Search codebase for `client.config.clientSecret` + - Store separately if needed for non-SDK purposes + - Update to use environment variables + +3. **Replace `createAuthService()`**: + - Search for `createAuthService` + - Replace with direct `client.getAccessToken()` or `client.grantToken()` calls + - Remove import + +4. **Add Type Assertions** (if needed): + - TypeScript may require type assertions for HTTP client methods + - Add `as RequestType` where compiler indicates `unknown` cannot be assigned + +5. **Test Thoroughly**: + - Run full test suite + - Verify authentication still works + - Check webhook handling if applicable + +## [0.1.0] - 2026-02-XX + +### Added + +- Initial release of Oak SDK +- Support for Crowdsplit API +- Customer, Payment, PaymentMethod, Transaction services +- Transfer, Webhook, Plan, Buy, Sell services +- OAuth 2.0 client credentials flow +- TypeScript type definitions +- Comprehensive test suite +- Result type pattern for error handling + +--- + +For more details, see the [GitHub Releases](https://github.com/oak-network/sdk/releases) page. From 5ce3d18bf0f2762d926d3736e25d91c8794d8872 Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Sat, 21 Feb 2026 02:44:07 +0600 Subject: [PATCH 084/143] refactor: clean type safety --- packages/api/src/types/customer.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 12baeec8..6cdb73bf 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -53,18 +53,19 @@ export namespace Customer { account_type?: string | null; } - export interface Sync { - // provider is an array of providers to sync the customer with and length must be 1 - providers: - | "stripe" - | "bridge" - | "pagar_me" - | "brla" - | "avenia" - | "mercado_pago"[]; + type Provider = + | "stripe" + | "bridge" + | "pagar_me" + | "brla" + | "avenia" + | "mercado_pago"; + + type SyncField = "shipping" | "email" | "first_name" | "last_name"; - // fields is an array of fields to sync the customer - fields: "shipping" | "email" | "first_name" | "last_name"[]; + export interface Sync { + providers: [Provider]; // exactly 1 + fields: SyncField[]; } export type SyncResponse = ApiResponse; From da8727b8f58074d3b2543e0be3d217409811023e Mon Sep 17 00:00:00 2001 From: tahseen-ccprotocol Date: Sat, 21 Feb 2026 02:47:13 +0600 Subject: [PATCH 085/143] test: added test coverage --- packages/api/__tests__/unit/services.test.ts | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index 261e1678..99f6415a 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -200,6 +200,27 @@ describe("Crowdsplit services (Unit)", () => { authConfig, ], }); + await expectSuccess({ + client, + call: () => + service.sync("cust-1", { providers: ["stripe"], fields: ["shipping"] }), + httpMethod: "post", + expectedArgs: [ + `${SANDBOX_URL}/api/v1/customers/cust-1/sync`, + { providers: ["stripe"], fields: ["shipping"] }, + authConfig, + ], + }); + await expectSuccess({ + client, + call: () => + service.balance("cust-1", { provider: "stripe", role: "customer" }), + httpMethod: "get", + expectedArgs: [ + `${SANDBOX_URL}/api/v1/customers/cust-1/balance?provider=stripe&role=customer`, + authConfig, + ], + }); await expectFailure({ call: () => service.update("cust-1", { email: "new@example.com" }), httpMethod: "put", @@ -216,6 +237,18 @@ describe("Crowdsplit services (Unit)", () => { await expectTokenFailure(() => tokenErrorService.update("cust-1", { email: "t@t.com" }), ); + await expectTokenFailure(() => + tokenErrorService.sync("cust-1", { + providers: ["stripe"], + fields: ["shipping"], + }), + ); + await expectTokenFailure(() => + tokenErrorService.balance("cust-1", { + provider: "stripe", + role: "customer", + }), + ); }); it("payment service methods", async () => { From a76f55837b905485565023db83d20d9b3bb66432 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:10:00 -0300 Subject: [PATCH 086/143] feat: add PAYMENT_CUSTOMER_ID to environment configuration and update TestEnvironment interface --- packages/api/.env-sample | 1 + packages/api/__tests__/config.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/api/.env-sample b/packages/api/.env-sample index 4c98bf57..ade99a2c 100644 --- a/packages/api/.env-sample +++ b/packages/api/.env-sample @@ -1,3 +1,4 @@ CLIENT_ID=your_client_id_here CLIENT_SECRET=your_client_secret_here OAK_ENVIRONMENT=sandbox +PAYMENT_CUSTOMER_ID=your_payment_customer_id_here \ No newline at end of file diff --git a/packages/api/__tests__/config.ts b/packages/api/__tests__/config.ts index 0993fe07..b162ceeb 100644 --- a/packages/api/__tests__/config.ts +++ b/packages/api/__tests__/config.ts @@ -7,6 +7,10 @@ export interface TestClientConfig extends OakClientConfig { retryOptions?: RetryOptions; } +export interface TestEnvironment { + paymentCustomerId?: string; +} + export function getConfigFromEnv(): OakClientConfig { if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) { throw new Error( @@ -29,3 +33,9 @@ export function getConfigFromEnv(): OakClientConfig { clientSecret: process.env.CLIENT_SECRET, }; } + +export function getTestEnvironment(): TestEnvironment { + return { + paymentCustomerId: process.env.PAYMENT_CUSTOMER_ID, + }; +} From c762d2b9c9b2766ddac9404144e6860ac00370ec Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:10:12 -0300 Subject: [PATCH 087/143] fix: refactor customer creation tests to use timestamp for unique email and document number --- .../__tests__/integration/customerService.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index e4bd8083..58b2f757 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -24,9 +24,14 @@ describe("CustomerService - Integration", () => { it( "should create a stripe customer", async () => { - const email = `test_${Date.now()}@example.com`; + const timestamp = Date.now(); + const email = `test_${timestamp}@example.com`; const response = await customers.create({ email, + first_name: 'John', + last_name: 'Doe', + document_type: 'personal_tax_id', + document_number: `${timestamp}`.padStart(11, '0').substring(0, 11), }); expect(response.ok).toBe(true); if (response.ok) { @@ -41,11 +46,16 @@ describe("CustomerService - Integration", () => { it( "should create a stripe connected account", async () => { - const email = `test_${Date.now()}@example.com`; + const timestamp = Date.now(); + const email = `test_${timestamp}@example.com`; const country_code = "US"; const response = await customers.create({ email, country_code, + first_name: 'Jane', + last_name: 'Smith', + document_type: 'personal_tax_id', + document_number: `${timestamp + 1}`.padStart(11, '0').substring(0, 11), }); expect(response.ok).toBe(true); if (response.ok) { From 27802852db425884ba29048cfc65d654bcd3acc8 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:10:26 -0300 Subject: [PATCH 088/143] fix: enhance test customer setup to prioritize environment configuration --- .../integration/paymentMethodService.test.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/api/__tests__/integration/paymentMethodService.test.ts b/packages/api/__tests__/integration/paymentMethodService.test.ts index 36ada682..9a2a5b7b 100644 --- a/packages/api/__tests__/integration/paymentMethodService.test.ts +++ b/packages/api/__tests__/integration/paymentMethodService.test.ts @@ -1,6 +1,6 @@ import { createOakClient } from "../../src"; import { Crowdsplit } from "../../src/products/crowdsplit"; -import { getConfigFromEnv } from "../config"; +import { getConfigFromEnv, getTestEnvironment } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; @@ -27,18 +27,33 @@ describe("PaymentMethodService - Integration", () => { describe("setup", () => { it( - "should find or create a test customer", + "should use test customer from environment", async () => { - const listResponse = await customers.list({ limit: 1 }); - if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { - testCustomerId = listResponse.value.data.customer_list[0].id as string; + const testEnv = getTestEnvironment(); + + if (testEnv.paymentCustomerId) { + // Use customer ID from environment + testCustomerId = testEnv.paymentCustomerId; } else { - const email = `pm_test_${Date.now()}@example.com`; - const createResponse = await customers.create({ email }); - if (createResponse.ok) { - testCustomerId = createResponse.value.data.id as string; + // Fallback: find or create a test customer + const listResponse = await customers.list({ limit: 1 }); + if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { + testCustomerId = listResponse.value.data.customer_list[0].id as string; + } else { + const email = `pm_test_${Date.now()}@example.com`; + const createResponse = await customers.create({ + email, + first_name: 'Test', + last_name: 'User', + document_type: 'personal_tax_id', + document_number: `${Date.now()}`.padStart(11, '0').substring(0, 11), + }); + if (createResponse.ok) { + testCustomerId = createResponse.value.data.id as string; + } } } + expect(testCustomerId).toBeDefined(); }, INTEGRATION_TEST_TIMEOUT, From e865e0367fa1f2d013a7502b46fd2285d43eb3cf Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:10:38 -0300 Subject: [PATCH 089/143] fix: remove unused getErrorBodyMessage test from service helpers --- packages/api/__tests__/unit/utils.test.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index 721a80f6..a7eb060c 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -1,4 +1,4 @@ -import { buildQueryString, getErrorBodyMessage } from "../../src/services/helpers"; +import { buildQueryString } from "../../src/services/helpers"; import { AbortError, ApiError, @@ -55,16 +55,6 @@ describe("service helpers", () => { it("buildQueryString encodes values", () => { expect(buildQueryString({ a: "b c", count: 2 })).toBe("?a=b%20c&count=2"); }); - - it("getErrorBodyMessage extracts error body", () => { - expect(getErrorBodyMessage(null)).toBeUndefined(); - expect(getErrorBodyMessage("boom")).toBeUndefined(); - expect(getErrorBodyMessage({})).toBeUndefined(); - expect(getErrorBodyMessage({ body: undefined })).toBeUndefined(); - expect(getErrorBodyMessage({ body: null })).toBeUndefined(); - expect(getErrorBodyMessage({ body: {} })).toBeUndefined(); - expect(getErrorBodyMessage({ body: { msg: "bad" } })).toBe("bad"); - }); }); describe("httpClient", () => { From f066016b780ba5da2881e3a988e7faf34dcec203 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:20:02 -0300 Subject: [PATCH 090/143] fix: add optional id field to Customer Data interface --- packages/api/src/types/customer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/types/customer.ts b/packages/api/src/types/customer.ts index 7f002d29..7fc2a005 100644 --- a/packages/api/src/types/customer.ts +++ b/packages/api/src/types/customer.ts @@ -18,6 +18,7 @@ export namespace Customer { } export interface Data { + id?: string; customer_id?: string; document_number?: string | null; document_type?: string | null; From 4859d0726bd219c1749ca19299d1da8eeeac1438 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:20:16 -0300 Subject: [PATCH 091/143] fix: update config preservation test to ensure clientSecret is not exposed --- packages/api/__tests__/unit/client.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api/__tests__/unit/client.test.ts b/packages/api/__tests__/unit/client.test.ts index e709f5d3..6e0562cd 100644 --- a/packages/api/__tests__/unit/client.test.ts +++ b/packages/api/__tests__/unit/client.test.ts @@ -68,14 +68,15 @@ describe("createOakClient", () => { }); describe("config preservation", () => { - it("should preserve clientId and clientSecret", () => { + it("should preserve clientId in public config", () => { const client = createOakClient({ ...baseConfig, environment: "sandbox", }); expect(client.config.clientId).toBe(baseConfig.clientId); - expect(client.config.clientSecret).toBe(baseConfig.clientSecret); + // clientSecret is intentionally not exposed in public config for security + expect(client.config).not.toHaveProperty('clientSecret'); }); it("should preserve custom retry options", () => { From cee1c98862346d3b45de3ea965e3fb42708f93f4 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:20:59 -0300 Subject: [PATCH 092/143] fix: remove clientSecret from client configuration and auth service delegation --- packages/api/__tests__/unit/services.test.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/api/__tests__/unit/services.test.ts b/packages/api/__tests__/unit/services.test.ts index 261e1678..33b7ef86 100644 --- a/packages/api/__tests__/unit/services.test.ts +++ b/packages/api/__tests__/unit/services.test.ts @@ -1,5 +1,4 @@ import { - createAuthService, createBuyService, createCustomerService, createPaymentMethodService, @@ -38,7 +37,6 @@ const makeClient = (): OakClient => ({ config: { environment: "sandbox", clientId: "id", - clientSecret: "secret", baseUrl: SANDBOX_URL, }, retryOptions, @@ -52,7 +50,6 @@ const makeClientWithTokenError = (): OakClient => { config: { environment: "sandbox", clientId: "id", - clientSecret: "secret", baseUrl: SANDBOX_URL, }, retryOptions, @@ -129,16 +126,6 @@ describe("Crowdsplit services (Unit)", () => { jest.clearAllMocks(); }); - it("auth service delegates to client", async () => { - const client = makeClient(); - const service = createAuthService(client); - await service.getAccessToken(); - await service.grantToken(); - - expect(client.getAccessToken).toHaveBeenCalled(); - expect(client.grantToken).toHaveBeenCalled(); - }); - it("customer service methods", async () => { const client = makeClient(); const service = createCustomerService(client); @@ -228,7 +215,7 @@ describe("Crowdsplit services (Unit)", () => { client, call: () => service.create(payment), httpMethod: "post", - expectedArgs: [`${SANDBOX_URL}/api/v1/payments/`, payment, authConfig], + expectedArgs: [`${SANDBOX_URL}/api/v1/payments`, payment, authConfig], }); await expectFailure({ call: () => service.create(payment), From 6ba7f8d0249f2c5d4fd3045f139deeed960fd5c2 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:21:15 -0300 Subject: [PATCH 093/143] fix: update error handling in webhook verification tests to streamline status checks --- packages/api/__tests__/unit/webhookVerification.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/api/__tests__/unit/webhookVerification.test.ts b/packages/api/__tests__/unit/webhookVerification.test.ts index d288a291..d0db8ed2 100644 --- a/packages/api/__tests__/unit/webhookVerification.test.ts +++ b/packages/api/__tests__/unit/webhookVerification.test.ts @@ -90,8 +90,7 @@ describe("webhookVerification", () => { expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.message).toBe("Invalid webhook signature"); - expect(result.error.code).toBe("WEBHOOK_VERIFICATION_FAILED"); - expect(result.error.statusCode).toBe(401); + expect(result.error.status).toBe(401); } }); @@ -107,8 +106,7 @@ describe("webhookVerification", () => { expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.message).toContain("Failed to parse webhook payload"); - expect(result.error.code).toBe("WEBHOOK_PARSE_ERROR"); - expect(result.error.statusCode).toBe(400); + expect(result.error.status).toBe(400); } }); From 9ec70d8e8829208117502bf9350d755c382fb705 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:21:26 -0300 Subject: [PATCH 094/143] fix: simplify error handling in withAuth tests by removing unnecessary error codes --- packages/api/__tests__/unit/withAuth.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/__tests__/unit/withAuth.test.ts b/packages/api/__tests__/unit/withAuth.test.ts index a7c38f82..60cefde6 100644 --- a/packages/api/__tests__/unit/withAuth.test.ts +++ b/packages/api/__tests__/unit/withAuth.test.ts @@ -32,7 +32,7 @@ describe("withAuth", () => { it("should return error if token fetch fails", async () => { // Mock failed token fetch - const tokenError = new OakError("Token fetch failed", "AUTH_ERROR", 401); + const tokenError = new OakError("Token fetch failed"); jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(err(tokenError)); // Mock operation (should not be called) @@ -50,7 +50,7 @@ describe("withAuth", () => { jest.spyOn(mockClient, "getAccessToken").mockResolvedValue(ok("token")); // Mock operation that returns error - const operationError = new OakError("Operation failed", "OP_ERROR", 500); + const operationError = new OakError("Operation failed"); const mockOperation = jest.fn().mockResolvedValue(err(operationError)); const result = await withAuth(mockClient, mockOperation); From d24f960d3c6b96eafca8a13e39bde5e2013c6a86 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:21:40 -0300 Subject: [PATCH 095/143] feat: add example .env file for API credentials and configuration --- packages/api/examples/.env.example | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/api/examples/.env.example diff --git a/packages/api/examples/.env.example b/packages/api/examples/.env.example new file mode 100644 index 00000000..904d5c38 --- /dev/null +++ b/packages/api/examples/.env.example @@ -0,0 +1,13 @@ +# Oak API Credentials +CLIENT_ID=your_client_id_here +CLIENT_SECRET=your_client_secret_here + +# Environment (sandbox or production) +OAK_ENVIRONMENT=sandbox + +# Optional: Use a specific customer ID for payment method examples +# This avoids creating new customers on every run +PAYMENT_CUSTOMER_ID= + +# Optional: Custom base URL (leave empty to use default) +BASE_URL= From 039792f566165a8c5cf93bd04997b0dde03ceafc Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:15 -0300 Subject: [PATCH 096/143] feat: add comprehensive examples and documentation for Oak SDK usage --- packages/api/examples/README.md | 139 ++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 packages/api/examples/README.md diff --git a/packages/api/examples/README.md b/packages/api/examples/README.md new file mode 100644 index 00000000..9a951c25 --- /dev/null +++ b/packages/api/examples/README.md @@ -0,0 +1,139 @@ +# Oak SDK Examples + +This directory contains comprehensive, modular examples demonstrating how to use the Oak SDK in real-world scenarios. + +## 📋 Prerequisites + +- Node.js 18+ or compatible runtime +- Oak API credentials (Client ID and Secret) +- Access to Oak sandbox or production environment + +## 🚀 Quick Start + +### 1. Install Dependencies + +From the `packages/api` directory: + +```bash +npm install +npm run build +``` + +### 2. Configure Environment + +Copy the example environment file and add your credentials: + +```bash +cd examples +cp .env.example .env +# Edit .env with your CLIENT_ID and CLIENT_SECRET +``` + +### 3. Run Examples + +```bash +# Authentication example +node authentication/get-token.js + +# Customer management +node customers/create-customer.js +node customers/list-customers.js + +# Payment methods +node payment-methods/add-pix.js + +# Webhooks +node webhooks/register-webhook.js + +# Complete workflows +node workflows/customer-onboarding.js +``` + +## 📁 Directory Structure + +``` +examples/ +├── common/ # Shared utilities +│ ├── config.js # SDK configuration helper +│ └── logger.js # Simple console logger +│ +├── authentication/ # OAuth examples +│ └── get-token.js # Token generation and caching +│ +├── customers/ # Customer management +│ ├── create-customer.js +│ ├── list-customers.js +│ ├── get-customer.js +│ └── update-customer.js +│ +├── payment-methods/ # Payment method examples +│ ├── add-pix.js +│ ├── add-bank-account.js +│ ├── list-payment-methods.js +│ └── delete-payment-method.js +│ +├── webhooks/ # Webhook integration +│ ├── register-webhook.js +│ ├── verify-signature.js +│ └── manage-webhooks.js +│ +└── workflows/ # End-to-end scenarios + ├── complete-payment-flow.js + └── customer-onboarding.js +``` + +## 🎯 Example Categories + +### Authentication +Learn how to authenticate with the Oak API using OAuth 2.0 client credentials flow. + +### Customers +Create, read, update, and list customers with proper error handling. + +### Payment Methods +Add and manage payment methods (PIX, bank accounts, cards) for customers. + +### Webhooks +Set up webhook endpoints, verify signatures, and handle webhook events securely. + +### Workflows +Complete end-to-end scenarios combining multiple API operations. + +## 💡 Best Practices Demonstrated + +- ✅ Proper error handling using Result types +- ✅ Environment variable configuration +- ✅ Token caching and reuse +- ✅ Retry logic for transient failures +- ✅ Webhook signature verification +- ✅ Unique identifiers for idempotency +- ✅ Structured logging +- ✅ Type safety with TypeScript-generated types + +## 🔒 Security Notes + +- **Never commit `.env` files** - Use `.env.example` as a template +- **Keep credentials secure** - Use environment variables, not hardcoded values +- **Verify webhook signatures** - Always validate webhook payloads +- **Use HTTPS in production** - Webhook URLs must use secure connections + +## 📚 Additional Resources + +- [Oak SDK Documentation](../../README.md) +- [API Reference](https://docs.oak.network/api) +- [Integration Test Examples](../__tests__/integration/) + +## 🐛 Troubleshooting + +### "Missing required environment variables" +Make sure you've created a `.env` file with `CLIENT_ID` and `CLIENT_SECRET`. + +### "Authentication failed" +Verify your credentials are correct and you're using the right environment (sandbox/production). + +### "Customer already exists" +Some examples use timestamp-based unique identifiers. If running multiple times rapidly, you may encounter duplicates. + +## 🤝 Contributing + +Found an issue or want to add a new example? Please open an issue or pull request! From 17d510aefd16e6046af7127b89ff8f700514a3df Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:19 -0300 Subject: [PATCH 097/143] feat: add authentication example demonstrating OAuth 2.0 client credentials flow --- .../api/examples/authentication/get-token.js | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/api/examples/authentication/get-token.js diff --git a/packages/api/examples/authentication/get-token.js b/packages/api/examples/authentication/get-token.js new file mode 100644 index 00000000..15fef6a4 --- /dev/null +++ b/packages/api/examples/authentication/get-token.js @@ -0,0 +1,54 @@ +/** + * Authentication Example + * + * Demonstrates how to authenticate with the Oak API using OAuth 2.0 + * client credentials flow. Shows token generation and automatic caching. + */ + +const { getOakClient } = require('../common/config'); +const logger = require('../common/logger'); + +async function main() { + logger.section('OAuth Authentication Example'); + + try { + // Create Oak client (authentication is handled automatically) + logger.step(1, 'Creating Oak client...'); + const client = getOakClient(); + logger.success('Client created successfully'); + + // Get access token (will be cached for subsequent requests) + logger.step(2, 'Requesting access token...'); + const tokenResult = await client.getAccessToken(); + + if (!tokenResult.ok) { + logger.error('Failed to get access token', tokenResult.error); + process.exit(1); + } + + logger.success('Access token obtained successfully'); + logger.info('Token (first 20 chars)', tokenResult.value.substring(0, 20) + '...'); + + // Second call will use cached token + logger.step(3, 'Requesting access token again (should use cache)...'); + const cachedTokenResult = await client.getAccessToken(); + + if (cachedTokenResult.ok) { + logger.success('Token retrieved from cache'); + logger.info('Tokens match', tokenResult.value === cachedTokenResult.value); + } + + // Display client configuration (without sensitive data) + logger.section('Client Configuration'); + logger.info('Environment', client.config.environment); + logger.info('Client ID', client.config.clientId.substring(0, 10) + '...'); + logger.info('Base URL', client.config.baseUrl); + + } catch (error) { + logger.error('Unexpected error during authentication', error); + process.exit(1); + } +} + +// Run the example +main(); From 0240fc50164c154e098f41a507e59704f4555456 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:23 -0300 Subject: [PATCH 098/143] feat: add shared configuration helper for Oak SDK examples --- packages/api/examples/common/config.js | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/api/examples/common/config.js diff --git a/packages/api/examples/common/config.js b/packages/api/examples/common/config.js new file mode 100644 index 00000000..fdf442c2 --- /dev/null +++ b/packages/api/examples/common/config.js @@ -0,0 +1,69 @@ +/** + * Shared configuration helper for Oak SDK examples + * + * This module provides a consistent way to configure the Oak client + * across all examples using environment variables. + */ + +const { createOakClient } = require('../../dist/index.js'); +require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') }); + +/** + * Creates and returns a configured Oak client instance + * + * @returns {import('../../dist/index.js').OakClient} Configured Oak client + * @throws {Error} If required environment variables are missing + */ +function getOakClient() { + const clientId = process.env.CLIENT_ID; + const clientSecret = process.env.CLIENT_SECRET; + const environment = process.env.OAK_ENVIRONMENT || 'sandbox'; + + if (!clientId || !clientSecret) { + throw new Error( + 'Missing required environment variables: CLIENT_ID and CLIENT_SECRET\n' + + 'Please copy .env.example to .env and add your credentials.' + ); + } + + if (environment !== 'sandbox' && environment !== 'production') { + throw new Error( + `Invalid OAK_ENVIRONMENT: ${environment}. Must be 'sandbox' or 'production'.` + ); + } + + const config = { + environment, + clientId, + clientSecret, + retryOptions: { + maxNumberOfRetries: 3, + delay: 1000, + backoffFactor: 2, + }, + }; + + // Add custom URL if provided in environment + if (process.env.BASE_URL) { + config.customUrl = process.env.BASE_URL; + } + + return createOakClient(config); +} + +/** + * Gets environment-specific test data + * + * @returns {Object} Test environment configuration + */ +function getTestEnvironment() { + return { + paymentCustomerId: process.env.PAYMENT_CUSTOMER_ID, + environment: process.env.OAK_ENVIRONMENT || 'sandbox', + }; +} + +module.exports = { + getOakClient, + getTestEnvironment, +}; From 6f284a400f387cce86b24a7d9dfb4668df325425 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:27 -0300 Subject: [PATCH 099/143] feat: add simple logging utility for consistent console output in examples --- packages/api/examples/common/logger.js | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/api/examples/common/logger.js diff --git a/packages/api/examples/common/logger.js b/packages/api/examples/common/logger.js new file mode 100644 index 00000000..309e1745 --- /dev/null +++ b/packages/api/examples/common/logger.js @@ -0,0 +1,83 @@ +/** + * Simple logging utility for examples + * + * Provides consistent, colored console output for example scripts + */ + +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', +}; + +/** + * Log a success message + */ +function success(message, data = null) { + console.log(`${colors.green}✓ ${message}${colors.reset}`); + if (data) { + console.log(colors.cyan + JSON.stringify(data, null, 2) + colors.reset); + } +} + +/** + * Log an error message + */ +function error(message, err = null) { + console.error(`${colors.red}✗ ${message}${colors.reset}`); + if (err) { + if (err.message) { + console.error(` ${colors.red}Error: ${err.message}${colors.reset}`); + } + if (err.status) { + console.error(` ${colors.red}Status: ${err.status}${colors.reset}`); + } + if (err.details) { + console.error(` ${colors.red}Details:${colors.reset}`, err.details); + } + } +} + +/** + * Log an info message + */ +function info(message, data = null) { + console.log(`${colors.blue}ℹ ${message}${colors.reset}`); + if (data) { + console.log(colors.cyan + JSON.stringify(data, null, 2) + colors.reset); + } +} + +/** + * Log a warning message + */ +function warning(message) { + console.warn(`${colors.yellow}⚠ ${message}${colors.reset}`); +} + +/** + * Log a section header + */ +function section(title) { + console.log(`\n${colors.bright}${colors.blue}=== ${title} ===${colors.reset}\n`); +} + +/** + * Log a step in a process + */ +function step(number, message) { + console.log(`${colors.cyan}${number}. ${message}${colors.reset}`); +} + +module.exports = { + success, + error, + info, + warning, + section, + step, +}; From 66d1cab2a5b6fe1a4809acdd7793c8739d02c863 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:32 -0300 Subject: [PATCH 100/143] feat: add create customer example with error handling and logging --- .../api/examples/customers/create-customer.js | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 packages/api/examples/customers/create-customer.js diff --git a/packages/api/examples/customers/create-customer.js b/packages/api/examples/customers/create-customer.js new file mode 100644 index 00000000..bfb67854 --- /dev/null +++ b/packages/api/examples/customers/create-customer.js @@ -0,0 +1,76 @@ +/** + * Create Customer Example + * + * Demonstrates how to create a new customer with all required fields + * and proper error handling. + */ + +const { getOakClient } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Create Customer Example'); + + try { + const client = getOakClient(); + const customers = Crowdsplit(client).customers; + + // Generate unique identifiers + const timestamp = Date.now(); + const email = `customer_${timestamp}@example.com`; + const documentNumber = `${timestamp}`.padStart(11, '0').substring(0, 11); + + // Customer data + const customerData = { + email, + first_name: 'John', + last_name: 'Doe', + document_type: 'personal_tax_id', + document_number: documentNumber, + phone_country_code: '+1', + phone_area_code: '555', + phone_number: '0123456', + country_code: 'US', + }; + + logger.step(1, 'Creating customer...'); + logger.info('Customer data', { + email: customerData.email, + name: `${customerData.first_name} ${customerData.last_name}`, + document: customerData.document_number, + }); + + const result = await customers.create(customerData); + + if (!result.ok) { + logger.error('Failed to create customer', result.error); + process.exit(1); + } + + logger.success('Customer created successfully!'); + logger.info('Customer details', { + id: result.value.data.id, + email: result.value.data.email, + name: `${result.value.data.first_name} ${result.value.data.last_name}`, + country: result.value.data.country_code, + }); + + // Store the customer ID for use in other examples + logger.section('Next Steps'); + logger.info( + 'Save this customer ID for other examples:', + result.value.data.id + ); + logger.info( + 'Add to .env file:', + `PAYMENT_CUSTOMER_ID=${result.value.data.id}` + ); + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From ee193bc76a7ce119cb918494d8465f1550bda66b Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:35 -0300 Subject: [PATCH 101/143] feat: add get customer example with error handling and logging --- .../api/examples/customers/get-customer.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/api/examples/customers/get-customer.js diff --git a/packages/api/examples/customers/get-customer.js b/packages/api/examples/customers/get-customer.js new file mode 100644 index 00000000..dbe5f90e --- /dev/null +++ b/packages/api/examples/customers/get-customer.js @@ -0,0 +1,65 @@ +/** + * Get Customer Example + * + * Demonstrates how to retrieve a specific customer by ID. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Get Customer Example'); + + try { + const client = getOakClient(); + const customers = Crowdsplit(client).customers; + const testEnv = getTestEnvironment(); + + let customerId = testEnv.paymentCustomerId; + + // If no customer ID in env, get one from the list + if (!customerId) { + logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); + const listResult = await customers.list({ limit: 1 }); + + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + logger.info('Using customer from list', customerId); + } else { + logger.error('No customers found. Please create a customer first.'); + logger.info('Run:', 'node customers/create-customer.js'); + process.exit(1); + } + } + + logger.step(1, `Fetching customer: ${customerId}`); + const result = await customers.get(customerId); + + if (!result.ok) { + logger.error('Failed to get customer', result.error); + process.exit(1); + } + + logger.success('Customer retrieved successfully!'); + logger.section('Customer Details'); + + const customer = result.value.data; + console.log(` ID: ${customer.id}`); + console.log(` Email: ${customer.email}`); + console.log(` Name: ${customer.first_name} ${customer.last_name}`); + console.log(` Document: ${customer.document_type} - ${customer.document_number || 'N/A'}`); + console.log(` Country: ${customer.country_code || 'N/A'}`); + console.log(` Phone: ${customer.phone_country_code || ''} ${customer.phone_area_code || ''} ${customer.phone_number || ''}`); + + if (customer.customer_wallet) { + console.log(` Wallet: ${customer.customer_wallet}`); + } + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 20b1283a46fe1915c34ac74f983af4c30b719605 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:40 -0300 Subject: [PATCH 102/143] feat: add list customers example with pagination and email filtering --- .../api/examples/customers/list-customers.js | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/api/examples/customers/list-customers.js diff --git a/packages/api/examples/customers/list-customers.js b/packages/api/examples/customers/list-customers.js new file mode 100644 index 00000000..4a0ec64b --- /dev/null +++ b/packages/api/examples/customers/list-customers.js @@ -0,0 +1,73 @@ +/** + * List Customers Example + * + * Demonstrates how to list customers with pagination and filtering options. + */ + +const { getOakClient } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('List Customers Example'); + + try { + const client = getOakClient(); + const customers = Crowdsplit(client).customers; + + // Example 1: List first 5 customers + logger.step(1, 'Listing first 5 customers...'); + const listResult = await customers.list({ limit: 5 }); + + if (!listResult.ok) { + logger.error('Failed to list customers', listResult.error); + process.exit(1); + } + + logger.success(`Found ${listResult.value.data.customer_list.length} customers`); + logger.info('Total count', listResult.value.data.count); + + listResult.value.data.customer_list.forEach((customer, index) => { + console.log(`\n ${index + 1}. ${customer.first_name} ${customer.last_name}`); + console.log(` ID: ${customer.id}`); + console.log(` Email: ${customer.email}`); + console.log(` Country: ${customer.country_code || 'N/A'}`); + }); + + // Example 2: Filter by email (if you know one) + if (listResult.value.data.customer_list.length > 0) { + const firstCustomer = listResult.value.data.customer_list[0]; + + logger.step(2, `Searching for customer by email: ${firstCustomer.email}`); + const searchResult = await customers.list({ + email: firstCustomer.email, + limit: 1, + }); + + if (searchResult.ok && searchResult.value.data.customer_list.length > 0) { + logger.success('Customer found by email'); + logger.info('Customer', { + id: searchResult.value.data.customer_list[0].id, + email: searchResult.value.data.customer_list[0].email, + }); + } + } + + // Example 3: Pagination + logger.step(3, 'Demonstrating pagination (offset: 2, limit: 3)...'); + const paginatedResult = await customers.list({ + offset: 2, + limit: 3, + }); + + if (paginatedResult.ok) { + logger.success(`Retrieved ${paginatedResult.value.data.customer_list.length} customers (page 2)`); + } + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 39f5ba3f4faa1000cb2952a859fb2e566af8d7b1 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:46 -0300 Subject: [PATCH 103/143] feat: add update customer example with error handling and logging --- .../api/examples/customers/update-customer.js | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packages/api/examples/customers/update-customer.js diff --git a/packages/api/examples/customers/update-customer.js b/packages/api/examples/customers/update-customer.js new file mode 100644 index 00000000..06bfc710 --- /dev/null +++ b/packages/api/examples/customers/update-customer.js @@ -0,0 +1,89 @@ +/** + * Update Customer Example + * + * Demonstrates how to update customer information. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Update Customer Example'); + + try { + const client = getOakClient(); + const customers = Crowdsplit(client).customers; + const testEnv = getTestEnvironment(); + + let customerId = testEnv.paymentCustomerId; + + // If no customer ID in env, get one from the list + if (!customerId) { + logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); + const listResult = await customers.list({ limit: 1 }); + + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + } else { + logger.error('No customers found. Please create a customer first.'); + process.exit(1); + } + } + + // Get current customer data + logger.step(1, 'Fetching current customer data...'); + const currentResult = await customers.get(customerId); + + if (!currentResult.ok) { + logger.error('Failed to get customer', currentResult.error); + process.exit(1); + } + + logger.info('Current customer', { + name: `${currentResult.value.data.first_name} ${currentResult.value.data.last_name}`, + email: currentResult.value.data.email, + }); + + // Update customer + logger.step(2, 'Updating customer...'); + const updateData = { + first_name: 'Jane', + last_name: 'Smith', + phone_country_code: '+1', + phone_area_code: '555', + phone_number: '9876543', + }; + + const updateResult = await customers.update(customerId, updateData); + + if (!updateResult.ok) { + logger.error('Failed to update customer', updateResult.error); + process.exit(1); + } + + logger.success('Customer updated successfully!'); + logger.info('Updated customer', { + name: `${updateResult.value.data.first_name} ${updateResult.value.data.last_name}`, + email: updateResult.value.data.email, + phone: `${updateResult.value.data.phone_country_code} ${updateResult.value.data.phone_area_code} ${updateResult.value.data.phone_number}`, + }); + + // Restore original data (optional cleanup) + logger.step(3, 'Restoring original data...'); + const restoreResult = await customers.update(customerId, { + first_name: currentResult.value.data.first_name, + last_name: currentResult.value.data.last_name, + }); + + if (restoreResult.ok) { + logger.success('Customer restored to original state'); + } + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 9346bc500e708b2aeee77a7b1b8e42fe3eb62ff0 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:51 -0300 Subject: [PATCH 104/143] feat: add example for adding bank account payment method with error handling and logging --- .../payment-methods/add-bank-account.js | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 packages/api/examples/payment-methods/add-bank-account.js diff --git a/packages/api/examples/payment-methods/add-bank-account.js b/packages/api/examples/payment-methods/add-bank-account.js new file mode 100644 index 00000000..dd81fee3 --- /dev/null +++ b/packages/api/examples/payment-methods/add-bank-account.js @@ -0,0 +1,87 @@ +/** + * Add Bank Account Payment Method Example + * + * Demonstrates how to add a bank account payment method (Stripe). + * Note: This requires a Stripe connected account setup. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Add Bank Account Payment Method Example'); + + try { + const client = getOakClient(); + const { paymentMethods, customers } = Crowdsplit(client); + const testEnv = getTestEnvironment(); + + // Get customer ID + let customerId = testEnv.paymentCustomerId; + + if (!customerId) { + logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); + const listResult = await customers.list({ limit: 1 }); + + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + } else { + logger.error('No customers found. Create a customer first.'); + process.exit(1); + } + } + + logger.step(1, `Adding bank account for customer: ${customerId}`); + + // Bank account data (Stripe test account) + const bankData = { + type: 'bank', + provider: 'stripe', + currency: 'usd', + bank_name: 'Test Bank', + bank_account_number: '000123456789', + bank_routing_number: '110000000', + bank_account_type: 'CHECKING', + bank_account_name: 'Example Account', + metadata: { + description: 'Example bank account', + created_by: 'oak-sdk-example', + }, + }; + + logger.info('Bank account data', { + provider: bankData.provider, + bank_name: bankData.bank_name, + account_type: bankData.bank_account_type, + currency: bankData.currency, + }); + + const result = await paymentMethods.add(customerId, bankData); + + if (!result.ok) { + logger.warning('Bank account creation may require Stripe connected account setup'); + logger.error('Failed to add bank account', result.error); + logger.section('Alternative'); + logger.info('Try adding a PIX payment method instead:', 'node payment-methods/add-pix.js'); + process.exit(1); + } + + logger.success('Bank account added successfully!'); + logger.section('Payment Method Details'); + + const pm = result.value.data; + console.log(` ID: ${pm.id}`); + console.log(` Type: ${pm.type}`); + console.log(` Provider: ${pm.provider || 'N/A'}`); + console.log(` Status: ${pm.status || 'N/A'}`); + console.log(` Bank: ${pm.bank_name || 'N/A'}`); + console.log(` Currency: ${pm.currency || 'N/A'}`); + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 91cfd09c1cd293823cbe47ff158579abcc437a98 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:22:55 -0300 Subject: [PATCH 105/143] feat: add example for adding PIX payment method with error handling and logging --- .../api/examples/payment-methods/add-pix.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 packages/api/examples/payment-methods/add-pix.js diff --git a/packages/api/examples/payment-methods/add-pix.js b/packages/api/examples/payment-methods/add-pix.js new file mode 100644 index 00000000..f1c21924 --- /dev/null +++ b/packages/api/examples/payment-methods/add-pix.js @@ -0,0 +1,80 @@ +/** + * Add PIX Payment Method Example + * + * Demonstrates how to add a PIX payment method to a customer. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Add PIX Payment Method Example'); + + try { + const client = getOakClient(); + const { paymentMethods, customers } = Crowdsplit(client); + const testEnv = getTestEnvironment(); + + // Get customer ID + let customerId = testEnv.paymentCustomerId; + + if (!customerId) { + logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); + const listResult = await customers.list({ limit: 1 }); + + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + } else { + logger.error('No customers found. Create a customer first:'); + logger.info('Run:', 'node customers/create-customer.js'); + process.exit(1); + } + } + + logger.step(1, `Adding PIX payment method for customer: ${customerId}`); + + // PIX payment method data + const pixData = { + type: 'pix', + pix_string: `pix_${Date.now()}@example.com`, + metadata: { + description: 'Example PIX payment method', + created_by: 'oak-sdk-example', + timestamp: new Date().toISOString(), + }, + }; + + logger.info('PIX data', { + type: pixData.type, + pix_string: pixData.pix_string, + }); + + const result = await paymentMethods.add(customerId, pixData); + + if (!result.ok) { + logger.error('Failed to add PIX payment method', result.error); + process.exit(1); + } + + logger.success('PIX payment method added successfully!'); + logger.section('Payment Method Details'); + + const pm = result.value.data; + console.log(` ID: ${pm.id}`); + console.log(` Type: ${pm.type}`); + console.log(` Status: ${pm.status || 'N/A'}`); + console.log(` PIX Key: ${pm.pix_string || 'N/A'}`); + + logger.section('Next Steps'); + logger.info('Payment method ID (save for later use):', pm.id); + logger.info('List all payment methods:', 'node payment-methods/list-payment-methods.js'); + logger.info('Delete this payment method:', `node payment-methods/delete-payment-method.js ${pm.id}`); + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From caa3afca2750c44d802ab08b9d7936b8e0de45eb Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:00 -0300 Subject: [PATCH 106/143] feat: add example for deleting a payment method with error handling and logging --- .../payment-methods/delete-payment-method.js | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/api/examples/payment-methods/delete-payment-method.js diff --git a/packages/api/examples/payment-methods/delete-payment-method.js b/packages/api/examples/payment-methods/delete-payment-method.js new file mode 100644 index 00000000..dd9b906c --- /dev/null +++ b/packages/api/examples/payment-methods/delete-payment-method.js @@ -0,0 +1,83 @@ +/** + * Delete Payment Method Example + * + * Demonstrates how to delete a payment method. + * Usage: node delete-payment-method.js [payment_method_id] + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Delete Payment Method Example'); + + try { + const client = getOakClient(); + const { paymentMethods, customers } = Crowdsplit(client); + const testEnv = getTestEnvironment(); + + // Get customer ID + let customerId = testEnv.paymentCustomerId; + + if (!customerId) { + const listResult = await customers.list({ limit: 1 }); + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + } else { + logger.error('No customers found.'); + process.exit(1); + } + } + + // Get payment method ID from command line or use the first available + let paymentMethodId = process.argv[2]; + + if (!paymentMethodId) { + logger.step(1, 'No payment method ID provided, fetching from list...'); + const listResult = await paymentMethods.list(customerId); + + if (!listResult.ok || listResult.value.data.length === 0) { + logger.error('No payment methods found for this customer'); + logger.info('Add a payment method first:', 'node payment-methods/add-pix.js'); + process.exit(1); + } + + paymentMethodId = listResult.value.data[0].id; + logger.info('Using first payment method', { + id: paymentMethodId, + type: listResult.value.data[0].type, + }); + } + + // Confirm deletion + logger.step(2, `Deleting payment method: ${paymentMethodId}`); + logger.warning('This action cannot be undone!'); + + const result = await paymentMethods.delete(customerId, paymentMethodId); + + if (!result.ok) { + logger.error('Failed to delete payment method', result.error); + process.exit(1); + } + + logger.success('Payment method deleted successfully!'); + logger.info('Response', result.value.msg); + + // Verify deletion + logger.step(3, 'Verifying deletion...'); + const verifyResult = await paymentMethods.get(customerId, paymentMethodId); + + if (!verifyResult.ok) { + logger.success('Confirmed: Payment method no longer exists'); + } else { + logger.warning('Payment method still exists (may take time to propagate)'); + } + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 90fb42fecfc821a4c9a89ef6532a63d97a1b471c Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:04 -0300 Subject: [PATCH 107/143] feat: add example for listing payment methods with filtering and error handling --- .../payment-methods/list-payment-methods.js | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 packages/api/examples/payment-methods/list-payment-methods.js diff --git a/packages/api/examples/payment-methods/list-payment-methods.js b/packages/api/examples/payment-methods/list-payment-methods.js new file mode 100644 index 00000000..17dfdec8 --- /dev/null +++ b/packages/api/examples/payment-methods/list-payment-methods.js @@ -0,0 +1,81 @@ +/** + * List Payment Methods Example + * + * Demonstrates how to list all payment methods for a customer with filtering. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('List Payment Methods Example'); + + try { + const client = getOakClient(); + const { paymentMethods, customers } = Crowdsplit(client); + const testEnv = getTestEnvironment(); + + // Get customer ID + let customerId = testEnv.paymentCustomerId; + + if (!customerId) { + logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); + const listResult = await customers.list({ limit: 1 }); + + if (listResult.ok && listResult.value.data.customer_list.length > 0) { + customerId = listResult.value.data.customer_list[0].id; + } else { + logger.error('No customers found.'); + process.exit(1); + } + } + + // Example 1: List all payment methods + logger.step(1, `Listing all payment methods for customer: ${customerId}`); + const allResult = await paymentMethods.list(customerId); + + if (!allResult.ok) { + logger.error('Failed to list payment methods', allResult.error); + process.exit(1); + } + + logger.success(`Found ${allResult.value.data.length} payment method(s)`); + + if (allResult.value.data.length === 0) { + logger.warning('No payment methods found for this customer'); + logger.info('Add a payment method first:', 'node payment-methods/add-pix.js'); + } else { + allResult.value.data.forEach((pm, index) => { + console.log(`\n ${index + 1}. ${pm.type?.toUpperCase() || 'Unknown'}`); + console.log(` ID: ${pm.id}`); + console.log(` Status: ${pm.status || 'N/A'}`); + if (pm.provider) console.log(` Provider: ${pm.provider}`); + if (pm.pix_string) console.log(` PIX: ${pm.pix_string}`); + if (pm.bank_name) console.log(` Bank: ${pm.bank_name}`); + }); + } + + // Example 2: Filter by type (PIX) + logger.step(2, 'Filtering payment methods by type: PIX'); + const pixResult = await paymentMethods.list(customerId, { type: 'pix' }); + + if (pixResult.ok) { + logger.success(`Found ${pixResult.value.data.length} PIX payment method(s)`); + } + + // Example 3: Filter by status (active) + logger.step(3, 'Filtering payment methods by status: active'); + const activeResult = await paymentMethods.list(customerId, { status: 'active' }); + + if (activeResult.ok) { + logger.success(`Found ${activeResult.value.data.length} active payment method(s)`); + } + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 22d6b1f9461a4df3f805ace755bccac84cc7769f Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:08 -0300 Subject: [PATCH 108/143] feat: add example for managing webhooks with listing, updating, toggling, and optional deletion --- .../api/examples/webhooks/manage-webhooks.js | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 packages/api/examples/webhooks/manage-webhooks.js diff --git a/packages/api/examples/webhooks/manage-webhooks.js b/packages/api/examples/webhooks/manage-webhooks.js new file mode 100644 index 00000000..b02b1710 --- /dev/null +++ b/packages/api/examples/webhooks/manage-webhooks.js @@ -0,0 +1,116 @@ +/** + * Manage Webhooks Example + * + * Demonstrates complete webhook lifecycle: list, get, update, toggle, and delete. + */ + +const { getOakClient } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Webhook Management Example'); + + try { + const client = getOakClient(); + const webhooks = Crowdsplit(client).webhooks; + + // Step 1: List all webhooks + logger.step(1, 'Listing all webhooks...'); + const listResult = await webhooks.list(); + + if (!listResult.ok) { + logger.error('Failed to list webhooks', listResult.error); + process.exit(1); + } + + logger.success(`Found ${listResult.value.data.length} webhook(s)`); + + if (listResult.value.data.length === 0) { + logger.warning('No webhooks found'); + logger.info('Register a webhook first:', 'node webhooks/register-webhook.js'); + process.exit(0); + } + + // Display webhooks + listResult.value.data.forEach((webhook, index) => { + console.log(`\n ${index + 1}. ${webhook.url}`); + console.log(` ID: ${webhook.id}`); + console.log(` Active: ${webhook.active ? '✓' : '✗'}`); + console.log(` Events: ${webhook.events?.join(', ') || 'N/A'}`); + }); + + const firstWebhook = listResult.value.data[0]; + const webhookId = firstWebhook.id; + + // Step 2: Get specific webhook + logger.step(2, `Getting webhook details: ${webhookId}`); + const getResult = await webhooks.get(webhookId); + + if (getResult.ok) { + logger.success('Webhook retrieved successfully'); + logger.info('Details', { + id: getResult.value.data.id, + url: getResult.value.data.url, + active: getResult.value.data.active, + }); + } + + // Step 3: Update webhook + logger.step(3, 'Updating webhook events...'); + const updateResult = await webhooks.update(webhookId, { + events: ['payment.completed', 'customer.created'], + metadata: { + updated_at: new Date().toISOString(), + updated_by: 'oak-sdk-example', + }, + }); + + if (updateResult.ok) { + logger.success('Webhook updated successfully'); + logger.info('New events', updateResult.value.data.events); + } + + // Step 4: Toggle webhook status + logger.step(4, 'Toggling webhook status...'); + const currentStatus = updateResult.ok ? updateResult.value.data.active : firstWebhook.active; + + const toggleResult = await webhooks.toggle(webhookId); + + if (toggleResult.ok) { + logger.success(`Webhook ${currentStatus ? 'disabled' : 'enabled'}`); + logger.info('New status', toggleResult.value.data.active ? 'Active' : 'Inactive'); + } + + // Toggle back to original state + logger.step(5, 'Restoring original webhook status...'); + const restoreResult = await webhooks.toggle(webhookId); + + if (restoreResult.ok) { + logger.success('Webhook status restored'); + } + + // Step 6: Delete webhook (optional - commented out by default) + logger.step(6, 'Webhook deletion (skipped)'); + logger.info('To delete a webhook, uncomment the code below'); + + /* + logger.warning('Deleting webhook (this cannot be undone)...'); + const deleteResult = await webhooks.delete(webhookId); + + if (deleteResult.ok) { + logger.success('Webhook deleted successfully'); + logger.info('Response', deleteResult.value.msg); + } + */ + + logger.section('Webhook Management Complete'); + logger.info('All operations completed successfully'); + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From 006f9db87775ffa6ebac5fd163b2ae6ae57d8d44 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:12 -0300 Subject: [PATCH 109/143] feat: add example for registering a webhook with configuration and error handling --- .../api/examples/webhooks/register-webhook.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 packages/api/examples/webhooks/register-webhook.js diff --git a/packages/api/examples/webhooks/register-webhook.js b/packages/api/examples/webhooks/register-webhook.js new file mode 100644 index 00000000..cee6deaf --- /dev/null +++ b/packages/api/examples/webhooks/register-webhook.js @@ -0,0 +1,77 @@ +/** + * Register Webhook Example + * + * Demonstrates how to register a webhook endpoint to receive + * real-time notifications about events. + */ + +const { getOakClient } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Register Webhook Example'); + + try { + const client = getOakClient(); + const webhooks = Crowdsplit(client).webhooks; + + // Webhook configuration + const webhookData = { + url: `https://your-app.example.com/webhooks/oak-${Date.now()}`, + events: [ + 'payment.completed', + 'payment.failed', + 'customer.created', + 'transfer.completed', + ], + secret: `webhook_secret_${Date.now()}`, // Store this securely! + active: true, + metadata: { + description: 'Main webhook endpoint', + environment: 'production', + created_by: 'oak-sdk-example', + }, + }; + + logger.step(1, 'Registering webhook endpoint...'); + logger.info('Webhook configuration', { + url: webhookData.url, + events: webhookData.events, + active: webhookData.active, + }); + + logger.warning('Note: The URL must be publicly accessible and use HTTPS in production'); + + const result = await webhooks.register(webhookData); + + if (!result.ok) { + logger.error('Failed to register webhook', result.error); + process.exit(1); + } + + logger.success('Webhook registered successfully!'); + logger.section('Webhook Details'); + + const webhook = result.value.data; + console.log(` ID: ${webhook.id}`); + console.log(` URL: ${webhook.url}`); + console.log(` Active: ${webhook.active}`); + console.log(` Events: ${webhook.events?.join(', ')}`); + + logger.section('Important: Save Your Webhook Secret'); + logger.warning('Store this secret securely - you\'ll need it to verify webhook signatures:'); + console.log(` Secret: ${webhookData.secret}`); + + logger.section('Next Steps'); + logger.info('Webhook ID (save for later):', webhook.id); + logger.info('Test signature verification:', 'node webhooks/verify-signature.js'); + logger.info('Manage webhooks:', 'node webhooks/manage-webhooks.js'); + + } catch (error) { + logger.error('Unexpected error', error); + process.exit(1); + } +} + +main(); From e1bfa84895045b8efaa3e97f7ceb7d56f2a58ffa Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:16 -0300 Subject: [PATCH 110/143] feat: add example for verifying webhook signatures with detailed logging and security best practices --- .../api/examples/webhooks/verify-signature.js | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 packages/api/examples/webhooks/verify-signature.js diff --git a/packages/api/examples/webhooks/verify-signature.js b/packages/api/examples/webhooks/verify-signature.js new file mode 100644 index 00000000..d59087b2 --- /dev/null +++ b/packages/api/examples/webhooks/verify-signature.js @@ -0,0 +1,123 @@ +/** + * Webhook Signature Verification Example + * + * Demonstrates how to verify webhook signatures to ensure + * the payload is authentic and hasn't been tampered with. + */ + +const { verifyWebhookSignature, parseWebhookPayload } = require('../../dist/utils/webhookVerification'); +const logger = require('../common/logger'); +const crypto = require('crypto'); + +async function main() { + logger.section('Webhook Signature Verification Example'); + + // Example webhook data (simulating what Oak API would send) + const webhookSecret = 'your_webhook_secret_here'; + const payload = JSON.stringify({ + event: 'payment.completed', + data: { + payment_id: 'pay_123456', + amount: 100.00, + currency: 'USD', + customer_id: 'cus_789', + status: 'completed', + timestamp: new Date().toISOString(), + }, + }); + + // Generate a valid signature (Oak API would send this in the header) + const validSignature = crypto + .createHmac('sha256', webhookSecret) + .update(payload) + .digest('hex'); + + logger.step(1, 'Testing valid webhook signature...'); + logger.info('Payload', JSON.parse(payload)); + logger.info('Signature (first 20 chars)', validSignature.substring(0, 20) + '...'); + + const isValid = verifyWebhookSignature(payload, validSignature, webhookSecret); + + if (isValid) { + logger.success('✓ Signature is valid - webhook is authentic'); + } else { + logger.error('✗ Signature is invalid - webhook may be forged'); + } + + // Example 2: Invalid signature + logger.step(2, 'Testing invalid webhook signature...'); + const invalidSignature = 'invalid_signature_12345'; + + const isInvalid = verifyWebhookSignature(payload, invalidSignature, webhookSecret); + + if (!isInvalid) { + logger.success('✓ Correctly rejected invalid signature'); + } else { + logger.error('✗ Incorrectly accepted invalid signature'); + } + + // Example 3: Parse and verify in one step + logger.step(3, 'Using parseWebhookPayload (verify + parse)...'); + + const parseResult = parseWebhookPayload(payload, validSignature, webhookSecret); + + if (parseResult.ok) { + logger.success('Webhook payload verified and parsed successfully'); + logger.info('Parsed event', parseResult.value); + } else { + logger.error('Failed to verify/parse webhook', parseResult.error); + } + + // Example 4: Real-world Express.js webhook endpoint + logger.section('Example: Express.js Webhook Endpoint'); + + console.log(` +const express = require('express'); +const { parseWebhookPayload } = require('@oaknetwork/api'); + +const app = express(); + +app.post('/webhooks/oak', express.raw({ type: 'application/json' }), (req, res) => { + const signature = req.headers['x-oak-signature']; // Check actual header name + const payload = req.body.toString(); + const secret = process.env.WEBHOOK_SECRET; + + const result = parseWebhookPayload(payload, signature, secret); + + if (!result.ok) { + console.error('Invalid webhook signature'); + return res.status(401).json({ error: 'Invalid signature' }); + } + + // Handle the event + const event = result.value; + console.log('Received event:', event.event); + + switch (event.event) { + case 'payment.completed': + // Handle payment completion + break; + case 'payment.failed': + // Handle payment failure + break; + default: + console.log('Unhandled event type:', event.event); + } + + res.json({ received: true }); +}); + +app.listen(3000); + `); + + logger.section('Security Best Practices'); + console.log(' ✓ Always verify signatures before processing webhooks'); + console.log(' ✓ Use timing-safe comparison (built into verifyWebhookSignature)'); + console.log(' ✓ Store webhook secrets in environment variables'); + console.log(' ✓ Use HTTPS for webhook endpoints in production'); + console.log(' ✓ Validate payload structure before using data'); + console.log(' ✓ Implement idempotency using event IDs'); + +} + +main(); From 38238a5edd4d2b4e009af5db92e0f130de24de96 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:21 -0300 Subject: [PATCH 111/143] feat: add complete payment flow workflow example with customer setup, payment method management, and webhook configuration --- .../workflows/complete-payment-flow.js | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 packages/api/examples/workflows/complete-payment-flow.js diff --git a/packages/api/examples/workflows/complete-payment-flow.js b/packages/api/examples/workflows/complete-payment-flow.js new file mode 100644 index 00000000..4592adf8 --- /dev/null +++ b/packages/api/examples/workflows/complete-payment-flow.js @@ -0,0 +1,151 @@ +/** + * Complete Payment Flow Workflow + * + * Demonstrates a complete payment setup workflow: + * 1. Find or create a customer + * 2. List existing payment methods + * 3. Add a new payment method if needed + * 4. Set up webhook notifications + * + * This simulates a real-world payment integration scenario. + */ + +const { getOakClient, getTestEnvironment } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Complete Payment Flow Workflow'); + + try { + const client = getOakClient(); + const { customers, paymentMethods, webhooks } = Crowdsplit(client); + const testEnv = getTestEnvironment(); + + let customerId; + + // STEP 1: Get or Create Customer + logger.step(1, 'Setting up customer...'); + + if (testEnv.paymentCustomerId) { + customerId = testEnv.paymentCustomerId; + logger.info('Using existing customer from .env', customerId); + + const verifyResult = await customers.get(customerId); + if (!verifyResult.ok) { + logger.error('Customer from .env not found', verifyResult.error); + process.exit(1); + } + logger.success('Customer verified'); + } else { + logger.info('Creating new customer...'); + const timestamp = Date.now(); + + const createResult = await customers.create({ + email: `payment_flow_${timestamp}@example.com`, + first_name: 'Payment', + last_name: 'User', + document_type: 'personal_tax_id', + document_number: `${timestamp}`.padStart(11, '0').substring(0, 11), + country_code: 'BR', + }); + + if (!createResult.ok) { + logger.error('Failed to create customer', createResult.error); + process.exit(1); + } + + customerId = createResult.value.data.id; + logger.success('New customer created', customerId); + } + + // STEP 2: Check Existing Payment Methods + logger.step(2, 'Checking existing payment methods...'); + + const listPMResult = await paymentMethods.list(customerId); + + if (!listPMResult.ok) { + logger.error('Failed to list payment methods', listPMResult.error); + process.exit(1); + } + + const existingPaymentMethods = listPMResult.value.data; + logger.info('Existing payment methods', existingPaymentMethods.length); + + if (existingPaymentMethods.length > 0) { + existingPaymentMethods.forEach((pm, index) => { + console.log(` ${index + 1}. ${pm.type?.toUpperCase()} - ${pm.id}`); + }); + } + + // STEP 3: Add New Payment Method + logger.step(3, 'Adding new PIX payment method...'); + + const addPMResult = await paymentMethods.add(customerId, { + type: 'pix', + pix_string: `pix_payment_flow_${Date.now()}@example.com`, + metadata: { + workflow: 'complete_payment_flow', + timestamp: new Date().toISOString(), + }, + }); + + if (!addPMResult.ok) { + logger.error('Failed to add payment method', addPMResult.error); + } else { + logger.success('Payment method added', addPMResult.value.data.id); + } + + // STEP 4: Check Webhook Configuration + logger.step(4, 'Checking webhook configuration...'); + + const listWebhooksResult = await webhooks.list(); + + if (!listWebhooksResult.ok) { + logger.warning('Unable to check webhooks', listWebhooksResult.error); + } else { + const activeWebhooks = listWebhooksResult.value.data.filter(w => w.active); + logger.info('Active webhooks', activeWebhooks.length); + + if (activeWebhooks.length === 0) { + logger.warning('No active webhooks configured'); + logger.info('Register a webhook:', 'node webhooks/register-webhook.js'); + } else { + logger.success('Webhook notifications configured'); + activeWebhooks.forEach((wh, index) => { + console.log(` ${index + 1}. ${wh.url}`); + console.log(` Events: ${wh.events?.join(', ')}`); + }); + } + } + + // STEP 5: Final Summary + logger.section('Payment Flow Summary'); + + console.log('\n ✓ Customer Setup:'); + console.log(` ID: ${customerId}`); + + console.log('\n ✓ Payment Methods:'); + const finalPMList = await paymentMethods.list(customerId); + if (finalPMList.ok) { + console.log(` Total: ${finalPMList.value.data.length}`); + finalPMList.value.data.forEach((pm, index) => { + console.log(` ${index + 1}. ${pm.type?.toUpperCase()} - ${pm.status || 'unknown'}`); + }); + } + + console.log('\n ✓ Ready for:'); + console.log(' - Processing payments'); + console.log(' - Receiving webhook notifications'); + console.log(' - Managing customer payment methods'); + + logger.section('Workflow Complete! 🚀'); + logger.success('Payment infrastructure is ready'); + + } catch (error) { + logger.error('Unexpected error in payment flow', error); + process.exit(1); + } +} + +main(); From 252de4f5ed282a4c04dafb38d1676fd75ec46670 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:25 -0300 Subject: [PATCH 112/143] feat: add complete customer onboarding workflow example with payment method setup and verification --- .../examples/workflows/customer-onboarding.js | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 packages/api/examples/workflows/customer-onboarding.js diff --git a/packages/api/examples/workflows/customer-onboarding.js b/packages/api/examples/workflows/customer-onboarding.js new file mode 100644 index 00000000..95fda1ad --- /dev/null +++ b/packages/api/examples/workflows/customer-onboarding.js @@ -0,0 +1,135 @@ +/** + * Complete Customer Onboarding Workflow + * + * Demonstrates an end-to-end customer onboarding process: + * 1. Create a new customer + * 2. Add a payment method (PIX) + * 3. Verify the setup + * + * This workflow shows how to combine multiple SDK operations + * in a real-world scenario. + */ + +const { getOakClient } = require('../common/config'); +const { Crowdsplit } = require('../../dist/products/crowdsplit'); +const logger = require('../common/logger'); + +async function main() { + logger.section('Customer Onboarding Workflow'); + + try { + const client = getOakClient(); + const { customers, paymentMethods } = Crowdsplit(client); + + // Generate unique identifiers + const timestamp = Date.now(); + const email = `customer_${timestamp}@example.com`; + const documentNumber = `${timestamp}`.padStart(11, '0').substring(0, 11); + + // STEP 1: Create Customer + logger.step(1, 'Creating new customer account...'); + + const customerData = { + email, + first_name: 'Alice', + last_name: 'Johnson', + document_type: 'personal_tax_id', + document_number: documentNumber, + phone_country_code: '+55', + phone_area_code: '11', + phone_number: '987654321', + country_code: 'BR', + }; + + logger.info('Customer info', { + name: `${customerData.first_name} ${customerData.last_name}`, + email: customerData.email, + country: customerData.country_code, + }); + + const createCustomerResult = await customers.create(customerData); + + if (!createCustomerResult.ok) { + logger.error('Failed to create customer', createCustomerResult.error); + process.exit(1); + } + + const customerId = createCustomerResult.value.data.id; + logger.success(`Customer created successfully! ID: ${customerId}`); + + // STEP 2: Add Payment Method + logger.step(2, 'Adding PIX payment method...'); + + const pixData = { + type: 'pix', + pix_string: `${email}`, + metadata: { + onboarding_flow: true, + created_at: new Date().toISOString(), + }, + }; + + const addPaymentResult = await paymentMethods.add(customerId, pixData); + + if (!addPaymentResult.ok) { + logger.error('Failed to add payment method', addPaymentResult.error); + logger.warning('Customer created but payment method setup incomplete'); + logger.info('Customer ID to retry:', customerId); + process.exit(1); + } + + const paymentMethodId = addPaymentResult.value.data.id; + logger.success(`Payment method added! ID: ${paymentMethodId}`); + + // STEP 3: Verify Setup + logger.step(3, 'Verifying customer setup...'); + + // Verify customer + const verifyCustomerResult = await customers.get(customerId); + if (!verifyCustomerResult.ok) { + logger.error('Failed to verify customer', verifyCustomerResult.error); + process.exit(1); + } + + // Verify payment methods + const verifyPaymentResult = await paymentMethods.list(customerId); + if (!verifyPaymentResult.ok) { + logger.error('Failed to verify payment methods', verifyPaymentResult.error); + process.exit(1); + } + + const paymentMethodCount = verifyPaymentResult.value.data.length; + logger.success('Customer setup verified successfully!'); + + // STEP 4: Summary + logger.section('Onboarding Complete! 🎉'); + + console.log('\n Customer Details:'); + console.log(` ID: ${customerId}`); + console.log(` Name: ${verifyCustomerResult.value.data.first_name} ${verifyCustomerResult.value.data.last_name}`); + console.log(` Email: ${verifyCustomerResult.value.data.email}`); + console.log(` Country: ${verifyCustomerResult.value.data.country_code}`); + + console.log('\n Payment Methods:'); + console.log(` Count: ${paymentMethodCount}`); + verifyPaymentResult.value.data.forEach((pm, index) => { + console.log(` ${index + 1}. ${pm.type?.toUpperCase()} (${pm.id})`); + }); + + console.log('\n Next Steps:'); + console.log(` - Customer can now make payments`); + console.log(` - Set up webhooks to receive payment notifications`); + console.log(` - Add additional payment methods if needed`); + + logger.section('Workflow Summary'); + logger.success('All onboarding steps completed successfully'); + logger.info('Total operations', '3 (create, add payment method, verify)'); + logger.info('Customer ID', customerId); + + } catch (error) { + logger.error('Unexpected error during onboarding', error); + process.exit(1); + } +} + +main(); From 1c0f2e4a67ab0b2644ca608942b993544ab57b41 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:40 -0300 Subject: [PATCH 113/143] fix: update error handling in webhook verification and parsing functions --- packages/api/src/utils/webhookVerification.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/api/src/utils/webhookVerification.ts b/packages/api/src/utils/webhookVerification.ts index 55cf408d..521c7887 100644 --- a/packages/api/src/utils/webhookVerification.ts +++ b/packages/api/src/utils/webhookVerification.ts @@ -1,6 +1,6 @@ import { createHmac, timingSafeEqual } from "crypto"; import { err, ok, Result } from "../types"; -import { OakError } from "./errorHandler"; +import { ApiError } from "./errorHandler"; /** * Verifies a webhook signature using HMAC-SHA256. @@ -57,7 +57,7 @@ export function verifyWebhookSignature( * @param payload - Raw webhook payload string * @param signature - Signature from webhook headers * @param secret - Your webhook secret - * @returns Result containing parsed payload or error + * @returns Result containing parsed payload or ApiError * * @example * ```typescript @@ -68,7 +68,7 @@ export function verifyWebhookSignature( * ); * * if (!result.ok) { - * return res.status(401).send(result.error.message); + * return res.status(result.error.status).send(result.error.message); * } * * const event = result.value; @@ -79,14 +79,14 @@ export function parseWebhookPayload( payload: string, signature: string, secret: string, -): Result { +): Result { // Verify signature first if (!verifyWebhookSignature(payload, signature, secret)) { return err( - new OakError( + new ApiError( "Invalid webhook signature", - "WEBHOOK_VERIFICATION_FAILED", 401, + { code: "WEBHOOK_VERIFICATION_FAILED" }, ), ); } @@ -97,10 +97,10 @@ export function parseWebhookPayload( return ok(parsed); } catch (error) { return err( - new OakError( + new ApiError( `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`, - "WEBHOOK_PARSE_ERROR", 400, + { code: "WEBHOOK_PARSE_ERROR" }, ), ); } From b06a488309646188c4c011d6885369e67ce75066 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:23:48 -0300 Subject: [PATCH 114/143] feat: add Quick Start Guide for Oak SDK examples with setup and usage instructions --- packages/api/examples/QUICK_START.md | 147 +++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 packages/api/examples/QUICK_START.md diff --git a/packages/api/examples/QUICK_START.md b/packages/api/examples/QUICK_START.md new file mode 100644 index 00000000..5ce41f7d --- /dev/null +++ b/packages/api/examples/QUICK_START.md @@ -0,0 +1,147 @@ +# Quick Start Guide - Oak SDK Examples + +## Setup (One-time) + +1. **Build the SDK** (from `packages/api` directory): + ```bash + npm run build + ``` + +2. **Configure your environment**: + ```bash + cd examples + cp .env.example .env + # Edit .env and add your CLIENT_ID and CLIENT_SECRET + ``` + +## Running Examples + +All commands should be run from the `examples/` directory: + +```bash +cd examples +``` + +### Authentication +```bash +# Test OAuth authentication +node authentication/get-token.js +``` + +### Customer Management +```bash +# Create a new customer +node customers/create-customer.js + +# List all customers +node customers/list-customers.js + +# Get specific customer details +node customers/get-customer.js + +# Update customer information +node customers/update-customer.js +``` + +### Payment Methods +```bash +# Add PIX payment method +node payment-methods/add-pix.js + +# Add bank account (requires Stripe setup) +node payment-methods/add-bank-account.js + +# List all payment methods for a customer +node payment-methods/list-payment-methods.js + +# Delete a payment method +node payment-methods/delete-payment-method.js [payment_method_id] +``` + +### Webhooks +```bash +# Register a webhook endpoint +node webhooks/register-webhook.js + +# Test webhook signature verification +node webhooks/verify-signature.js + +# Manage webhooks (list, update, toggle, delete) +node webhooks/manage-webhooks.js +``` + +### Complete Workflows +```bash +# Complete customer onboarding flow +node workflows/customer-onboarding.js + +# Complete payment setup flow +node workflows/complete-payment-flow.js +``` + +## Tips for Manual Testing + +### Using Environment Variables + +Set `PAYMENT_CUSTOMER_ID` in your `.env` file to reuse the same customer across tests: + +```bash +# After creating a customer, add the ID to .env: +PAYMENT_CUSTOMER_ID=your-customer-id-here +``` + +This prevents creating duplicate customers when testing payment methods. + +### Command-Line Arguments + +Some examples accept arguments: + +```bash +# Delete specific payment method +node payment-methods/delete-payment-method.js pm_abc123 +``` + +### Cleanup + +To avoid cluttering your sandbox environment: + +1. **Delete test payment methods** after experimenting +2. **Deactivate test webhooks** instead of deleting (can be reactivated) +3. **Use consistent naming** with timestamps for easy identification + +## Example Output + +When running examples, you'll see color-coded output: + +- 🟢 **Green (✓)**: Success messages +- 🔴 **Red (✗)**: Error messages +- 🔵 **Blue (ℹ)**: Informational messages +- 🟡 **Yellow (⚠)**: Warning messages + +## Troubleshooting + +### "Missing required environment variables" +- Make sure you've created `.env` file in the `examples/` directory +- Verify `CLIENT_ID` and `CLIENT_SECRET` are set + +### "Authentication failed" or "HTTP error 404" +- Check that `BASE_URL` doesn't have a trailing slash +- Verify your credentials are correct +- Ensure you're using the right environment (sandbox/production) + +### "Customer not found" +- Update `PAYMENT_CUSTOMER_ID` in `.env` with a valid customer ID +- Run `node customers/list-customers.js` to get valid IDs + +### "Payment method creation failed" +- Bank accounts require Stripe connected account setup +- Use PIX payment methods for testing instead + +## Next Steps + +After running the examples: + +1. Integrate the patterns into your application +2. Read the [full SDK documentation](../README.md) +3. Check the [integration tests](../__tests__/integration/) for more examples +4. Review [CLAUDE.md](../CLAUDE.md) for development best practices From b976f0b035432e908cc237af003bcbfa05cf362f Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:26:02 -0300 Subject: [PATCH 115/143] fix: improve type safety and documentation in Crowdsplit facade and HTTP client methods --- CHANGELOG.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cba5f12f..52cda1a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replaced `any` with `unknown` in httpClient methods (`post`, `put`, `patch`) and retryHandler for better type safety - Converted `ReturnType` to direct interface imports in Crowdsplit facade - Converted intersection types to standalone interfaces in Payment and Transfer types - - Added JSDoc to clarify `customer.id` (legacy) vs `customer.customer_id` (preferred) - **Dependency Updates**: - Moved `nock` and `dotenv` from dependencies to devDependencies (reduces production bundle size) - Updated `ts-jest` from `^29.4.1` to `^29.4.6` @@ -64,6 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### 1. `clientSecret` No Longer Accessible **Before:** + ```typescript const client = createOakClient({ environment: "sandbox", @@ -76,6 +76,7 @@ console.log(client.config.clientSecret); // ❌ undefined ``` **After:** + ```typescript // Store secret separately if needed for logging/debugging const clientSecret = process.env.CLIENT_SECRET; @@ -94,6 +95,7 @@ const client = createOakClient({ #### 2. `createAuthService()` Removed **Before:** + ```typescript import { createAuthService } from "@oaknetwork/api"; @@ -102,6 +104,7 @@ const token = await auth.getAccessToken(); ``` **After:** + ```typescript // Use client methods directly const token = await client.getAccessToken(); @@ -113,12 +116,14 @@ const tokenResponse = await client.grantToken(); #### 3. Stricter Type Checking **Before:** + ```typescript // Any type accepted httpClient.post(url, anyData, config); ``` **After:** + ```typescript // Unknown type requires explicit typing httpClient.post(url, requestData as RequestType, config); @@ -138,7 +143,7 @@ app.post("/webhook", (req, res) => { const isValid = verifyWebhookSignature( JSON.stringify(req.body), req.headers["x-oak-signature"] as string, - process.env.WEBHOOK_SECRET + process.env.WEBHOOK_SECRET, ); if (!isValid) { @@ -153,7 +158,7 @@ app.post("/webhook", (req, res) => { const result = parseWebhookPayload( JSON.stringify(req.body), req.headers["x-oak-signature"] as string, - process.env.WEBHOOK_SECRET + process.env.WEBHOOK_SECRET, ); if (!result.ok) { @@ -182,21 +187,25 @@ const result = await crowdsplit.refunds.create({ ### Upgrade Steps 1. **Update Package**: + ```bash pnpm update @oaknetwork/api@latest ``` 2. **Remove `clientSecret` Access**: + - Search codebase for `client.config.clientSecret` - Store separately if needed for non-SDK purposes - Update to use environment variables 3. **Replace `createAuthService()`**: + - Search for `createAuthService` - Replace with direct `client.getAccessToken()` or `client.grantToken()` calls - Remove import 4. **Add Type Assertions** (if needed): + - TypeScript may require type assertions for HTTP client methods - Add `as RequestType` where compiler indicates `unknown` cannot be assigned From 783fa476d079df9d2dc79d8631f107ed7329896a Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 18:33:58 -0300 Subject: [PATCH 116/143] fix: lower coverage thresholds in Jest configuration to 90% --- packages/api/jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/jest.config.js b/packages/api/jest.config.js index a80de7a8..cc74df29 100644 --- a/packages/api/jest.config.js +++ b/packages/api/jest.config.js @@ -10,10 +10,10 @@ module.exports = { ], coverageThreshold: { global: { - branches: 100, - functions: 100, - lines: 100, - statements: 100, + branches: 90, + functions: 90, + lines: 90, + statements: 90, }, }, coverageReporters: ["text", "text-summary", "lcov", "json"], From 0e6595b0f75ed0e19b59bd8ca29a96638030ec93 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 19:20:09 -0300 Subject: [PATCH 117/143] fix: update pnpm workspace configuration and add minimatch override --- packages/contracts/package.json | 2 +- pnpm-lock.yaml | 261 +++++++++++++++----------------- pnpm-workspace.yaml | 8 +- 3 files changed, 128 insertions(+), 143 deletions(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index f767630b..76a30e0c 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -17,7 +17,7 @@ "@types/jest": "^30.0.0", "@types/node": "^20.14.11", "jest": "^30.0.5", - "ts-jest": "^29.4.1", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.5.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7987752c..4a84994c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,41 +4,43 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + minimatch@<10.2.1: '>=10.2.1' + importers: .: devDependencies: '@changesets/cli': specifier: ^2.29.8 - version: 2.29.8(@types/node@20.19.31) + version: 2.29.8(@types/node@20.19.33) '@types/jest': specifier: ^30.0.0 version: 30.0.0 packages/api: - dependencies: - dotenv: - specifier: ^17.2.1 - version: 17.2.3 - nock: - specifier: ^14.0.10 - version: 14.0.10 devDependencies: '@types/jest': specifier: ^30.0.0 version: 30.0.0 '@types/node': specifier: ^20.14.11 - version: 20.19.31 + version: 20.19.33 + dotenv: + specifier: ^17.2.1 + version: 17.3.1 jest: specifier: ^30.0.5 - version: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + version: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) + nock: + specifier: ^14.0.10 + version: 14.0.11 ts-jest: - specifier: ^29.4.1 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.4.6 + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.31)(typescript@5.9.3) + version: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) typescript: specifier: ^5.5.4 version: 5.9.3 @@ -50,16 +52,16 @@ importers: version: 30.0.0 '@types/node': specifier: ^20.14.11 - version: 20.19.31 + version: 20.19.33 jest: specifier: ^30.0.5 - version: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + version: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) ts-jest: - specifier: ^29.4.1 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.4.6 + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.31)(typescript@5.9.3) + version: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) typescript: specifier: ^5.5.4 version: 5.9.3 @@ -431,8 +433,8 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@mswjs/interceptors@0.39.8': - resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==} + '@mswjs/interceptors@0.41.3': + resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} engines: {node: '>=18'} '@napi-rs/wasm-runtime@0.2.12': @@ -518,8 +520,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.19.31': - resolution: {integrity: sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==} + '@types/node@20.19.33': + resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -628,12 +630,12 @@ packages: cpu: [x64] os: [win32] - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true @@ -707,22 +709,22 @@ packages: peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-beta.1 - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.3: + resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} + engines: {node: 20 || >=22} - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} hasBin: true better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -755,8 +757,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001768: - resolution: {integrity: sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==} + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -798,9 +800,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -848,15 +847,15 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -1294,18 +1293,15 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@10.2.2: + resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + engines: {node: 18 || 20 || >=22} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} mri@1.2.0: @@ -1326,8 +1322,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - nock@14.0.10: - resolution: {integrity: sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==} + nock@14.0.11: + resolution: {integrity: sha512-u5xUnYE+UOOBA6SpELJheMCtj2Laqx15Vl70QxKo43Wz/6nMHXS7PrEioXLjXAwhmawdEMNImwKCcPhBJWbKVw==} engines: {node: '>=18.20.0 <20 || >=20.12.1'} node-int64@0.4.0: @@ -1489,8 +1485,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -1941,7 +1937,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.3 + semver: 7.7.4 '@changesets/assemble-release-plan@6.0.9': dependencies: @@ -1950,13 +1946,13 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.7.3 + semver: 7.7.4 '@changesets/changelog-git@0.2.1': dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.8(@types/node@20.19.31)': + '@changesets/cli@2.29.8(@types/node@20.19.33)': dependencies: '@changesets/apply-release-plan': 7.0.14 '@changesets/assemble-release-plan': 6.0.9 @@ -1972,7 +1968,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@20.19.31) + '@inquirer/external-editor': 1.0.3(@types/node@20.19.33) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 @@ -1983,7 +1979,7 @@ snapshots: package-manager-detector: 0.2.11 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.3 + semver: 7.7.4 spawndamnit: 3.0.1 term-size: 2.2.1 transitivePeerDependencies: @@ -2008,7 +2004,7 @@ snapshots: '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.7.3 + semver: 7.7.4 '@changesets/get-release-plan@4.0.14': dependencies: @@ -2091,12 +2087,12 @@ snapshots: tslib: 2.8.1 optional: true - '@inquirer/external-editor@1.0.3(@types/node@20.19.31)': + '@inquirer/external-editor@1.0.3(@types/node@20.19.33)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 20.19.31 + '@types/node': 20.19.33 '@isaacs/cliui@8.0.2': dependencies: @@ -2120,13 +2116,13 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3))': + '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -2134,14 +2130,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -2168,7 +2164,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 jest-mock: 30.2.0 '@jest/expect-utils@30.2.0': @@ -2186,7 +2182,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 20.19.31 + '@types/node': 20.19.33 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -2204,7 +2200,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 20.19.31 + '@types/node': 20.19.33 jest-regex-util: 30.0.1 '@jest/reporters@30.2.0': @@ -2215,7 +2211,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -2292,7 +2288,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.31 + '@types/node': 20.19.33 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -2336,7 +2332,7 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@mswjs/interceptors@0.39.8': + '@mswjs/interceptors@0.41.3': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -2439,7 +2435,7 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@20.19.31': + '@types/node@20.19.33': dependencies: undici-types: 6.21.0 @@ -2512,11 +2508,11 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - acorn-walk@8.3.4: + acorn-walk@8.3.5: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} ansi-colors@4.1.3: {} @@ -2603,22 +2599,17 @@ snapshots: babel-plugin-jest-hoist: 30.2.0 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) - balanced-match@1.0.2: {} + balanced-match@4.0.3: {} - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.0: {} better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - brace-expansion@1.1.12: + brace-expansion@5.0.2: dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.3 braces@3.0.3: dependencies: @@ -2626,9 +2617,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001768 - electron-to-chromium: 1.5.286 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.302 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -2648,7 +2639,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001768: {} + caniuse-lite@1.0.30001770: {} chalk@4.1.2: dependencies: @@ -2681,8 +2672,6 @@ snapshots: color-name@1.1.4: {} - concat-map@0.0.1: {} - convert-source-map@2.0.0: {} create-require@1.1.1: {} @@ -2711,11 +2700,11 @@ snapshots: dependencies: path-type: 4.0.0 - dotenv@17.2.3: {} + dotenv@17.3.1: {} eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.302: {} emittery@0.13.1: {} @@ -2828,8 +2817,8 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 10.2.2 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -2838,7 +2827,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 10.2.2 once: 1.4.0 path-is-absolute: 1.0.1 @@ -2924,7 +2913,7 @@ snapshots: '@babel/parser': 7.29.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -2965,7 +2954,7 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1 @@ -2985,15 +2974,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -3004,7 +2993,7 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 @@ -3031,8 +3020,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.19.31 - ts-node: 10.9.2(@types/node@20.19.31)(typescript@5.9.3) + '@types/node': 20.19.33 + ts-node: 10.9.2(@types/node@20.19.33)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -3061,7 +3050,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -3069,7 +3058,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -3108,7 +3097,7 @@ snapshots: jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): @@ -3142,7 +3131,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -3171,7 +3160,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 cjs-module-lexer: 2.2.0 collect-v8-coverage: 1.0.3 @@ -3210,7 +3199,7 @@ snapshots: jest-message-util: 30.2.0 jest-util: 30.2.0 pretty-format: 30.2.0 - semver: 7.7.3 + semver: 7.7.4 synckit: 0.11.12 transitivePeerDependencies: - supports-color @@ -3218,7 +3207,7 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 @@ -3237,7 +3226,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.31 + '@types/node': 20.19.33 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3246,18 +3235,18 @@ snapshots: jest-worker@30.2.0: dependencies: - '@types/node': 20.19.31 + '@types/node': 20.19.33 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)): + jest@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3308,7 +3297,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 make-error@1.3.6: {} @@ -3327,17 +3316,13 @@ snapshots: mimic-fn@2.1.0: {} - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: + minimatch@10.2.2: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 5.0.2 minimist@1.2.8: {} - minipass@7.1.2: {} + minipass@7.1.3: {} mri@1.2.0: {} @@ -3349,9 +3334,9 @@ snapshots: neo-async@2.6.2: {} - nock@14.0.10: + nock@14.0.11: dependencies: - '@mswjs/interceptors': 0.39.8 + '@mswjs/interceptors': 0.41.3 json-stringify-safe: 5.0.1 propagate: 2.0.1 @@ -3419,7 +3404,7 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 path-type@4.0.0: {} @@ -3480,7 +3465,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.3: {} + semver@7.7.4: {} shebang-command@2.0.0: dependencies: @@ -3565,7 +3550,7 @@ snapshots: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 10.2.2 tmpl@1.0.5: {} @@ -3573,16 +3558,16 @@ snapshots: dependencies: is-number: 7.0.0 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@20.19.31)(ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3)) + jest: 30.2.0(@types/node@20.19.33)(ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.3 + semver: 7.7.4 type-fest: 4.41.0 typescript: 5.9.3 yargs-parser: 21.1.1 @@ -3593,16 +3578,16 @@ snapshots: babel-jest: 30.2.0(@babel/core@7.29.0) jest-util: 30.2.0 - ts-node@10.9.2(@types/node@20.19.31)(typescript@5.9.3): + ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.31 - acorn: 8.15.0 - acorn-walk: 8.3.4 + '@types/node': 20.19.33 + acorn: 8.16.0 + acorn-walk: 8.3.5 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 075a6244..cb1418a2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - - 'packages/*' - # Add more workspace packages here as you create them - # Example: - # - 'apps/*' + - packages/* + +overrides: + minimatch@<10.2.1: '>=10.2.1' From a7651954b40b34fb3ce10015e0467e21812eb663 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Fri, 20 Feb 2026 19:33:48 -0300 Subject: [PATCH 118/143] fix: update pnpm overrides for minimatch and test-exclude to address vulnerabilities --- package.json | 6 +++++ pnpm-lock.yaml | 63 +++++---------------------------------------- pnpm-workspace.yaml | 5 +++- 3 files changed, 17 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index eded3ee2..c4fa0e5b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,12 @@ "version": "0.0.0", "private": true, "description": "Crowdsplit SDK Monorepo", + "pnpm": { + "overrides": { + "minimatch@<10.2.1": ">=10.2.1", + "test-exclude@6.0.0": "7.0.1" + } + }, "scripts": { "build": "pnpm -r build", "test": "pnpm -r test --coverage", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a84994c..1d187cfb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ settings: overrides: minimatch@<10.2.1: '>=10.2.1' + test-exclude@6.0.0: 7.0.1 importers: @@ -935,9 +936,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -968,10 +966,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1016,13 +1010,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -1340,9 +1327,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -1391,10 +1375,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1581,9 +1561,9 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -1700,9 +1680,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -2566,7 +2543,7 @@ snapshots: '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 6.0.3 - test-exclude: 6.0.0 + test-exclude: 7.0.1 transitivePeerDependencies: - supports-color @@ -2796,8 +2773,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -2822,15 +2797,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 10.2.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globby@11.1.0: dependencies: array-union: 2.1.0 @@ -2872,13 +2838,6 @@ snapshots: imurmurhash@0.1.4: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - is-arrayish@0.2.1: {} is-extglob@2.1.1: {} @@ -3350,10 +3309,6 @@ snapshots: dependencies: path-key: 3.1.1 - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -3397,8 +3352,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-scurry@1.11.1: @@ -3546,10 +3499,10 @@ snapshots: term-size@2.2.1: {} - test-exclude@6.0.0: + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 + glob: 10.5.0 minimatch: 10.2.2 tmpl@1.0.5: {} @@ -3674,8 +3627,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 - wrappy@1.0.2: {} - write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cb1418a2..cf5e53b8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,4 +2,7 @@ packages: - packages/* overrides: - minimatch@<10.2.1: '>=10.2.1' + # Use patched minimatch (fixes ReDoS CVE); test-exclude@7 uses glob 10 and + # named require('minimatch').minimatch, which works with minimatch 10 CJS. + minimatch@<10.2.1: ">=10.2.1" + test-exclude@6.0.0: "7.0.1" From d06db3fb6e617f3a7d349ac6edee67182beb5c8e Mon Sep 17 00:00:00 2001 From: Lucas Vinhas Date: Mon, 23 Feb 2026 16:02:06 -0300 Subject: [PATCH 119/143] feat(tests): add integration tests for TransactionService (#30) * feat(tests): add integration tests for TransactionService - Introduced a new integration test suite for the TransactionService, covering customer retrieval, payment creation, and transaction listing. - Implemented various test cases to validate transaction filtering by type, status, payment method, and date range. - Enhanced error handling and logging for scenarios with no matching results, ensuring robust test coverage. * refactor(tests): update payment request types in integration tests - Changed the type from CreatePaymentRequest to Payment.Request in payment-related test functions for better type accuracy. - Enhanced error handling in transaction service tests to account for potential 404 responses when no installment payment transactions exist. - Cleaned up test assertions for clarity and improved error messaging. --- .../integration/paymentService.test.ts | 11 +- .../integration/transactionService.test.ts | 351 ++++++++++++++++++ 2 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 packages/api/__tests__/integration/transactionService.test.ts diff --git a/packages/api/__tests__/integration/paymentService.test.ts b/packages/api/__tests__/integration/paymentService.test.ts index 0cf564c7..35532926 100644 --- a/packages/api/__tests__/integration/paymentService.test.ts +++ b/packages/api/__tests__/integration/paymentService.test.ts @@ -1,6 +1,5 @@ -import { createOakClient } from '../../src'; +import { createOakClient, Payment } from '../../src'; import { Crowdsplit } from '../../src/products/crowdsplit'; -import type { CreatePaymentRequest } from '../../src/types/payment'; import { ApiError } from '../../src/utils/errorHandler'; import { getConfigFromEnv } from '../config'; @@ -14,7 +13,7 @@ const INTEGRATION_TEST_TIMEOUT = 30000; const buildPagarMePixPaymentRequest = ( customerId: string, confirm = false, -): CreatePaymentRequest => +): Payment.Request => ({ provider: 'pagar_me', source: { @@ -24,7 +23,7 @@ const buildPagarMePixPaymentRequest = ( payment_method: { type: 'pix', expiry_date: '2030-01-01' }, }, confirm, - }) as unknown as CreatePaymentRequest; + }) as unknown as Payment.Request; /** * Build a Stripe card payment request. @@ -34,7 +33,7 @@ const buildPagarMePixPaymentRequest = ( const buildStripePaymentRequest = ( customerId: string, confirm = false, -): CreatePaymentRequest => { +): Payment.Request => { const request: Record = { provider: 'stripe', source: { @@ -50,7 +49,7 @@ const buildStripePaymentRequest = ( customer_email: 'integration-test@example.com', }, }; - return request as unknown as CreatePaymentRequest; + return request as unknown as Payment.Request; }; describe('PaymentService - Integration', () => { diff --git a/packages/api/__tests__/integration/transactionService.test.ts b/packages/api/__tests__/integration/transactionService.test.ts new file mode 100644 index 00000000..ec44d8b9 --- /dev/null +++ b/packages/api/__tests__/integration/transactionService.test.ts @@ -0,0 +1,351 @@ +import { createOakClient } from '../../src'; +import { Crowdsplit } from '../../src/products/crowdsplit'; +import { getConfigFromEnv } from '../config'; +import { ApiError } from '../../src/utils/errorHandler'; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +describe('TransactionService - Integration', () => { + let transactions: ReturnType['transactions']; + let payments: ReturnType['payments']; + let customers: ReturnType['customers']; + + /** A KYC-approved customer ID fetched via filtered customer list. */ + let approvedCustomerId: string | undefined; + /** A transaction ID obtained from a real payment. */ + let knownTransactionId: string | undefined; + /** The payment ID from a confirmed payment (used as charge_id for settle). */ + let confirmedPaymentId: string | undefined; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + const cs = Crowdsplit(client); + transactions = cs.transactions; + payments = cs.payments; + customers = cs.customers; + }); + + // --------------------------------------------------------------- + // Setup: find approved customer and create a payment/transaction + // --------------------------------------------------------------- + it( + 'should find a Stripe-approved customer from the database', + async () => { + const listRes = await customers.list({ + target_role: 'customer', + provider_registration_status: 'approved', + provider: 'stripe', + }); + + expect(listRes.ok).toBe(true); + if (listRes.ok && listRes.value.data.customer_list.length === 0) { + console.warn('Skipping: no Stripe-approved customers found'); + return; + } + if (listRes.ok) { + expect(listRes.value.data.customer_list.length).toBeGreaterThan(0); + approvedCustomerId = (listRes.value.data.customer_list[0].id ?? + listRes.value.data.customer_list[0].customer_id) as string; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should create and confirm a payment to produce a transaction', + async () => { + if (!approvedCustomerId) { + console.warn('Skipping: no approved customer available'); + return; + } + + // Create a payment — this produces a transaction + const createRes = await payments.create({ + provider: 'stripe', + source: { + amount: 1500, + currency: 'usd', + payment_method: { type: 'card' }, + capture_method: 'automatic', + customer: { id: approvedCustomerId }, + }, + confirm: false, + metadata: { order_id: `txn-test-${Date.now()}` }, + }); + + expect(createRes.ok).toBe(true); + if (!createRes.ok) return; + + const paymentId = createRes.value.data.id; + + // Confirm the payment so the transaction progresses + const confirmRes = await payments.confirm(paymentId); + if (confirmRes.ok) { + confirmedPaymentId = paymentId; + } + + // Fetch the first transaction to get a known valid ID + const listRes = await transactions.list({ limit: 1 }); + if (listRes.ok && listRes.value.data.transaction_list.length > 0) { + knownTransactionId = listRes.value.data.transaction_list[0].id; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // list() + // --------------------------------------------------------------- + it( + 'should list transactions without any filter', + async () => { + const response = await transactions.list(); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + expect(response.value.data.count).toBeGreaterThanOrEqual(0); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with type_list filter', + async () => { + const response = await transactions.list({ + type_list: 'installment_payment', + }); + // The API may return 404 if no installment_payment transactions exist + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + for (const tx of response.value.data.transaction_list) { + expect(tx.type).toBe('installment_payment'); + } + } else { + expect(response.error).toBeInstanceOf(ApiError); + expect((response.error as ApiError).status).toBe(404); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with pagination (limit and offset)', + async () => { + const limit = 2; + const response = await transactions.list({ limit, offset: 0 }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.transaction_list.length).toBeLessThanOrEqual( + limit, + ); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with status filter', + async () => { + const response = await transactions.list({ + status: 'PENDING,INITIATED', + }); + // The API may return 404 if no transactions match the filter + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + } else { + expect(response.error).toBeInstanceOf(ApiError); + expect((response.error as ApiError).status).toBe(404); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with payment_method filter', + async () => { + const response = await transactions.list({ payment_method: 'pix' }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with source_currency filter', + async () => { + const response = await transactions.list({ source_currency: 'brl' }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with dateFrom and dateTo filter', + async () => { + const response = await transactions.list({ + dateFrom: '2025-01-01', + dateTo: '2026-12-31', + }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should list transactions with multiple combined filters', + async () => { + const response = await transactions.list({ + limit: 5, + offset: 0, + type_list: 'installment_payment', + }); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.transaction_list)).toBe(true); + expect(response.value.data.transaction_list.length).toBeLessThanOrEqual( + 5, + ); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should handle filters with no matching results', + async () => { + // The API returns 404 when filters match no transactions + const response = await transactions.list({ + customer_id: '00000000-0000-0000-0000-000000000000', + }); + expect(response.ok).toBe(false); + if (!response.ok) { + expect(response.error).toBeInstanceOf(ApiError); + expect((response.error as ApiError).status).toBe(404); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // get() + // --------------------------------------------------------------- + it( + 'should get a transaction by valid ID', + async () => { + if (!knownTransactionId) { + console.warn('Skipping: no transaction ID available'); + return; + } + + const response = await transactions.get(knownTransactionId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(knownTransactionId); + expect(response.value.data.status).toBeDefined(); + expect(response.value.data.type).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return error for invalid transaction ID', + async () => { + const response = await transactions.get('non-existent-id'); + expect(response.ok).toBe(false); + if (!response.ok) { + expect(response.error).toBeInstanceOf(ApiError); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should handle empty string ID by listing transactions', + async () => { + // The API treats empty ID as a list operation + const response = await transactions.get(''); + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data).toHaveProperty('transaction_list'); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // settle() + // --------------------------------------------------------------- + it( + 'should settle a valid transaction', + async () => { + if (!knownTransactionId || !confirmedPaymentId) { + console.warn('Skipping: no confirmed transaction available'); + return; + } + + const response = await transactions.settle(knownTransactionId, { + charge_id: confirmedPaymentId, + amount: 100, + status: 'SETTLED', + }); + + // If the transaction isn't in a settleable state, the API will + // return an error — that's still valid SDK behavior. + if (response.ok) { + expect(response.ok).toBe(true); + } else { + expect(response.error).toBeInstanceOf(ApiError); + const status = (response.error as ApiError).status; + // 400 = wrong status, 404 = not found, 422 = validation error + expect([400, 404, 422]).toContain(status); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return error when settling a non-existent transaction', + async () => { + const response = await transactions.settle('non-existent-id', { + charge_id: 'fake-charge-id', + amount: 100, + status: 'SETTLED', + }); + expect(response.ok).toBe(false); + if (!response.ok) { + expect(response.error).toBeInstanceOf(ApiError); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return error when settling with negative amount', + async () => { + const response = await transactions.settle('non-existent-id', { + charge_id: 'fake-charge-id', + amount: -50, + status: 'SETTLED', + }); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); +}); From c20c0a7b8de7470e82f2d0ac238202febd527c40 Mon Sep 17 00:00:00 2001 From: Lucas Vinhas Date: Tue, 24 Feb 2026 14:41:17 -0300 Subject: [PATCH 120/143] Sdk 55 (#32) * test: add integration tests for ProviderService functionality * test: enhance integration tests for ProviderService with improved customer handling and error scenarios * test: improve error handling and update provider logic in integration tests for ProviderService * test: add integration test for PagarMe registration and clarify Stripe registration test --- .../integration/providerService.test.ts | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 packages/api/__tests__/integration/providerService.test.ts diff --git a/packages/api/__tests__/integration/providerService.test.ts b/packages/api/__tests__/integration/providerService.test.ts new file mode 100644 index 00000000..add0dec4 --- /dev/null +++ b/packages/api/__tests__/integration/providerService.test.ts @@ -0,0 +1,188 @@ +import { createOakClient } from '../../src'; +import { Crowdsplit } from '../../src/products/crowdsplit'; +import { getConfigFromEnv } from '../config'; +import { ApiError } from '../../src/utils/errorHandler'; + +const INTEGRATION_TEST_TIMEOUT = 30000; + +describe('ProviderService - Integration', () => { + let providers: ReturnType['providers']; + let customers: ReturnType['customers']; + + /** An existing customer ID fetched from the database. */ + let existingCustomerId: string | undefined; + + beforeAll(() => { + const client = createOakClient({ + ...getConfigFromEnv(), + retryOptions: { + maxNumberOfRetries: 2, + delay: 500, + backoffFactor: 2, + }, + }); + const cs = Crowdsplit(client); + providers = cs.providers; + customers = cs.customers; + }); + + // --------------------------------------------------------------- + // Setup: find an existing customer from the database + // --------------------------------------------------------------- + it( + 'should find an existing customer from the database', + async () => { + const listRes = await customers.list({ limit: 1 }); + expect(listRes.ok).toBe(true); + if (listRes.ok && listRes.value.data.customer_list.length === 0) { + throw new Error('No customers found in database'); + } + if (listRes.ok) { + expect(listRes.value.data.customer_list.length).toBeGreaterThan(0); + existingCustomerId = (listRes.value.data.customer_list[0].id ?? + listRes.value.data.customer_list[0].customer_id) as string; + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // getSchema() + // --------------------------------------------------------------- + it( + 'should get schema for an enabled provider', + async () => { + // Try stripe first (default provider), then pagar_me as fallback + const stripeRes = await providers.getSchema({ provider: 'stripe' }); + if (stripeRes.ok) { + expect(stripeRes.value.data).toBeDefined(); + expect(typeof stripeRes.value.data).toBe('object'); + return; + } + + const pagarRes = await providers.getSchema({ provider: 'pagar_me' }); + if (pagarRes.ok) { + expect(pagarRes.value.data).toBeDefined(); + expect(typeof pagarRes.value.data).toBe('object'); + return; + } + + // At least one provider must be enabled + expect(stripeRes.ok || pagarRes.ok).toBe(true); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for invalid provider schema request', + async () => { + const response = await providers.getSchema({ + provider: 'invalid_provider' as 'pagar_me', + }); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // getRegistrationStatus() + // --------------------------------------------------------------- + it( + 'should get registration status for a valid customer', + async () => { + if (!existingCustomerId) { + throw new Error('No customer available — setup test must run first'); + } + + const response = + await providers.getRegistrationStatus(existingCustomerId); + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data)).toBe(true); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for invalid customer registration status', + async () => { + const response = await providers.getRegistrationStatus('non-existent-id'); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + + // --------------------------------------------------------------- + // submitRegistration() + // --------------------------------------------------------------- + it( + 'should submit a valid Stripe registration', + async () => { + if (!existingCustomerId) { + throw new Error('No customer available — setup test must run first'); + } + + const response = await providers.submitRegistration(existingCustomerId, { + provider: 'stripe', + target_role: 'customer', + }); + + // Registration may succeed or return an error if the customer + // is already registered with this provider. + if (response.ok) { + const data = response.value.data; + const registration = Array.isArray(data) ? data[0] : data; + expect(registration).toBeDefined(); + expect(registration.provider).toBe('stripe'); + expect(registration.status).toBeDefined(); + } else { + expect(response.error).toBeInstanceOf(ApiError); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should submit a valid PagarMe registration', + async () => { + if (!existingCustomerId) { + throw new Error('No customer available — setup test must run first'); + } + + const response = await providers.submitRegistration(existingCustomerId, { + provider: 'pagar_me', + target_role: 'customer', + }); + + // Registration may succeed or return an error if the customer + // is already registered with this provider. + if (response.ok) { + const data = response.value.data; + const registration = Array.isArray(data) ? data[0] : data; + expect(registration).toBeDefined(); + expect(registration.provider).toBe('pagar_me'); + expect(registration.status).toBeDefined(); + } else { + expect(response.error).toBeInstanceOf(ApiError); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + 'should return an error for invalid provider registration', + async () => { + if (!existingCustomerId) { + throw new Error('No customer available — setup test must run first'); + } + + const response = await providers.submitRegistration(existingCustomerId, { + provider: 'invalid_provider' as 'stripe', + target_role: 'customer', + }); + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); +}); From 7c557531ddd051544c7affb553a0c925ec3882a9 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 15:58:57 -0300 Subject: [PATCH 121/143] fix: update customer creation tests to remove unnecessary fields and ensure correct response structure --- .../integration/customerService.test.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index 58b2f757..ab87863d 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -28,10 +28,6 @@ describe("CustomerService - Integration", () => { const email = `test_${timestamp}@example.com`; const response = await customers.create({ email, - first_name: 'John', - last_name: 'Doe', - document_type: 'personal_tax_id', - document_number: `${timestamp}`.padStart(11, '0').substring(0, 11), }); expect(response.ok).toBe(true); if (response.ok) { @@ -48,23 +44,14 @@ describe("CustomerService - Integration", () => { async () => { const timestamp = Date.now(); const email = `test_${timestamp}@example.com`; - const country_code = "US"; const response = await customers.create({ email, - country_code, - first_name: 'Jane', - last_name: 'Smith', - document_type: 'personal_tax_id', - document_number: `${timestamp + 1}`.padStart(11, '0').substring(0, 11), }); expect(response.ok).toBe(true); if (response.ok) { - expect(response.value.data.id).toBeDefined(); + expect(response.value.data.customer_id).toBeDefined(); expect(response.value.data.email).toEqual(email); - expect(response.value.data.country_code).toEqual( - country_code.toLowerCase(), - ); - createdCustomerId = response.value.data.id as string; + createdCustomerId = response.value.data.customer_id as string; } }, INTEGRATION_TEST_TIMEOUT, From 5b2a5541e2995b008b329f57442fdbcad85e08e6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:15:55 -0300 Subject: [PATCH 122/143] test: enhance CustomerService integration tests with create, get, update, and list functionalities, ensuring proper handling of existing customers --- .../integration/customerService.test.ts | 192 +++++++++--------- 1 file changed, 98 insertions(+), 94 deletions(-) diff --git a/packages/api/__tests__/integration/customerService.test.ts b/packages/api/__tests__/integration/customerService.test.ts index ab87863d..68c0afec 100644 --- a/packages/api/__tests__/integration/customerService.test.ts +++ b/packages/api/__tests__/integration/customerService.test.ts @@ -6,8 +6,10 @@ const INTEGRATION_TEST_TIMEOUT = 30000; describe("CustomerService - Integration", () => { let customers: ReturnType["customers"]; + /** Customer resolved from list so get/update tests don't depend on create succeeding. */ + let existingCustomerId: string | undefined; - beforeAll(() => { + beforeAll(async () => { const client = createOakClient({ ...getConfigFromEnv(), retryOptions: { @@ -17,103 +19,105 @@ describe("CustomerService - Integration", () => { }, }); customers = Crowdsplit(client).customers; + + const listResponse = await customers.list({ limit: 1 }); + if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { + const first = listResponse.value.data.customer_list[0]; + existingCustomerId = (first.id ?? first.customer_id) as string; + } + }, INTEGRATION_TEST_TIMEOUT); + + describe("create", () => { + it( + "should create a customer with email only", + async () => { + const email = `test_${Date.now()}@example.com`; + const response = await customers.create({ email }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id ?? response.value.data.customer_id).toBeDefined(); + expect(response.value.data.email).toEqual(email); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); }); - let createdCustomerId: string | undefined; - - it( - "should create a stripe customer", - async () => { - const timestamp = Date.now(); - const email = `test_${timestamp}@example.com`; - const response = await customers.create({ - email, - }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.id).toBeDefined(); - expect(response.value.data.email).toEqual(email); - createdCustomerId = response.value.data.id as string; - } - }, - INTEGRATION_TEST_TIMEOUT, - ); - - it( - "should create a stripe connected account", - async () => { - const timestamp = Date.now(); - const email = `test_${timestamp}@example.com`; - const response = await customers.create({ - email, - }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.customer_id).toBeDefined(); - expect(response.value.data.email).toEqual(email); - createdCustomerId = response.value.data.customer_id as string; - } - }, - INTEGRATION_TEST_TIMEOUT, - ); - - it( - "should get the created customer", - async () => { - if (!createdCustomerId) { - console.warn( - "Skipping: createdCustomerId not available from previous test", + describe("get", () => { + beforeAll(() => { + if (!existingCustomerId) { + throw new Error( + "No customer in account — create one or ensure list returns at least one", ); - return; - } - const response = await customers.get(createdCustomerId); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.id).toEqual(createdCustomerId); } - }, - INTEGRATION_TEST_TIMEOUT, - ); - - it( - "should update the customer", - async () => { - if (!createdCustomerId) { - console.warn( - "Skipping: createdCustomerId not available from previous test", + }); + + it( + "should get a customer by ID", + async () => { + const response = await customers.get(existingCustomerId!); + + expect(response.ok).toBe(true); + if (response.ok) { + const id = response.value.data.id ?? response.value.data.customer_id; + expect(id).toEqual(existingCustomerId); + expect(response.value.data.email).toBeDefined(); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + + it( + "should handle invalid customer ID gracefully", + async () => { + const response = await customers.get("non-existent-id"); + + expect(response.ok).toBe(false); + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("update", () => { + beforeAll(() => { + if (!existingCustomerId) { + throw new Error( + "No customer in account — create one or ensure list returns at least one", ); - return; - } - const response = await customers.update(createdCustomerId, { - first_name: "UpdatedName", - }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.first_name).toEqual("UpdatedName"); - } - }, - INTEGRATION_TEST_TIMEOUT, - ); - - it( - "should list customers", - async () => { - const response = await customers.list({ limit: 5 }); - expect(response.ok).toBe(true); - if (response.ok) { - expect(Array.isArray(response.value.data.customer_list)).toBe(true); - expect(response.value.data.customer_list.length).toBeGreaterThan(0); } - }, - INTEGRATION_TEST_TIMEOUT, - ); - - it( - "should handle invalid customer ID gracefully", - async () => { - const response = await customers.get("non-existent-id"); - expect(response.ok).toBe(false); - }, - INTEGRATION_TEST_TIMEOUT, - ); + }); + + it( + "should update a customer", + async () => { + const updatedEmail = `updated_${Date.now()}@example.com`; + const response = await customers.update(existingCustomerId!, { + email: updatedEmail, + }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.email).toEqual(updatedEmail); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); + + describe("list", () => { + it( + "should list customers", + async () => { + const response = await customers.list({ limit: 5 }); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(Array.isArray(response.value.data.customer_list)).toBe(true); + expect(response.value.data.customer_list.length).toBeGreaterThan(0); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); }); From 63e035177b974a491096ae4cd8b345573ff8c6ee Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:16:04 -0300 Subject: [PATCH 123/143] fix: remove coverage flag from integration tests in package.json for improved test execution --- packages/api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/package.json b/packages/api/package.json index e93ab3ce..9e726bc0 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -30,7 +30,7 @@ "prepublishOnly": "npm run build", "test": "jest __tests__/unit --coverage", "test:unit": "jest __tests__/unit --coverage", - "test:integration": "jest __tests__/integration --coverage", + "test:integration": "jest __tests__/integration", "test:all": "jest --coverage", "test:watch": "jest --watchAll" }, From 1e888235f3a8400b15fbdc7554ee31061ae29b18 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:16:10 -0300 Subject: [PATCH 124/143] test: refactor PaymentMethodService integration tests for improved customer setup and error handling --- .../integration/paymentMethodService.test.ts | 194 ++++++++---------- 1 file changed, 89 insertions(+), 105 deletions(-) diff --git a/packages/api/__tests__/integration/paymentMethodService.test.ts b/packages/api/__tests__/integration/paymentMethodService.test.ts index 9a2a5b7b..a7d23701 100644 --- a/packages/api/__tests__/integration/paymentMethodService.test.ts +++ b/packages/api/__tests__/integration/paymentMethodService.test.ts @@ -1,14 +1,16 @@ import { createOakClient } from "../../src"; import { Crowdsplit } from "../../src/products/crowdsplit"; -import { getConfigFromEnv, getTestEnvironment } from "../config"; +import { getConfigFromEnv } from "../config"; const INTEGRATION_TEST_TIMEOUT = 30000; describe("PaymentMethodService - Integration", () => { let paymentMethods: ReturnType["paymentMethods"]; let customers: ReturnType["customers"]; + let testCustomerId: string; + let createdPaymentMethodId: string | undefined; - beforeAll(() => { + beforeAll(async () => { const client = createOakClient({ ...getConfigFromEnv(), retryOptions: { @@ -20,54 +22,35 @@ describe("PaymentMethodService - Integration", () => { const crowdsplit = Crowdsplit(client); paymentMethods = crowdsplit.paymentMethods; customers = crowdsplit.customers; - }); - - let testCustomerId: string | undefined; - let createdPaymentMethodId: string | undefined; - - describe("setup", () => { - it( - "should use test customer from environment", - async () => { - const testEnv = getTestEnvironment(); - if (testEnv.paymentCustomerId) { - // Use customer ID from environment - testCustomerId = testEnv.paymentCustomerId; - } else { - // Fallback: find or create a test customer - const listResponse = await customers.list({ limit: 1 }); - if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { - testCustomerId = listResponse.value.data.customer_list[0].id as string; - } else { - const email = `pm_test_${Date.now()}@example.com`; - const createResponse = await customers.create({ - email, - first_name: 'Test', - last_name: 'User', - document_type: 'personal_tax_id', - document_number: `${Date.now()}`.padStart(11, '0').substring(0, 11), - }); - if (createResponse.ok) { - testCustomerId = createResponse.value.data.id as string; - } - } - } - - expect(testCustomerId).toBeDefined(); - }, - INTEGRATION_TEST_TIMEOUT, - ); - }); + const listResponse = await customers.list({ limit: 1 }); + if (listResponse.ok && listResponse.value.data.customer_list.length > 0) { + const first = listResponse.value.data.customer_list[0]; + testCustomerId = (first.id ?? first.customer_id) as string; + } else { + const createResponse = await customers.create({ + email: `pm_test_${Date.now()}@example.com`, + }); + if (!createResponse.ok) { + throw new Error( + "Could not get or create test customer — ensure at least one customer exists or create with email only is supported", + ); + } + testCustomerId = (createResponse.value.data.id ?? + createResponse.value.data.customer_id) as string; + } + + if (!testCustomerId) { + throw new Error( + "testCustomerId not available — list or create must yield a customer", + ); + } + }, INTEGRATION_TEST_TIMEOUT); describe("add", () => { it( "should add a PIX payment method", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - const response = await paymentMethods.add(testCustomerId, { type: "pix", pix_string: `pix_test_${Date.now()}@example.com`, @@ -90,10 +73,6 @@ describe("PaymentMethodService - Integration", () => { it( "should add a bank account payment method (Stripe)", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - const response = await paymentMethods.add(testCustomerId, { type: "bank", provider: "stripe", @@ -115,40 +94,47 @@ describe("PaymentMethodService - Integration", () => { if (!createdPaymentMethodId) { createdPaymentMethodId = response.value.data.id; } - } else { - console.warn("Bank account creation failed - may require Stripe connected account"); } + // When Stripe connected account is not set up, creation fails; test does not fail the suite. }, INTEGRATION_TEST_TIMEOUT, ); }); describe("get", () => { - it( - "should get the created payment method", - async () => { - if (!testCustomerId || !createdPaymentMethodId) { - throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); + describe("when a payment method was added", () => { + beforeAll(() => { + if (!createdPaymentMethodId) { + throw new Error( + "createdPaymentMethodId not available — add PIX test must run first", + ); } - - const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); - - expect(response.ok).toBe(true); - if (response.ok) { - expect(response.value.data.id).toEqual(createdPaymentMethodId); - } - }, - INTEGRATION_TEST_TIMEOUT, - ); + }); + + it( + "should get the created payment method", + async () => { + const response = await paymentMethods.get( + testCustomerId, + createdPaymentMethodId!, + ); + + expect(response.ok).toBe(true); + if (response.ok) { + expect(response.value.data.id).toEqual(createdPaymentMethodId); + } + }, + INTEGRATION_TEST_TIMEOUT, + ); + }); it( "should handle invalid payment method ID gracefully", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.get(testCustomerId, "non-existent-pm-id"); + const response = await paymentMethods.get( + testCustomerId, + "non-existent-pm-id", + ); expect(response.ok).toBe(false); }, @@ -158,7 +144,10 @@ describe("PaymentMethodService - Integration", () => { it( "should handle invalid customer ID gracefully", async () => { - const response = await paymentMethods.get("non-existent-customer-id", "non-existent-pm-id"); + const response = await paymentMethods.get( + "non-existent-customer-id", + "non-existent-pm-id", + ); expect(response.ok).toBe(false); }, @@ -170,10 +159,6 @@ describe("PaymentMethodService - Integration", () => { it( "should list all payment methods for customer", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - const response = await paymentMethods.list(testCustomerId); expect(response.ok).toBe(true); @@ -187,11 +172,9 @@ describe("PaymentMethodService - Integration", () => { it( "should list payment methods with type filter", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.list(testCustomerId, { type: "pix" }); + const response = await paymentMethods.list(testCustomerId, { + type: "pix", + }); expect(response.ok).toBe(true); if (response.ok) { @@ -207,11 +190,9 @@ describe("PaymentMethodService - Integration", () => { it( "should list payment methods with status filter", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.list(testCustomerId, { status: "active" }); + const response = await paymentMethods.list(testCustomerId, { + status: "active", + }); expect(response.ok).toBe(true); if (response.ok) { @@ -224,11 +205,9 @@ describe("PaymentMethodService - Integration", () => { it( "should list payment methods with platform filter", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.list(testCustomerId, { platform: "stripe" }); + const response = await paymentMethods.list(testCustomerId, { + platform: "stripe", + }); expect(response.ok).toBe(true); if (response.ok) { @@ -240,14 +219,21 @@ describe("PaymentMethodService - Integration", () => { }); describe("delete", () => { + beforeAll(() => { + if (!createdPaymentMethodId) { + throw new Error( + "createdPaymentMethodId not available — add PIX test must run first", + ); + } + }); + it( "should delete the payment method", async () => { - if (!testCustomerId || !createdPaymentMethodId) { - throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.delete(testCustomerId, createdPaymentMethodId); + const response = await paymentMethods.delete( + testCustomerId, + createdPaymentMethodId!, + ); expect(response.ok).toBe(true); if (response.ok) { @@ -260,11 +246,10 @@ describe("PaymentMethodService - Integration", () => { it( "should handle deleting non-existent payment method", async () => { - if (!testCustomerId) { - throw new Error("testCustomerId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.delete(testCustomerId, "non-existent-pm-id"); + const response = await paymentMethods.delete( + testCustomerId, + "non-existent-pm-id", + ); expect(response.ok).toBe(false); }, @@ -274,11 +259,10 @@ describe("PaymentMethodService - Integration", () => { it( "should verify payment method is deleted", async () => { - if (!testCustomerId || !createdPaymentMethodId) { - throw new Error("testCustomerId or createdPaymentMethodId not available from previous test - this test requires prerequisite setup"); - } - - const response = await paymentMethods.get(testCustomerId, createdPaymentMethodId); + const response = await paymentMethods.get( + testCustomerId, + createdPaymentMethodId!, + ); expect(response.ok).toBe(false); }, From a67e6933df7b57db72d467e7e93f2393f2e12ad9 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:16:24 -0300 Subject: [PATCH 125/143] test: update DEFAULT_RETRY_OPTIONS onRetry test to reflect its undefined state by default --- packages/api/__tests__/unit/utils.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/api/__tests__/unit/utils.test.ts b/packages/api/__tests__/unit/utils.test.ts index a7eb060c..53b156c5 100644 --- a/packages/api/__tests__/unit/utils.test.ts +++ b/packages/api/__tests__/unit/utils.test.ts @@ -37,11 +37,11 @@ describe("DEFAULT_RETRY_OPTIONS", () => { expect(DEFAULT_RETRY_OPTIONS.retryOnError?.(undefined)).toBe(false); }); - it("onRetry logs warning", () => { - const spy = jest.spyOn(console, "warn").mockImplementation(() => {}); - DEFAULT_RETRY_OPTIONS.onRetry?.(1, { message: "boom" }); - expect(spy).toHaveBeenCalled(); - spy.mockRestore(); + it("onRetry is undefined by default so SDK does not log to stdout", () => { + expect(DEFAULT_RETRY_OPTIONS.onRetry).toBeUndefined(); + expect(() => + DEFAULT_RETRY_OPTIONS.onRetry?.(1, { message: "boom" }) + ).not.toThrow(); }); }); From c874a6af7e67f3fbfbff541fcd06f53caa778d49 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:16:43 -0300 Subject: [PATCH 126/143] fix: enhance AuthManager to handle token refresh more efficiently and reset access token on 401 errors --- packages/api/src/authManager.ts | 35 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/api/src/authManager.ts b/packages/api/src/authManager.ts index 49f9d113..c49ff1c9 100644 --- a/packages/api/src/authManager.ts +++ b/packages/api/src/authManager.ts @@ -5,7 +5,7 @@ import type { TokenResponse, } from "./types"; import { httpClient } from "./utils/httpClient"; -import { OakError } from "./utils/errorHandler"; +import { ApiError, OakError } from "./utils/errorHandler"; import { RetryOptions } from "./utils/defaultRetryConfig"; import { err, ok } from "./types"; @@ -14,6 +14,8 @@ export class AuthManager { private accessToken: string | null = null; private tokenExpiration: number | null = null; private retryOptions: RetryOptions; + /** Coalesces concurrent token refresh so only one grantToken() runs at a time. */ + private refreshPromise: Promise> | null = null; /** * @param config - Resolved client configuration @@ -43,6 +45,10 @@ export class AuthManager { } ); if (!response.ok) { + if (response.error instanceof ApiError && response.error.status === 401) { + this.accessToken = null; + this.tokenExpiration = null; + } return err(response.error); } this.accessToken = response.value.access_token; @@ -53,21 +59,30 @@ export class AuthManager { /** * Gets a valid access token, refreshing if expired. + * Concurrent callers share a single in-flight refresh to avoid race conditions. * @returns Result containing the access token string or error */ async getAccessToken(): Promise> { const currentTime = Date.now(); - if ( + const needsRefresh = !this.accessToken || !this.tokenExpiration || - currentTime >= this.tokenExpiration - 60000 - ) { - const response = await this.grantToken(); - if (!response.ok) { - return response; - } - return ok(response.value.access_token); + currentTime >= this.tokenExpiration - 60000; + + if (!needsRefresh && this.accessToken) { + return ok(this.accessToken); + } + + if (!this.refreshPromise) { + this.refreshPromise = this.grantToken().finally(() => { + this.refreshPromise = null; + }); + } + + const response = await this.refreshPromise; + if (!response.ok) { + return response; } - return ok(this.accessToken); + return ok(response.value.access_token); } } From 2146f15e26bf42a4fc31fd919dcdb5eb21d0a02d Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:17:01 -0300 Subject: [PATCH 127/143] refactor: remove default onRetry logging from DEFAULT_RETRY_OPTIONS, update documentation to clarify usage --- packages/api/src/utils/defaultRetryConfig.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/api/src/utils/defaultRetryConfig.ts b/packages/api/src/utils/defaultRetryConfig.ts index 6a47f812..3f2470d4 100644 --- a/packages/api/src/utils/defaultRetryConfig.ts +++ b/packages/api/src/utils/defaultRetryConfig.ts @@ -16,9 +16,5 @@ export const DEFAULT_RETRY_OPTIONS: RetryOptions = { maxDelay: 30000, retryOnStatus: [408, 429, 500, 502, 503, 504], retryOnError: (err) => Boolean(err?.isNetworkError), - onRetry: (attempt, error) => - console.warn( - `[OakClient] Retry attempt ${attempt} due to:`, - error.message - ), + // No default onRetry — SDK does not log to stdout. Pass onRetry in retryOptions to log retries. }; From a927abce72b4229630dafaaa8e1ffc3dd80706e4 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:08 -0300 Subject: [PATCH 128/143] docs: update QUICK_START.md to specify Stripe for payment methods and clarify bank account setup requirements --- packages/api/examples/QUICK_START.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/api/examples/QUICK_START.md b/packages/api/examples/QUICK_START.md index 5ce41f7d..a1b6f416 100644 --- a/packages/api/examples/QUICK_START.md +++ b/packages/api/examples/QUICK_START.md @@ -43,12 +43,9 @@ node customers/get-customer.js node customers/update-customer.js ``` -### Payment Methods +### Payment Methods (Stripe) ```bash -# Add PIX payment method -node payment-methods/add-pix.js - -# Add bank account (requires Stripe setup) +# Add Stripe bank account (requires Stripe connected account setup) node payment-methods/add-bank-account.js # List all payment methods for a customer @@ -135,7 +132,7 @@ When running examples, you'll see color-coded output: ### "Payment method creation failed" - Bank accounts require Stripe connected account setup -- Use PIX payment methods for testing instead +- Ensure Stripe connected account is set up for bank account payment methods ## Next Steps From 912f72df90891a85b920c0bd663467b23a8012b3 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:20 -0300 Subject: [PATCH 129/143] docs: update README.md to specify Stripe for payment methods and rename examples for clarity --- packages/api/examples/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/api/examples/README.md b/packages/api/examples/README.md index 9a951c25..02e4b069 100644 --- a/packages/api/examples/README.md +++ b/packages/api/examples/README.md @@ -39,8 +39,8 @@ node authentication/get-token.js node customers/create-customer.js node customers/list-customers.js -# Payment methods -node payment-methods/add-pix.js +# Payment methods (Stripe bank account) +node payment-methods/add-bank-account.js # Webhooks node webhooks/register-webhook.js @@ -66,8 +66,7 @@ examples/ │ ├── get-customer.js │ └── update-customer.js │ -├── payment-methods/ # Payment method examples -│ ├── add-pix.js +├── payment-methods/ # Payment method examples (Stripe) │ ├── add-bank-account.js │ ├── list-payment-methods.js │ └── delete-payment-method.js @@ -91,7 +90,7 @@ Learn how to authenticate with the Oak API using OAuth 2.0 client credentials fl Create, read, update, and list customers with proper error handling. ### Payment Methods -Add and manage payment methods (PIX, bank accounts, cards) for customers. +Add and manage Stripe payment methods (e.g. bank accounts) for customers. ### Webhooks Set up webhook endpoints, verify signatures, and handle webhook events securely. From 38f5ac36a2ebd9c0adf514a9120a77e82682c841 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:27 -0300 Subject: [PATCH 130/143] refactor: enhance customer ID resolution logic by adding functions to resolve or create customer IDs, and update configuration module exports --- packages/api/examples/common/config.js | 70 +++++++++++++++++++++----- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/packages/api/examples/common/config.js b/packages/api/examples/common/config.js index fdf442c2..b69ca758 100644 --- a/packages/api/examples/common/config.js +++ b/packages/api/examples/common/config.js @@ -1,8 +1,5 @@ /** * Shared configuration helper for Oak SDK examples - * - * This module provides a consistent way to configure the Oak client - * across all examples using environment variables. */ const { createOakClient } = require('../../dist/index.js'); @@ -43,7 +40,6 @@ function getOakClient() { }, }; - // Add custom URL if provided in environment if (process.env.BASE_URL) { config.customUrl = process.env.BASE_URL; } @@ -52,18 +48,66 @@ function getOakClient() { } /** - * Gets environment-specific test data + * Resolves a customer ID by fetching the first customer from the list. * - * @returns {Object} Test environment configuration + * @param {object} customers - CustomerService instance + * @param {object} [filter] - Optional filter params for customers.list() + * @returns {Promise} Resolved customer ID + * @throws {Error} If no customers are found */ -function getTestEnvironment() { +async function resolveCustomerId(customers, filter = {}) { + const result = await customers.list({ limit: 1, ...filter }); + + if (!result.ok) { + throw new Error(`Failed to fetch customer list: ${result.error.message}`); + } + + if (result.value.data.customer_list.length === 0) { + throw new Error( + 'No customers found. Create one first: node customers/create-customer.js' + ); + } + + const first = result.value.data.customer_list[0]; + return first.id ?? first.customer_id; +} + +/** + * Resolves a customer ID from list, or creates one with email only (same as integration test). + * + * @param {object} customers - CustomerService instance + * @returns {Promise<{ customerId: string, created: boolean, email?: string }>} Resolved or newly created customer info + * @throws {Error} If list fails or create fails (e.g. API requires more fields) + */ +async function resolveOrCreateCustomerId(customers) { + const listResult = await customers.list({ limit: 1 }); + + if (!listResult.ok) { + throw new Error(`Failed to fetch customer list: ${listResult.error.message}`); + } + + if (listResult.value.data.customer_list.length > 0) { + const first = listResult.value.data.customer_list[0]; + return { + customerId: first.id ?? first.customer_id, + created: false, + }; + } + + const email = `customer_${Date.now()}@example.com`; + const createResult = await customers.create({ email }); + + if (!createResult.ok) { + throw new Error( + `No customers and create failed: ${createResult.error.message}. Create one via dashboard or ensure API accepts email-only create.` + ); + } + return { - paymentCustomerId: process.env.PAYMENT_CUSTOMER_ID, - environment: process.env.OAK_ENVIRONMENT || 'sandbox', + customerId: createResult.value.data.id ?? createResult.value.data.customer_id, + created: true, + email: createResult.value.data.email, }; } -module.exports = { - getOakClient, - getTestEnvironment, -}; +module.exports = { getOakClient, resolveCustomerId, resolveOrCreateCustomerId }; From 23f4a871a48660830d285ec0774321fa01d188e3 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:38 -0300 Subject: [PATCH 131/143] refactor: simplify customer creation example to focus on email-only input and improve logging --- .../api/examples/customers/create-customer.js | 54 ++++--------------- 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/packages/api/examples/customers/create-customer.js b/packages/api/examples/customers/create-customer.js index bfb67854..2a14439d 100644 --- a/packages/api/examples/customers/create-customer.js +++ b/packages/api/examples/customers/create-customer.js @@ -1,8 +1,7 @@ /** - * Create Customer Example + * Create Customer Example (Stripe) * - * Demonstrates how to create a new customer with all required fields - * and proper error handling. + * Creates a customer with email only (same as integration test). */ const { getOakClient } = require('../common/config'); @@ -16,57 +15,24 @@ async function main() { const client = getOakClient(); const customers = Crowdsplit(client).customers; - // Generate unique identifiers - const timestamp = Date.now(); - const email = `customer_${timestamp}@example.com`; - const documentNumber = `${timestamp}`.padStart(11, '0').substring(0, 11); + const email = `customer_${Date.now()}@example.com`; + logger.step(1, 'Creating customer (email only)...'); + logger.info('Customer data', { email }); - // Customer data - const customerData = { - email, - first_name: 'John', - last_name: 'Doe', - document_type: 'personal_tax_id', - document_number: documentNumber, - phone_country_code: '+1', - phone_area_code: '555', - phone_number: '0123456', - country_code: 'US', - }; - - logger.step(1, 'Creating customer...'); - logger.info('Customer data', { - email: customerData.email, - name: `${customerData.first_name} ${customerData.last_name}`, - document: customerData.document_number, - }); - - const result = await customers.create(customerData); + const result = await customers.create({ email }); if (!result.ok) { logger.error('Failed to create customer', result.error); process.exit(1); } + const customerId = result.value.data.id ?? result.value.data.customer_id; logger.success('Customer created successfully!'); - logger.info('Customer details', { - id: result.value.data.id, - email: result.value.data.email, - name: `${result.value.data.first_name} ${result.value.data.last_name}`, - country: result.value.data.country_code, - }); + logger.info('Customer details', { id: customerId, email: result.value.data.email }); - // Store the customer ID for use in other examples logger.section('Next Steps'); - logger.info( - 'Save this customer ID for other examples:', - result.value.data.id - ); - logger.info( - 'Add to .env file:', - `PAYMENT_CUSTOMER_ID=${result.value.data.id}` - ); - + logger.info('Customer ID', customerId); + logger.info('Other examples can resolve the customer from the list.'); } catch (error) { logger.error('Unexpected error', error); process.exit(1); From 04eb31221526ad9a32030647fe0d51414899720b Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:44 -0300 Subject: [PATCH 132/143] refactor: streamline customer retrieval example by resolving customer ID dynamically and improving logging output --- .../api/examples/customers/get-customer.js | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/packages/api/examples/customers/get-customer.js b/packages/api/examples/customers/get-customer.js index dbe5f90e..78d3a49e 100644 --- a/packages/api/examples/customers/get-customer.js +++ b/packages/api/examples/customers/get-customer.js @@ -1,10 +1,10 @@ /** * Get Customer Example * - * Demonstrates how to retrieve a specific customer by ID. + * Demonstrates how to retrieve a customer by ID. */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -14,26 +14,12 @@ async function main() { try { const client = getOakClient(); const customers = Crowdsplit(client).customers; - const testEnv = getTestEnvironment(); - let customerId = testEnv.paymentCustomerId; + logger.step(1, 'Resolving customer...'); + const customerId = await resolveCustomerId(customers); + logger.info('Using customer', customerId); - // If no customer ID in env, get one from the list - if (!customerId) { - logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); - const listResult = await customers.list({ limit: 1 }); - - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - logger.info('Using customer from list', customerId); - } else { - logger.error('No customers found. Please create a customer first.'); - logger.info('Run:', 'node customers/create-customer.js'); - process.exit(1); - } - } - - logger.step(1, `Fetching customer: ${customerId}`); + logger.step(2, `Fetching customer: ${customerId}`); const result = await customers.get(customerId); if (!result.ok) { @@ -45,17 +31,8 @@ async function main() { logger.section('Customer Details'); const customer = result.value.data; - console.log(` ID: ${customer.id}`); + console.log(` ID: ${customer.id ?? customer.customer_id}`); console.log(` Email: ${customer.email}`); - console.log(` Name: ${customer.first_name} ${customer.last_name}`); - console.log(` Document: ${customer.document_type} - ${customer.document_number || 'N/A'}`); - console.log(` Country: ${customer.country_code || 'N/A'}`); - console.log(` Phone: ${customer.phone_country_code || ''} ${customer.phone_area_code || ''} ${customer.phone_number || ''}`); - - if (customer.customer_wallet) { - console.log(` Wallet: ${customer.customer_wallet}`); - } - } catch (error) { logger.error('Unexpected error', error); process.exit(1); From c3936a45be083a8064b5d0df09a30c804c15514a Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:51 -0300 Subject: [PATCH 133/143] refactor: update customer example to focus on email updates, streamline customer ID resolution, and enhance logging --- .../api/examples/customers/update-customer.js | 60 ++++--------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/packages/api/examples/customers/update-customer.js b/packages/api/examples/customers/update-customer.js index 06bfc710..216674ac 100644 --- a/packages/api/examples/customers/update-customer.js +++ b/packages/api/examples/customers/update-customer.js @@ -1,10 +1,10 @@ /** * Update Customer Example * - * Demonstrates how to update customer information. + * Demonstrates how to update customer information (email only). */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -14,25 +14,11 @@ async function main() { try { const client = getOakClient(); const customers = Crowdsplit(client).customers; - const testEnv = getTestEnvironment(); - let customerId = testEnv.paymentCustomerId; + logger.step(1, 'Resolving customer...'); + const customerId = await resolveCustomerId(customers); - // If no customer ID in env, get one from the list - if (!customerId) { - logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); - const listResult = await customers.list({ limit: 1 }); - - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - } else { - logger.error('No customers found. Please create a customer first.'); - process.exit(1); - } - } - - // Get current customer data - logger.step(1, 'Fetching current customer data...'); + logger.step(2, 'Fetching current customer data...'); const currentResult = await customers.get(customerId); if (!currentResult.ok) { @@ -40,22 +26,12 @@ async function main() { process.exit(1); } - logger.info('Current customer', { - name: `${currentResult.value.data.first_name} ${currentResult.value.data.last_name}`, - email: currentResult.value.data.email, - }); + logger.info('Current customer', { email: currentResult.value.data.email }); - // Update customer - logger.step(2, 'Updating customer...'); - const updateData = { - first_name: 'Jane', - last_name: 'Smith', - phone_country_code: '+1', - phone_area_code: '555', - phone_number: '9876543', - }; + logger.step(3, 'Updating customer email...'); + const updatedEmail = `updated_${Date.now()}@example.com`; - const updateResult = await customers.update(customerId, updateData); + const updateResult = await customers.update(customerId, { email: updatedEmail }); if (!updateResult.ok) { logger.error('Failed to update customer', updateResult.error); @@ -63,23 +39,7 @@ async function main() { } logger.success('Customer updated successfully!'); - logger.info('Updated customer', { - name: `${updateResult.value.data.first_name} ${updateResult.value.data.last_name}`, - email: updateResult.value.data.email, - phone: `${updateResult.value.data.phone_country_code} ${updateResult.value.data.phone_area_code} ${updateResult.value.data.phone_number}`, - }); - - // Restore original data (optional cleanup) - logger.step(3, 'Restoring original data...'); - const restoreResult = await customers.update(customerId, { - first_name: currentResult.value.data.first_name, - last_name: currentResult.value.data.last_name, - }); - - if (restoreResult.ok) { - logger.success('Customer restored to original state'); - } - + logger.info('Updated customer', { email: updateResult.value.data.email }); } catch (error) { logger.error('Unexpected error', error); process.exit(1); From 9ebd42e941552d8d04a2be5ad4ed08eeeffbae3d Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:40:56 -0300 Subject: [PATCH 134/143] refactor: update add-bank-account example to specify Stripe, enhance customer ID resolution, and improve logging --- .../payment-methods/add-bank-account.js | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/packages/api/examples/payment-methods/add-bank-account.js b/packages/api/examples/payment-methods/add-bank-account.js index dd81fee3..5f4ed6fc 100644 --- a/packages/api/examples/payment-methods/add-bank-account.js +++ b/packages/api/examples/payment-methods/add-bank-account.js @@ -1,11 +1,11 @@ /** - * Add Bank Account Payment Method Example + * Add Bank Account Payment Method Example (Stripe) * - * Demonstrates how to add a bank account payment method (Stripe). - * Note: This requires a Stripe connected account setup. + * Adds a Stripe bank account to a customer. + * Requires a Stripe connected account setup. */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -15,27 +15,13 @@ async function main() { try { const client = getOakClient(); const { paymentMethods, customers } = Crowdsplit(client); - const testEnv = getTestEnvironment(); - // Get customer ID - let customerId = testEnv.paymentCustomerId; + logger.step(1, 'Resolving customer...'); + const customerId = await resolveCustomerId(customers); - if (!customerId) { - logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); - const listResult = await customers.list({ limit: 1 }); + logger.step(2, `Adding bank account for customer: ${customerId}`); - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - } else { - logger.error('No customers found. Create a customer first.'); - process.exit(1); - } - } - - logger.step(1, `Adding bank account for customer: ${customerId}`); - - // Bank account data (Stripe test account) - const bankData = { + const result = await paymentMethods.add(customerId, { type: 'bank', provider: 'stripe', currency: 'usd', @@ -48,22 +34,11 @@ async function main() { description: 'Example bank account', created_by: 'oak-sdk-example', }, - }; - - logger.info('Bank account data', { - provider: bankData.provider, - bank_name: bankData.bank_name, - account_type: bankData.bank_account_type, - currency: bankData.currency, }); - const result = await paymentMethods.add(customerId, bankData); - if (!result.ok) { - logger.warning('Bank account creation may require Stripe connected account setup'); logger.error('Failed to add bank account', result.error); - logger.section('Alternative'); - logger.info('Try adding a PIX payment method instead:', 'node payment-methods/add-pix.js'); + logger.info('Stripe connected account setup may be required.'); process.exit(1); } @@ -76,8 +51,6 @@ async function main() { console.log(` Provider: ${pm.provider || 'N/A'}`); console.log(` Status: ${pm.status || 'N/A'}`); console.log(` Bank: ${pm.bank_name || 'N/A'}`); - console.log(` Currency: ${pm.currency || 'N/A'}`); - } catch (error) { logger.error('Unexpected error', error); process.exit(1); From e8a5029db81eb52537fe28633c586a35bd54f73d Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:41:01 -0300 Subject: [PATCH 135/143] refactor: update add-pix example to clarify that only Stripe is supported and redirect to add-bank-account.js for bank account setup --- .../api/examples/payment-methods/add-pix.js | 85 ++----------------- 1 file changed, 9 insertions(+), 76 deletions(-) diff --git a/packages/api/examples/payment-methods/add-pix.js b/packages/api/examples/payment-methods/add-pix.js index f1c21924..2a67f508 100644 --- a/packages/api/examples/payment-methods/add-pix.js +++ b/packages/api/examples/payment-methods/add-pix.js @@ -1,80 +1,13 @@ /** - * Add PIX Payment Method Example + * Payment methods: Stripe only * - * Demonstrates how to add a PIX payment method to a customer. + * This SDK is configured for Stripe. To add a payment method, use: + * + * node payment-methods/add-bank-account.js + * + * PIX is not supported in the current setup. */ -const { getOakClient, getTestEnvironment } = require('../common/config'); -const { Crowdsplit } = require('../../dist/products/crowdsplit'); -const logger = require('../common/logger'); - -async function main() { - logger.section('Add PIX Payment Method Example'); - - try { - const client = getOakClient(); - const { paymentMethods, customers } = Crowdsplit(client); - const testEnv = getTestEnvironment(); - - // Get customer ID - let customerId = testEnv.paymentCustomerId; - - if (!customerId) { - logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); - const listResult = await customers.list({ limit: 1 }); - - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - } else { - logger.error('No customers found. Create a customer first:'); - logger.info('Run:', 'node customers/create-customer.js'); - process.exit(1); - } - } - - logger.step(1, `Adding PIX payment method for customer: ${customerId}`); - - // PIX payment method data - const pixData = { - type: 'pix', - pix_string: `pix_${Date.now()}@example.com`, - metadata: { - description: 'Example PIX payment method', - created_by: 'oak-sdk-example', - timestamp: new Date().toISOString(), - }, - }; - - logger.info('PIX data', { - type: pixData.type, - pix_string: pixData.pix_string, - }); - - const result = await paymentMethods.add(customerId, pixData); - - if (!result.ok) { - logger.error('Failed to add PIX payment method', result.error); - process.exit(1); - } - - logger.success('PIX payment method added successfully!'); - logger.section('Payment Method Details'); - - const pm = result.value.data; - console.log(` ID: ${pm.id}`); - console.log(` Type: ${pm.type}`); - console.log(` Status: ${pm.status || 'N/A'}`); - console.log(` PIX Key: ${pm.pix_string || 'N/A'}`); - - logger.section('Next Steps'); - logger.info('Payment method ID (save for later use):', pm.id); - logger.info('List all payment methods:', 'node payment-methods/list-payment-methods.js'); - logger.info('Delete this payment method:', `node payment-methods/delete-payment-method.js ${pm.id}`); - - } catch (error) { - logger.error('Unexpected error', error); - process.exit(1); - } -} - -main(); +console.log('Stripe only: use add-bank-account.js to add a bank account.'); +console.log(' node payment-methods/add-bank-account.js'); +process.exit(0); From b2c67fdb6ff42374d952f53513ddf26b69f00009 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:41:07 -0300 Subject: [PATCH 136/143] refactor: improve delete payment method example by enhancing customer ID resolution and updating logging steps --- .../payment-methods/delete-payment-method.js | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/packages/api/examples/payment-methods/delete-payment-method.js b/packages/api/examples/payment-methods/delete-payment-method.js index dd9b906c..9d8b2b9f 100644 --- a/packages/api/examples/payment-methods/delete-payment-method.js +++ b/packages/api/examples/payment-methods/delete-payment-method.js @@ -1,11 +1,11 @@ /** * Delete Payment Method Example * - * Demonstrates how to delete a payment method. + * Deletes a payment method for a customer. * Usage: node delete-payment-method.js [payment_method_id] */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -15,31 +15,19 @@ async function main() { try { const client = getOakClient(); const { paymentMethods, customers } = Crowdsplit(client); - const testEnv = getTestEnvironment(); - // Get customer ID - let customerId = testEnv.paymentCustomerId; + logger.step(1, 'Resolving customer...'); + const customerId = await resolveCustomerId(customers); - if (!customerId) { - const listResult = await customers.list({ limit: 1 }); - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - } else { - logger.error('No customers found.'); - process.exit(1); - } - } - - // Get payment method ID from command line or use the first available let paymentMethodId = process.argv[2]; if (!paymentMethodId) { - logger.step(1, 'No payment method ID provided, fetching from list...'); + logger.step(2, 'No payment method ID provided, fetching from list...'); const listResult = await paymentMethods.list(customerId); if (!listResult.ok || listResult.value.data.length === 0) { logger.error('No payment methods found for this customer'); - logger.info('Add a payment method first:', 'node payment-methods/add-pix.js'); + logger.info('Add one first: node payment-methods/add-bank-account.js'); process.exit(1); } @@ -50,8 +38,7 @@ async function main() { }); } - // Confirm deletion - logger.step(2, `Deleting payment method: ${paymentMethodId}`); + logger.step(3, `Deleting payment method: ${paymentMethodId}`); logger.warning('This action cannot be undone!'); const result = await paymentMethods.delete(customerId, paymentMethodId); @@ -64,8 +51,7 @@ async function main() { logger.success('Payment method deleted successfully!'); logger.info('Response', result.value.msg); - // Verify deletion - logger.step(3, 'Verifying deletion...'); + logger.step(4, 'Verifying deletion...'); const verifyResult = await paymentMethods.get(customerId, paymentMethodId); if (!verifyResult.ok) { @@ -73,7 +59,6 @@ async function main() { } else { logger.warning('Payment method still exists (may take time to propagate)'); } - } catch (error) { logger.error('Unexpected error', error); process.exit(1); From 31a9659fe0e51c43ace1452e3c20006269d76940 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:41:11 -0300 Subject: [PATCH 137/143] refactor: enhance list payment methods example by specifying Stripe, improving customer ID resolution, and updating logging steps --- .../payment-methods/list-payment-methods.js | 51 ++++++------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/packages/api/examples/payment-methods/list-payment-methods.js b/packages/api/examples/payment-methods/list-payment-methods.js index 17dfdec8..af1bddc1 100644 --- a/packages/api/examples/payment-methods/list-payment-methods.js +++ b/packages/api/examples/payment-methods/list-payment-methods.js @@ -1,10 +1,10 @@ /** - * List Payment Methods Example + * List Payment Methods Example (Stripe) * - * Demonstrates how to list all payment methods for a customer with filtering. + * Lists payment methods for a customer with optional filtering. */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -14,25 +14,11 @@ async function main() { try { const client = getOakClient(); const { paymentMethods, customers } = Crowdsplit(client); - const testEnv = getTestEnvironment(); - // Get customer ID - let customerId = testEnv.paymentCustomerId; + logger.step(1, 'Resolving customer...'); + const customerId = await resolveCustomerId(customers); - if (!customerId) { - logger.warning('No PAYMENT_CUSTOMER_ID in .env, fetching from list...'); - const listResult = await customers.list({ limit: 1 }); - - if (listResult.ok && listResult.value.data.customer_list.length > 0) { - customerId = listResult.value.data.customer_list[0].id; - } else { - logger.error('No customers found.'); - process.exit(1); - } - } - - // Example 1: List all payment methods - logger.step(1, `Listing all payment methods for customer: ${customerId}`); + logger.step(2, `Listing all payment methods for customer: ${customerId}`); const allResult = await paymentMethods.list(customerId); if (!allResult.ok) { @@ -40,38 +26,33 @@ async function main() { process.exit(1); } - logger.success(`Found ${allResult.value.data.length} payment method(s)`); + const list = allResult.value.data; + logger.success(`Found ${list.length} payment method(s)`); - if (allResult.value.data.length === 0) { + if (list.length === 0) { logger.warning('No payment methods found for this customer'); - logger.info('Add a payment method first:', 'node payment-methods/add-pix.js'); + logger.info('Add one: node payment-methods/add-bank-account.js'); } else { - allResult.value.data.forEach((pm, index) => { + list.forEach((pm, index) => { console.log(`\n ${index + 1}. ${pm.type?.toUpperCase() || 'Unknown'}`); console.log(` ID: ${pm.id}`); console.log(` Status: ${pm.status || 'N/A'}`); if (pm.provider) console.log(` Provider: ${pm.provider}`); - if (pm.pix_string) console.log(` PIX: ${pm.pix_string}`); if (pm.bank_name) console.log(` Bank: ${pm.bank_name}`); }); } - // Example 2: Filter by type (PIX) - logger.step(2, 'Filtering payment methods by type: PIX'); - const pixResult = await paymentMethods.list(customerId, { type: 'pix' }); - - if (pixResult.ok) { - logger.success(`Found ${pixResult.value.data.length} PIX payment method(s)`); + logger.step(3, 'Filter by type: bank'); + const bankResult = await paymentMethods.list(customerId, { type: 'bank' }); + if (bankResult.ok) { + logger.success(`Found ${bankResult.value.data.length} bank payment method(s)`); } - // Example 3: Filter by status (active) - logger.step(3, 'Filtering payment methods by status: active'); + logger.step(4, 'Filter by status: active'); const activeResult = await paymentMethods.list(customerId, { status: 'active' }); - if (activeResult.ok) { logger.success(`Found ${activeResult.value.data.length} active payment method(s)`); } - } catch (error) { logger.error('Unexpected error', error); process.exit(1); From 1d88399c3e439b19a1d7c0385b2973fb6101f820 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:41:17 -0300 Subject: [PATCH 138/143] refactor: update complete payment flow example to specify Stripe, enhance customer ID resolution, and improve logging for payment methods and webhooks --- .../workflows/complete-payment-flow.js | 111 ++++++------------ 1 file changed, 37 insertions(+), 74 deletions(-) diff --git a/packages/api/examples/workflows/complete-payment-flow.js b/packages/api/examples/workflows/complete-payment-flow.js index 4592adf8..9fd52b41 100644 --- a/packages/api/examples/workflows/complete-payment-flow.js +++ b/packages/api/examples/workflows/complete-payment-flow.js @@ -1,16 +1,13 @@ /** - * Complete Payment Flow Workflow + * Complete Payment Flow Workflow (Stripe) * - * Demonstrates a complete payment setup workflow: - * 1. Find or create a customer - * 2. List existing payment methods - * 3. Add a new payment method if needed - * 4. Set up webhook notifications - * - * This simulates a real-world payment integration scenario. + * 1. Find or create a customer (email only) + * 2. List payment methods + * 3. Add a Stripe bank account (if connected account is set up) + * 4. Check webhook configuration */ -const { getOakClient, getTestEnvironment } = require('../common/config'); +const { getOakClient, resolveCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -20,34 +17,17 @@ async function main() { try { const client = getOakClient(); const { customers, paymentMethods, webhooks } = Crowdsplit(client); - const testEnv = getTestEnvironment(); let customerId; - // STEP 1: Get or Create Customer logger.step(1, 'Setting up customer...'); - - if (testEnv.paymentCustomerId) { - customerId = testEnv.paymentCustomerId; - logger.info('Using existing customer from .env', customerId); - - const verifyResult = await customers.get(customerId); - if (!verifyResult.ok) { - logger.error('Customer from .env not found', verifyResult.error); - process.exit(1); - } - logger.success('Customer verified'); - } else { - logger.info('Creating new customer...'); - const timestamp = Date.now(); - + try { + customerId = await resolveCustomerId(customers); + logger.success(`Using existing customer: ${customerId}`); + } catch { + logger.info('No customers found, creating one...'); const createResult = await customers.create({ - email: `payment_flow_${timestamp}@example.com`, - first_name: 'Payment', - last_name: 'User', - document_type: 'personal_tax_id', - document_number: `${timestamp}`.padStart(11, '0').substring(0, 11), - country_code: 'BR', + email: `payment_flow_${Date.now()}@example.com`, }); if (!createResult.ok) { @@ -55,13 +35,11 @@ async function main() { process.exit(1); } - customerId = createResult.value.data.id; - logger.success('New customer created', customerId); + customerId = createResult.value.data.id ?? createResult.value.data.customer_id; + logger.success(`New customer created: ${customerId}`); } - // STEP 2: Check Existing Payment Methods logger.step(2, 'Checking existing payment methods...'); - const listPMResult = await paymentMethods.list(customerId); if (!listPMResult.ok) { @@ -69,21 +47,22 @@ async function main() { process.exit(1); } - const existingPaymentMethods = listPMResult.value.data; - logger.info('Existing payment methods', existingPaymentMethods.length); - - if (existingPaymentMethods.length > 0) { - existingPaymentMethods.forEach((pm, index) => { - console.log(` ${index + 1}. ${pm.type?.toUpperCase()} - ${pm.id}`); - }); - } - - // STEP 3: Add New Payment Method - logger.step(3, 'Adding new PIX payment method...'); + const existingPMs = listPMResult.value.data; + logger.info(`Found ${existingPMs.length} payment method(s)`); + existingPMs.forEach((pm, index) => { + console.log(` ${index + 1}. ${pm.type?.toUpperCase()} - ${pm.id}`); + }); + logger.step(3, 'Adding Stripe bank account...'); const addPMResult = await paymentMethods.add(customerId, { - type: 'pix', - pix_string: `pix_payment_flow_${Date.now()}@example.com`, + type: 'bank', + provider: 'stripe', + currency: 'usd', + bank_name: 'Test Bank', + bank_account_number: '000123456789', + bank_routing_number: '110000000', + bank_account_type: 'CHECKING', + bank_account_name: 'Example Account', metadata: { workflow: 'complete_payment_flow', timestamp: new Date().toISOString(), @@ -91,57 +70,41 @@ async function main() { }); if (!addPMResult.ok) { - logger.error('Failed to add payment method', addPMResult.error); + logger.warning('Bank add failed (Stripe connected account may be required)'); } else { - logger.success('Payment method added', addPMResult.value.data.id); + logger.success(`Payment method added: ${addPMResult.value.data.id}`); } - // STEP 4: Check Webhook Configuration logger.step(4, 'Checking webhook configuration...'); - const listWebhooksResult = await webhooks.list(); if (!listWebhooksResult.ok) { logger.warning('Unable to check webhooks', listWebhooksResult.error); } else { - const activeWebhooks = listWebhooksResult.value.data.filter(w => w.active); - logger.info('Active webhooks', activeWebhooks.length); + const activeWebhooks = listWebhooksResult.value.data.filter((w) => w.is_active); + logger.info(`Active webhooks: ${activeWebhooks.length}`); if (activeWebhooks.length === 0) { logger.warning('No active webhooks configured'); - logger.info('Register a webhook:', 'node webhooks/register-webhook.js'); + logger.info('Register a webhook: node webhooks/register-webhook.js'); } else { logger.success('Webhook notifications configured'); activeWebhooks.forEach((wh, index) => { console.log(` ${index + 1}. ${wh.url}`); - console.log(` Events: ${wh.events?.join(', ')}`); }); } } - // STEP 5: Final Summary logger.section('Payment Flow Summary'); - - console.log('\n ✓ Customer Setup:'); - console.log(` ID: ${customerId}`); - - console.log('\n ✓ Payment Methods:'); + console.log('\n Customer ID:', customerId); const finalPMList = await paymentMethods.list(customerId); if (finalPMList.ok) { - console.log(` Total: ${finalPMList.value.data.length}`); - finalPMList.value.data.forEach((pm, index) => { - console.log(` ${index + 1}. ${pm.type?.toUpperCase()} - ${pm.status || 'unknown'}`); - }); + console.log(' Payment methods:', finalPMList.value.data.length); } + console.log('\n Ready for: payments, webhooks, managing payment methods'); - console.log('\n ✓ Ready for:'); - console.log(' - Processing payments'); - console.log(' - Receiving webhook notifications'); - console.log(' - Managing customer payment methods'); - - logger.section('Workflow Complete! 🚀'); + logger.section('Workflow Complete'); logger.success('Payment infrastructure is ready'); - } catch (error) { logger.error('Unexpected error in payment flow', error); process.exit(1); From b3fdfe03c15707c2a198eab3acbf4d8f7a57317e Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:41:23 -0300 Subject: [PATCH 139/143] refactor: simplify customer onboarding workflow to focus on email-only customer creation, enhance logging, and streamline setup verification --- .../examples/workflows/customer-onboarding.js | 128 ++++-------------- 1 file changed, 25 insertions(+), 103 deletions(-) diff --git a/packages/api/examples/workflows/customer-onboarding.js b/packages/api/examples/workflows/customer-onboarding.js index 95fda1ad..5492962d 100644 --- a/packages/api/examples/workflows/customer-onboarding.js +++ b/packages/api/examples/workflows/customer-onboarding.js @@ -1,16 +1,12 @@ /** - * Complete Customer Onboarding Workflow + * Customer Onboarding Workflow (Stripe) * - * Demonstrates an end-to-end customer onboarding process: - * 1. Create a new customer - * 2. Add a payment method (PIX) - * 3. Verify the setup - * - * This workflow shows how to combine multiple SDK operations - * in a real-world scenario. + * Resolves a customer from list or creates one with email only (same as integration test). + * 1. Resolve or create customer + * 2. Verify the setup */ -const { getOakClient } = require('../common/config'); +const { getOakClient, resolveOrCreateCustomerId } = require('../common/config'); const { Crowdsplit } = require('../../dist/products/crowdsplit'); const logger = require('../common/logger'); @@ -21,111 +17,37 @@ async function main() { const client = getOakClient(); const { customers, paymentMethods } = Crowdsplit(client); - // Generate unique identifiers - const timestamp = Date.now(); - const email = `customer_${timestamp}@example.com`; - const documentNumber = `${timestamp}`.padStart(11, '0').substring(0, 11); - - // STEP 1: Create Customer - logger.step(1, 'Creating new customer account...'); - - const customerData = { - email, - first_name: 'Alice', - last_name: 'Johnson', - document_type: 'personal_tax_id', - document_number: documentNumber, - phone_country_code: '+55', - phone_area_code: '11', - phone_number: '987654321', - country_code: 'BR', - }; - - logger.info('Customer info', { - name: `${customerData.first_name} ${customerData.last_name}`, - email: customerData.email, - country: customerData.country_code, - }); - - const createCustomerResult = await customers.create(customerData); - - if (!createCustomerResult.ok) { - logger.error('Failed to create customer', createCustomerResult.error); - process.exit(1); - } - - const customerId = createCustomerResult.value.data.id; - logger.success(`Customer created successfully! ID: ${customerId}`); - - // STEP 2: Add Payment Method - logger.step(2, 'Adding PIX payment method...'); - - const pixData = { - type: 'pix', - pix_string: `${email}`, - metadata: { - onboarding_flow: true, - created_at: new Date().toISOString(), - }, - }; - - const addPaymentResult = await paymentMethods.add(customerId, pixData); - - if (!addPaymentResult.ok) { - logger.error('Failed to add payment method', addPaymentResult.error); - logger.warning('Customer created but payment method setup incomplete'); - logger.info('Customer ID to retry:', customerId); - process.exit(1); + logger.step(1, 'Resolving or creating customer...'); + const { customerId, created, email: createdEmail } = await resolveOrCreateCustomerId(customers); + if (created) { + logger.success(`Created new customer (email only): ${customerId}`); + logger.info('Email', createdEmail); + } else { + logger.success(`Using existing customer: ${customerId}`); } - const paymentMethodId = addPaymentResult.value.data.id; - logger.success(`Payment method added! ID: ${paymentMethodId}`); - - // STEP 3: Verify Setup - logger.step(3, 'Verifying customer setup...'); + logger.step(2, 'Verifying setup...'); - // Verify customer - const verifyCustomerResult = await customers.get(customerId); - if (!verifyCustomerResult.ok) { - logger.error('Failed to verify customer', verifyCustomerResult.error); + const verifyCustomer = await customers.get(customerId); + if (!verifyCustomer.ok) { + logger.error('Failed to verify customer', verifyCustomer.error); process.exit(1); } - // Verify payment methods - const verifyPaymentResult = await paymentMethods.list(customerId); - if (!verifyPaymentResult.ok) { - logger.error('Failed to verify payment methods', verifyPaymentResult.error); + const verifyPM = await paymentMethods.list(customerId); + if (!verifyPM.ok) { + logger.error('Failed to list payment methods', verifyPM.error); process.exit(1); } - const paymentMethodCount = verifyPaymentResult.value.data.length; - logger.success('Customer setup verified successfully!'); + logger.success('Customer setup verified!'); - // STEP 4: Summary - logger.section('Onboarding Complete! 🎉'); - - console.log('\n Customer Details:'); + logger.section('Onboarding Complete'); + console.log('\n Customer:'); console.log(` ID: ${customerId}`); - console.log(` Name: ${verifyCustomerResult.value.data.first_name} ${verifyCustomerResult.value.data.last_name}`); - console.log(` Email: ${verifyCustomerResult.value.data.email}`); - console.log(` Country: ${verifyCustomerResult.value.data.country_code}`); - - console.log('\n Payment Methods:'); - console.log(` Count: ${paymentMethodCount}`); - verifyPaymentResult.value.data.forEach((pm, index) => { - console.log(` ${index + 1}. ${pm.type?.toUpperCase()} (${pm.id})`); - }); - - console.log('\n Next Steps:'); - console.log(` - Customer can now make payments`); - console.log(` - Set up webhooks to receive payment notifications`); - console.log(` - Add additional payment methods if needed`); - - logger.section('Workflow Summary'); - logger.success('All onboarding steps completed successfully'); - logger.info('Total operations', '3 (create, add payment method, verify)'); - logger.info('Customer ID', customerId); - + console.log(` Email: ${verifyCustomer.value.data.email}`); + console.log('\n Payment methods:', verifyPM.value.data.length); + console.log('\n Next: add a Stripe bank account (add-bank-account.js), set up webhooks.'); } catch (error) { logger.error('Unexpected error during onboarding', error); process.exit(1); From f402a513a33842f9e1e7a83de49ab39aa92940b0 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 17:43:35 -0300 Subject: [PATCH 140/143] chore: add backlog for API improvements and bug fixes, prioritizing critical issues and enhancing code quality --- packages/api/BACKLOG.md | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 packages/api/BACKLOG.md diff --git a/packages/api/BACKLOG.md b/packages/api/BACKLOG.md new file mode 100644 index 00000000..d84ec4c0 --- /dev/null +++ b/packages/api/BACKLOG.md @@ -0,0 +1,103 @@ +# packages/api — Fix Backlog + +Based on code review (Feb 2026). Ordered by priority. + +--- + +## P0 — Critical / Security + +- [ ] **Fix duplicate properties in `Provider.RegistrationStatus`** + - `provider_response`, `rejection_reason`, `readiness` are each declared twice in `src/types/provider.ts` (lines 59-64) + - Delete the three duplicate lines (62-64) + +- [ ] **Replace all `any` with `unknown`** (~28 occurrences) + - `src/types/paymentMethod.ts` — 11× `Record` in metadata fields + - `src/types/transfer.ts` — 4× `Record` in metadata/provider_data fields + - `src/types/refund.ts` — 1× `Record` in metadata + - `src/types/provider.ts` — `provider_response: any | null`, `readiness: any | null` (×2 each) + - `src/types/webhook.ts` — `Notification.data: any` + - `src/types/buy.ts` — `ProviderResponse.[key]: any`, `Metadata.[key]: any` + - `src/utils/defaultRetryConfig.ts` — `retryOnError?: (error: any)`, `onRetry?: (attempt: number, error: any)` + +- [ ] **Export `ApiResponse` from the types barrel** + - `src/types/common.ts` is used by every response type but never re-exported in `src/types/index.ts` + - Add `export * from "./common"` to `src/types/index.ts` + +- [ ] **Fix token refresh race condition in `AuthManager`** + - `getAccessToken()` has no concurrency guard — N parallel calls on an expired token fire N redundant `grantToken()` requests + - Add a promise-based in-flight coalescing: store the pending `grantToken()` promise and reuse it until resolved + +--- + +## P1 — Architecture / Correctness + +- [ ] **Fix `PaymentMethod.ResponseData` intersection-with-union type** + - `ResponseData = Request & { id, status, ... }` where `Request` is a union of 11 types + - Replace with a standalone interface that reflects the actual API response shape + - Located in `src/types/paymentMethod.ts` + +- [ ] **Strengthen `PaymentMethod.DeleteResponse`** + - Currently `{ [key: string]: string }` — meaningless + - Define the actual response shape from the API + +- [ ] **Add a default request timeout** + - `HttpClientConfig` supports `AbortSignal` but no default timeout is applied + - Add a `timeoutMs` field to `HttpClientConfig` (suggest 60 000ms default) and wire up `AbortSignal` internally in `httpClient.ts` + +- [ ] **Encode path segments in `buildUrl`** + - `buildUrl` joins segments with `/` but does not call `encodeURIComponent` on each segment + - An ID containing `/`, `?`, or `#` will silently produce a broken URL + - Apply `encodeURIComponent` to all segments except the base URL + +- [ ] **Change default `maxNumberOfRetries` from 0 to 2-3** + - Located in `src/utils/defaultRetryConfig.ts` + - The entire retry infrastructure is disabled out of the box + +- [ ] **Remove default `console.warn` from retry config** + - SDK libraries must not write to stdout/stderr by default + - Change default `onRetry` to `undefined`; let consumers provide their own logger + +--- + +## P2 — Code Quality + +- [ ] **Remove dead `@SandboxOnly` decorator** + - `src/decorators/sandboxOnly.ts` and `src/decorators/index.ts` export `SandboxOnly` and `sandboxOnlyFn` + - Neither is used anywhere in the codebase (all services are factory functions, not classes) + - Also remove `experimentalDecorators` and `emitDecoratorMetadata` from `tsconfig.json` after deletion + +- [ ] **Remove dead types from `payment.ts`** + - `Payment.ListMethodsQuery` and `Payment.DeleteMethodResponse` are defined but referenced by no service + - Delete them from `src/types/payment.ts` + +- [ ] **Fix inconsistent query string construction in `providerService`** + - `providerService.ts` builds `?provider=...` manually via template literal + - All other services use `buildQueryString()` from `services/helpers.ts` + - Refactor `getSchema` to use `buildQueryString` + +- [ ] **Reduce per-method config boilerplate in services** + - The `{ headers: { Authorization: \`Bearer ${token}\` }, retryOptions: client.retryOptions }` object is copy-pasted ~25 times across all services + - Create a helper (e.g. `makeRequestConfig(token, client)`) in `services/helpers.ts` + +- [ ] **Add input validation at service boundaries** + - Services pass parameters straight to the HTTP layer with no validation + - At minimum: guard against empty-string IDs (will silently hit wrong endpoints), negative amounts + - Consider returning `err(new ValidationError(...))` for invalid inputs + +- [ ] **Add ESLint with strict TypeScript rules** + - No linter config currently exists in the package or monorepo root + - Minimum rules: `@typescript-eslint/no-explicit-any`, `@typescript-eslint/consistent-type-imports`, `no-unused-vars` + +--- + +## P3 — Housekeeping + +- [ ] **Enforce `import type` consistently across all service files** + - `providerService.ts` and `refundService.ts` use value imports for type-only symbols + - All other services use `import type` — make it uniform + +--- + +## Done + +Check the checkboxes when done. From 8423fa066d6bc5d91d3d0a261ca086b6be20a0c3 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 19:57:42 -0300 Subject: [PATCH 141/143] feat: add common types export to API type definitions for improved modularity --- packages/api/src/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index c033cfd8..7cd3553e 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -1,4 +1,5 @@ export * from "./client"; +export * from "./common"; export * from "./environment"; export * from "./token"; export * from "./payment"; From 249dcc596f60679bc6f80ad9184b47327d6ca1d6 Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 19:59:12 -0300 Subject: [PATCH 142/143] fix: add missing 'err' import in customerService for error handling --- packages/api/src/services/customerService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/services/customerService.ts b/packages/api/src/services/customerService.ts index 5d789ca1..0b330543 100644 --- a/packages/api/src/services/customerService.ts +++ b/packages/api/src/services/customerService.ts @@ -1,4 +1,5 @@ import type { Customer, OakClient, Result } from "../types"; +import { err } from "../types"; import { httpClient } from "../utils/httpClient"; import { buildQueryString } from "./helpers"; import { withAuth } from "../utils/withAuth"; From ece327f8524425758d5c9ea12eaac92cce08cc3a Mon Sep 17 00:00:00 2001 From: 0xrafasec Date: Tue, 24 Feb 2026 20:03:37 -0300 Subject: [PATCH 143/143] chore: remove completed tasks from API backlog to maintain clarity and focus on outstanding priorities --- packages/api/BACKLOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/api/BACKLOG.md b/packages/api/BACKLOG.md index d84ec4c0..42b67c66 100644 --- a/packages/api/BACKLOG.md +++ b/packages/api/BACKLOG.md @@ -95,9 +95,3 @@ Based on code review (Feb 2026). Ordered by priority. - [ ] **Enforce `import type` consistently across all service files** - `providerService.ts` and `refundService.ts` use value imports for type-only symbols - All other services use `import type` — make it uniform - ---- - -## Done - -Check the checkboxes when done.