From 1c2872a0c6f1b2c8cf00b335ad8dbbb378ff2838 Mon Sep 17 00:00:00 2001
From: Htet Shwe
Date: Mon, 29 Dec 2025 08:55:29 +0700
Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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: {}