From 1c2872a0c6f1b2c8cf00b335ad8dbbb378ff2838 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 08:55:29 +0700 Subject: [PATCH 01/11] fix: remove trailing comma in package.json for valid JSON --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 951325d..d4d5efe 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:integration": "RUN_INTEGRATION_TESTS=true jest src/__tests__/integration.test.ts --runInBand" }, "dependencies": { - "ethereum-cryptography": "^3.2.0", + "ethereum-cryptography": "^3.2.0" }, "devDependencies": { "@swc/core": "^1.3.102", From 015984ea51910adeb0bb2d7c7889590e45bc131a Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 13:24:48 +0700 Subject: [PATCH 02/11] refactor: replace SDKServerSide with Formo client in analytics and event queue --- src/FormoAnalytics.ts | 10 +++++----- src/queue/EventQueue.test.ts | 6 +++--- src/queue/EventQueue.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/FormoAnalytics.ts b/src/FormoAnalytics.ts index d72790f..77a03d7 100644 --- a/src/FormoAnalytics.ts +++ b/src/FormoAnalytics.ts @@ -1,4 +1,4 @@ -import { SDKServerSide } from "../sdks/sdk-server-side-typescript"; +import Formo from "../sdks/sdk-server-side-typescript"; import { randomUUID } from "crypto"; import { TrackAPIEvent, @@ -49,7 +49,7 @@ export { ValidationError } from "./validators"; * ``` */ export class FormoAnalytics { - private client: SDKServerSide; + private client: Formo; private queue: EventQueue; /** @@ -62,9 +62,9 @@ export class FormoAnalytics { throw new Error("writeKey is required and must be a string"); } - this.client = new SDKServerSide({ - bearerToken: writeKey, - environment: "production", // events.formo.so + this.client = new Formo({ + apiKey: writeKey, + // baseURL defaults to https://events.formo.so }); this.queue = new EventQueue(this.client, options); diff --git a/src/queue/EventQueue.test.ts b/src/queue/EventQueue.test.ts index e340d21..c50a472 100644 --- a/src/queue/EventQueue.test.ts +++ b/src/queue/EventQueue.test.ts @@ -1,11 +1,11 @@ // EventQueue Tests import { EventQueue, QueueOptions } from "./EventQueue"; -import { SDKServerSide } from "../../sdks/sdk-server-side-typescript"; +import Formo from "../../sdks/sdk-server-side-typescript"; import { IFormoEvent } from "./type"; import { randomUUID } from "crypto"; -// Mock the SDKServerSide client +// Mock the Formo client const createMockClient = ( trackFn: jest.Mock = jest.fn().mockResolvedValue({}) ) => { @@ -13,7 +13,7 @@ const createMockClient = ( rawEvents: { track: trackFn, }, - } as unknown as SDKServerSide; + } as unknown as Formo; }; // Create a minimal valid event payload diff --git a/src/queue/EventQueue.ts b/src/queue/EventQueue.ts index 98d9efd..873da4d 100644 --- a/src/queue/EventQueue.ts +++ b/src/queue/EventQueue.ts @@ -1,6 +1,6 @@ // Event queue for batching and sending events -import { SDKServerSide } from "../../sdks/sdk-server-side-typescript"; +import Formo from "../../sdks/sdk-server-side-typescript"; import { IFormoEvent, IEventQueue } from "./type"; type Callback = (...args: unknown[]) => void; @@ -37,7 +37,7 @@ function clamp(value: number, min: number, max: number): number { } export class EventQueue implements IEventQueue { - private client: SDKServerSide; + private client: Formo; private queue: QueueItem[] = []; private timer: ReturnType | null = null; private pendingFlush: Promise | null = null; @@ -47,7 +47,7 @@ export class EventQueue implements IEventQueue { private maxQueueSize: number; private retryCount: number; - constructor(client: SDKServerSide, options: QueueOptions = {}) { + constructor(client: Formo, options: QueueOptions = {}) { this.client = client; this.flushAt = clamp( options.flushAt ?? DEFAULT_FLUSH_AT, From b5de77bb60ed2cb47598ee5f78a27f3898b77087 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 14:52:22 +0700 Subject: [PATCH 03/11] feat: Replace Stainless SDK client with direct HTTP event submission and introduce new internal event types. --- .gitignore | 64 +++- .stainless/stainless.yml | 119 ------- .stainless/workspace.json | 10 - jest.config.js | 12 +- openapi.json | 309 ----------------- src/FormoAnalytics.ts | 14 +- src/queue/EventQueue.ts | 34 +- src/queue/type.ts | 6 +- src/types/index.ts | 53 +++ {src => test}/__tests__/integration.test.ts | 41 ++- {src => test}/queue/EventQueue.test.ts | 352 +++++++++----------- 11 files changed, 332 insertions(+), 682 deletions(-) delete mode 100644 .stainless/stainless.yml delete mode 100644 .stainless/workspace.json delete mode 100644 openapi.json rename {src => test}/__tests__/integration.test.ts (88%) rename {src => test}/queue/EventQueue.test.ts (61%) diff --git a/.gitignore b/.gitignore index f236e5c..97515e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,63 @@ -node_modules/ -dist/ +# Logs +logs *.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# nyc test coverage +.nyc_output +coverage + +# Dependency directories +node_modules/ + +# Build output +dist + +# TypeScript build artifacts +tsconfig.tsbuildinfo +*.tsbuildinfo + +# Compiled JS files in src (TypeScript source only) +src/**/*.js +test/**/*.js +!src/**/*.spec.js +!test/**/*.spec.js + +# Optional npm cache directory +.npm + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file .env -.DS_Store \ No newline at end of file +.env.test +.env.local + +# parcel-bundler cache +.cache + +# OS files +.DS_Store +Thumbs.db + +# IDE +.idea +.vscode +*.swp +*.swo \ No newline at end of file diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml deleted file mode 100644 index fbae9d7..0000000 --- a/.stainless/stainless.yml +++ /dev/null @@ -1,119 +0,0 @@ -# yaml-language-server: $schema=https://app.stainless.com/config.schema.json - -# The main edition for the config, see the [docs] for more information. -# -# [docs]: https://www.stainless.com/docs/reference/editions -edition: 2025-10-10 - -organization: - # Name of your organization or company, used to determine the name of the client - # and headings. - name: Formo - # Link to your API documentation. - docs: 'https://docs.formo.so' - # Contact email for bug reports, questions, and support requests. - contact: '' - -# `targets` define the output targets and their customization options, such as -# whether to emit the Node SDK and what its package name should be. -targets: - typescript: - # The edition for this target, see the [docs] for more information. - # - # [docs]: https://www.stainless.com/docs/reference/editions - edition: typescript.2025-10-10 - package_name: '@formo/sdk-server-side' - production_repo: null - publish: - npm: false - -# `environments` are a map of the name of the environment (e.g. "sandbox", -# "production") to the corresponding url to use. -environments: - production: https://events.formo.so - -# `client_settings` define settings for the API client, such as extra constructor -# arguments (used for authentication), retry behavior, idempotency, etc. -client_settings: - opts: - api_key: - type: string - nullable: false - read_env: FORMO_API_KEY - auth: - security_scheme: BearerAuth - -# `resources` define the structure and organization for your API, such as how -# methods and models are grouped together and accessed. See the [configuration -# guide] for more information. -# -# [configuration guide]: https://www.stainless.com/docs/guides/configure#resources -resources: - raw_events: - methods: - track: post /v0/raw_events - identify: post /v0/raw_events - models: - event: "#/components/schemas/Event" - event_context: "#/components/schemas/EventContext" - event_properties: "#/components/schemas/EventProperties" - -settings: - # All generated integration tests that hit the prism mock http server are marked - # as skipped. Removing this setting or setting it to false enables tests, but - # doing so may result in test failures due to bugs in the test server. - # - # [prism mock http server]: https://stoplight.io/open-source/prism - - disable_mock_tests: true - license: Apache-2.0 - -security: - - BearerAuth: [] - -# `readme` is used to configure the code snippets that will be rendered in the -# README.md of various SDKs. In particular, you can change the `headline` -# snippet's endpoint and the arguments to call it with. -readme: - example_requests: - default: - type: request - endpoint: post /v0/raw_events - params: - type: track - channel: server - version: '0' - anonymous_id: user-device-uuid-123 - user_id: user-abc-456 - event: Purchase Completed - properties: - product_id: prod_123 - amount: 99.99 - currency: USD - context: - library_name: Formo Node SDK - library_version: 1.0.0 - address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' - original_timestamp: '2025-12-23T10:00:00.000Z' - sent_at: '2025-12-23T10:00:00.000Z' - message_id: event-uuid-789 - headline: - type: request - endpoint: post /v0/raw_events - params: - type: identify - channel: server - version: '0' - anonymous_id: user-device-uuid-123 - user_id: user-abc-456 - properties: - email: user@example.com - name: John Doe - plan: premium - context: - library_name: Formo Node SDK - library_version: 1.0.0 - address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' - original_timestamp: '2025-12-23T10:00:00.000Z' - sent_at: '2025-12-23T10:00:00.000Z' - message_id: event-uuid-790 diff --git a/.stainless/workspace.json b/.stainless/workspace.json deleted file mode 100644 index 8f69766..0000000 --- a/.stainless/workspace.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "project": "sdk-server-side", - "openapi_spec": "../openapi.json", - "stainless_config": "stainless.yml", - "targets": { - "typescript": { - "output_path": "../sdks/sdk-server-side-typescript" - } - } -} diff --git a/jest.config.js b/jest.config.js index 29eb4b5..f276654 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,18 +4,8 @@ const config = { transform: { "^.+\\.(t|j)sx?$": ["@swc/jest", { sourceMaps: "inline" }], }, - moduleNameMapper: { - "^sdk-server-side$": - "/sdks/sdk-server-side-typescript/src/index.ts", - "^sdk-server-side/(.*)$": - "/sdks/sdk-server-side-typescript/src/$1", - }, - roots: ["/src"], + roots: ["/src", "/test"], testMatch: ["**/*.test.ts"], - modulePathIgnorePatterns: [ - "/sdks/sdk-server-side-typescript/ecosystem-tests/", - "/sdks/sdk-server-side-typescript/dist/", - ], }; module.exports = config; diff --git a/openapi.json b/openapi.json deleted file mode 100644 index ffa9b7b..0000000 --- a/openapi.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "Formo Events API", - "version": "1.0.0", - "description": "API for tracking user events and analytics" - }, - "servers": [ - { - "url": "https://events.formo.so", - "description": "Events API" - } - ], - "components": { - "securitySchemes": { - "BearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT", - "description": "JWT Bearer token for authentication. Format: 'Bearer '" - } - }, - "schemas": { - "Error": { - "type": "object", - "properties": { - "isSuccess": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string" - } - } - }, - "EventContext": { - "type": "object", - "description": "Contextual information about the event", - "properties": { - "user_agent": { - "type": "string", - "description": "The user agent of the device" - }, - "locale": { - "type": "string", - "description": "Language of the device (e.g., en-US, en-GB)" - }, - "timezone": { - "type": "string", - "description": "Timezone of the user (e.g., America/New_York)" - }, - "location": { - "type": "string", - "description": "Geographic location country code (e.g., US, NG)" - }, - "ref": { - "type": "string", - "description": "Referral code or identifier" - }, - "referrer": { - "type": "string", - "description": "The referrer URL where the user came from" - }, - "utm_campaign": { - "type": "string", - "description": "UTM campaign parameter" - }, - "utm_content": { - "type": "string", - "description": "UTM content parameter" - }, - "utm_medium": { - "type": "string", - "description": "UTM medium parameter" - }, - "utm_source": { - "type": "string", - "description": "UTM source parameter" - }, - "utm_term": { - "type": "string", - "description": "UTM term parameter" - }, - "page_title": { - "type": "string", - "description": "Title of the current page" - }, - "page_url": { - "type": "string", - "description": "Full URL of the current page" - }, - "library_name": { - "type": "string", - "description": "Name of the SDK used (e.g., Formo Web SDK)" - }, - "library_version": { - "type": "string", - "description": "Version of the SDK used" - }, - "browser": { - "type": "string", - "description": "Name of the browser (e.g., chrome, firefox, safari)" - }, - "screen_width": { - "type": "integer", - "description": "Width of the device screen in pixels" - }, - "screen_height": { - "type": "integer", - "description": "Height of the device screen in pixels" - }, - "screen_density": { - "type": "number", - "description": "Pixel density of the device screen (devicePixelRatio)" - }, - "viewport_width": { - "type": "integer", - "description": "Width of the browser viewport in pixels" - }, - "viewport_height": { - "type": "integer", - "description": "Height of the browser viewport in pixels" - } - } - }, - "EventProperties": { - "type": "object", - "description": "Event-specific properties. Can contain any key-value pairs relevant to the event.", - "additionalProperties": true - }, - "Event": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["identify", "track", "page", "connect", "disconnect", "detect", "chain", "signature", "transaction"], - "description": "Type of the event" - }, - "channel": { - "type": "string", - "enum": ["web", "mobile", "server"], - "description": "Channel/source of the event" - }, - "version": { - "type": "string", - "description": "Event spec version" - }, - "anonymous_id": { - "type": "string", - "description": "Pseudo-identifier for the user (device ID)" - }, - "user_id": { - "type": "string", - "nullable": true, - "description": "Unique user identifier" - }, - "address": { - "type": "string", - "nullable": true, - "description": "Wallet address" - }, - "event": { - "type": "string", - "description": "Name of the event (for track events)" - }, - "context": { - "$ref": "#/components/schemas/EventContext" - }, - "properties": { - "$ref": "#/components/schemas/EventProperties" - }, - "original_timestamp": { - "type": "string", - "format": "date-time", - "description": "Time when the event occurred on the client" - }, - "sent_at": { - "type": "string", - "format": "date-time", - "description": "Time when the event was sent from the client" - }, - "message_id": { - "type": "string", - "description": "Unique identifier for the event" - } - }, - "required": [ - "type", - "channel", - "version", - "anonymous_id", - "context", - "original_timestamp", - "sent_at", - "message_id" - ] - } - } - }, - "paths": { - "/v0/raw_events": { - "post": { - "summary": "Track Event", - "description": "Sends an event to the Formo Events API. Use the SDK Write Key from your project settings as the Bearer token for authentication.", - "operationId": "trackEvent", - "tags": ["Events API"], - "servers": [ - { - "url": "https://events.formo.so", - "description": "Events API" - } - ], - "security": [ - { - "BearerAuth": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Event" - }, - "example": { - "type": "page", - "channel": "web", - "version": "0", - "anonymous_id": "c2bc0ebe-d852-49d1-9efd-e45744850ae0", - "user_id": "a46e6878-1ed5-4a81-9185-83608df2fcb6", - "address": "0x9CC3cB28cd94eB4423B15cdA73346e204f59a407", - "event": "", - "context": { - "user_agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36", - "locale": "en-GB", - "timezone": "Africa/Lagos", - "location": "NG", - "ref": "", - "referrer": "", - "utm_campaign": "", - "utm_content": "", - "utm_medium": "", - "utm_source": "", - "utm_term": "", - "page_title": "WalletConnect Content Cabal", - "page_url": "https://app.formo.so/lByfTbeUSL_mxy_TN2m3d", - "library_name": "Formo Web SDK", - "library_version": "1.25.0", - "browser": "chrome", - "screen_width": 393, - "screen_height": 873, - "screen_density": 2.75, - "viewport_width": 392, - "viewport_height": 735 - }, - "properties": { - "url": "https://app.formo.so/lByfTbeUSL_mxy_TN2m3d", - "path": "/lByfTbeUSL_mxy_TN2m3d", - "hash": "", - "query": "" - }, - "original_timestamp": "2025-04-23T13:15:17.000Z", - "sent_at": "2025-04-23T13:15:18.000Z", - "message_id": "48555101eee2f44ac0f0632fcb7c7c9f6ce0012ae395ae79f8a0d515e4f5e41f" - } - } - } - }, - "responses": { - "200": { - "description": "Event successfully tracked", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "successful_rows": { - "type": "integer", - "description": "Number of events successfully ingested", - "example": 1 - }, - "quarantined_rows": { - "type": "integer", - "description": "Number of events that failed validation and were quarantined", - "example": 0 - } - } - }, - "example": { - "successful_rows": 1, - "quarantined_rows": 0 - } - } - } - }, - "400": { - "description": "Bad Request - Invalid event data" - }, - "401": { - "description": "Unauthorized - Missing or invalid Bearer token" - }, - "500": { - "description": "Server Error" - } - } - } - } - } -} \ No newline at end of file diff --git a/src/FormoAnalytics.ts b/src/FormoAnalytics.ts index 77a03d7..8a80bdc 100644 --- a/src/FormoAnalytics.ts +++ b/src/FormoAnalytics.ts @@ -1,4 +1,3 @@ -import Formo from "../sdks/sdk-server-side-typescript"; import { randomUUID } from "crypto"; import { TrackAPIEvent, @@ -49,7 +48,6 @@ export { ValidationError } from "./validators"; * ``` */ export class FormoAnalytics { - private client: Formo; private queue: EventQueue; /** @@ -57,17 +55,15 @@ export class FormoAnalytics { * @param writeKey - Your Formo project write key * @param options - Optional configuration */ - constructor(writeKey: string, options: AnalyticsOptions = {}) { + constructor( + public readonly writeKey: string, + options: AnalyticsOptions = {} + ) { if (!writeKey || typeof writeKey !== "string") { throw new Error("writeKey is required and must be a string"); } - this.client = new Formo({ - apiKey: writeKey, - // baseURL defaults to https://events.formo.so - }); - - this.queue = new EventQueue(this.client, options); + this.queue = new EventQueue(writeKey, options); } /** diff --git a/src/queue/EventQueue.ts b/src/queue/EventQueue.ts index 873da4d..6535b88 100644 --- a/src/queue/EventQueue.ts +++ b/src/queue/EventQueue.ts @@ -1,6 +1,5 @@ // Event queue for batching and sending events -import Formo from "../../sdks/sdk-server-side-typescript"; import { IFormoEvent, IEventQueue } from "./type"; type Callback = (...args: unknown[]) => void; @@ -17,6 +16,9 @@ export interface QueueOptions { retryCount?: number; // Retry failed requests N times } +// API Configuration +const EVENTS_API_HOST = "https://events.formo.so/v0/raw_events"; + // Constants with defaults const DEFAULT_FLUSH_AT = 20; const MAX_FLUSH_AT = 100; @@ -37,7 +39,7 @@ function clamp(value: number, min: number, max: number): number { } export class EventQueue implements IEventQueue { - private client: Formo; + private writeKey: string; private queue: QueueItem[] = []; private timer: ReturnType | null = null; private pendingFlush: Promise | null = null; @@ -47,8 +49,12 @@ export class EventQueue implements IEventQueue { private maxQueueSize: number; private retryCount: number; - constructor(client: Formo, options: QueueOptions = {}) { - this.client = client; + constructor(writeKey: string, options: QueueOptions = {}) { + if (!writeKey || typeof writeKey !== "string") { + throw new Error("writeKey is required and must be a string"); + } + + this.writeKey = writeKey; this.flushAt = clamp( options.flushAt ?? DEFAULT_FLUSH_AT, MIN_FLUSH_AT, @@ -138,10 +144,24 @@ export class EventQueue implements IEventQueue { for (let attempt = 0; attempt <= this.retryCount; attempt++) { try { - // Send each event through the Stainless client - for (const payload of payloads) { - await this.client.rawEvents.track(payload); + // Send events via HTTP POST + const response = await fetch(EVENTS_API_HOST, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${this.writeKey}`, + }, + body: JSON.stringify(payloads), + }); + + if (!response.ok) { + const error = new Error( + `HTTP ${response.status}: ${response.statusText}` + ) as Error & { status: number }; + (error as Error & { status: number }).status = response.status; + throw error; } + return; // Success } catch (err) { lastError = err as Error; diff --git a/src/queue/type.ts b/src/queue/type.ts index 6643035..eb536db 100644 --- a/src/queue/type.ts +++ b/src/queue/type.ts @@ -1,8 +1,8 @@ -import { RawEventTrackParams } from "../../sdks/sdk-server-side-typescript/src/resources/raw-events"; +import { IFormoEvent } from "../types"; -export type IFormoEvent = RawEventTrackParams; +export type { IFormoEvent }; -// event queue interface +// Event queue interface export interface IEventQueue { enqueue( event: IFormoEvent, diff --git a/src/types/index.ts b/src/types/index.ts index eb2e93c..bf04398 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -38,3 +38,56 @@ export interface AnalyticsOptions { maxQueueSize?: number; // Flush when queue > N bytes (default: 500KB) retryCount?: number; // Retry failed requests N times (default: 3) } + +// ============================================ +// Internal Types (for EventQueue) +// ============================================ + +export type EventChannel = "web" | "mobile" | "server"; + +export type EventType = + | "identify" + | "track"; + // | "page" + // | "connect" + // | "disconnect" + // | "detect" + // | "chain" + // | "signature" + // | "transaction"; + +/** + * Contextual information about the event + */ +export interface EventContext { + library_name?: string; + library_version?: string; + // Additional context fields can be added + [key: string]: unknown; +} + +/** + * Internal event payload sent to the API + */ +export interface IFormoEvent { + anonymous_id: string; + channel: EventChannel; + context: EventContext; + message_id: string; + original_timestamp: string; + sent_at: string; + type: EventType; + version: string; + address?: string | null; + event?: string; + properties?: IFormoEventProperties; + user_id?: string | null; +} + +/** + * API response from raw_events endpoint + */ +export interface FormoEventResponse { + quarantined_rows?: number; + successful_rows?: number; +} diff --git a/src/__tests__/integration.test.ts b/test/__tests__/integration.test.ts similarity index 88% rename from src/__tests__/integration.test.ts rename to test/__tests__/integration.test.ts index 4d5d15d..d21c3b6 100644 --- a/src/__tests__/integration.test.ts +++ b/test/__tests__/integration.test.ts @@ -12,8 +12,10 @@ * - Slow test runs during development */ -import { FormoAnalytics, ValidationError } from "../FormoAnalytics"; -import { randomUUID } from "crypto"; +import { FormoAnalytics, ValidationError } from "../../src/FormoAnalytics"; + +// Test ID prefix for identifying test data +const TEST_PREFIX = "TEST_SDK_NODE"; // Skip all integration tests by default // Change to `describe` to run, or use: FORMO_WRITE_KEY=xxx pnpm run test:integration @@ -33,9 +35,6 @@ const getWriteKey = (): string => { return key; }; -// Generate a valid UUID for testing -const generateTestId = () => randomUUID(); - integrationDescribe("FormoAnalytics Integration Tests", () => { let analytics: FormoAnalytics; @@ -53,7 +52,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { describe("Track Events", () => { test("successfully tracks a basic event", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.track({ @@ -69,13 +68,13 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { }); test("successfully tracks event with all optional fields", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.track({ anonymousId: testId, event: "Integration Test - Full Track", - userId: `user-${testId}`, + userId: `${TEST_PREFIX}-user-${testId}`, properties: { testId, revenue: 99.99, @@ -93,7 +92,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { }); test("successfully tracks event with Ethereum address", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.track({ @@ -109,7 +108,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { }); test("tracks event with lowercase address (auto-checksummed)", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.track({ @@ -124,12 +123,12 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { describe("Identify Events", () => { test("successfully identifies a user", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.identify({ anonymousId: testId, - userId: `user-${testId}`, + userId: `${TEST_PREFIX}-user-${testId}`, properties: { email: `test-${testId}@example.com`, name: "Integration Test User", @@ -141,7 +140,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { }); test("successfully identifies user with Ethereum address", async () => { - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; await expect( analytics.identify({ @@ -177,7 +176,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { test("rejects track with empty event name", async () => { await expect( analytics.track({ - anonymousId: generateTestId(), + anonymousId: "00000000-0000-0000-0000-000000000000", event: "", // Invalid }) ).rejects.toThrow(ValidationError); @@ -186,7 +185,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { test("rejects track with invalid address", async () => { await expect( analytics.track({ - anonymousId: generateTestId(), + anonymousId: "00000000-0000-0000-0000-000000000000", event: "Test Event", address: "not-a-valid-address", // Invalid }) @@ -196,7 +195,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { test("rejects identify with empty userId", async () => { await expect( analytics.identify({ - anonymousId: generateTestId(), + anonymousId: "00000000-0000-0000-0000-000000000000", userId: "", // Invalid }) ).rejects.toThrow(ValidationError); @@ -211,7 +210,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { flushInterval: 60000, // Long interval so we control flush }); - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; // Queue multiple events (should not send immediately) for (let i = 0; i < 5; i++) { @@ -231,7 +230,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { flushAt: 3, // Low threshold }); - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; // This should trigger an auto-flush after the 3rd event for (let i = 0; i < 3; i++) { @@ -257,7 +256,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { // The SDK should either reject or handle the error gracefully try { await badAnalytics.track({ - anonymousId: generateTestId(), + anonymousId: "00000000-0000-0000-0000-000000000000", event: "Should Fail", }); } catch (error) { @@ -283,7 +282,7 @@ if (require.main === module) { } const analytics = new FormoAnalytics(writeKey, { flushAt: 1 }); - const testId = generateTestId(); + const testId = "00000000-0000-0000-0000-000000000000"; try { console.log("1. Tracking event..."); @@ -300,7 +299,7 @@ if (require.main === module) { console.log("\n2. Identifying user..."); await analytics.identify({ anonymousId: testId, - userId: `manual-test-user-${testId}`, + userId: `${TEST_PREFIX}-manual-test-user-${testId}`, properties: { source: "manual-test", }, diff --git a/src/queue/EventQueue.test.ts b/test/queue/EventQueue.test.ts similarity index 61% rename from src/queue/EventQueue.test.ts rename to test/queue/EventQueue.test.ts index c50a472..4b92603 100644 --- a/src/queue/EventQueue.test.ts +++ b/test/queue/EventQueue.test.ts @@ -1,20 +1,12 @@ // EventQueue Tests -import { EventQueue, QueueOptions } from "./EventQueue"; -import Formo from "../../sdks/sdk-server-side-typescript"; -import { IFormoEvent } from "./type"; +import { EventQueue } from "../../src/queue/EventQueue"; +import { IFormoEvent } from "../../src/queue/type"; import { randomUUID } from "crypto"; -// Mock the Formo client -const createMockClient = ( - trackFn: jest.Mock = jest.fn().mockResolvedValue({}) -) => { - return { - rawEvents: { - track: trackFn, - }, - } as unknown as Formo; -}; +// Mock fetch globally +const mockFetch = jest.fn(); +global.fetch = mockFetch; // Create a minimal valid event payload const createEvent = (overrides: Partial = {}): IFormoEvent => ({ @@ -34,6 +26,9 @@ describe("EventQueue", () => { // Use fake timers for interval-based tests beforeEach(() => { jest.useFakeTimers(); + // Reset fetch mock + mockFetch.mockReset(); + mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); // Prevent process handlers from interfering with tests jest.spyOn(process, "on").mockImplementation(() => process); }); @@ -45,21 +40,17 @@ describe("EventQueue", () => { describe("Basic Functionality", () => { test("enqueue adds events to the queue", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); // High threshold to prevent auto-flush + const queue = new EventQueue("test-write-key", { flushAt: 100 }); // High threshold to prevent auto-flush await queue.enqueue(createEvent({ event: "event_1" })); await queue.enqueue(createEvent({ event: "event_2" })); expect(queue.length).toBe(2); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); }); test("flush sends all queued events", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); await queue.enqueue(createEvent({ event: "event_1" })); await queue.enqueue(createEvent({ event: "event_2" })); @@ -68,66 +59,69 @@ describe("EventQueue", () => { await queue.flush(); expect(queue.length).toBe(0); - expect(mockTrack).toHaveBeenCalledTimes(3); + expect(mockFetch).toHaveBeenCalledTimes(1); + // Verify the fetch was called with correct auth header + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: "Basic test-write-key", + }), + }) + ); }); test("flush with empty queue does nothing", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); const callback = jest.fn(); await queue.flush(callback); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalled(); }); }); describe("Flush on Count Threshold", () => { test("auto-flushes when queue reaches flushAt count", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 3 }); + const queue = new EventQueue("test-write-key", { flushAt: 3 }); await queue.enqueue(createEvent({ event: "event_1" })); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); await queue.enqueue(createEvent({ event: "event_2" })); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); await queue.enqueue(createEvent({ event: "event_3" })); // Should auto-flush after 3rd event - expect(mockTrack).toHaveBeenCalledTimes(3); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(queue.length).toBe(0); }); test("flushAt is clamped to valid range (1-100)", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - // Too low - should clamp to 1 - const queueLow = new EventQueue(client, { flushAt: 0 }); + const queueLow = new EventQueue("test-write-key", { flushAt: 0 }); await queueLow.enqueue(createEvent()); - expect(mockTrack).toHaveBeenCalledTimes(1); // Flushed immediately at 1 + expect(mockFetch).toHaveBeenCalledTimes(1); // Flushed immediately at 1 - mockTrack.mockClear(); + mockFetch.mockClear(); // Too high - should clamp to 100 - const queueHigh = new EventQueue(client, { flushAt: 200 }); + const queueHigh = new EventQueue("test-write-key", { flushAt: 200 }); for (let i = 0; i < 100; i++) { await queueHigh.enqueue(createEvent({ event: `event_${i}` })); } - expect(mockTrack).toHaveBeenCalledTimes(100); // Flushed at 100, not 200 + expect(mockFetch).toHaveBeenCalledTimes(1); // Flushed at 100, not 200 }); }); describe("Flush on Size Threshold", () => { test("auto-flushes when queue exceeds maxQueueSize", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); // Set a very small maxQueueSize - const queue = new EventQueue(client, { flushAt: 100, maxQueueSize: 100 }); + const queue = new EventQueue("test-write-key", { + flushAt: 100, + maxQueueSize: 100, + }); // Create a large event that exceeds the size limit const largeEvent = createEvent({ @@ -138,22 +132,20 @@ describe("EventQueue", () => { await queue.enqueue(largeEvent); // Should auto-flush because size exceeded - expect(mockTrack).toHaveBeenCalled(); + expect(mockFetch).toHaveBeenCalled(); expect(queue.length).toBe(0); }); }); describe("Interval Flushing", () => { test("auto-flushes after flushInterval timeout", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { + const queue = new EventQueue("test-write-key", { flushAt: 100, flushInterval: 5000, // 5 seconds }); await queue.enqueue(createEvent({ event: "event_1" })); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); // Advance time by 5 seconds jest.advanceTimersByTime(5000); @@ -161,15 +153,12 @@ describe("EventQueue", () => { // Need to wait for the flush promise to resolve await Promise.resolve(); - expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(1); }); test("flushInterval is clamped to valid range (1s-5min)", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - // Too low - should clamp to 1000ms - const queueLow = new EventQueue(client, { + const queueLow = new EventQueue("test-write-key", { flushAt: 100, flushInterval: 100, // 100ms, should clamp to 1000ms }); @@ -177,19 +166,17 @@ describe("EventQueue", () => { jest.advanceTimersByTime(500); await Promise.resolve(); - expect(mockTrack).not.toHaveBeenCalled(); // Not yet + expect(mockFetch).not.toHaveBeenCalled(); // Not yet jest.advanceTimersByTime(600); // Total 1100ms await Promise.resolve(); - expect(mockTrack).toHaveBeenCalled(); // Now it should have flushed + expect(mockFetch).toHaveBeenCalled(); // Now it should have flushed }); }); describe("Callback Execution", () => { test("enqueue callback is called on successful flush", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1 }); // Flush immediately + const queue = new EventQueue("test-write-key", { flushAt: 1 }); // Flush immediately const callback = jest.fn(); await queue.enqueue(createEvent(), callback); @@ -198,21 +185,23 @@ describe("EventQueue", () => { }); test("enqueue callback is called on failed flush", async () => { - const error = new Error("Network error"); - const mockTrack = jest.fn().mockRejectedValue(error); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 0 }); // No retries + mockFetch.mockRejectedValue(new Error("Network error")); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 0, + }); // No retries const callback = jest.fn(); await queue.enqueue(createEvent(), callback); - expect(callback).toHaveBeenCalledWith(error, expect.any(Object)); + expect(callback).toHaveBeenCalledWith( + expect.any(Error), + expect.any(Object) + ); }); test("flush callback is called with all payloads", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); await queue.enqueue(createEvent({ event: "event_1" })); await queue.enqueue(createEvent({ event: "event_2" })); @@ -232,15 +221,18 @@ describe("EventQueue", () => { describe("Retry Logic", () => { test("retries on 5xx server errors", async () => { - const error500 = { status: 500, message: "Internal Server Error" }; - const mockTrack = jest - .fn() + const error500 = Object.assign(new Error("Internal Server Error"), { + status: 500, + }); + mockFetch .mockRejectedValueOnce(error500) .mockRejectedValueOnce(error500) - .mockResolvedValue({}); + .mockResolvedValue({ ok: true }); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 3 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 3, + }); // Use runAllTimersAsync to properly handle async operations with fake timers const enqueuePromise = queue.enqueue(createEvent()); @@ -248,64 +240,74 @@ describe("EventQueue", () => { await enqueuePromise; // Should have retried twice before succeeding on 3rd attempt - expect(mockTrack).toHaveBeenCalledTimes(3); + expect(mockFetch).toHaveBeenCalledTimes(3); }, 10000); test("retries on 429 rate limit", async () => { - const error429 = { status: 429, message: "Too Many Requests" }; - const mockTrack = jest - .fn() - .mockRejectedValueOnce(error429) - .mockResolvedValue({}); + const error429 = Object.assign(new Error("Too Many Requests"), { + status: 429, + }); + mockFetch.mockRejectedValueOnce(error429).mockResolvedValue({ ok: true }); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 3 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 3, + }); const enqueuePromise = queue.enqueue(createEvent()); await jest.runAllTimersAsync(); await enqueuePromise; - expect(mockTrack).toHaveBeenCalledTimes(2); + expect(mockFetch).toHaveBeenCalledTimes(2); }, 10000); test("retries on network errors (ECONNRESET, ETIMEDOUT)", async () => { - const networkError = { code: "ECONNRESET" }; - const mockTrack = jest - .fn() + const networkError = Object.assign(new Error("Connection reset"), { + code: "ECONNRESET", + }); + mockFetch .mockRejectedValueOnce(networkError) - .mockResolvedValue({}); + .mockResolvedValue({ ok: true }); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 3 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 3, + }); const enqueuePromise = queue.enqueue(createEvent()); await jest.runAllTimersAsync(); await enqueuePromise; - expect(mockTrack).toHaveBeenCalledTimes(2); + expect(mockFetch).toHaveBeenCalledTimes(2); }, 10000); test("does NOT retry on 4xx client errors", async () => { - const error400 = { status: 400, message: "Bad Request" }; - const mockTrack = jest.fn().mockRejectedValue(error400); + const error400 = Object.assign(new Error("Bad Request"), { status: 400 }); + mockFetch.mockRejectedValue(error400); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 3 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 3, + }); const callback = jest.fn(); await queue.enqueue(createEvent(), callback); // Should only call once, no retries for 4xx - expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(error400, expect.any(Object)); }); test("gives up after retryCount attempts", async () => { - const error500 = { status: 500, message: "Internal Server Error" }; - const mockTrack = jest.fn().mockRejectedValue(error500); + const error500 = Object.assign(new Error("Internal Server Error"), { + status: 500, + }); + mockFetch.mockRejectedValue(error500); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 2 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 2, + }); const callback = jest.fn(); const enqueuePromise = queue.enqueue(createEvent(), callback); @@ -313,16 +315,20 @@ describe("EventQueue", () => { await enqueuePromise; // 1 initial + 2 retries = 3 calls - expect(mockTrack).toHaveBeenCalledTimes(3); + expect(mockFetch).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledWith(error500, expect.any(Object)); }, 10000); test("exponential backoff timing is correct", async () => { - const error500 = { status: 500 }; - const mockTrack = jest.fn().mockRejectedValue(error500); + const error500 = Object.assign(new Error("Server Error"), { + status: 500, + }); + mockFetch.mockRejectedValue(error500); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1, retryCount: 3 }); + const queue = new EventQueue("test-write-key", { + flushAt: 1, + retryCount: 3, + }); // Start enqueue (will fail and start retrying) const enqueuePromise = queue.enqueue(createEvent()); @@ -332,15 +338,13 @@ describe("EventQueue", () => { await enqueuePromise; // 1 initial + 3 retries = 4 calls total - expect(mockTrack).toHaveBeenCalledTimes(4); + expect(mockFetch).toHaveBeenCalledTimes(4); }, 10000); }); describe("Queue Length Property", () => { test("length property reflects current queue size", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); expect(queue.length).toBe(0); @@ -358,28 +362,26 @@ describe("EventQueue", () => { describe("Flush Batching", () => { test("flush only sends up to flushAt events at a time", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); // Use a high flushAt so we can add 5 events without auto-flushing - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); // Add 5 events for (let i = 0; i < 5; i++) { await queue.enqueue(createEvent({ event: `event_${i}` })); } expect(queue.length).toBe(5); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); // Now create a queue with flushAt: 3 to test batching behavior - const queue2 = new EventQueue(client, { flushAt: 3 }); + const queue2 = new EventQueue("test-write-key", { flushAt: 3 }); // Add 5 events (first 3 will auto-flush) await queue2.enqueue(createEvent({ event: `event_0` })); await queue2.enqueue(createEvent({ event: `event_1` })); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); await queue2.enqueue(createEvent({ event: `event_2` })); // triggers flush - expect(mockTrack).toHaveBeenCalledTimes(3); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(queue2.length).toBe(0); // Add 2 more and manually flush @@ -388,21 +390,22 @@ describe("EventQueue", () => { expect(queue2.length).toBe(2); await queue2.flush(); - expect(mockTrack).toHaveBeenCalledTimes(5); + expect(mockFetch).toHaveBeenCalledTimes(2); expect(queue2.length).toBe(0); }); }); describe("Pending Flush Handling", () => { test("concurrent flush calls wait for pending flush", async () => { - let resolveTrack: () => void; - const trackPromise = new Promise((resolve) => { - resolveTrack = resolve; + let resolveRequest: () => void; + const requestPromise = new Promise((resolve) => { + resolveRequest = resolve; }); - const mockTrack = jest.fn().mockImplementation(() => trackPromise); + mockFetch.mockImplementation(() => + requestPromise.then(() => ({ ok: true })) + ); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); await queue.enqueue(createEvent()); @@ -411,21 +414,19 @@ describe("EventQueue", () => { // Start second flush (should wait for first) const flush2 = queue.flush(); - // Resolve the track call - resolveTrack!(); + // Resolve the request + resolveRequest!(); await Promise.all([flush1, flush2]); - // Should only call track once for the single event - expect(mockTrack).toHaveBeenCalledTimes(1); + // Should only call fetch once for the single event + expect(mockFetch).toHaveBeenCalledTimes(1); }); }); describe("Edge Cases - Large Payloads", () => { test("handles very large event properties", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); // Create event with very large properties (100KB+) const largeData = "x".repeat(100_000); @@ -446,21 +447,11 @@ describe("EventQueue", () => { await queue.enqueue(largeEvent); await queue.flush(); - expect(mockTrack).toHaveBeenCalledTimes(1); - expect(mockTrack).toHaveBeenCalledWith( - expect.objectContaining({ - event: "large_event", - properties: expect.objectContaining({ - bigString: largeData, - }), - }) - ); + expect(mockFetch).toHaveBeenCalledTimes(1); }); test("handles many small events efficiently", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { + const queue = new EventQueue("test-write-key", { flushAt: 100, maxQueueSize: 10_000_000, }); @@ -472,15 +463,12 @@ describe("EventQueue", () => { } // Should have auto-flushed 5 times (500 events / 100 flushAt = 5 flushes) - // Each flush sends 100 events individually - expect(mockTrack).toHaveBeenCalledTimes(eventCount); + expect(mockFetch).toHaveBeenCalledTimes(5); expect(queue.length).toBe(0); }); test("handles events with special characters in properties", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1 }); + const queue = new EventQueue("test-write-key", { flushAt: 1 }); const specialEvent = createEvent({ event: "special_chars_event", @@ -498,20 +486,11 @@ describe("EventQueue", () => { await queue.enqueue(specialEvent); - expect(mockTrack).toHaveBeenCalledWith( - expect.objectContaining({ - properties: expect.objectContaining({ - unicode: "🚀🎉💻", - quotes: 'He said "hello"', - }), - }) - ); + expect(mockFetch).toHaveBeenCalledTimes(1); }); test("handles events with null and undefined values in properties", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1 }); + const queue = new EventQueue("test-write-key", { flushAt: 1 }); const eventWithNulls = createEvent({ event: "nulls_event", @@ -528,17 +507,11 @@ describe("EventQueue", () => { await queue.enqueue(eventWithNulls); - expect(mockTrack).toHaveBeenCalledWith( - expect.objectContaining({ - event: "nulls_event", - }) - ); + expect(mockFetch).toHaveBeenCalledTimes(1); }); test("handles deeply nested properties", async () => { - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 1 }); + const queue = new EventQueue("test-write-key", { flushAt: 1 }); // Create deeply nested object (10 levels) let nested: Record = { value: "deepest" }; @@ -553,17 +526,15 @@ describe("EventQueue", () => { await queue.enqueue(deepEvent); - expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(1); }); }); describe("Graceful Shutdown Handlers", () => { test("registers beforeExit handler on construction", () => { const processOnSpy = jest.spyOn(process, "on"); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - new EventQueue(client); + new EventQueue("test-write-key"); expect(processOnSpy).toHaveBeenCalledWith( "beforeExit", @@ -573,10 +544,8 @@ describe("EventQueue", () => { test("registers SIGTERM handler on construction", () => { const processOnSpy = jest.spyOn(process, "on"); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - new EventQueue(client); + new EventQueue("test-write-key"); expect(processOnSpy).toHaveBeenCalledWith( "SIGTERM", @@ -586,10 +555,8 @@ describe("EventQueue", () => { test("registers SIGINT handler on construction", () => { const processOnSpy = jest.spyOn(process, "on"); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - new EventQueue(client); + new EventQueue("test-write-key"); expect(processOnSpy).toHaveBeenCalledWith("SIGINT", expect.any(Function)); }); @@ -604,23 +571,21 @@ describe("EventQueue", () => { return process; }); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); // Add some events await queue.enqueue(createEvent({ event: "pending_1" })); await queue.enqueue(createEvent({ event: "pending_2" })); expect(queue.length).toBe(2); - expect(mockTrack).not.toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); // Simulate beforeExit expect(beforeExitHandler).toBeDefined(); await beforeExitHandler!(); // Events should be flushed - expect(mockTrack).toHaveBeenCalledTimes(2); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(queue.length).toBe(0); }); @@ -637,9 +602,7 @@ describe("EventQueue", () => { return process; }); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); await queue.enqueue(createEvent({ event: "pending_sigterm" })); expect(queue.length).toBe(1); @@ -649,7 +612,7 @@ describe("EventQueue", () => { await sigtermHandler!(); // Events should be flushed and process.exit called - expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(0); }); @@ -666,9 +629,7 @@ describe("EventQueue", () => { return process; }); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - const queue = new EventQueue(client, { flushAt: 100 }); + const queue = new EventQueue("test-write-key", { flushAt: 100 }); await queue.enqueue(createEvent({ event: "pending_sigint" })); expect(queue.length).toBe(1); @@ -678,7 +639,7 @@ describe("EventQueue", () => { await sigintHandler!(); // Events should be flushed and process.exit called - expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(0); }); @@ -691,16 +652,27 @@ describe("EventQueue", () => { return process; }); - const mockTrack = jest.fn().mockResolvedValue({}); - const client = createMockClient(mockTrack); - new EventQueue(client, { flushAt: 100 }); + new EventQueue("test-write-key", { flushAt: 100 }); // Simulate beforeExit with empty queue expect(beforeExitHandler).toBeDefined(); await beforeExitHandler!(); - // Should not throw, should not call track - expect(mockTrack).not.toHaveBeenCalled(); + // Should not throw, should not call fetch + expect(mockFetch).not.toHaveBeenCalled(); + }); + }); + + describe("WriteKey Validation", () => { + test("throws error if writeKey is empty", () => { + expect(() => new EventQueue("")).toThrow("writeKey is required"); + }); + + test("throws error if writeKey is not a string", () => { + expect(() => new EventQueue(null as any)).toThrow("writeKey is required"); + expect(() => new EventQueue(undefined as any)).toThrow( + "writeKey is required" + ); }); }); }); From 6410982fcac801f58e364b30cb8c9e8ced8bcf54 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 15:00:32 +0700 Subject: [PATCH 04/11] tests: Consolidate integration tests for track/identify and batching --- test/__tests__/integration.test.ts | 270 +++++++++-------------------- 1 file changed, 78 insertions(+), 192 deletions(-) diff --git a/test/__tests__/integration.test.ts b/test/__tests__/integration.test.ts index d21c3b6..530b432 100644 --- a/test/__tests__/integration.test.ts +++ b/test/__tests__/integration.test.ts @@ -16,6 +16,7 @@ import { FormoAnalytics, ValidationError } from "../../src/FormoAnalytics"; // Test ID prefix for identifying test data const TEST_PREFIX = "TEST_SDK_NODE"; +const TEST_ID = "00000000-0000-0000-0000-000000000000"; // Skip all integration tests by default // Change to `describe` to run, or use: FORMO_WRITE_KEY=xxx pnpm run test:integration @@ -40,7 +41,7 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { beforeAll(() => { analytics = new FormoAnalytics(getWriteKey(), { - flushAt: 1, // Flush immediately for testing + flushAt: 10, // Batch events to reduce API calls retryCount: 2, }); }); @@ -50,220 +51,108 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { await analytics.flush(); }); - describe("Track Events", () => { - test("successfully tracks a basic event", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.track({ - anonymousId: testId, - event: "Integration Test - Basic Track", - properties: { - testId, - testType: "basic", - timestamp: new Date().toISOString(), - }, - }) - ).resolves.toBeUndefined(); - }); - - test("successfully tracks event with all optional fields", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.track({ - anonymousId: testId, - event: "Integration Test - Full Track", - userId: `${TEST_PREFIX}-user-${testId}`, - properties: { - testId, - revenue: 99.99, - currency: "USD", - points: 100, - items: ["item1", "item2"], - nested: { level1: { level2: "value" } }, - }, - context: { - ip: "127.0.0.1", - userAgent: "Integration Test", - }, - }) - ).resolves.toBeUndefined(); - }); - - test("successfully tracks event with Ethereum address", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.track({ - anonymousId: testId, - event: "Integration Test - With Address", - address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", // Vitalik's address - properties: { - testId, - hasAddress: true, - }, - }) - ).resolves.toBeUndefined(); - }); - - test("tracks event with lowercase address (auto-checksummed)", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.track({ - anonymousId: testId, - event: "Integration Test - Lowercase Address", - address: "0xab5801a7d398351b8be11c439e05c5b3259aec9b", // lowercase - properties: { testId }, - }) - ).resolves.toBeUndefined(); - }); - }); - - describe("Identify Events", () => { - test("successfully identifies a user", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.identify({ - anonymousId: testId, - userId: `${TEST_PREFIX}-user-${testId}`, - properties: { - email: `test-${testId}@example.com`, - name: "Integration Test User", - plan: "test", - createdAt: new Date().toISOString(), - }, - }) - ).resolves.toBeUndefined(); - }); - - test("successfully identifies user with Ethereum address", async () => { - const testId = "00000000-0000-0000-0000-000000000000"; - - await expect( - analytics.identify({ - anonymousId: testId, - userId: `user-${testId}`, - address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", - properties: { - walletConnected: true, - }, - }) - ).resolves.toBeUndefined(); - }); + // Combined test: sending 1 track and 1 identify = 2 events total + test("track and identify with all features", async () => { + // Track with all optional fields + await expect( + analytics.track({ + anonymousId: TEST_ID, + event: `${TEST_PREFIX} - Integration Test - Track`, + userId: `${TEST_PREFIX}-user`, + address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", // Vitalik's address + properties: { + testId: TEST_ID, + revenue: 99.99, + currency: "USD", + nested: { level1: { level2: "value" } }, + }, + context: { + userAgent: "Integration Test", + }, + }) + ).resolves.toBeUndefined(); + + // Identify with all optional fields + await expect( + analytics.identify({ + anonymousId: TEST_ID, + userId: `${TEST_PREFIX}-user`, + address: "0xab5801a7d398351b8be11c439e05c5b3259aec9b", // lowercase + properties: { + email: "test@example.com", + plan: "test", + createdAt: new Date().toISOString(), + }, + }) + ).resolves.toBeUndefined(); }); - describe("Validation (still enforced in integration)", () => { - test("rejects track with invalid anonymousId format", async () => { + // Validation tests (0 events sent, only local validation) + describe("Validation (local only)", () => { + test("rejects invalid inputs", async () => { + // Invalid anonymousId await expect( analytics.track({ anonymousId: "not-a-uuid", event: "Test Event", }) ).rejects.toThrow(ValidationError); - }); - test("successfully tracks with auto-generated anonymousId", async () => { + // Empty event name await expect( analytics.track({ - event: "Integration Test - Auto ID", - }) - ).resolves.toBeUndefined(); - }); - - test("rejects track with empty event name", async () => { - await expect( - analytics.track({ - anonymousId: "00000000-0000-0000-0000-000000000000", + anonymousId: TEST_ID, event: "", // Invalid }) ).rejects.toThrow(ValidationError); - }); - test("rejects track with invalid address", async () => { + // Invalid address await expect( analytics.track({ - anonymousId: "00000000-0000-0000-0000-000000000000", + anonymousId: TEST_ID, event: "Test Event", address: "not-a-valid-address", // Invalid }) ).rejects.toThrow(ValidationError); - }); - test("rejects identify with empty userId", async () => { + // Empty userId await expect( analytics.identify({ - anonymousId: "00000000-0000-0000-0000-000000000000", + anonymousId: TEST_ID, userId: "", // Invalid }) ).rejects.toThrow(ValidationError); }); - }); - - describe("Batching and Flush", () => { - test("batched events are sent on flush", async () => { - // Create a new analytics instance with higher batch threshold - const batchedAnalytics = new FormoAnalytics(getWriteKey(), { - flushAt: 10, // Higher threshold - flushInterval: 60000, // Long interval so we control flush - }); - - const testId = "00000000-0000-0000-0000-000000000000"; - // Queue multiple events (should not send immediately) - for (let i = 0; i < 5; i++) { - await batchedAnalytics.track({ - anonymousId: testId, - event: `Integration Test - Batch ${i}`, - properties: { batchIndex: i, testId }, - }); - } - - // Manually flush - this should send all events - await expect(batchedAnalytics.flush()).resolves.toBeUndefined(); + test("auto-generates anonymousId locally", async () => { + // This technically queues 1 event but we won't wait for flush unless we force it + // The test is just checking the promise resolution + await expect( + analytics.track({ + event: `${TEST_PREFIX} - Integration Test - Auto ID`, + }) + ).resolves.toBeUndefined(); }); + }); - test("auto-flushes when reaching batch threshold", async () => { - const batchedAnalytics = new FormoAnalytics(getWriteKey(), { - flushAt: 3, // Low threshold - }); - - const testId = "00000000-0000-0000-0000-000000000000"; - - // This should trigger an auto-flush after the 3rd event - for (let i = 0; i < 3; i++) { - await batchedAnalytics.track({ - anonymousId: testId, - event: `Integration Test - Auto Batch ${i}`, - properties: { batchIndex: i, testId }, - }); - } - - // If we got here without errors, the batch was sent successfully - expect(true).toBe(true); + // Batching test (sends 1 batch of 3 events = 3 events total) + test("batching and flush", async () => { + const batchedAnalytics = new FormoAnalytics(getWriteKey(), { + flushAt: 10, + flushInterval: 60000, }); - }); - describe("Error Handling", () => { - test("handles invalid write key gracefully", async () => { - const badAnalytics = new FormoAnalytics("invalid-write-key", { - flushAt: 1, - retryCount: 0, // No retries for faster test + // Queue 3 events + for (let i = 0; i < 3; i++) { + await batchedAnalytics.track({ + anonymousId: TEST_ID, + event: `${TEST_PREFIX} - Integration Test - Batch`, + properties: { index: i }, }); + } - // The SDK should either reject or handle the error gracefully - try { - await badAnalytics.track({ - anonymousId: "00000000-0000-0000-0000-000000000000", - event: "Should Fail", - }); - } catch (error) { - // Error is expected with invalid key - expect(error).toBeDefined(); - } - }); + // Manually flush - this should send all events in one batch + await expect(batchedAnalytics.flush()).resolves.toBeUndefined(); }); }); @@ -281,25 +170,24 @@ if (require.main === module) { process.exit(1); } - const analytics = new FormoAnalytics(writeKey, { flushAt: 1 }); - const testId = "00000000-0000-0000-0000-000000000000"; + const manualAnalytics = new FormoAnalytics(writeKey, { flushAt: 1 }); try { console.log("1. Tracking event..."); - await analytics.track({ - anonymousId: testId, - event: "Manual Integration Test", + await manualAnalytics.track({ + anonymousId: TEST_ID, + event: `${TEST_PREFIX} - Manual Integration Test`, properties: { - testId, + testId: TEST_ID, runAt: new Date().toISOString(), }, }); console.log(" ✅ Track event sent successfully"); console.log("\n2. Identifying user..."); - await analytics.identify({ - anonymousId: testId, - userId: `${TEST_PREFIX}-manual-test-user-${testId}`, + await manualAnalytics.identify({ + anonymousId: TEST_ID, + userId: `${TEST_PREFIX}-manual-test-user`, properties: { source: "manual-test", }, @@ -307,12 +195,10 @@ if (require.main === module) { console.log(" ✅ Identify event sent successfully"); console.log("\n3. Flushing..."); - await analytics.flush(); + await manualAnalytics.flush(); console.log(" ✅ Flush completed"); console.log("\n🎉 All manual integration tests passed!"); - console.log(` Test ID: ${testId}`); - console.log(" Check your Formo dashboard to verify events arrived."); } catch (error) { console.error("\n❌ Test failed:", error); process.exit(1); From 263b9f1e350b9cdcfb33d49ad68ca7b3bce8601a Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 15:05:57 +0700 Subject: [PATCH 05/11] fix: Update test script paths from `src/` to `test/` directory. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d4d5efe..beb9524 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "clean": "rm -rf dist", "prepublishOnly": "npm run clean && npm run build", "test": "jest", - "test:queue": "jest src/queue/EventQueue.test.ts", - "test:integration": "RUN_INTEGRATION_TESTS=true jest src/__tests__/integration.test.ts --runInBand" + "test:queue": "jest test/queue/EventQueue.test.ts", + "test:integration": "RUN_INTEGRATION_TESTS=true jest test/__tests__/integration.test.ts --runInBand" }, "dependencies": { "ethereum-cryptography": "^3.2.0" From e3fb39eaaecd66dda640ebcdbede22bc73e3182c Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 15:16:58 +0700 Subject: [PATCH 06/11] docs: Remove Stainless SDK generation details and enhance testing instructions and README examples. --- CONTRIBUTING.md | 138 +++++++++++++++++------------------------------- README.md | 18 +++---- 2 files changed, 56 insertions(+), 100 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9f0df4..e025a7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,6 @@ Thank you for your interest in contributing to the Formo Node SDK! This guide wi - [Project Structure](#project-structure) - [Setting Up the Development Environment](#setting-up-the-development-environment) -- [Stainless SDK Generation](#stainless-sdk-generation) - [Making Changes](#making-changes) - [Running Tests](#running-tests) - [Code Style](#code-style) @@ -15,18 +14,16 @@ Thank you for your interest in contributing to the Formo Node SDK! This guide wi ``` sdk-node/ -├── src/ # Main SDK source code (manually maintained) +├── src/ # Main SDK source code │ ├── FormoAnalytics.ts # Main SDK class │ ├── queue/ # Event batching and retry logic │ ├── types/ # TypeScript type definitions │ ├── utils/ # Utilities (address checksumming, etc.) │ └── validators/ # Input validation -├── sdks/ -│ └── sdk-server-side-typescript/ # Generated API client (via Stainless) -├── openapi.json # OpenAPI specification for the Formo API -├── .stainless/ # Stainless configuration -│ ├── stainless.yml # Stainless SDK configuration -│ └── workspace.json # Stainless workspace settings +├── test/ # Tests +│ ├── __tests__/ # Integration tests +│ └── queue/ # Unit tests for queue/events +├── scripts/ # Utility and test scripts └── package.json ``` @@ -53,118 +50,77 @@ sdk-node/ pnpm build ``` -## Stainless SDK Generation - -This project uses [Stainless](https://www.stainless.com/) to generate type-safe API clients from our OpenAPI specification. The generated code lives in `sdks/sdk-server-side-typescript/`. - -### Initial Setup - -If you need to set up Stainless for the first time: - -1. **Install the Stainless CLI:** - - ```bash - brew install stainless-api/tap/stl - ``` - -2. **Initialize Stainless in your project:** - - ```bash - stl init - ``` +## Making Changes - During initialization, you'll be prompted to: +The core SDK logic in `src/` is manually maintained. - - Select your OpenAPI specification file (`openapi.json`) - - Choose the target language(s) (TypeScript for this project) - - Configure output directories +Key components: -3. **Configuration files:** +- `FormoAnalytics.ts`: Main entry point and public API. +- `queue/`: Handles event buffering, batching, and retrying. +- `types/`: Use this for all shared interfaces and types. - The `.stainless/` directory is included in the repository and contains: +When making changes: - - `stainless.yml` - Main configuration file for SDK generation - - `workspace.json` - Workspace settings +1. Create a feature branch from `main`. +2. Make your changes with appropriate tests. +3. Ensure all unit tests pass: `npm test`. +4. If modifying API interactions, verify with integration tests (see below). +5. Submit a pull request. - Commit any changes to these files to ensure all contributors stay in sync. +## Running Tests -### Regenerating the SDK +### Unit Tests -When the OpenAPI specification (`openapi.json`) or Stainless configuration (`.stainless/stainless.yml`) is updated, you need to regenerate the SDK: +Run the full unit test suite: ```bash -# Commit your changes first -git add .stainless/stainless.yml openapi.json -git commit -m "Update API specification" - -# Create a new build on your current branch -stl builds create --branch $(git branch --show-current) +npm test ``` -Alternatively, use development mode to see live updates and errors: +Or just the queue/logic tests: ```bash -stl dev +npm run test:queue ``` -This will regenerate the files in `sdks/sdk-server-side-typescript/` based on the current OpenAPI spec and Stainless configuration. - -### Modifying Generated Code - -> **Important:** Most of the code in `sdks/sdk-server-side-typescript/` is auto-generated. +### Integration Tests -- Modifications to generated files may persist between generations but could result in merge conflicts. -- The generator will **never** modify the contents of `src/lib/` and `examples/` directories within the generated SDK. -- For custom logic, prefer adding code to the main `src/` directory rather than modifying generated files. +These tests make real network requests to the Formo API. You need a valid Write Key. -### Updating the OpenAPI Specification - -When making API changes: - -1. Update `openapi.json` with the new endpoints, schemas, or modifications -2. Update `.stainless/stainless.yml` if needed (e.g., new resources, methods, or examples) -3. Validate the configuration: `stl lint` -4. Commit your changes: `git add openapi.json .stainless/stainless.yml && git commit -m "Update API spec"` -5. Regenerate the SDK: `stl builds create --branch $(git branch --show-current)` -6. Test the changes thoroughly -7. Review and commit the regenerated SDK files in `sdks/sdk-server-side-typescript/` - -## Making Changes +```bash +FORMO_WRITE_KEY=your-key npm run test:integration +``` -### Main SDK Code (`src/`) +### Manual Testing Script -The core SDK logic in `src/` is manually maintained: +To verify functionality with a real script in a separate environment: -- `FormoAnalytics.ts` - Main entry point and public API -- `queue/` - Event batching, retry logic, and graceful shutdown -- `types/` - TypeScript interfaces and type definitions -- `utils/` - Helper functions for address checksumming, property normalization -- `validators/` - Input validation logic +1. Create a `.env` file with `FORMO_WRITE_KEY=your-key` +2. Run the manual test script: + ```bash + pnpm run script:test-analytics + ``` -When making changes: +### Testing Packaging -1. Create a feature branch from `main` -2. Make your changes with appropriate tests -3. Ensure all tests pass: `pnpm test` -4. Submit a pull request +To simulate consuming the package as a real user: -### Generated SDK Code (`sdks/`) +1. Create a tarball: -ForUpdate `.stainless/stainless.yml` if adding new methods or resources 3. Validate with `stl lint` 4. Commit: `git add openapi.json .stainless/stainless.yml && git commit -m "Update API"` 5. Regenerate the SDK: `stl builds create --branch $(git branch --show-current)` 6. Test the changes 7. Review and commit the regeneratednapi.json`with the required changes -2. Regenerate the SDK:`stainless generate` 3. Test the changes 4. Commit both files + ```bash + npm pack + ``` -## Running Tests + This creates a file like `formo-analytics-node-1.0.0.tgz`. -```bash -# Run all tests -pnpm test +2. Install it in another project: -# Run tests in watch mode -pnpm test:watch + ```bash + npm install /path/to/sdk-node/formo-analytics-node-1.0.0.tgz + ``` -# Run integration tests (requires API key) -FORMO_WRITE_KEY=your-key pnpm run test:integration -``` +3. Verify usage works as expected. ## Code Style diff --git a/README.md b/README.md index cbff0ac..ba8d5e8 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ FORMO_WRITE_KEY=your-write-key ``` 2. Create a test script (e.g., `scripts/test-analytics.ts`) to verify the SDK works correctly: -Below shows a sample test script: + Below shows a sample test script: ```typescript /** @@ -249,8 +249,8 @@ async function main() { console.log("Sending test track event..."); analytics.track({ - anonymousId: "00000000-0000-0000-0000-000000000000", // or a valid UUID - event: "SDK Backend Link Test", + anonymousId: "00000000-0000-0000-0000-000000000000", + event: "TEST_SDK_NODE Backend Link Test", properties: { source: "formono-backend", timestamp: timestamp, @@ -259,8 +259,8 @@ async function main() { }); console.log(" Event queued:", { - anonymousId: "00000000-0000-0000-0000-000000000000", // or a valid UUID - event: "SDK Backend Link Test", + anonymousId: "00000000-0000-0000-0000-000000000000", + event: "TEST_SDK_NODE Backend Link Test", properties: { source: "formono-backend", timestamp: timestamp, @@ -271,8 +271,8 @@ async function main() { console.log("\nSending test identify event..."); analytics.identify({ - anonymousId: "00000000-0000-0000-0000-000000000000", // or a valid UUID - userId: "test-user-backend", + anonymousId: "00000000-0000-0000-0000-000000000000", + userId: "TEST_SDK_NODE_test-user-backend", properties: { email: "test@formono-backend.local", source: "formono-backend", @@ -281,8 +281,8 @@ async function main() { }); console.log(" Identify event queued:", { - anonymousId: "00000000-0000-0000-0000-000000000000", // or a valid UUID - userId: "test-user-backend", + anonymousId: "00000000-0000-0000-0000-000000000000", + userId: "TEST_SDK_NODE_test-user-backend", properties: { email: "test@formono-backend.local", source: "formono-backend", From 7caa62e093e7c0061306ed705fe7549c768c6f49 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Mon, 29 Dec 2025 15:32:08 +0700 Subject: [PATCH 07/11] fix: Update Authorization header from Basic to Bearer in event queue and its tests. --- src/queue/EventQueue.ts | 2 +- test/queue/EventQueue.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/queue/EventQueue.ts b/src/queue/EventQueue.ts index 6535b88..1bc895f 100644 --- a/src/queue/EventQueue.ts +++ b/src/queue/EventQueue.ts @@ -149,7 +149,7 @@ export class EventQueue implements IEventQueue { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Basic ${this.writeKey}`, + Authorization: `Bearer ${this.writeKey}`, }, body: JSON.stringify(payloads), }); diff --git a/test/queue/EventQueue.test.ts b/test/queue/EventQueue.test.ts index 4b92603..d5faeeb 100644 --- a/test/queue/EventQueue.test.ts +++ b/test/queue/EventQueue.test.ts @@ -65,7 +65,7 @@ describe("EventQueue", () => { expect.any(String), expect.objectContaining({ headers: expect.objectContaining({ - Authorization: "Basic test-write-key", + Authorization: "Bearer test-write-key", }), }) ); From 0deeccf2483eea73fc2e3aec80311ef65833e0f9 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Tue, 30 Dec 2025 12:01:40 +0700 Subject: [PATCH 08/11] chore: make userId optional, address required for identify, update the docs --- CONTRIBUTING.md | 163 ++++++------- README.md | 373 ++--------------------------- package.json | 3 +- src/FormoAnalytics.ts | 8 +- src/test.ts | 15 +- src/types/index.ts | 15 +- src/validators/validate.ts | 18 +- test/__tests__/integration.test.ts | 7 +- 8 files changed, 125 insertions(+), 477 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e025a7c..1be0a25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,142 +1,119 @@ -# Contributing to Formo Node SDK +# How to contribute -Thank you for your interest in contributing to the Formo Node SDK! This guide will help you get started. +If you want to contribute or run a local version of the Formo Node SDK, follow these steps: -## Table of Contents +## Build the SDK Locally -- [Project Structure](#project-structure) -- [Setting Up the Development Environment](#setting-up-the-development-environment) -- [Making Changes](#making-changes) -- [Running Tests](#running-tests) -- [Code Style](#code-style) +Run the following command to build both CommonJS and ESM versions of the SDK: -## Project Structure - -``` -sdk-node/ -├── src/ # Main SDK source code -│ ├── FormoAnalytics.ts # Main SDK class -│ ├── queue/ # Event batching and retry logic -│ ├── types/ # TypeScript type definitions -│ ├── utils/ # Utilities (address checksumming, etc.) -│ └── validators/ # Input validation -├── test/ # Tests -│ ├── __tests__/ # Integration tests -│ └── queue/ # Unit tests for queue/events -├── scripts/ # Utility and test scripts -└── package.json +```bash +pnpm install +pnpm build +pnpm test ``` -## Setting Up the Development Environment +## Testing Locally -1. **Clone the repository:** +### Link the Local Package - ```bash - git clone - cd sdk-node - ``` +To test your SDK changes in a test app, you can link the package locally using `npm link` or `pnpm link`. -2. **Install dependencies:** +For example, if your projects are in the same directory: - This project uses [pnpm](https://pnpm.io/). Other package managers may work but are not officially supported. +``` +~/ +├── your-app/ +└── sdk-node/ +``` - ```bash - pnpm install - ``` +Run the following commands: -3. **Build the project:** +```bash +# In ~/your-app +pnpm link ../sdk-node +``` - ```bash - pnpm build - ``` +Or with npm: -## Making Changes +```bash +# In ~/your-app +npm link ../sdk-node +``` -The core SDK logic in `src/` is manually maintained. +### Apply Changes -Key components: +Any changes you make to your local package require rebuilding to be reflected: -- `FormoAnalytics.ts`: Main entry point and public API. -- `queue/`: Handles event buffering, batching, and retrying. -- `types/`: Use this for all shared interfaces and types. +```bash +# In ~/sdk-node +pnpm build +``` -When making changes: +The changes will automatically be available in the linked project. -1. Create a feature branch from `main`. -2. Make your changes with appropriate tests. -3. Ensure all unit tests pass: `npm test`. -4. If modifying API interactions, verify with integration tests (see below). -5. Submit a pull request. +### Unlink the Package -## Running Tests +To remove the link: -### Unit Tests +```bash +# In ~/your-app +pnpm unlink ../sdk-node +``` -Run the full unit test suite: +Or with npm: ```bash -npm test +# In ~/your-app +npm unlink ../sdk-node ``` -Or just the queue/logic tests: +## Running Tests + +Run the test suite: ```bash -npm run test:queue +pnpm test ``` ### Integration Tests -These tests make real network requests to the Formo API. You need a valid Write Key. +These tests make real network requests to the Formo API. You need a valid Write Key: ```bash -FORMO_WRITE_KEY=your-key npm run test:integration +FORMO_WRITE_KEY=your-key pnpm run test:integration ``` -### Manual Testing Script - -To verify functionality with a real script in a separate environment: +## Linting -1. Create a `.env` file with `FORMO_WRITE_KEY=your-key` -2. Run the manual test script: - ```bash - pnpm run script:test-analytics - ``` +Check code style and types: -### Testing Packaging +```bash +pnpm lint +``` -To simulate consuming the package as a real user: +## Publishing -1. Create a tarball: +1. **Update the version** using npm: ```bash - npm pack + npm version patch # For bug fixes + npm version minor # For new features + npm version major # For breaking changes ``` - This creates a file like `formo-analytics-node-1.0.0.tgz`. + This automatically: -2. Install it in another project: + - Updates `package.json` with the new version + - Creates a git commit with the change + - Creates a version tag (e.g., `v1.0.1`) + +2. **Push the commit and tag**: ```bash - npm install /path/to/sdk-node/formo-analytics-node-1.0.0.tgz + git push --follow-tags ``` -3. Verify usage works as expected. - -## Code Style - -This project uses: - -- [Prettier](https://prettier.io/) for code formatting -- [ESLint](https://eslint.org/) for linting - -```bash -# Check linting -pnpm lint - -# Fix linting and formatting issues -pnpm fix -``` - -## Questions? - -If you have questions or need help, please open an issue on GitHub. +3. **Automatic workflow execution**: + - GitHub Actions workflow triggers on the `v*` tag + - Builds and tests the package + - Publishes to npm diff --git a/README.md b/README.md index ba8d5e8..cd585de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ -# Formo Node SDK - -Server-side Node.js SDK for [Formo Analytics](https://formo.so). +

+

Formo Node SDK

+

+ Server-side analytics for Node.js applications. +
+ Website + · + Docs + · + Dashboard + · + Slack + · + X +

+

## Installation @@ -10,356 +23,14 @@ npm install @formo/analytics-node pnpm add @formo/analytics-node ``` -## Quick Start - -```typescript -import { FormoAnalytics } from "@formo/analytics-node"; - -const analytics = new FormoAnalytics("your-write-key"); - -// Track an event -await analytics.track({ - anonymousId: "device-uuid", - event: "Purchase Completed", - properties: { - orderId: "123", - total: 99.99, - currency: "USD", - }, -}); - -// Identify a user -await analytics.identify({ - anonymousId: "device-uuid", - userId: "user-123", - properties: { - email: "user@example.com", - plan: "premium", - }, -}); - -// Flush pending events before shutdown -await analytics.flush(); -``` - -## API Reference - -### `new FormoAnalytics(writeKey, options?)` - -Create a new analytics instance. - -| Option | Type | Default | Description | -| --------------- | -------- | -------- | -------------------------------- | -| `flushAt` | `number` | `20` | Flush when N events are queued | -| `flushInterval` | `number` | `30000` | Flush every N milliseconds | -| `maxQueueSize` | `number` | `500000` | Flush when queue exceeds N bytes | -| `retryCount` | `number` | `3` | Retry failed requests N times | - -```typescript -const analytics = new FormoAnalytics("your-write-key", { - flushAt: 10, - flushInterval: 10000, -}); -``` - -### `analytics.track(event)` - -Track a custom event. - -```typescript -await analytics.track({ - // Required - anonymousId: "device-uuid", // Device/session identifier - event: "Button Clicked", // Event name - - // Optional - userId: "user-123", // Your user identifier - properties: {}, // Event properties - address: "0x...", // Ethereum wallet address - context: {}, // Additional context -}); -``` - -### `analytics.identify(event)` - -Identify a user and their traits. - -```typescript -await analytics.identify({ - // Required - anonymousId: "device-uuid", // Device/session identifier - userId: "user-123", // Your user identifier - - // Optional - properties: {}, // User properties - address: "0x...", // Ethereum wallet address - context: {}, // Additional context -}); -``` - -### `analytics.flush()` - -Manually flush all pending events. - -```typescript -// Call before process exit to ensure all events are sent -await analytics.flush(); -``` - -## Ethereum Address Handling - -The SDK automatically validates and checksums Ethereum addresses using EIP-55: - -```typescript -await analytics.track({ - anonymousId: "device-uuid", - event: "Wallet Connected", - address: "0xab5801a7d398351b8be11c439e05c5b3259aec9b", // lowercase - // Stored as: 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B (checksummed) -}); -``` - -Invalid addresses will throw a `ValidationError`. - -## Error Handling - -```typescript -import { FormoAnalytics, ValidationError } from "@formo/analytics-node"; - -try { - await analytics.track({ - anonymousId: "", - event: "Test", - }); -} catch (error) { - if (error instanceof ValidationError) { - console.error(`Validation failed: ${error.field} - ${error.reason}`); - } -} -``` - -## Graceful Shutdown - -The SDK automatically registers handlers for `SIGTERM`, `SIGINT`, and `beforeExit` to flush pending events before process termination. - -For manual control: - -```typescript -process.on("SIGTERM", async () => { - await analytics.flush(); - process.exit(0); -}); -``` - -## Local Testing & Development - -To test SDK changes locally before publishing, you can link the SDK to another project. - -### Step 1: Link the SDK globally - -Clone this repository and link it globally: - -```bash -git clone -cd sdk-node -pnpm install -pnpm link --global -``` - -### Step 2: Add as dependency in your project - -In your backend project's `package.json`, add the SDK as a file dependency: - -```json -{ - "dependencies": { - "@formo/analytics-node": "file:/path/to/sdk-node" - } -} -``` - -Replace `/path/to/sdk-node` with the absolute path to your cloned SDK directory. - -### Step 3: Install dependencies - -```bash -cd your-backend-project -pnpm install -``` - -### Step 4: Test the SDK - -1. Create a `.env` file in your project: - -```env -FORMO_WRITE_KEY=your-write-key -``` - -2. Create a test script (e.g., `scripts/test-analytics.ts`) to verify the SDK works correctly: - Below shows a sample test script: - -```typescript -/** - * Analytics SDK Test Script - * - * This script tests the @formo/analytics-node SDK by sending a test track event. - * - * Usage: - * pnpm run script:test-analytics - * - * Environment Variables Required: - * - FORMO_WRITE_KEY: The write key from formo.so project settings - * - * Or pass the write key as an argument: - * pnpm run script:test-analytics - */ -import { config } from "dotenv"; - -// Load environment variables -config(); - -async function main() { - console.log("🧪 Testing @formo/analytics-node SDK...\n"); - - // Import directly from the linked SDK source - const { FormoAnalytics } = await import("@formo/analytics-node"); - - // Get write key from argument or environment variable - const args = process.argv.slice(2); - const writeKey = args[0] || process.env.FORMO_WRITE_KEY; - - if (!writeKey) { - console.error("❌ Error: No write key provided."); - console.error( - " Please provide a write key as an argument or set FORMO_WRITE_KEY environment variable." - ); - console.error("\n Usage: pnpm run script:test-analytics "); - process.exit(1); - } - - console.log(`Using write key: ${writeKey.substring(0, 8)}...`); - - // Initialize - const analytics = new FormoAnalytics(writeKey); - - console.log("✅ SDK initialized successfully\n"); - - // Send a test track event - const timestamp = new Date().toISOString(); - console.log("Sending test track event..."); - - analytics.track({ - anonymousId: "00000000-0000-0000-0000-000000000000", - event: "TEST_SDK_NODE Backend Link Test", - properties: { - source: "formono-backend", - timestamp: timestamp, - testId: `test-${Date.now()}`, - }, - }); - - console.log(" Event queued:", { - anonymousId: "00000000-0000-0000-0000-000000000000", - event: "TEST_SDK_NODE Backend Link Test", - properties: { - source: "formono-backend", - timestamp: timestamp, - }, - }); - - // Send a test identify event - console.log("\nSending test identify event..."); - - analytics.identify({ - anonymousId: "00000000-0000-0000-0000-000000000000", - userId: "TEST_SDK_NODE_test-user-backend", - properties: { - email: "test@formono-backend.local", - source: "formono-backend", - testTimestamp: timestamp, - }, - }); - - console.log(" Identify event queued:", { - anonymousId: "00000000-0000-0000-0000-000000000000", - userId: "TEST_SDK_NODE_test-user-backend", - properties: { - email: "test@formono-backend.local", - source: "formono-backend", - }, - }); - - // Flush to ensure the event is sent immediately - console.log("\n🔄 Flushing events..."); - - try { - await analytics.flush(); - console.log("✅ Events flushed successfully!\n"); - console.log("🎉 Test complete!"); - console.log(" ➡️ Check the formo.so Activity page to verify the event"); - console.log(" ➡️ Check Tinybird to confirm data ingestion\n"); - process.exit(0); - } catch (error) { - console.error("❌ Error flushing events:", error); - process.exit(1); - } -} - -// Run the script -main().catch((error) => { - console.error("❌ Fatal error:", error); - process.exit(1); -}); -``` - -Add a script to your `package.json`: - -```json -{ - "scripts": { - "script:test-analytics": "ts-node ./scripts/test-analytics.ts" - } -} -``` - -Run the test: - -```bash -# Using .env file -pnpm run script:test-analytics - -# Or passing as an argument -pnpm run script:test-analytics - -# Or passing as an environment variable -FORMO_WRITE_KEY= pnpm run script:test-analytics -``` - ---- - -## Development +## Configuration -### Running Tests +Visit Formo's [Developer Docs](https://docs.formo.so/sdks/server) for detailed guides on configuration, batching, and error handling. -```bash -# Unit tests -pnpm test +## Support -# Integration tests (requires API key) -FORMO_WRITE_KEY=your-key pnpm run test:integration -``` +Join the [Formo community Slack channel](https://formo.so/slack) for help and questions. -### Project Structure +## Contributing -``` -sdk-node/ -├── src/ -│ ├── FormoAnalytics.ts # Main SDK class -│ ├── queue/ # Event batching and retry logic -│ ├── types/ # TypeScript type definitions -│ ├── utils/ # Address checksumming, property normalization -│ └── validators/ # Input validation -├── sdks/ -│ └── sdk-server-side-typescript/ # Generated API client (Stainless) -└── package.json -``` +[Contributions](https://github.com/getformo/sdk-node/blob/main/CONTRIBUTING.md) are welcome! Feel free to open fixes and feature suggestions. diff --git a/package.json b/package.json index beb9524..8da3f64 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "prepublishOnly": "npm run clean && npm run build", "test": "jest", "test:queue": "jest test/queue/EventQueue.test.ts", - "test:integration": "RUN_INTEGRATION_TESTS=true jest test/__tests__/integration.test.ts --runInBand" + "test:integration": "RUN_INTEGRATION_TESTS=true jest test/__tests__/integration.test.ts --runInBand", + "lint": "tsc --noEmit" }, "dependencies": { "ethereum-cryptography": "^3.2.0" diff --git a/src/FormoAnalytics.ts b/src/FormoAnalytics.ts index 8a80bdc..63c30cf 100644 --- a/src/FormoAnalytics.ts +++ b/src/FormoAnalytics.ts @@ -39,7 +39,7 @@ export { ValidationError } from "./validators"; * * // Identify a user * await analytics.identify({ - * userId: "user-123", + * address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", * properties: { email: "user@example.com", plan: "premium" } * }); * @@ -115,10 +115,10 @@ export class FormoAnalytics { * Identify a user * * @param event - Identify event - * @param event.userId - Required. Your user identifier + * @param event.address - Required. Ethereum wallet address * @param event.anonymousId - Optional. Device/session identifier (generated if not provided) + * @param event.userId - Optional. Your user identifier * @param event.properties - Optional. User traits/properties - * @param event.address - Optional. Ethereum wallet address * @param event.context - Optional. Additional context * * @throws ValidationError if options are invalid @@ -140,7 +140,7 @@ export class FormoAnalytics { channel: "server", version: VERSION, anonymous_id: event.anonymousId!, - user_id: event.userId, + user_id: event.userId ?? "", properties: event.properties ?? {}, context: { library_name: LIBRARY_NAME, diff --git a/src/test.ts b/src/test.ts index d8e934f..c7e8864 100644 --- a/src/test.ts +++ b/src/test.ts @@ -61,13 +61,13 @@ async function main() { } } - // Test 4: Validation error - missing userId for identify - console.log("\nTest 4: Validation error (missing userId for identify)..."); + // Test 4: Validation error - missing address for identify + console.log("\nTest 4: Validation error (missing address for identify)..."); try { await analytics.identify({ anonymousId: "00000000-0000-0000-0000-000000000000", - userId: "", // Empty userId should fail - }); + userId: "user-123", + } as any); // intentionally missing required address to test validation console.log("❌ Should have thrown ValidationError"); } catch (err) { if (err instanceof ValidationError) { @@ -91,14 +91,15 @@ async function main() { console.log(`❌ Failed to track event without anonymousId: ${err}`); } - // Test 6: Identify with properties - console.log("\nTest 6: Identify with properties..."); + // Test 6: Identify with address and properties + console.log("\nTest 6: Identify with address and properties..."); try { await analytics.identify({ + address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", userId: "user-123", properties: { email: "test@example.com" }, }); - console.log("✅ Successfully identified user with properties"); + console.log("✅ Successfully identified user with address and properties"); } catch (err) { console.log(`❌ Failed identify with properties: ${err}`); } diff --git a/src/types/index.ts b/src/types/index.ts index bf04398..b874f49 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -22,12 +22,12 @@ export interface TrackAPIEvent { export interface IdentifyAPIEvent { // Required. - userId: string; // Your application's user identifier + address: Address; // Ethereum wallet address (validated and checksummed) // Optional anonymousId?: string; // Device/session identifier for anonymous tracking + userId?: string; // Your application's user identifier properties?: IFormoEventProperties; // User properties - address?: Address; // Ethereum wallet address (validated and checksummed) context?: IFormoEventContext; // contextual information } @@ -45,16 +45,7 @@ export interface AnalyticsOptions { export type EventChannel = "web" | "mobile" | "server"; -export type EventType = - | "identify" - | "track"; - // | "page" - // | "connect" - // | "disconnect" - // | "detect" - // | "chain" - // | "signature" - // | "transaction"; +export type EventType = "identify" | "track"; /** * Contextual information about the event diff --git a/src/validators/validate.ts b/src/validators/validate.ts index 58b08ab..3193b84 100644 --- a/src/validators/validate.ts +++ b/src/validators/validate.ts @@ -19,7 +19,10 @@ export type { TrackAPIEvent, IdentifyAPIEvent } from "../types"; * Provides clear error messages for invalid inputs */ export class ValidationError extends Error { - constructor(public readonly field: string, public readonly reason: string) { + constructor( + public readonly field: string, + public readonly reason: string + ) { super(`Invalid ${field}: ${reason}`); this.name = "ValidationError"; } @@ -95,9 +98,12 @@ export function validateIdentifyEvent(event: IdentifyAPIEvent): void { throw new ValidationError("anonymousId", "must be a valid UUID"); } - // Required: userId must be a non-empty string - if (!isNonEmptyString(event.userId)) { - throw new ValidationError("userId", "must be a non-empty string"); + // Optional: userId must be a non-empty string if provided + if (!isNullOrUndefined(event.userId) && !isNonEmptyString(event.userId)) { + throw new ValidationError( + "userId", + "must be a non-empty string if provided" + ); } // Optional: properties must be an object if provided @@ -110,8 +116,8 @@ export function validateIdentifyEvent(event: IdentifyAPIEvent): void { throw new ValidationError("context", "must be an object if provided"); } - // Optional: address must be a valid Ethereum address if provided - if (!isNullOrUndefined(event.address) && !isAddress(event.address)) { + // Required: address must be a valid Ethereum address + if (!isAddress(event.address)) { throw new ValidationError( "address", "must be a valid Ethereum address (0x followed by 40 hex characters)" diff --git a/test/__tests__/integration.test.ts b/test/__tests__/integration.test.ts index 530b432..ab512a9 100644 --- a/test/__tests__/integration.test.ts +++ b/test/__tests__/integration.test.ts @@ -115,12 +115,12 @@ integrationDescribe("FormoAnalytics Integration Tests", () => { }) ).rejects.toThrow(ValidationError); - // Empty userId + // Missing address for identify await expect( analytics.identify({ anonymousId: TEST_ID, - userId: "", // Invalid - }) + userId: `${TEST_PREFIX}-user`, + } as any) // Missing required address ).rejects.toThrow(ValidationError); }); @@ -186,6 +186,7 @@ if (require.main === module) { console.log("\n2. Identifying user..."); await manualAnalytics.identify({ + address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", anonymousId: TEST_ID, userId: `${TEST_PREFIX}-manual-test-user`, properties: { From 4ca81a491c06310d234f916ced91d102056886c0 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Tue, 30 Dec 2025 12:26:12 +0700 Subject: [PATCH 09/11] chore: switch to npm for dependency management --- package-lock.json | 12 +- pnpm-lock.yaml | 2839 --------------------------------------------- 2 files changed, 8 insertions(+), 2843 deletions(-) delete mode 100644 pnpm-lock.yaml diff --git a/package-lock.json b/package-lock.json index 40858ee..2ba6a8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,14 @@ { - "name": "sdk-node", + "name": "@formo/analytics-node", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "@formo/analytics-node", + "version": "1.0.0", "dependencies": { - "ethereum-cryptography": "^3.2.0", - "typescript": "^5.9.3" + "ethereum-cryptography": "^3.2.0" }, "devDependencies": { "@swc/core": "^1.3.102", @@ -14,7 +16,8 @@ "@types/jest": "^29.4.0", "@types/node": "^25.0.3", "jest": "^29.4.0", - "ts-jest": "^29.1.0" + "ts-jest": "^29.1.0", + "typescript": "^5.9.3" } }, "node_modules/@babel/code-frame": { @@ -4629,6 +4632,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 8e0da1c..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2839 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - ethereum-cryptography: - specifier: ^3.2.0 - version: 3.2.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - devDependencies: - '@swc/core': - specifier: ^1.3.102 - version: 1.15.7 - '@swc/jest': - specifier: ^0.2.29 - version: 0.2.39(@swc/core@1.15.7) - '@types/jest': - specifier: ^29.4.0 - version: 29.5.14 - '@types/node': - specifier: ^25.0.3 - version: 25.0.3 - jest: - specifier: ^29.4.0 - version: 29.7.0(@types/node@25.0.3) - ts-jest: - specifier: ^29.1.0 - version: 29.4.6(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.0.3))(typescript@5.9.3) - -packages: - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/create-cache-key-function@30.2.0': - resolution: {integrity: sha512-44F4l4Enf+MirJN8X/NhdGkl71k5rBYiwdVlo4HxOwbu0sHV8QKrGEedb1VUU4K3W7fBKE0HGfbn7eZm0Ti3zg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/pattern@30.0.1': - resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/schemas@30.0.5': - resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@30.2.0': - resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.0': - resolution: {integrity: sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/bip32@1.7.0': - resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - - '@scure/bip39@1.6.0': - resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sinclair/typebox@0.34.41': - resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - - '@swc/core-darwin-arm64@1.15.7': - resolution: {integrity: sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - - '@swc/core-darwin-x64@1.15.7': - resolution: {integrity: sha512-ZAFuvtSYZTuXPcrhanaD5eyp27H8LlDzx2NAeVyH0FchYcuXf0h5/k3GL9ZU6Jw9eQ63R1E8KBgpXEJlgRwZUQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-linux-arm-gnueabihf@1.15.7': - resolution: {integrity: sha512-K3HTYocpqnOw8KcD8SBFxiDHjIma7G/X+bLdfWqf+qzETNBrzOub/IEkq9UaeupaJiZJkPptr/2EhEXXWryS/A==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.15.7': - resolution: {integrity: sha512-HCnVIlsLnCtQ3uXcXgWrvQ6SAraskLA9QJo9ykTnqTH6TvUYqEta+TdTdGjzngD6TOE7XjlAiUs/RBtU8Z0t+Q==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-musl@1.15.7': - resolution: {integrity: sha512-/OOp9UZBg4v2q9+x/U21Jtld0Wb8ghzBScwhscI7YvoSh4E8RALaJ1msV8V8AKkBkZH7FUAFB7Vbv0oVzZsezA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-x64-gnu@1.15.7': - resolution: {integrity: sha512-VBbs4gtD4XQxrHuQ2/2+TDZpPQQgrOHYRnS6SyJW+dw0Nj/OomRqH+n5Z4e/TgKRRbieufipeIGvADYC/90PYQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-musl@1.15.7': - resolution: {integrity: sha512-kVuy2unodso6p0rMauS2zby8/bhzoGRYxBDyD6i2tls/fEYAE74oP0VPFzxIyHaIjK1SN6u5TgvV9MpyJ5xVug==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-win32-arm64-msvc@1.15.7': - resolution: {integrity: sha512-uddYoo5Xmo1XKLhAnh4NBIyy5d0xk33x1sX3nIJboFySLNz878ksCFCZ3IBqrt1Za0gaoIWoOSSSk0eNhAc/sw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.15.7': - resolution: {integrity: sha512-rqq8JjNMLx3QNlh0aPTtN/4+BGLEHC94rj9mkH1stoNRf3ra6IksNHMHy+V1HUqElEgcZyx+0yeXx3eLOTcoFw==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-x64-msvc@1.15.7': - resolution: {integrity: sha512-4BK06EGdPnuplgcNhmSbOIiLdRgHYX3v1nl4HXo5uo4GZMfllXaCyBUes+0ePRfwbn9OFgVhCWPcYYjMT6hycQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core@1.15.7': - resolution: {integrity: sha512-kTGB8XI7P+pTKW83tnUEDVP4zduF951u3UAOn5eTi0vyW6MvL56A3+ggMdfuVFtDI0/DsbSzf5z34HVBbuScWw==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '>=0.5.17' - peerDependenciesMeta: - '@swc/helpers': - optional: true - - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - - '@swc/jest@0.2.39': - resolution: {integrity: sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==} - engines: {npm: '>= 7.0.0'} - peerDependencies: - '@swc/core': '*' - - '@swc/types@0.1.25': - resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - - '@types/jest@29.5.14': - resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - - '@types/node@25.0.3': - resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - babel-preset-current-node-syntax@1.2.0: - resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} - peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - baseline-browser-mapping@2.9.11: - resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} - hasBin: true - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001761: - resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.3: - resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - 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==} - - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - dedent@1.7.1: - resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - ethereum-cryptography@3.2.0: - resolution: {integrity: sha512-Urr5YVsalH+Jo0sYkTkv1MyI9bLYZwW8BENZCeE1QYaTHETEYx0Nv/SVsWkSqpYrzweg6d8KMY1wTjH/1m/BIg==} - engines: {node: ^14.21.3 || >=16, npm: '>=9'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=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} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - - imurmurhash@0.1.4: - 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==} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-regex-util@30.0.1: - resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - 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'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - path-exists@4.0.0: - 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'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - ts-jest@29.4.6: - resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - 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 - esbuild: '*' - 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 - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.5': {} - - '@babel/core@7.28.5': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.5': - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.5 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - - '@babel/parser@7.28.5': - dependencies: - '@babel/types': 7.28.5 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - - '@babel/traverse@7.28.5': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@bcoe/v8-coverage@0.2.3': {} - - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.2 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.3': {} - - '@jest/console@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - '@jest/core@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.0.3) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/create-cache-key-function@30.2.0': - dependencies: - '@jest/types': 30.2.0 - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - jest-mock: 29.7.0 - - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 - - '@jest/expect@29.7.0': - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 25.0.3 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - '@jest/globals@29.7.0': - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/pattern@30.0.1': - dependencies: - '@types/node': 25.0.3 - jest-regex-util: 30.0.1 - - '@jest/reporters@29.7.0': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.0.3 - chalk: 4.1.2 - collect-v8-coverage: 1.0.3 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.2.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jest/schemas@30.0.5': - dependencies: - '@sinclair/typebox': 0.34.41 - - '@jest/source-map@29.6.3': - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - '@jest/test-result@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.3 - - '@jest/test-sequencer@29.7.0': - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 - - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.28.5 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.31 - babel-plugin-istanbul: 6.1.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: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - '@jest/types@29.6.3': - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 25.0.3 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - - '@jest/types@30.2.0': - 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': 25.0.3 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@noble/ciphers@1.3.0': {} - - '@noble/curves@1.9.0': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.8.0': {} - - '@scure/base@1.2.6': {} - - '@scure/bip32@1.7.0': - dependencies: - '@noble/curves': 1.9.0 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/bip39@1.6.0': - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@sinclair/typebox@0.27.8': {} - - '@sinclair/typebox@0.34.41': {} - - '@sinonjs/commons@3.0.1': - dependencies: - type-detect: 4.0.8 - - '@sinonjs/fake-timers@10.3.0': - dependencies: - '@sinonjs/commons': 3.0.1 - - '@swc/core-darwin-arm64@1.15.7': - optional: true - - '@swc/core-darwin-x64@1.15.7': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.15.7': - optional: true - - '@swc/core-linux-arm64-gnu@1.15.7': - optional: true - - '@swc/core-linux-arm64-musl@1.15.7': - optional: true - - '@swc/core-linux-x64-gnu@1.15.7': - optional: true - - '@swc/core-linux-x64-musl@1.15.7': - optional: true - - '@swc/core-win32-arm64-msvc@1.15.7': - optional: true - - '@swc/core-win32-ia32-msvc@1.15.7': - optional: true - - '@swc/core-win32-x64-msvc@1.15.7': - optional: true - - '@swc/core@1.15.7': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.25 - optionalDependencies: - '@swc/core-darwin-arm64': 1.15.7 - '@swc/core-darwin-x64': 1.15.7 - '@swc/core-linux-arm-gnueabihf': 1.15.7 - '@swc/core-linux-arm64-gnu': 1.15.7 - '@swc/core-linux-arm64-musl': 1.15.7 - '@swc/core-linux-x64-gnu': 1.15.7 - '@swc/core-linux-x64-musl': 1.15.7 - '@swc/core-win32-arm64-msvc': 1.15.7 - '@swc/core-win32-ia32-msvc': 1.15.7 - '@swc/core-win32-x64-msvc': 1.15.7 - - '@swc/counter@0.1.3': {} - - '@swc/jest@0.2.39(@swc/core@1.15.7)': - dependencies: - '@jest/create-cache-key-function': 30.2.0 - '@swc/core': 1.15.7 - '@swc/counter': 0.1.3 - jsonc-parser: 3.3.1 - - '@swc/types@0.1.25': - dependencies: - '@swc/counter': 0.1.3 - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.28.5 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.28.5 - - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 25.0.3 - - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@3.0.4': - dependencies: - '@types/istanbul-lib-report': 3.0.3 - - '@types/jest@29.5.14': - dependencies: - expect: 29.7.0 - pretty-format: 29.7.0 - - '@types/node@25.0.3': - dependencies: - undici-types: 7.16.0 - - '@types/stack-utils@2.0.3': {} - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-regex@5.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - babel-jest@29.7.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.5) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.28.0 - - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - - babel-preset-jest@29.6.3(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) - - balanced-match@1.0.2: {} - - baseline-browser-mapping@2.9.11: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.9.11 - caniuse-lite: 1.0.30001761 - electron-to-chromium: 1.5.267 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - bs-logger@0.2.6: - dependencies: - fast-json-stable-stringify: 2.1.0 - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - - buffer-from@1.1.2: {} - - callsites@3.1.0: {} - - camelcase@5.3.1: {} - - camelcase@6.3.0: {} - - caniuse-lite@1.0.30001761: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - char-regex@1.0.2: {} - - ci-info@3.9.0: {} - - cjs-module-lexer@1.4.3: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - co@4.6.0: {} - - collect-v8-coverage@1.0.3: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - - convert-source-map@2.0.0: {} - - create-jest@29.7.0(@types/node@25.0.3): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@25.0.3) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - dedent@1.7.1: {} - - deepmerge@4.3.1: {} - - detect-newline@3.1.0: {} - - diff-sequences@29.6.3: {} - - electron-to-chromium@1.5.267: {} - - emittery@0.13.1: {} - - emoji-regex@8.0.0: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - escalade@3.2.0: {} - - escape-string-regexp@2.0.0: {} - - esprima@4.0.1: {} - - ethereum-cryptography@3.2.0: - dependencies: - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.0 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - exit@0.1.2: {} - - expect@29.7.0: - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - - fast-json-stable-stringify@2.1.0: {} - - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-package-type@0.1.0: {} - - get-stream@6.0.1: {} - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - graceful-fs@4.2.11: {} - - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - - has-flag@4.0.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - html-escaper@2.0.2: {} - - human-signals@2.1.0: {} - - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - - 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-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-fn@2.1.0: {} - - is-number@7.0.0: {} - - is-stream@2.0.1: {} - - isexe@2.0.0: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jest-changed-files@29.7.0: - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - jest-circus@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.7.1 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-cli@29.7.0(@types/node@25.0.3): - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@25.0.3) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@25.0.3) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-config@29.7.0(@types/node@25.0.3): - dependencies: - '@babel/core': 7.28.5 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.5) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 25.0.3 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-docblock@29.7.0: - dependencies: - detect-newline: 3.1.0 - - jest-each@29.7.0: - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 - - jest-environment-node@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - jest-get-type@29.6.3: {} - - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 25.0.3 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - - jest-leak-detector@29.7.0: - dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-message-util@29.7.0: - dependencies: - '@babel/code-frame': 7.27.1 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - jest-util: 29.7.0 - - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 - - jest-regex-util@29.6.3: {} - - jest-regex-util@30.0.1: {} - - jest-resolve-dependencies@29.7.0: - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - jest-resolve@29.7.0: - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.11 - resolve.exports: 2.0.3 - slash: 3.0.0 - - jest-runner@29.7.0: - dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - - jest-runtime@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - chalk: 4.1.2 - cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.3 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - jest-snapshot@29.7.0: - dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - '@babel/types': 7.28.5 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - - jest-util@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - jest-validate@29.7.0: - dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 - - jest-watcher@29.7.0: - dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 25.0.3 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 - - jest-worker@29.7.0: - dependencies: - '@types/node': 25.0.3 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - jest@29.7.0(@types/node@25.0.3): - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@25.0.3) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - js-tokens@4.0.0: {} - - js-yaml@3.14.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - jsesc@3.1.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json5@2.2.3: {} - - jsonc-parser@3.3.1: {} - - kleur@3.0.3: {} - - leven@3.1.0: {} - - lines-and-columns@1.2.4: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - lodash.memoize@4.1.2: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - make-dir@4.0.0: - dependencies: - semver: 7.7.3 - - make-error@1.3.6: {} - - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - - merge-stream@2.0.0: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mimic-fn@2.1.0: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimist@1.2.8: {} - - ms@2.1.3: {} - - natural-compare@1.4.0: {} - - neo-async@2.6.2: {} - - node-int64@0.4.0: {} - - node-releases@2.0.27: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - 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 - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - pirates@4.0.7: {} - - pkg-dir@4.2.0: - dependencies: - find-up: 4.1.0 - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - pure-rand@6.1.0: {} - - react-is@18.3.1: {} - - require-directory@2.1.1: {} - - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - - resolve-from@5.0.0: {} - - resolve.exports@2.0.3: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - semver@6.3.1: {} - - semver@7.7.3: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@3.0.7: {} - - sisteransi@1.0.5: {} - - slash@3.0.0: {} - - source-map-support@0.5.13: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - sprintf-js@1.0.3: {} - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-bom@4.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-json-comments@3.1.1: {} - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - - tmpl@1.0.5: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - ts-jest@29.4.6(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@30.2.0)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.0.3))(typescript@5.9.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.8 - jest: 29.7.0(@types/node@25.0.3) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.7.3 - type-fest: 4.41.0 - typescript: 5.9.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.28.5 - '@jest/transform': 29.7.0 - '@jest/types': 30.2.0 - babel-jest: 29.7.0(@babel/core@7.28.5) - jest-util: 29.7.0 - - type-detect@4.0.8: {} - - type-fest@0.21.3: {} - - type-fest@4.41.0: {} - - typescript@5.9.3: {} - - uglify-js@3.19.3: - optional: true - - undici-types@7.16.0: {} - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 - - walker@1.0.8: - dependencies: - makeerror: 1.0.12 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wordwrap@1.0.0: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrappy@1.0.2: {} - - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - y18n@5.0.8: {} - - yallist@3.1.1: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} From 0ac7098f02b7e77ceca305e574fb6fe9b71a2960 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Tue, 30 Dec 2025 12:51:42 +0700 Subject: [PATCH 10/11] fix: default user_id to null instead of an empty string when undefined --- src/FormoAnalytics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FormoAnalytics.ts b/src/FormoAnalytics.ts index 63c30cf..0b0b56a 100644 --- a/src/FormoAnalytics.ts +++ b/src/FormoAnalytics.ts @@ -140,7 +140,7 @@ export class FormoAnalytics { channel: "server", version: VERSION, anonymous_id: event.anonymousId!, - user_id: event.userId ?? "", + user_id: event.userId ?? null, properties: event.properties ?? {}, context: { library_name: LIBRARY_NAME, From 00c0d7d1f79ea3335e3bde00aebc72e275bd43c3 Mon Sep 17 00:00:00 2001 From: Htet Shwe Date: Tue, 30 Dec 2025 12:56:48 +0700 Subject: [PATCH 11/11] feat: add MIT license file and declare it in package.json --- LICENSE | 21 +++++++++++++++++++++ package-lock.json | 1 + package.json | 1 + 3 files changed, 23 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4aaceff --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Formo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2ba6a8d..7400703 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@formo/analytics-node", "version": "1.0.0", + "license": "MIT", "dependencies": { "ethereum-cryptography": "^3.2.0" }, diff --git a/package.json b/package.json index 8da3f64..9c945dd 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "@formo/analytics-node", "version": "1.0.0", "main": "dist/cjs/src/index.js", + "license": "MIT", "module": "dist/esm/src/index.js", "types": "dist/cjs/src/index.d.ts", "scripts": {