From 4f0710b7fced6283802db59bb525389d47b4116b Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 20:32:28 +0300 Subject: [PATCH 01/50] feat: add LLM rules --- AGENTS.md | 24 ++++++++++++++++++++++++ CLAUDE.md | 1 + 2 files changed, 25 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..59b1542 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,24 @@ +# AGENTS.md + +## Project Overview +This repo contains `@adapty/core`, platform-agnostic JS/TS code shared by Adapty +React Native and Capacitor SDKs. The package is public on npm but treated as an +internal dependency (dev builds are mainly for SDK CI). + +## Build Output +- Publish compiled JS only: ESM + CJS + `.d.ts`. +- Do not publish raw TypeScript as the only artifact (no build-on-install). + +## Versioning +- Dev builds use `0.0.0-dev.` (full commit SHA). +- Downstream SDKs should pin the exact dev version when testing CI. + +## CI Publishing Policy +- Registry: npmjs.com (public). +- `dev` branch: publish after merge, using dist-tag `dev`. +- `master` branch: publish only on git tag (release), default dist-tag `latest`. + +## Tooling +- Use Yarn for install/build scripts. +- Use npm for publish. +- Build tool: `tsdown`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..eef4bd2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md \ No newline at end of file From e129cd42f4c550dab93964d0e6124d369ac52d57 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:35:32 +0300 Subject: [PATCH 02/50] feat: init package --- .eslintrc.js | 18 + .gitignore | 33 + .prettierrc.json | 8 + jest.config.js | 15 + package.json | 68 + src/index.ts | 8 + tsconfig.json | 53 + tsdown.config.ts | 14 + yarn.lock | 3351 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 3568 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 jest.config.js create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json create mode 100644 tsdown.config.ts create mode 100644 yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a681fc8 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,18 @@ +module.exports = { + root: true, + extends: ['prettier'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + rules: { + 'no-console': 'error', + }, + + "overrides": [ + { + "files": ["scripts/**/*.js"], + "rules": { + "no-console": "off" + } + } + ] +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9935272 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +*.tsbuildinfo + +# Testing +coverage/ +.nyc_output/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.cursor/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment +.env +.env.local +.env.*.local diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..6bf50f2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 2, + "arrowParens": "always" +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..3ba007c --- /dev/null +++ b/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.test.ts', + '!src/**/*.spec.ts', + '!src/**/__tests__/**', + ], + moduleFileExtensions: ['ts', 'js', 'json'], + coverageDirectory: 'coverage', + verbose: true, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e7dd18c --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json.schemastore.org/package", + "name": "@adapty/core", + "version": "0.0.0-dev.0000000000000000000000000000000000000000", + "description": "Platform-agnostic core for Adapty JS SDKs", + "license": "MIT", + "author": "Adapty team ", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist/", + "!dist/**/*.test.*", + "!dist/**/*.spec.*" + ], + "sideEffects": false, + "scripts": { + "build": "tsdown", + "clean": "rm -rf dist .tsbuildinfo", + "prebuild": "yarn clean", + "lint": "eslint ./src --ext .ts", + "format": "prettier ./src -w", + "format-check": "prettier ./src --check", + "test": "jest", + "tsc": "tsc --noEmit" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "@types/jest": "^29.5.3", + "eslint": "8.46.0", + "eslint-config-prettier": "^8.8.0", + "jest": "^29.3.1", + "prettier": "^3.0.0", + "ts-jest": "^29.1.1", + "tsdown": "^0.9.0", + "typescript": "^5.9.3" + }, + "dependencies": { + "tslib": "^2.5.0" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/adaptyteam/AdaptySDK-JS-Core.git" + }, + "homepage": "https://adapty.io/docs/", + "bugs": { + "url": "https://github.com/adaptyteam/AdaptySDK-JS-Core/issues" + }, + "keywords": [ + "adapty", + "sdk", + "core", + "subscriptions", + "in-app-purchases" + ] +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f27e635 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,8 @@ +/** + * @adapty/core - Platform-agnostic core for Adapty SDKs + * + * This is the main entry point for the library. + * Add your exports here as you develop the SDK. + */ + +export const version = '0.0.0-dev.0000000000000000000000000000000000000000'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bb2c2ec --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "include": ["src"], + "compilerOptions": { + /* Language and Environment */ + "target": "ES2020", + "lib": ["ES2020"], + + /* Modules */ + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + + /* Emit */ + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "removeComments": false, + "importHelpers": true, + + /* Interop Constraints */ + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + + /* Type Checking - Strict Mode */ + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "alwaysStrict": true, + + /* Type Checking - Additional Checks */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + + /* Completeness */ + "skipLibCheck": true + } +} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000..ed7c235 --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: ['./src/index.ts'], + format: ['esm', 'cjs'], + dts: true, + outDir: 'dist', + fixedExtension: true, + platform: 'neutral', + target: 'es2020', + clean: true, + skipNodeModulesBundle: true, + treeshake: true, +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7f5da24 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3351 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" + integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" + integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.1", "@babel/generator@^7.28.6", "@babel/generator@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" + integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== + dependencies: + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.0", "@babel/parser@^7.27.1", "@babel/parser@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + dependencies: + "@babel/types" "^7.28.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" + integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@emnapi/core@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" + integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" + integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/eslintrc@^2.1.1": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@^8.46.0": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.11.10": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + 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" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + 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.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + 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.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@napi-rs/wasm-runtime@^0.2.4", "@napi-rs/wasm-runtime@^0.2.9": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@oxc-project/types@0.66.0": + version "0.66.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.66.0.tgz#170b7e3bc317a9baa5019de6f2a231b8c1428977" + integrity sha512-KF5Wlo2KzQ+jmuCtrGISZoUfdHom7qHavNfPLW2KkeYJfYMGwtiia8KjwtsvNJ49qRiXImOCkPeVPd4bMlbR7w== + +"@oxc-resolver/binding-darwin-arm64@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-9.0.2.tgz#345ff11258dbec11d333bf088a79b864f5f03ec5" + integrity sha512-MVyRgP2gzJJtAowjG/cHN3VQXwNLWnY+FpOEsyvDepJki1SdAX/8XDijM1yN6ESD1kr9uhBKjGelC6h3qtT+rA== + +"@oxc-resolver/binding-darwin-x64@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-9.0.2.tgz#cd19f263a31b601356002ebd2eb8dda193753704" + integrity sha512-7kV0EOFEZ3sk5Hjy4+bfA6XOQpCwbDiDkkHN4BHHyrBHsXxUR05EcEJPPL1WjItefg+9+8hrBmoK0xRoDs41+A== + +"@oxc-resolver/binding-freebsd-x64@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-9.0.2.tgz#e6e0b9b9409cb4eb71307ca880f1d868cce88c94" + integrity sha512-6OvkEtRXrt8sJ4aVfxHRikjain9nV1clIsWtJ1J3J8NG1ZhjyJFgT00SCvqxbK+pzeWJq6XzHyTCN78ML+lY2w== + +"@oxc-resolver/binding-linux-arm-gnueabihf@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-9.0.2.tgz#e4ef8e0a831fa05101ca8fc0501cd7b536093e66" + integrity sha512-aYpNL6o5IRAUIdoweW21TyLt54Hy/ZS9tvzNzF6ya1ckOQ8DLaGVPjGpmzxdNja9j/bbV6aIzBH7lNcBtiOTkQ== + +"@oxc-resolver/binding-linux-arm64-gnu@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-9.0.2.tgz#c95345cf91b9597a469a8d3028d0b182e2e22055" + integrity sha512-RGFW4vCfKMFEIzb9VCY0oWyyY9tR1/o+wDdNePhiUXZU4SVniRPQaZ1SJ0sUFI1k25pXZmzQmIP6cBmazi/Dew== + +"@oxc-resolver/binding-linux-arm64-musl@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-9.0.2.tgz#d968aadd446a6c1d729764f3ddd0e1b66e3ec53f" + integrity sha512-lxx/PibBfzqYvut2Y8N2D0Ritg9H8pKO+7NUSJb9YjR/bfk2KRmP8iaUz3zB0JhPtf/W3REs65oKpWxgflGToA== + +"@oxc-resolver/binding-linux-riscv64-gnu@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-9.0.2.tgz#698f364bf15489e69c8441c842a09a468c7389ca" + integrity sha512-yD28ptS/OuNhwkpXRPNf+/FvrO7lwURLsEbRVcL1kIE0GxNJNMtKgIE4xQvtKDzkhk6ZRpLho5VSrkkF+3ARTQ== + +"@oxc-resolver/binding-linux-s390x-gnu@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-9.0.2.tgz#dfcbf0531f0ade1197275f40e6f8900635af187c" + integrity sha512-WBwEJdspoga2w+aly6JVZeHnxuPVuztw3fPfWrei2P6rNM5hcKxBGWKKT6zO1fPMCB4sdDkFohGKkMHVV1eryQ== + +"@oxc-resolver/binding-linux-x64-gnu@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-9.0.2.tgz#73f7157f3b052c44c206b672b970da060125c533" + integrity sha512-a2z3/cbOOTUq0UTBG8f3EO/usFcdwwXnCejfXv42HmV/G8GjrT4fp5+5mVDoMByH3Ce3iVPxj1LmS6OvItKMYQ== + +"@oxc-resolver/binding-linux-x64-musl@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-9.0.2.tgz#2b047ce69cb7fa1900174d43e4a7b6027146a449" + integrity sha512-bHZF+WShYQWpuswB9fyxcgMIWVk4sZQT0wnwpnZgQuvGTZLkYJ1JTCXJMtaX5mIFHf69ngvawnwPIUA4Feil0g== + +"@oxc-resolver/binding-wasm32-wasi@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-9.0.2.tgz#df8eab1815ae0da0c70f0f9dda4bcd84c70d7024" + integrity sha512-I5cSgCCh5nFozGSHz+PjIOfrqW99eUszlxKLgoNNzQ1xQ2ou9ZJGzcZ94BHsM9SpyYHLtgHljmOZxCT9bgxYNA== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.9" + +"@oxc-resolver/binding-win32-arm64-msvc@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-9.0.2.tgz#9530c2e08ebb5d02870b004135ef0b4438a620b7" + integrity sha512-5IhoOpPr38YWDWRCA5kP30xlUxbIJyLAEsAK7EMyUgqygBHEYLkElaKGgS0X5jRXUQ6l5yNxuW73caogb2FYaw== + +"@oxc-resolver/binding-win32-x64-msvc@9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-9.0.2.tgz#15dc9cecd2e89bbcad07247477fc04dd8c3ffbbe" + integrity sha512-Qc40GDkaad9rZksSQr2l/V9UubigIHsW69g94Gswc2sKYB3XfJXfIfyV8WTJ67u6ZMXsZ7BH1msSC6Aen75mCg== + +"@oxc-transform/binding-darwin-arm64@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-darwin-arm64/-/binding-darwin-arm64-0.67.0.tgz#8039adae60bc4e4e081471ae928a878c48412cb0" + integrity sha512-P3zBMhpOQceNSys3/ZqvrjuRvcIbVzfGFN/tH34HlVkOjOmfGK1mOWjORsGAZtbgh1muXrF6mQETLzFjfYndXQ== + +"@oxc-transform/binding-darwin-x64@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-darwin-x64/-/binding-darwin-x64-0.67.0.tgz#66d60973795491c9affd877db4e89422abd65b82" + integrity sha512-B52aeo/C3spYHcwFQ4nAbDkwbMKf0K6ncWM8GrVUgGu8PPECLBhjPCW11kPW/lt9FxwrdgVYVzPYlZ6wmJmpEA== + +"@oxc-transform/binding-linux-arm-gnueabihf@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.67.0.tgz#7bb3cbb5ef986ef90f02b968f7c5eb72d0e805d1" + integrity sha512-5Ir1eQrC9lvj/rR1TJVGwOR4yLgXTLmfKHIfpVH7GGSQrzK7VMUfHWX+dAsX1VutaeE8puXIqtYvf9cHLw78dw== + +"@oxc-transform/binding-linux-arm64-gnu@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.67.0.tgz#322791422b9f6b3bbe1165d2b81c1e09e709d0e1" + integrity sha512-zTqfPET5+hZfJ3/dMqJboKxrpXMXk+j2HVdvX0wVhW2MI7n7hwELl+In6Yu20nXuEyJkNQlWHbNPCUfpM+cBWw== + +"@oxc-transform/binding-linux-arm64-musl@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.67.0.tgz#181515ca26efbedb7e63342360afb3aba0b3a5fd" + integrity sha512-jzz/ATUhZ8wetb4gm5GwzheZns3Qj1CZ+DIMmD8nBxQXszmTS/fqnAPpgzruyLqkXBUuUfF3pHv5f/UmuHReuQ== + +"@oxc-transform/binding-linux-x64-gnu@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.67.0.tgz#03f1304e511fd14a4916f97c2d3859716ec03970" + integrity sha512-Qy2+tfglJ8yX6guC1EDAnuuzRZIXciXO9UwOewxyiahLxwuTpj/wvvZN3Cb1SA3c14zrwb2TNMZvaXS1/OS5Pg== + +"@oxc-transform/binding-linux-x64-musl@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-linux-x64-musl/-/binding-linux-x64-musl-0.67.0.tgz#eb78b814434050f1ed21ddd9d89eaa15b5a96249" + integrity sha512-tHoYgDIRhgvh+/wIrzAk3cUoj/LSSoJAdsZW9XRlaixFW/TF2puxRyaS1hRco0bcKTwotXl/eDYqZmhIfUyGRQ== + +"@oxc-transform/binding-wasm32-wasi@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-wasm32-wasi/-/binding-wasm32-wasi-0.67.0.tgz#515266a609da6dab4189db4622a41ead25678851" + integrity sha512-ZPT+1HECf7WUnotodIuS8tvSkwaiCdC2DDw8HVRmlerbS6iPYIPKyBCvkSM4RyUx0kljZtB9AciLCkEbwy5/zA== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.9" + +"@oxc-transform/binding-win32-arm64-msvc@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.67.0.tgz#31272c97ee7738d871a558d75e9eedea37e283ae" + integrity sha512-+E3lOHCk4EuIk6IjshBAARknAUpgH+gHTtZxCPqK4AWYA+Tls2J6C0FVM48uZ4m3rZpAq8ZszM9JZVAkOaynBQ== + +"@oxc-transform/binding-win32-x64-msvc@0.67.0": + version "0.67.0" + resolved "https://registry.yarnpkg.com/@oxc-transform/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.67.0.tgz#c6f05980e52bddd5d988f3f6301e38f4a3524ecb" + integrity sha512-3pIIFb9g5aFrAODTQVJYitq+ONHgDJ4IYk/7pk+jsG6JpKUkURd0auUlxvriO11fFit5hdwy+wIbU4kBvyRUkg== + +"@quansync/fs@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@quansync/fs/-/fs-1.0.0.tgz#17131b1f1c261fcfb63893272c488df89c73f48f" + integrity sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ== + dependencies: + quansync "^1.0.0" + +"@rolldown/binding-darwin-arm64@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.8-commit.151352b.tgz#3c54631c77476ebe1a48b49e320fd47dd5612c15" + integrity sha512-2F4bhDtV6CHBx7JMiT9xvmxkcZLHFmonfbli36RyfvgThDOAu92bis28zDTdguDY85lN/jBRKX/eOvX+T5hMkg== + +"@rolldown/binding-darwin-x64@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.8-commit.151352b.tgz#5ec4db55ef699617ef0e74bb19d8c3f74a5c289d" + integrity sha512-8VMChhFLeD/oOAQUspFtxZaV7ctDob63w626kwvBBIHtlpY2Ohw4rsfjjtGckyrTCI/RROgZv/TVVEsG3GkgLw== + +"@rolldown/binding-freebsd-x64@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.8-commit.151352b.tgz#b3cbaf5d563dfee20a81a65a8ed36986131147cc" + integrity sha512-4W28EgaIidbWIpwB3hESMBfiOSs7LBFpJGa8JIV488qLEnTR/pqzxDEoOPobhRSJ1lJlv0vUgA8+DKBIldo2gw== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.8-commit.151352b.tgz#98d50a8f4f0c62916115602c3653c3797256d5d3" + integrity sha512-1ECtyzIKlAHikR7BhS4hk7Hxw8xCH6W3S+Sb74EM0vy5AqPvWSbgLfAwagYC7gNDcMMby3I757X7qih5fIrGiw== + +"@rolldown/binding-linux-arm64-gnu@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.8-commit.151352b.tgz#eca5021af23268e080b14af76f59c868faf185ef" + integrity sha512-wU1kp8qPRUKC8N82dNs3F5+UyKRww9TUEO5dQ5mxCb0cG+y4l5rVaXpMgvL0VuQahPVvTMs577QPhJGb4iDONw== + +"@rolldown/binding-linux-arm64-musl@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.8-commit.151352b.tgz#a25b5a37b0a2a05edbd852c5ddeeb318bbb5cd1d" + integrity sha512-odDjO2UtEEMAzwmLHEOKylJjQa+em1REAO9H19PA+O+lPu6evVbre5bqu8qCjEtHG1Q034LpZR86imCP2arb/w== + +"@rolldown/binding-linux-x64-gnu@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.8-commit.151352b.tgz#df7e313afaf0aa80c07c951d1a900e0d4159c1d8" + integrity sha512-Ty2T67t2Oj1lg417ATRENxdk8Jkkksc/YQdCJyvkGqteHe60pSU2GGP/tLWGB+I0Ox+u387bzU/SmfmrHZk9aw== + +"@rolldown/binding-linux-x64-musl@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.8-commit.151352b.tgz#2bdb3ed2f7ca9aeaa7fdbaf3ce8825ee32a84d8e" + integrity sha512-Fm1TxyeVE+gy74HM26CwbEOUndIoWAMgWkVDxYBD64tayvp5JvltpGHaqCg6x5i+X2F5XCDCItqwVlC7/mTxIw== + +"@rolldown/binding-wasm32-wasi@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.8-commit.151352b.tgz#c0d9e6e269b38acd342ab43cab7e7ef5b61cc2a1" + integrity sha512-AEZzTyGerfkffXmtv7kFJbHWkryNeolk0Br+yhH1wZyN6Tt6aebqICDL8KNRO2iExoEWzyYS6dPxh0QmvNTfUQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.4" + +"@rolldown/binding-win32-arm64-msvc@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.8-commit.151352b.tgz#c1c1ab5b5bcdf8b06c65b09c3b7eba42d941d49f" + integrity sha512-0lskDFKQwf5PMjl17qHAroU6oVU0Zn8NbAH/PdM9QB1emOzyFDGa20d4kESGeo3Uq7xOKXcTORJV/JwKIBORqw== + +"@rolldown/binding-win32-ia32-msvc@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.8-commit.151352b.tgz#acbe151252efa2594b884fe6073fd99dd113f670" + integrity sha512-DfG1S0zGKnUfr95cNCmR4YPiZ/moS7Tob5eV+9r5JGeHZVWFHWwvJdR0jArj6Ty0LbBFDTVVB3iAvqRSji+l0Q== + +"@rolldown/binding-win32-x64-msvc@1.0.0-beta.8-commit.151352b": + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.8-commit.151352b.tgz#e55f5d93d7da0ca40a07f50a57aedaad7757dd64" + integrity sha512-5HZEtc8U2I1O903hXBynWtWaf+qzAFj66h5B7gOtVcvqIk+lKRVSupA85OdIvR7emrsYU25ikpfiU5Jhg9kTbQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.3": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.1.0.tgz#95cc584f1f478301efc86de4f1867e5875e83571" + integrity sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA== + dependencies: + undici-types "~7.16.0" + +"@types/semver@^7.3.12": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.59.6": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.59.6": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@valibot/to-json-schema@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@valibot/to-json-schema/-/to-json-schema-1.0.0.tgz#69ce115d3b2c3d924a784f36247aff1949ad4daf" + integrity sha512-/9crJgPptVsGCL6X+JPDQyaJwkalSZ/52WuF8DiRUxJgcmpNdzYRfZ+gqMEP8W3CTVfuMWPqqvIgfwJ97f9Etw== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.15.0, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansis@^3.17.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" + integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +ast-kit@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ast-kit/-/ast-kit-1.4.3.tgz#030f1bfb55bd72d426dc1d0ba82a8de5c75acd7c" + integrity sha512-MdJqjpodkS5J149zN0Po+HPshkTdUyrvF7CKTafUgv69vBSPtncrj+3IiUgqdd7ElIEkbeXCsEouBUwLrw9Ilg== + dependencies: + "@babel/parser" "^7.27.0" + pathe "^2.0.3" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.9.0: + version "2.9.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" + integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001759: + version "1.0.30001766" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz#b6f6b55cb25a2d888d9393104d14751c6a7d6f7a" + integrity sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +consola@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.1.tgz#364661eea3d73f3faba7089214420ec2f8f13e15" + integrity sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defu@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" + integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dts-resolver@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/dts-resolver/-/dts-resolver-1.2.0.tgz#1495d8319f28e41a5369c6d6d05c2cb8ba790ad1" + integrity sha512-+xNF7raXYI1E3IFB+f3JqvoKYFI8R+1Mh9mpI75yNm3F5XuiC6ErEXe2Lqh9ach+4MQ1tOefzjxulhWGVclYbg== + dependencies: + oxc-resolver "^9.0.0" + pathe "^2.0.3" + +electron-to-chromium@^1.5.263: + version "1.5.279" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz#67dfdeb22fd81412d0d18d1d9b2c749e9b8945cb" + integrity sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +empathic@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/empathic/-/empathic-1.1.0.tgz#a0de7dcaab07695bcab54117116d44c92b89e79f" + integrity sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.8.0: + version "8.10.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz#0642e53625ebc62c31c24726b0f050df6bd97a2e" + integrity sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@8.46.0: + version "8.46.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" + integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.1" + "@eslint/js" "^8.46.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.2" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + 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-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-tsconfig@^4.10.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" + integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hookable@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" + integrity sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.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.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + 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.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + 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" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + 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" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + 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" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + 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" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + 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.5.3" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.3.1: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +jiti@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lightningcss-android-arm64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz#609ff48332adff452a8157a7c2842fd692a8eac4" + integrity sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg== + +lightningcss-darwin-arm64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz#a13da040a7929582bab3ace9a67bdc146e99fc2d" + integrity sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg== + +lightningcss-darwin-x64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz#f7482c311273571ec0c2bd8277c1f5f6e90e03a4" + integrity sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA== + +lightningcss-freebsd-x64@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz#91df1bb290f1cb7bb2af832d7d0d8809225e0124" + integrity sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A== + +lightningcss-linux-arm-gnueabihf@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz#c3cad5ae8b70045f21600dc95295ab6166acf57e" + integrity sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g== + +lightningcss-linux-arm64-gnu@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz#a5c4f6a5ac77447093f61b209c0bd7fef1f0a3e3" + integrity sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg== + +lightningcss-linux-arm64-musl@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz#af26ab8f829b727ada0a200938a6c8796ff36900" + integrity sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg== + +lightningcss-linux-x64-gnu@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz#a891d44e84b71c0d88959feb9a7522bbf61450ee" + integrity sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA== + +lightningcss-linux-x64-musl@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz#8c8b21def851f4d477fa897b80cb3db2b650bc6e" + integrity sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA== + +lightningcss-win32-arm64-msvc@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz#79000fb8c57e94a91b8fc643e74d5a54407d7080" + integrity sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w== + +lightningcss-win32-x64-msvc@1.31.1: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz#7f025274c81c7d659829731e09c8b6f442209837" + integrity sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw== + +lightningcss@^1.29.3: + version "1.31.1" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.31.1.tgz#1a19dd327b547a7eda1d5c296ebe1e72df5a184b" + integrity sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.31.1" + lightningcss-darwin-arm64 "1.31.1" + lightningcss-darwin-x64 "1.31.1" + lightningcss-freebsd-x64 "1.31.1" + lightningcss-linux-arm-gnueabihf "1.31.1" + lightningcss-linux-arm64-gnu "1.31.1" + lightningcss-linux-arm64-musl "1.31.1" + lightningcss-linux-x64-gnu "1.31.1" + lightningcss-linux-x64-musl "1.31.1" + lightningcss-win32-arm64-msvc "1.31.1" + lightningcss-win32-x64-msvc "1.31.1" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.30.17: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +oxc-resolver@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-9.0.2.tgz#0a86ee1e26f6c3f5f2af73dece276f8f588d4ef8" + integrity sha512-w838ygc1p7rF+7+h5vR9A+Y9Fc4imy6C3xPthCMkdFUgFvUWkmABeNB8RBDQ6+afk44Q60/UMMQ+gfDUW99fBA== + optionalDependencies: + "@oxc-resolver/binding-darwin-arm64" "9.0.2" + "@oxc-resolver/binding-darwin-x64" "9.0.2" + "@oxc-resolver/binding-freebsd-x64" "9.0.2" + "@oxc-resolver/binding-linux-arm-gnueabihf" "9.0.2" + "@oxc-resolver/binding-linux-arm64-gnu" "9.0.2" + "@oxc-resolver/binding-linux-arm64-musl" "9.0.2" + "@oxc-resolver/binding-linux-riscv64-gnu" "9.0.2" + "@oxc-resolver/binding-linux-s390x-gnu" "9.0.2" + "@oxc-resolver/binding-linux-x64-gnu" "9.0.2" + "@oxc-resolver/binding-linux-x64-musl" "9.0.2" + "@oxc-resolver/binding-wasm32-wasi" "9.0.2" + "@oxc-resolver/binding-win32-arm64-msvc" "9.0.2" + "@oxc-resolver/binding-win32-x64-msvc" "9.0.2" + +oxc-transform@^0.67.0: + version "0.67.0" + resolved "https://registry.yarnpkg.com/oxc-transform/-/oxc-transform-0.67.0.tgz#2a1933368eee95decc544dcd4de10d7e63f324fc" + integrity sha512-QXwmpLfNrXZoHgIjEtDEf6lhwmvHouNtstNgg/UveczVIjo8VSzd5h25Ea96PoX9KzReJUY/qYa4QSNkJpZGfA== + optionalDependencies: + "@oxc-transform/binding-darwin-arm64" "0.67.0" + "@oxc-transform/binding-darwin-x64" "0.67.0" + "@oxc-transform/binding-linux-arm-gnueabihf" "0.67.0" + "@oxc-transform/binding-linux-arm64-gnu" "0.67.0" + "@oxc-transform/binding-linux-arm64-musl" "0.67.0" + "@oxc-transform/binding-linux-x64-gnu" "0.67.0" + "@oxc-transform/binding-linux-x64-musl" "0.67.0" + "@oxc-transform/binding-wasm32-wasi" "0.67.0" + "@oxc-transform/binding-win32-arm64-msvc" "0.67.0" + "@oxc-transform/binding-win32-x64-msvc" "0.67.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.0.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +quansync@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/quansync/-/quansync-1.0.0.tgz#1c29acccd544cd68d97a7350c5099e0a9bc7e5ee" + integrity sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + dependencies: + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rolldown-plugin-dts@^0.9.4: + version "0.9.11" + resolved "https://registry.yarnpkg.com/rolldown-plugin-dts/-/rolldown-plugin-dts-0.9.11.tgz#1ab4c0824cc1eb00f3d59ce2d89d96343a0fe7b0" + integrity sha512-iCIRKmvPLwRV4UKSxhaBo+5wDkvc3+MFiqYYvu7sGLSohzxoDn9WEsjN3y7A6xg3aCuxHh6rlRp8xbX98r1rSg== + dependencies: + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + ast-kit "^1.4.3" + debug "^4.4.0" + dts-resolver "^1.0.1" + get-tsconfig "^4.10.0" + oxc-transform "^0.67.0" + +rolldown@1.0.0-beta.8-commit.151352b: + version "1.0.0-beta.8-commit.151352b" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-beta.8-commit.151352b.tgz#0cf9935a53ff2622369e02ac2b7e899a83468b32" + integrity sha512-TCb6GVaFBk4wB0LERofFDxTO5X1/Sgahr7Yn5UA9XjuFtCwL1CyEhUHX5lUIstcMxjbkLjn2z4TAGwisr6Blvw== + dependencies: + "@oxc-project/types" "0.66.0" + "@valibot/to-json-schema" "1.0.0" + ansis "^3.17.0" + valibot "1.0.0" + optionalDependencies: + "@rolldown/binding-darwin-arm64" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-darwin-x64" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-freebsd-x64" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-linux-arm64-gnu" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-linux-arm64-musl" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-linux-x64-gnu" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-linux-x64-musl" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-wasm32-wasi" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-win32-arm64-msvc" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-win32-ia32-msvc" "1.0.0-beta.8-commit.151352b" + "@rolldown/binding-win32-x64-msvc" "1.0.0-beta.8-commit.151352b" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tinyexec@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" + integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== + +tinyglobby@^0.2.13: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-jest@^29.1.1: + version "29.4.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9" + integrity sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.3" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +tsdown@^0.9.0: + version "0.9.9" + resolved "https://registry.yarnpkg.com/tsdown/-/tsdown-0.9.9.tgz#1d3046b2638a134b52d943e0ba3048b00acf5f71" + integrity sha512-IIGX55rkhaPomNSVrIbA58DRBwTO4ehlDTsw20XSooGqoEZbwpunDc1dRE73wKb1rHdwwBO6NMLOcgV2n1qhpA== + dependencies: + ansis "^3.17.0" + cac "^6.7.14" + chokidar "^4.0.3" + consola "^3.4.2" + debug "^4.4.0" + diff "^7.0.0" + empathic "^1.0.0" + hookable "^5.5.3" + lightningcss "^1.29.3" + rolldown "1.0.0-beta.8-commit.151352b" + rolldown-plugin-dts "^0.9.4" + tinyexec "^1.0.1" + tinyglobby "^0.2.13" + unconfig "^7.3.2" + unplugin-lightningcss "^0.3.3" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.4.0, tslib@^2.5.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typescript@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +unconfig-core@7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/unconfig-core/-/unconfig-core-7.4.2.tgz#20b676d6b57a04bade4b96da65c783d25e8b286c" + integrity sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg== + dependencies: + "@quansync/fs" "^1.0.0" + quansync "^1.0.0" + +unconfig@^7.3.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/unconfig/-/unconfig-7.4.2.tgz#ac4c0e0e6c9586196b5f9ba440c5167c59a65643" + integrity sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ== + dependencies: + "@quansync/fs" "^1.0.0" + defu "^6.1.4" + jiti "^2.6.1" + quansync "^1.0.0" + unconfig-core "7.4.2" + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +unplugin-lightningcss@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/unplugin-lightningcss/-/unplugin-lightningcss-0.3.3.tgz#74987b08bfe5a548b8d17f083981658ccb719d36" + integrity sha512-mMNRCNIcxc/3410w7sJdXcPxn0IGZdEpq42OBDyckdGkhOeWYZCG9RkHs72TFyBsS82a4agFDOFU8VrFKF2ZvA== + dependencies: + lightningcss "^1.29.3" + magic-string "^0.30.17" + unplugin "^2.3.2" + +unplugin@^2.3.2: + version "2.3.11" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.11.tgz#411e020dd2ba90e2fbe1e7bd63a5a399e6ee3b54" + integrity sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww== + dependencies: + "@jridgewell/remapping" "^2.3.5" + acorn "^8.15.0" + picomatch "^4.0.3" + webpack-virtual-modules "^0.6.2" + +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +valibot@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.0.0.tgz#1f82514296abcd23d0ae4280088ba85f8651c564" + integrity sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +webpack-virtual-modules@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" + integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From f834419cd44b3f1630081e744a2cc6c0687fadee Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:38:25 +0300 Subject: [PATCH 03/50] fix lint --- .eslintrc.js => .eslintrc.cjs | 0 jest.config.js => jest.config.cjs | 0 package.json | 7 +++++-- 3 files changed, 5 insertions(+), 2 deletions(-) rename .eslintrc.js => .eslintrc.cjs (100%) rename jest.config.js => jest.config.cjs (100%) diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/jest.config.js b/jest.config.cjs similarity index 100% rename from jest.config.js rename to jest.config.cjs diff --git a/package.json b/package.json index e7dd18c..26c13ed 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,13 @@ "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.mts", "exports": { ".": { - "types": "./dist/index.d.ts", + "types": { + "import": "./dist/index.d.mts", + "require": "./dist/index.d.cts" + }, "import": "./dist/index.mjs", "require": "./dist/index.cjs" } From 07201cd3175784e7292f9a85986fac007a86206a Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:41:16 +0300 Subject: [PATCH 04/50] feat: update linter to match ts --- package.json | 8 +- yarn.lock | 359 ++++++++++++++++++++++----------------------------- 2 files changed, 161 insertions(+), 206 deletions(-) diff --git a/package.json b/package.json index 26c13ed..2a034ee 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,11 @@ "tsc": "tsc --noEmit" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.6", + "@typescript-eslint/eslint-plugin": "^8.21.0", + "@typescript-eslint/parser": "^8.21.0", "@types/jest": "^29.5.3", - "eslint": "8.46.0", - "eslint-config-prettier": "^8.8.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "jest": "^29.3.1", "prettier": "^3.0.0", "ts-jest": "^29.1.1", diff --git a/yarn.lock b/yarn.lock index 7f5da24..d775719 100644 --- a/yarn.lock +++ b/yarn.lock @@ -292,19 +292,19 @@ dependencies: tslib "^2.4.0" -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.12.2", "@eslint-community/regexpp@^4.6.1": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== -"@eslint/eslintrc@^2.1.1": +"@eslint/eslintrc@^2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== @@ -319,17 +319,17 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.46.0": +"@eslint/js@8.57.1": version "8.57.1" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@humanwhocodes/config-array@^0.11.10": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== dependencies: - "@humanwhocodes/object-schema" "^2.0.2" + "@humanwhocodes/object-schema" "^2.0.3" debug "^4.3.1" minimatch "^3.0.5" @@ -338,7 +338,7 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.2": +"@humanwhocodes/object-schema@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== @@ -602,12 +602,12 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -901,11 +901,6 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - "@types/node@*": version "25.1.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-25.1.0.tgz#95cc584f1f478301efc86de4f1867e5875e83571" @@ -913,11 +908,6 @@ dependencies: undici-types "~7.16.0" -"@types/semver@^7.3.12": - version "7.7.1" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" - integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== - "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -935,89 +925,106 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.6": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== +"@typescript-eslint/eslint-plugin@^8.21.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz#d8899e5c2eccf5c4a20d01c036a193753748454d" + integrity sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/type-utils" "8.54.0" + "@typescript-eslint/utils" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.4.0" + +"@typescript-eslint/parser@^8.21.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.54.0.tgz#3d01a6f54ed247deb9982621f70e7abf1810bd97" + integrity sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA== + dependencies: + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.54.0.tgz#f582aceb3d752544c8e1b11fea8d95d00cf9adc6" + integrity sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.54.0" + "@typescript-eslint/types" "^8.54.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz#307dc8cbd80157e2772c2d36216857415a71ab33" + integrity sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg== + dependencies: + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" + +"@typescript-eslint/tsconfig-utils@8.54.0", "@typescript-eslint/tsconfig-utils@^8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz#71dd7ba1674bd48b172fc4c85b2f734b0eae3dbc" + integrity sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw== + +"@typescript-eslint/type-utils@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz#64965317dd4118346c2fa5ee94492892200e9fb9" + integrity sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA== + dependencies: + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" + "@typescript-eslint/utils" "8.54.0" + debug "^4.4.3" + ts-api-utils "^2.4.0" + +"@typescript-eslint/types@8.54.0", "@typescript-eslint/types@^8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.54.0.tgz#c12d41f67a2e15a8a96fbc5f2d07b17331130889" + integrity sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA== + +"@typescript-eslint/typescript-estree@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz#3c7716905b2b811fadbd2114804047d1bfc86527" + integrity sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA== + dependencies: + "@typescript-eslint/project-service" "8.54.0" + "@typescript-eslint/tsconfig-utils" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" + debug "^4.4.3" + minimatch "^9.0.5" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + +"@typescript-eslint/utils@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.54.0.tgz#c79a4bcbeebb4f571278c0183ed1cb601d84c6c8" + integrity sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA== dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.59.6": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== - dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== - dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== +"@typescript-eslint/visitor-keys@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz#0e4b50124b210b8600b245dd66cbad52deb15590" + integrity sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" + "@typescript-eslint/types" "8.54.0" + eslint-visitor-keys "^4.2.1" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== "@valibot/to-json-schema@1.0.0": version "1.0.0" @@ -1093,11 +1100,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - ast-kit@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/ast-kit/-/ast-kit-1.4.3.tgz#030f1bfb55bd72d426dc1d0ba82a8de5c75acd7c" @@ -1187,6 +1189,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1347,7 +1356,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -1394,13 +1403,6 @@ diff@^7.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -1458,18 +1460,10 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.8.0: - version "8.10.2" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz#0642e53625ebc62c31c24726b0f050df6bd97a2e" - integrity sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A== - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" +eslint-config-prettier@^9.1.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz#90deb4fa0259592df774b600dbd1d2249a78ce91" + integrity sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ== eslint-scope@^7.2.2: version "7.2.2" @@ -1479,23 +1473,29 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@8.46.0: - version "8.46.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" - integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^8.57.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.1" - "@eslint/js" "^8.46.0" - "@humanwhocodes/config-array" "^0.11.10" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -1503,7 +1503,7 @@ eslint@8.46.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.2" + eslint-visitor-keys "^3.4.3" espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" @@ -1555,11 +1555,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -1606,17 +1601,6 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -1732,13 +1716,6 @@ get-tsconfig@^4.10.0: dependencies: resolve-pkg-maps "^1.0.0" -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -1765,18 +1742,6 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -1831,6 +1796,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -1892,7 +1862,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2551,12 +2521,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.8: +micromatch@^4.0.4: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -2576,6 +2541,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -2586,11 +2558,6 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2754,11 +2721,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - pathe@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" @@ -2950,7 +2912,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3: +semver@^7.5.3, semver@^7.5.4, semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== @@ -3084,7 +3046,7 @@ tinyexec@^1.0.1: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== -tinyglobby@^0.2.13: +tinyglobby@^0.2.13, tinyglobby@^0.2.15: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -3104,6 +3066,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== + ts-jest@^29.1.1: version "29.4.6" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9" @@ -3140,23 +3107,11 @@ tsdown@^0.9.0: unconfig "^7.3.2" unplugin-lightningcss "^0.3.3" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.4.0, tslib@^2.5.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From 3d23bb4d03021f92704ebdf796e0cb4353450b19 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:41:32 +0300 Subject: [PATCH 05/50] feat: add README.md --- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 10c8aed..957cf5d 100644 --- a/README.md +++ b/README.md @@ -1 +1,80 @@ -# AdaptySDK-JS-Core \ No newline at end of file +# @adapty/core + +Platform-agnostic core for Adapty SDKs. + +This package contains shared TypeScript/JavaScript code used by Adapty React Native and Capacitor SDKs. + +## Installation + +```bash +yarn install +``` + +## Development + +### Build + +```bash +yarn build +``` + +Builds the library to `dist/` folder with: +- ESM format (`index.mjs`) +- CommonJS format (`index.cjs`) +- TypeScript declarations (`.d.mts`, `.d.cts`) +- Source maps + +### Type Checking + +```bash +yarn tsc +``` + +### Linting + +```bash +yarn lint +``` + +### Formatting + +```bash +# Check formatting +yarn format-check + +# Fix formatting +yarn format +``` + +### Testing + +```bash +yarn test +``` + +## Project Structure + +``` +. +├── src/ # Source TypeScript files +├── dist/ # Build output (gitignored) +├── package.json # Package configuration +├── tsconfig.json # TypeScript configuration +├── tsdown.config.ts # Build tool configuration +├── .eslintrc.cjs # ESLint configuration +├── .prettierrc.json # Prettier configuration +└── jest.config.cjs # Jest configuration +``` + +## Publishing + +This package is published to npm as `@adapty/core`: + +- **Dev builds**: `0.0.0-dev.` with `dev` dist-tag +- **Production releases**: Semantic versioning with `latest` dist-tag + +See `AGENTS.md` for detailed publishing policy. + +## License + +MIT \ No newline at end of file From 350994d5204068ed5d1a2b3a4c53373234597d25 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:41:49 +0300 Subject: [PATCH 06/50] feat: add .npmignore --- .npmignore | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b7344fa --- /dev/null +++ b/.npmignore @@ -0,0 +1,39 @@ +# Source files (we publish only dist/) +src/ + +# Config files +tsconfig.json +tsdown.config.ts +.eslintrc.cjs +.prettierrc.json +jest.config.cjs + +# Development files +.gitignore +.npmignore +QUESTIONNAIRE.md +CLAUDE.md +.cursor/ + +# Tests +**/__tests__/ +**/*.test.ts +**/*.spec.ts +coverage/ + +# Build artifacts +*.tsbuildinfo +.tsbuildinfo + +# CI/CD +.github/ +.husky/ + +# IDE +.vscode/ +.idea/ + +# Logs +*.log +yarn-error.log +npm-debug.log From 0bbdee8ab06f2208948344d8a3ded4f22982ddbb Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:48:45 +0300 Subject: [PATCH 07/50] feat: update AGENTS.md --- AGENTS.md | 185 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 170 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 59b1542..b8adbbc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,24 +1,179 @@ # AGENTS.md +Rules for LLM agents working with `@adapty/core` repository. + ## Project Overview -This repo contains `@adapty/core`, platform-agnostic JS/TS code shared by Adapty -React Native and Capacitor SDKs. The package is public on npm but treated as an -internal dependency (dev builds are mainly for SDK CI). + +This repo contains `@adapty/core`, platform-agnostic JS/TS code shared by Adapty React Native and Capacitor SDKs. The package is public on npm but treated as an internal dependency (dev builds are mainly for SDK CI). + +**Package Name:** `@adapty/core` +**Language:** TypeScript (strict mode) +**Module System:** ESM (package.json has `"type": "module"`) + +## Project Structure + +``` +. +├── src/ # Source TypeScript files +│ └── index.ts # Main entry point +├── dist/ # Build output (gitignored, generated by tsdown) +├── package.json # Package configuration +├── tsconfig.json # TypeScript configuration (strict mode) +├── tsdown.config.ts # Build tool configuration +├── .eslintrc.cjs # ESLint config (CommonJS - note .cjs extension!) +├── .prettierrc.json # Prettier config +├── jest.config.cjs # Jest config (CommonJS - note .cjs extension!) +├── .gitignore # Git ignore rules +├── .npmignore # NPM publish filter +├── README.md # Documentation +├── AGENTS.md # This file +├── CLAUDE.md # Points to this file +└── LICENSE # MIT license +``` + +## Configuration Files + +**IMPORTANT:** Due to `"type": "module"` in package.json, all CommonJS config files MUST use `.cjs` extension: +- `.eslintrc.cjs` (NOT `.eslintrc.js`) +- `jest.config.cjs` (NOT `jest.config.js`) + +ESM config files use `.ts` or `.mts` extension: +- `tsdown.config.ts` + +## TypeScript Configuration + +**Strict Mode:** Enabled with maximum type safety. + +Key compiler options: +- `strict: true` - All strict type-checking enabled +- `noImplicitAny: true` - No implicit any types allowed +- `strictNullChecks: true` - Null/undefined checking +- `noUnusedLocals: true` - Report unused variables +- `noUnusedParameters: true` - Report unused parameters +- `noImplicitReturns: true` - All code paths must return +- `noFallthroughCasesInSwitch: true` - No fallthrough in switches +- `noUncheckedIndexedAccess: true` - Index access includes undefined +- `noImplicitOverride: true` - Require override keyword +- `noPropertyAccessFromIndexSignature: true` - Use brackets for index access +- `allowUnreachableCode: false` - Report unreachable code + +**Target:** ES2020 +**Module:** ESNext +**Module Resolution:** bundler ## Build Output -- Publish compiled JS only: ESM + CJS + `.d.ts`. -- Do not publish raw TypeScript as the only artifact (no build-on-install). + +Built with `tsdown` (powered by Rolldown). Outputs multiple formats: + +- **ESM:** `dist/index.mjs` (+ source map) +- **CommonJS:** `dist/index.cjs` +- **TypeScript Declarations:** + - `dist/index.d.mts` (for ESM imports) + - `dist/index.d.cts` (for CJS requires) + - Declaration maps for both + +**Build command:** `yarn build` + +Published files (via `files` in package.json): +- Only `dist/` folder +- Exclude test files (`*.test.*`, `*.spec.*`) + +Source TypeScript files (`src/`) are NOT published - only compiled output. ## Versioning -- Dev builds use `0.0.0-dev.` (full commit SHA). -- Downstream SDKs should pin the exact dev version when testing CI. + +- **Dev builds:** `0.0.0-dev.` (40-char commit SHA) + - Example: `0.0.0-dev.abc123...xyz789` + - Placeholder in repo: `0.0.0-dev.0000000000000000000000000000000000000000` + - CI must replace with actual git SHA before publishing +- **Production releases:** Semantic versioning (e.g., `1.0.0`, `1.0.1`) +- Downstream SDKs should pin exact dev version when testing CI ## CI Publishing Policy -- Registry: npmjs.com (public). -- `dev` branch: publish after merge, using dist-tag `dev`. -- `master` branch: publish only on git tag (release), default dist-tag `latest`. - -## Tooling -- Use Yarn for install/build scripts. -- Use npm for publish. -- Build tool: `tsdown`. + +- **Registry:** npmjs.com (public via `publishConfig.access: "public"`) +- **`dev` branch:** Publish after merge using dist-tag `dev` + - Command: `npm publish --tag dev` +- **`master` branch:** Publish ONLY on git tag (release) + - Command: `npm publish` (default `latest` tag) + +## Package Manager & Tooling + +### Primary Tools +- **Package Manager:** Yarn (for install/build/scripts) +- **Publish Tool:** npm (for publishing to registry) +- **Build Tool:** tsdown v0.9.x +- **TypeScript:** v5.9.3 +- **Linter:** ESLint v8.57.x + @typescript-eslint v8.21.x +- **Formatter:** Prettier v3.x +- **Test Runner:** Jest v29.3.x + ts-jest v29.x + +## Code Quality Rules + +### Linting +- ESLint configured with `@typescript-eslint` plugin +- Extends `eslint-config-prettier` (no conflicts with Prettier) +- Rule: `no-console: error` (console.log forbidden, except in scripts/) + +### Formatting +- Prettier for all `.ts` files +- Semi: true +- Single quotes: true +- Trailing comma: all +- Arrow parens: always + +### Testing +- Jest with ts-jest preset +- Test files: `*.test.ts` or `__tests__/**/*.test.ts` +- Coverage collected from `src/**/*.ts` + +## Development Workflow + +1. **Setup:** `yarn install` +2. **Develop:** Edit files in `src/` +3. **Check types:** `yarn tsc` +4. **Lint:** `yarn lint` +5. **Format:** `yarn format` +6. **Test:** `yarn test` +7. **Build:** `yarn build` +8. **Verify:** Check `dist/` output + +## Common Tasks for LLM Agents + +### Adding New Source Files +1. Create `.ts` file in `src/` +2. Export from `src/index.ts` if public API +3. Run `yarn tsc` to check types +4. Run `yarn lint` to check style +5. Run `yarn build` to verify output + +### Updating Dependencies +- Use exact versions for critical deps (TypeScript, tsdown) +- Use caret ranges (`^`) for dev tools +- Always run `yarn install` after updating package.json +- Verify with `yarn lint && yarn tsc && yarn test && yarn build` + +### Fixing Type Errors +- No `any` types allowed (noImplicitAny: true) +- Use `unknown` for truly unknown types +- Provide explicit return types for public APIs +- Mark unused params with underscore prefix: `_unusedParam` + +### Config File Issues +- If seeing "module is not defined" error → file needs `.cjs` extension +- If seeing "require is not defined" error → file needs `.mjs` or `.ts` extension +- Remember: package.json has `"type": "module"` so all `.js` files are ESM + +## Do NOT + +- Do NOT publish raw TypeScript files (only publish `dist/`) +- Do NOT use `any` type (strict mode prevents this) +- Do NOT use `console.log` in library code (ESLint will error) +- Do NOT change TypeScript version without testing +- Do NOT rename `.cjs` config files to `.js` +- Do NOT create builds that depend on build-on-install (tsc on user machine) +- Do NOT modify version in package.json (CI handles this) + +## Reference Files + +- `README.md` - User-facing documentation From bf5a67a47e5bebde58fd8b9318d6a5bdfc2e5ad8 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 28 Jan 2026 21:54:52 +0300 Subject: [PATCH 08/50] feat: move RN source as is --- src/coders/adapty-access-level.test.ts | 107 ++ src/coders/adapty-access-level.ts | 116 ++ src/coders/adapty-configuration.test.ts | 220 ++++ src/coders/adapty-configuration.ts | 80 ++ src/coders/adapty-discount-phase.test.ts | 72 ++ src/coders/adapty-discount-phase.ts | 42 + src/coders/adapty-identify-params.test.ts | 140 +++ src/coders/adapty-identify-params.ts | 29 + .../adapty-installation-details.test.ts | 50 + src/coders/adapty-installation-details.ts | 35 + src/coders/adapty-installation-status.test.ts | 71 ++ src/coders/adapty-installation-status.ts | 55 + src/coders/adapty-native-error.test.ts | 63 + src/coders/adapty-native-error.ts | 30 + src/coders/adapty-non-subscription.test.ts | 62 + src/coders/adapty-non-subscription.ts | 37 + src/coders/adapty-onboarding-builder.test.ts | 42 + src/coders/adapty-onboarding-builder.ts | 20 + src/coders/adapty-onboarding.test.ts | 119 ++ src/coders/adapty-onboarding.ts | 57 + src/coders/adapty-paywall-builder.test.ts | 43 + src/coders/adapty-paywall-builder.ts | 25 + src/coders/adapty-paywall-product.test.ts | 169 +++ src/coders/adapty-paywall-product.ts | 92 ++ src/coders/adapty-paywall.test.ts | 160 +++ src/coders/adapty-paywall.ts | 75 ++ src/coders/adapty-placement.test.ts | 56 + src/coders/adapty-placement.ts | 26 + src/coders/adapty-price.test.ts | 44 + src/coders/adapty-price.ts | 32 + src/coders/adapty-profile-parameters.test.ts | 27 + src/coders/adapty-profile-parameters.ts | 36 + src/coders/adapty-profile.test.ts | 230 ++++ src/coders/adapty-profile.ts | 54 + src/coders/adapty-purchase-params.test.ts | 91 ++ src/coders/adapty-purchase-params.ts | 58 + src/coders/adapty-purchase-result.test.ts | 271 +++++ src/coders/adapty-purchase-result.ts | 69 ++ src/coders/adapty-remote-config.test.ts | 44 + src/coders/adapty-remote-config.ts | 43 + .../adapty-subscription-details.test.ts | 123 ++ src/coders/adapty-subscription-details.ts | 60 + ...apty-subscription-offer-identifier.test.ts | 57 + .../adapty-subscription-offer-identifier.ts | 25 + src/coders/adapty-subscription-offer.test.ts | 98 ++ src/coders/adapty-subscription-offer.ts | 46 + src/coders/adapty-subscription-period.test.ts | 40 + src/coders/adapty-subscription-period.ts | 17 + src/coders/adapty-subscription.test.ts | 98 ++ src/coders/adapty-subscription.ts | 119 ++ ...y-ui-create-onboarding-view-params.test.ts | 35 + ...adapty-ui-create-onboarding-view-params.ts | 19 + ...apty-ui-create-paywall-view-params.test.ts | 306 +++++ .../adapty-ui-create-paywall-view-params.ts | 178 +++ src/coders/adapty-ui-dialog-config.test.ts | 25 + src/coders/adapty-ui-dialog-config.ts | 35 + src/coders/adapty-ui-media-cache.test.ts | 21 + src/coders/adapty-ui-media-cache.ts | 27 + src/coders/adapty-ui-onboarding-meta.test.ts | 49 + src/coders/adapty-ui-onboarding-meta.ts | 35 + .../adapty-ui-onboarding-state-params.test.ts | 46 + .../adapty-ui-onboarding-state-params.ts | 30 + ...ui-onboarding-state-updated-action.test.ts | 128 ++ ...apty-ui-onboarding-state-updated-action.ts | 97 ++ src/coders/array.ts | 34 + src/coders/bridge-error.test.ts | 42 + src/coders/bridge-error.ts | 93 ++ src/coders/coder.ts | 184 +++ src/coders/date.ts | 53 + src/coders/error-coder.ts | 8 + src/coders/hashmap.ts | 35 + src/coders/index.ts | 3 + src/coders/json.ts | 20 + src/coders/parse-onboarding.ts | 135 +++ src/coders/parse-paywall.ts | 224 ++++ src/coders/parse.test.ts | 242 ++++ src/coders/parse.ts | 178 +++ src/coders/product-reference.test.ts | 81 ++ src/coders/product-reference.ts | 56 + src/coders/types.ts | 62 + src/coders/utils.ts | 67 ++ src/types/api.d.ts | 1061 +++++++++++++++++ src/types/bridge.ts | 99 ++ src/types/error.ts | 152 +++ src/types/index.ts | 847 +++++++++++++ src/types/inputs.ts | 310 +++++ src/types/onboarding-events.ts | 82 ++ src/types/paywall-events.ts | 125 ++ src/types/schema.d.ts | 4 + 89 files changed, 9003 insertions(+) create mode 100644 src/coders/adapty-access-level.test.ts create mode 100644 src/coders/adapty-access-level.ts create mode 100644 src/coders/adapty-configuration.test.ts create mode 100644 src/coders/adapty-configuration.ts create mode 100644 src/coders/adapty-discount-phase.test.ts create mode 100644 src/coders/adapty-discount-phase.ts create mode 100644 src/coders/adapty-identify-params.test.ts create mode 100644 src/coders/adapty-identify-params.ts create mode 100644 src/coders/adapty-installation-details.test.ts create mode 100644 src/coders/adapty-installation-details.ts create mode 100644 src/coders/adapty-installation-status.test.ts create mode 100644 src/coders/adapty-installation-status.ts create mode 100644 src/coders/adapty-native-error.test.ts create mode 100644 src/coders/adapty-native-error.ts create mode 100644 src/coders/adapty-non-subscription.test.ts create mode 100644 src/coders/adapty-non-subscription.ts create mode 100644 src/coders/adapty-onboarding-builder.test.ts create mode 100644 src/coders/adapty-onboarding-builder.ts create mode 100644 src/coders/adapty-onboarding.test.ts create mode 100644 src/coders/adapty-onboarding.ts create mode 100644 src/coders/adapty-paywall-builder.test.ts create mode 100644 src/coders/adapty-paywall-builder.ts create mode 100644 src/coders/adapty-paywall-product.test.ts create mode 100644 src/coders/adapty-paywall-product.ts create mode 100644 src/coders/adapty-paywall.test.ts create mode 100644 src/coders/adapty-paywall.ts create mode 100644 src/coders/adapty-placement.test.ts create mode 100644 src/coders/adapty-placement.ts create mode 100644 src/coders/adapty-price.test.ts create mode 100644 src/coders/adapty-price.ts create mode 100644 src/coders/adapty-profile-parameters.test.ts create mode 100644 src/coders/adapty-profile-parameters.ts create mode 100644 src/coders/adapty-profile.test.ts create mode 100644 src/coders/adapty-profile.ts create mode 100644 src/coders/adapty-purchase-params.test.ts create mode 100644 src/coders/adapty-purchase-params.ts create mode 100644 src/coders/adapty-purchase-result.test.ts create mode 100644 src/coders/adapty-purchase-result.ts create mode 100644 src/coders/adapty-remote-config.test.ts create mode 100644 src/coders/adapty-remote-config.ts create mode 100644 src/coders/adapty-subscription-details.test.ts create mode 100644 src/coders/adapty-subscription-details.ts create mode 100644 src/coders/adapty-subscription-offer-identifier.test.ts create mode 100644 src/coders/adapty-subscription-offer-identifier.ts create mode 100644 src/coders/adapty-subscription-offer.test.ts create mode 100644 src/coders/adapty-subscription-offer.ts create mode 100644 src/coders/adapty-subscription-period.test.ts create mode 100644 src/coders/adapty-subscription-period.ts create mode 100644 src/coders/adapty-subscription.test.ts create mode 100644 src/coders/adapty-subscription.ts create mode 100644 src/coders/adapty-ui-create-onboarding-view-params.test.ts create mode 100644 src/coders/adapty-ui-create-onboarding-view-params.ts create mode 100644 src/coders/adapty-ui-create-paywall-view-params.test.ts create mode 100644 src/coders/adapty-ui-create-paywall-view-params.ts create mode 100644 src/coders/adapty-ui-dialog-config.test.ts create mode 100644 src/coders/adapty-ui-dialog-config.ts create mode 100644 src/coders/adapty-ui-media-cache.test.ts create mode 100644 src/coders/adapty-ui-media-cache.ts create mode 100644 src/coders/adapty-ui-onboarding-meta.test.ts create mode 100644 src/coders/adapty-ui-onboarding-meta.ts create mode 100644 src/coders/adapty-ui-onboarding-state-params.test.ts create mode 100644 src/coders/adapty-ui-onboarding-state-params.ts create mode 100644 src/coders/adapty-ui-onboarding-state-updated-action.test.ts create mode 100644 src/coders/adapty-ui-onboarding-state-updated-action.ts create mode 100644 src/coders/array.ts create mode 100644 src/coders/bridge-error.test.ts create mode 100644 src/coders/bridge-error.ts create mode 100644 src/coders/coder.ts create mode 100644 src/coders/date.ts create mode 100644 src/coders/error-coder.ts create mode 100644 src/coders/hashmap.ts create mode 100644 src/coders/index.ts create mode 100644 src/coders/json.ts create mode 100644 src/coders/parse-onboarding.ts create mode 100644 src/coders/parse-paywall.ts create mode 100644 src/coders/parse.test.ts create mode 100644 src/coders/parse.ts create mode 100644 src/coders/product-reference.test.ts create mode 100644 src/coders/product-reference.ts create mode 100644 src/coders/types.ts create mode 100644 src/coders/utils.ts create mode 100644 src/types/api.d.ts create mode 100644 src/types/bridge.ts create mode 100644 src/types/error.ts create mode 100644 src/types/index.ts create mode 100644 src/types/inputs.ts create mode 100644 src/types/onboarding-events.ts create mode 100644 src/types/paywall-events.ts create mode 100644 src/types/schema.d.ts diff --git a/src/coders/adapty-access-level.test.ts b/src/coders/adapty-access-level.test.ts new file mode 100644 index 0000000..1895e74 --- /dev/null +++ b/src/coders/adapty-access-level.test.ts @@ -0,0 +1,107 @@ +import type { + AdaptyAccessLevel, + CancellationReason, + OfferType, + VendorStore, +} from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyAccessLevelCoder } from './adapty-access-level'; + +type Model = AdaptyAccessLevel; +const mocks: Def['AdaptyProfile.AccessLevel'][] = [ + { + activated_at: '2023-08-08T12:00:00.000Z', + active_introductory_offer_type: 'offer1', + active_promotional_offer_id: 'iosOffer1', + active_promotional_offer_type: 'promo1', + billing_issue_detected_at: '2023-08-08T12:00:00.000Z', + cancellation_reason: 'user_cancelled', + expires_at: '2023-10-08T12:00:00.000Z', + id: 'accessLevel1', + is_active: true, + is_in_grace_period: false, + is_lifetime: false, + is_refund: false, + offer_id: 'androidOffer1', + renewed_at: '2023-09-08T12:00:00.000Z', + starts_at: '2023-08-08T12:00:00.000Z', + store: 'appstore', + unsubscribed_at: '2023-08-08T12:00:00.000Z', + vendor_product_id: 'product1', + will_renew: true, + }, + { + activated_at: '2023-07-15T14:30:00.000Z', + id: 'accessLevel2', + is_active: false, + is_in_grace_period: true, + is_lifetime: true, + store: 'appstore', + is_refund: true, + will_renew: false, + vendor_product_id: 'product1', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + activatedAt: new Date(mock.activated_at), + ...(mock.cancellation_reason && { + cancellationReason: mock.cancellation_reason as CancellationReason, + }), + ...(mock.expires_at && { expiresAt: new Date(mock.expires_at) }), + id: mock.id, + isActive: mock.is_active, + isInGracePeriod: mock.is_in_grace_period, + isLifetime: mock.is_lifetime, + isRefund: mock.is_refund, + ...(mock.renewed_at && { renewedAt: new Date(mock.renewed_at) }), + store: mock.store as VendorStore, + ...(mock.starts_at && { startsAt: new Date(mock.starts_at) }), + ...(mock.billing_issue_detected_at && { + billingIssueDetectedAt: new Date(mock.billing_issue_detected_at), + }), + ...(mock.unsubscribed_at && { + unsubscribedAt: new Date(mock.unsubscribed_at), + }), + vendorProductId: mock.vendor_product_id as string, + willRenew: mock.will_renew, + ...(mock.active_introductory_offer_type && { + activeIntroductoryOfferType: + mock.active_introductory_offer_type as OfferType, + }), + ...(mock.active_promotional_offer_id && { + activePromotionalOfferId: mock.active_promotional_offer_id, + }), + ...(mock.active_promotional_offer_type && { + activePromotionalOfferType: + mock.active_promotional_offer_type as OfferType, + }), + android: { + ...(mock.offer_id && { + offerId: mock.offer_id, + }), + }, + }; +} + +describe('AdaptyAccessLevelCoder', () => { + let coder: AdaptyAccessLevelCoder; + + beforeEach(() => { + coder = new AdaptyAccessLevelCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-access-level.ts b/src/coders/adapty-access-level.ts new file mode 100644 index 0000000..5291e13 --- /dev/null +++ b/src/coders/adapty-access-level.ts @@ -0,0 +1,116 @@ +import type { Def } from '@/types/schema'; +import type { AdaptyAccessLevel } from '../types'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { DateCoder } from './date'; + +type Model = AdaptyAccessLevel; +type Serializable = Def['AdaptyProfile.AccessLevel']; + +export class AdaptyAccessLevelCoder extends SimpleCoder { + protected properties: Properties = { + activatedAt: { + key: 'activated_at', + required: true, + type: 'string', + converter: new DateCoder(), + }, + activeIntroductoryOfferType: { + key: 'active_introductory_offer_type', + required: false, + type: 'string', + }, + activePromotionalOfferId: { + key: 'active_promotional_offer_id', + required: false, + type: 'string', + }, + activePromotionalOfferType: { + key: 'active_promotional_offer_type', + required: false, + type: 'string', + }, + billingIssueDetectedAt: { + key: 'billing_issue_detected_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + cancellationReason: { + key: 'cancellation_reason', + required: false, + type: 'string', + }, + expiresAt: { + key: 'expires_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + id: { + key: 'id', + required: true, + type: 'string', + }, + isActive: { + key: 'is_active', + required: true, + type: 'boolean', + }, + isInGracePeriod: { + key: 'is_in_grace_period', + required: true, + type: 'boolean', + }, + isLifetime: { + key: 'is_lifetime', + required: true, + type: 'boolean', + }, + isRefund: { + key: 'is_refund', + required: true, + type: 'boolean', + }, + renewedAt: { + key: 'renewed_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + startsAt: { + key: 'starts_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + store: { + key: 'store', + required: true, + type: 'string', + }, + unsubscribedAt: { + key: 'unsubscribed_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + vendorProductId: { + key: 'vendor_product_id', + required: true, + type: 'string', + }, + willRenew: { + key: 'will_renew', + required: true, + type: 'boolean', + }, + android: { + offerId: { + key: 'offer_id', + required: false, + type: 'string', + }, + }, + }; +} diff --git a/src/coders/adapty-configuration.test.ts b/src/coders/adapty-configuration.test.ts new file mode 100644 index 0000000..63f2327 --- /dev/null +++ b/src/coders/adapty-configuration.test.ts @@ -0,0 +1,220 @@ +import { AdaptyConfigurationCoder } from '@/coders/adapty-configuration'; +import { LogLevel } from '@/types/inputs'; +import version from '@/version'; + +jest.mock('react-native', () => ({ + Platform: { + OS: 'ios', + }, +})); + +describe('AdaptyConfigurationCoder', () => { + const coder = new AdaptyConfigurationCoder(); + const apiKey = 'test-api-key'; + + it('should encode minimal configuration', () => { + const params = {}; + const result = coder.encode(apiKey, params); + + expect(result).toMatchObject({ + api_key: apiKey, + cross_platform_sdk_name: 'react-native', + cross_platform_sdk_version: version, + observer_mode: false, + ip_address_collection_disabled: false, + server_cluster: 'default', + activate_ui: true, + media_cache: { + memory_storage_total_cost_limit: 100 * 1024 * 1024, + memory_storage_count_limit: 2147483647, + disk_storage_size_limit: 100 * 1024 * 1024, + }, + }); + }); + + it('should encode full configuration with all parameters on ios', () => { + const params = { + customerUserId: 'user123', + observerMode: true, + ipAddressCollectionDisabled: true, + logLevel: LogLevel.VERBOSE, + serverCluster: 'eu' as const, + backendProxyHost: 'proxy.example.com', + backendProxyPort: 8080, + activateUi: false, + mediaCache: { + memoryStorageTotalCostLimit: 50 * 1024 * 1024, + memoryStorageCountLimit: 1000, + diskStorageSizeLimit: 200 * 1024 * 1024, + }, + ios: { + idfaCollectionDisabled: true, + appAccountToken: 'token123', + clearDataOnBackup: true, + }, + android: { + adIdCollectionDisabled: true, + obfuscatedAccountId: 'id123', + pendingPrepaidPlansEnabled: true, + localAccessLevelAllowed: true, + }, + }; + + const result = coder.encode(apiKey, params); + + expect(result).toMatchObject({ + api_key: apiKey, + cross_platform_sdk_name: 'react-native', + cross_platform_sdk_version: version, + customer_user_id: 'user123', + customer_identity_parameters: { + app_account_token: 'token123', + }, + observer_mode: true, + ip_address_collection_disabled: true, + clear_data_on_backup: true, + log_level: 'verbose', + server_cluster: 'eu', + backend_proxy_host: 'proxy.example.com', + backend_proxy_port: 8080, + activate_ui: false, + media_cache: { + memory_storage_total_cost_limit: 50 * 1024 * 1024, + memory_storage_count_limit: 1000, + disk_storage_size_limit: 200 * 1024 * 1024, + }, + }); + }); + + it('should encode full configuration with all parameters on android', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'android' }; + const params = { + customerUserId: 'user123', + observerMode: true, + ipAddressCollectionDisabled: true, + logLevel: LogLevel.VERBOSE, + serverCluster: 'eu' as const, + backendProxyHost: 'proxy.example.com', + backendProxyPort: 8080, + activateUi: false, + mediaCache: { + memoryStorageTotalCostLimit: 50 * 1024 * 1024, + memoryStorageCountLimit: 1000, + diskStorageSizeLimit: 200 * 1024 * 1024, + }, + ios: { + idfaCollectionDisabled: true, + appAccountToken: 'token123', + }, + android: { + adIdCollectionDisabled: true, + obfuscatedAccountId: 'id123', + pendingPrepaidPlansEnabled: true, + localAccessLevelAllowed: true, + }, + }; + + const result = coder.encode(apiKey, params); + + expect(result).toMatchObject({ + api_key: apiKey, + cross_platform_sdk_name: 'react-native', + cross_platform_sdk_version: version, + customer_user_id: 'user123', + customer_identity_parameters: { + obfuscated_account_id: 'id123', + }, + observer_mode: true, + ip_address_collection_disabled: true, + log_level: 'verbose', + server_cluster: 'eu', + backend_proxy_host: 'proxy.example.com', + backend_proxy_port: 8080, + activate_ui: false, + google_enable_pending_prepaid_plans: true, + google_local_access_level_allowed: true, + media_cache: { + memory_storage_total_cost_limit: 50 * 1024 * 1024, + memory_storage_count_limit: 1000, + disk_storage_size_limit: 200 * 1024 * 1024, + }, + }); + + require('react-native').Platform = originalPlatform; + }); + + it('should handle partial parameters', () => { + const params = { + customerUserId: 'user456', + logLevel: LogLevel.WARN, + serverCluster: 'cn' as const, + ios: { + idfaCollectionDisabled: false, + }, + }; + + const result = coder.encode(apiKey, params); + + expect(result).toMatchObject({ + api_key: apiKey, + cross_platform_sdk_name: 'react-native', + cross_platform_sdk_version: version, + customer_user_id: 'user456', + observer_mode: false, + ip_address_collection_disabled: false, + log_level: 'warn', + server_cluster: 'cn', + activate_ui: true, + media_cache: { + memory_storage_total_cost_limit: 100 * 1024 * 1024, + memory_storage_count_limit: 2147483647, + disk_storage_size_limit: 100 * 1024 * 1024, + }, + }); + }); + + it('should handle undefined logLevel', () => { + const params = {}; + const result = coder.encode(apiKey, params); + + expect(result.media_cache).toEqual({ + memory_storage_total_cost_limit: 100 * 1024 * 1024, + memory_storage_count_limit: 2147483647, + disk_storage_size_limit: 100 * 1024 * 1024, + }); + expect(result.log_level).toBeUndefined(); + }); + + it('should handle clearDataOnBackup parameter conditionally', () => { + const paramsWithout = {}; + const resultWithout = coder.encode(apiKey, paramsWithout); + expect(resultWithout.clear_data_on_backup).toBeUndefined(); + + const paramsWithFalse = { ios: { clearDataOnBackup: false } }; + const resultWithFalse = coder.encode(apiKey, paramsWithFalse); + expect(resultWithFalse.clear_data_on_backup).toBe(false); + + const paramsWithTrue = { ios: { clearDataOnBackup: true } }; + const resultWithTrue = coder.encode(apiKey, paramsWithTrue); + expect(resultWithTrue.clear_data_on_backup).toBe(true); + }); + + it('should prefer params media cache over default', () => { + const params = { + mediaCache: { + memoryStorageTotalCostLimit: 25 * 1024 * 1024, + memoryStorageCountLimit: 500, + diskStorageSizeLimit: 75 * 1024 * 1024, + }, + }; + + const result = coder.encode(apiKey, params); + + expect(result.media_cache).toEqual({ + memory_storage_total_cost_limit: 25 * 1024 * 1024, + memory_storage_count_limit: 500, + disk_storage_size_limit: 75 * 1024 * 1024, + }); + }); +}); diff --git a/src/coders/adapty-configuration.ts b/src/coders/adapty-configuration.ts new file mode 100644 index 0000000..9a39987 --- /dev/null +++ b/src/coders/adapty-configuration.ts @@ -0,0 +1,80 @@ +import * as Input from '@/types/inputs'; +import type { Def } from '@/types/schema'; +import { Platform } from 'react-native'; +import { AdaptyUiMediaCacheCoder } from '@/coders/adapty-ui-media-cache'; +import version from '@/version'; + +type Model = Input.ActivateParamsInput; +type Serializable = Def['AdaptyConfiguration']; + +export class AdaptyConfigurationCoder { + encode(apiKey: string, params: Model): Serializable { + const config: Serializable = { + api_key: apiKey, + cross_platform_sdk_name: 'react-native', + cross_platform_sdk_version: version, + }; + + if (params.customerUserId) { + config['customer_user_id'] = params.customerUserId; + } + + config['observer_mode'] = params.observerMode ?? false; + config['ip_address_collection_disabled'] = + params.ipAddressCollectionDisabled ?? false; + + if (params.logLevel) { + config['log_level'] = params.logLevel; + } + + config['server_cluster'] = params.serverCluster ?? 'default'; + + if (params.backendProxyHost) { + config['backend_proxy_host'] = params.backendProxyHost; + } + + if (params.backendProxyPort) { + config['backend_proxy_port'] = params.backendProxyPort; + } + + config['activate_ui'] = params.activateUi ?? true; + + const mediaCacheCoder = new AdaptyUiMediaCacheCoder(); + config['media_cache'] = mediaCacheCoder.encode( + params.mediaCache ?? { + memoryStorageTotalCostLimit: 100 * 1024 * 1024, + memoryStorageCountLimit: 2147483647, + diskStorageSizeLimit: 100 * 1024 * 1024, + }, + ); + + if (Platform.OS === 'ios') { + config['apple_idfa_collection_disabled'] = + params.ios?.idfaCollectionDisabled ?? false; + if (params.ios?.appAccountToken) { + config['customer_identity_parameters'] = { + app_account_token: params.ios.appAccountToken, + }; + } + if (params.ios?.clearDataOnBackup !== undefined) { + config['clear_data_on_backup'] = params.ios.clearDataOnBackup; + } + } + + if (Platform.OS === 'android') { + config['google_adid_collection_disabled'] = + params.android?.adIdCollectionDisabled ?? false; + config['google_enable_pending_prepaid_plans'] = + params.android?.pendingPrepaidPlansEnabled ?? false; + config['google_local_access_level_allowed'] = + params.android?.localAccessLevelAllowed ?? false; + if (params.android?.obfuscatedAccountId) { + config['customer_identity_parameters'] = { + obfuscated_account_id: params.android.obfuscatedAccountId, + }; + } + } + + return config; + } +} diff --git a/src/coders/adapty-discount-phase.test.ts b/src/coders/adapty-discount-phase.test.ts new file mode 100644 index 0000000..971514b --- /dev/null +++ b/src/coders/adapty-discount-phase.test.ts @@ -0,0 +1,72 @@ +import type { AdaptyDiscountPhase, OfferType } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyDiscountPhaseCoder } from './adapty-discount-phase'; +import { AdaptyPriceCoder } from './adapty-price'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; + +type Model = AdaptyDiscountPhase; +const mocks: Def['AdaptySubscriptionOffer.Phase'][] = [ + { + localized_number_of_periods: '6 months', + localized_subscription_period: '1 month', + number_of_periods: 6, + payment_mode: 'pay_as_you_go', + price: { + amount: 9.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$9.99', + }, + subscription_period: { unit: 'month', number_of_units: 1 }, + }, + { + number_of_periods: 2, + payment_mode: 'pay_up_front', + price: { + amount: 59.99, + currency_code: 'EUR', + currency_symbol: '€', + localized_string: '€59.99', + }, + subscription_period: { unit: 'year', number_of_units: 1 }, + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _price = new AdaptyPriceCoder(); + const _subscription = new AdaptySubscriptionPeriodCoder(); + + return { + numberOfPeriods: mock.number_of_periods, + paymentMode: mock.payment_mode as OfferType, + price: _price.decode(mock.price), + subscriptionPeriod: _subscription.decode(mock.subscription_period), + ...(mock.localized_number_of_periods && { + localizedNumberOfPeriods: mock.localized_number_of_periods, + }), + ...(mock.localized_subscription_period && { + localizedSubscriptionPeriod: mock.localized_subscription_period, + }), + }; +} + +describe('AdaptyDiscountPhaseCoder', () => { + let coder: AdaptyDiscountPhaseCoder; + + beforeEach(() => { + coder = new AdaptyDiscountPhaseCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-discount-phase.ts b/src/coders/adapty-discount-phase.ts new file mode 100644 index 0000000..5d7b092 --- /dev/null +++ b/src/coders/adapty-discount-phase.ts @@ -0,0 +1,42 @@ +import type { AdaptyDiscountPhase } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; +import { AdaptyPriceCoder } from './adapty-price'; + +type Model = AdaptyDiscountPhase; +type Serializable = Def['AdaptySubscriptionOffer.Phase']; + +export class AdaptyDiscountPhaseCoder extends SimpleCoder { + protected properties: Properties = { + localizedNumberOfPeriods: { + key: 'localized_number_of_periods', + required: false, + type: 'string', + }, + localizedSubscriptionPeriod: { + key: 'localized_subscription_period', + required: false, + type: 'string', + }, + numberOfPeriods: { + key: 'number_of_periods', + required: true, + type: 'number', + }, + paymentMode: { key: 'payment_mode', required: true, type: 'string' }, + price: { + key: 'price', + required: true, + type: 'object', + converter: new AdaptyPriceCoder(), + }, + subscriptionPeriod: { + key: 'subscription_period', + required: true, + type: 'object', + converter: new AdaptySubscriptionPeriodCoder(), + }, + }; +} diff --git a/src/coders/adapty-identify-params.test.ts b/src/coders/adapty-identify-params.test.ts new file mode 100644 index 0000000..8da9716 --- /dev/null +++ b/src/coders/adapty-identify-params.test.ts @@ -0,0 +1,140 @@ +import { AdaptyIdentifyParamsCoder } from '@/coders/adapty-identify-params'; + +describe('AdaptyIdentifyParamsCoder', () => { + const coder = new AdaptyIdentifyParamsCoder(); + + it('should return undefined for empty params', () => { + const result = coder.encode({}); + expect(result).toBeUndefined(); + }); + + it('should return undefined for undefined params', () => { + const result = coder.encode(undefined); + expect(result).toBeUndefined(); + }); + + it('should encode iOS app account token on iOS platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'ios' }; + + const params = { + ios: { + appAccountToken: 'ios-token-123', + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + app_account_token: 'ios-token-123', + }); + + require('react-native').Platform = originalPlatform; + }); + + it('should encode Android obfuscated account ID on Android platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'android' }; + + const params = { + android: { + obfuscatedAccountId: 'android-id-456', + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + obfuscated_account_id: 'android-id-456', + }); + + require('react-native').Platform = originalPlatform; + }); + + it('should only encode iOS parameters when on iOS platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'ios' }; + + const params = { + ios: { + appAccountToken: 'ios-token-123', + }, + android: { + obfuscatedAccountId: 'android-id-456', + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + app_account_token: 'ios-token-123', + }); + + require('react-native').Platform = originalPlatform; + }); + + it('should only encode Android parameters when on Android platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'android' }; + + const params = { + ios: { + appAccountToken: 'ios-token-123', + }, + android: { + obfuscatedAccountId: 'android-id-456', + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + obfuscated_account_id: 'android-id-456', + }); + + require('react-native').Platform = originalPlatform; + }); + + it('should ignore iOS parameters on Android platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'android' }; + + const params = { + ios: { + appAccountToken: 'ios-token-123', + }, + }; + + const result = coder.encode(params); + expect(result).toBeUndefined(); + + require('react-native').Platform = originalPlatform; + }); + + it('should ignore Android parameters on iOS platform', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'ios' }; + + const params = { + android: { + obfuscatedAccountId: 'android-id-456', + }, + }; + + const result = coder.encode(params); + expect(result).toBeUndefined(); + + require('react-native').Platform = originalPlatform; + }); + + it('should handle empty platform objects', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'ios' }; + + const params = { + ios: {}, + android: {}, + }; + + const result = coder.encode(params); + expect(result).toBeUndefined(); + + require('react-native').Platform = originalPlatform; + }); +}); diff --git a/src/coders/adapty-identify-params.ts b/src/coders/adapty-identify-params.ts new file mode 100644 index 0000000..8cfdf7b --- /dev/null +++ b/src/coders/adapty-identify-params.ts @@ -0,0 +1,29 @@ +import * as Input from '@/types/inputs'; +import { Platform } from 'react-native'; + +type Model = Input.IdentifyParamsInput; +type Serializable = { + app_account_token?: string; + obfuscated_account_id?: string; + obfuscated_profile_id?: string; +}; + +export class AdaptyIdentifyParamsCoder { + encode(params?: Model): Serializable | undefined { + if (!params) { + return undefined; + } + + const result: Serializable = {}; + + if (Platform.OS === 'ios' && params.ios?.appAccountToken) { + result.app_account_token = params.ios.appAccountToken; + } + + if (Platform.OS === 'android' && params.android?.obfuscatedAccountId) { + result.obfuscated_account_id = params.android.obfuscatedAccountId; + } + + return Object.keys(result).length > 0 ? result : undefined; + } +} diff --git a/src/coders/adapty-installation-details.test.ts b/src/coders/adapty-installation-details.test.ts new file mode 100644 index 0000000..4440e00 --- /dev/null +++ b/src/coders/adapty-installation-details.test.ts @@ -0,0 +1,50 @@ +import type { AdaptyInstallationDetails } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyInstallationDetailsCoder } from '@/coders/adapty-installation-details'; + +type Model = AdaptyInstallationDetails; +type MockDef = Def['AdaptyInstallationDetails']; + +const mocks: MockDef[] = [ + { + install_id: 'unique-install-id-12345', + install_time: '2023-10-15T10:30:00.000Z', + app_launch_count: 42, + payload: 'custom-payload-data', + }, + { + install_time: '2023-10-15T10:30:00.000Z', + app_launch_count: 1, + // Optional fields omitted + }, +]; + +function toModel(mock: MockDef): Model { + return { + installTime: new Date(mock.install_time), + appLaunchCount: mock.app_launch_count, + installId: mock.install_id, + payload: mock.payload, + }; +} + +describe('AdaptyInstallationDetailsCoder', () => { + let coder: AdaptyInstallationDetailsCoder; + + beforeEach(() => { + coder = new AdaptyInstallationDetailsCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-installation-details.ts b/src/coders/adapty-installation-details.ts new file mode 100644 index 0000000..527fcca --- /dev/null +++ b/src/coders/adapty-installation-details.ts @@ -0,0 +1,35 @@ +import { AdaptyInstallationDetails } from '@/types'; +import type { Def } from '@/types/schema'; +import { Converter } from './types'; +import { DateCoder } from './date'; + +export class AdaptyInstallationDetailsCoder + implements + Converter +{ + encode(model: AdaptyInstallationDetails): Def['AdaptyInstallationDetails'] { + const result: Def['AdaptyInstallationDetails'] = { + install_time: new DateCoder().encode(model.installTime), + app_launch_count: model.appLaunchCount, + }; + + if (model.installId) { + result.install_id = model.installId; + } + + if (model.payload) { + result.payload = model.payload; + } + + return result; + } + + decode(json: Def['AdaptyInstallationDetails']): AdaptyInstallationDetails { + return { + installTime: new DateCoder().decode(json.install_time), + appLaunchCount: json.app_launch_count, + installId: json.install_id, + payload: json.payload, + }; + } +} diff --git a/src/coders/adapty-installation-status.test.ts b/src/coders/adapty-installation-status.test.ts new file mode 100644 index 0000000..bfede33 --- /dev/null +++ b/src/coders/adapty-installation-status.test.ts @@ -0,0 +1,71 @@ +import type { AdaptyInstallationStatus } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyInstallationStatusCoder } from '@/coders/adapty-installation-status'; + +type Model = AdaptyInstallationStatus; +type MockDef = Def['AdaptyInstallationStatus']; + +const mocks: MockDef[] = [ + { + status: 'not_available', + }, + { + status: 'not_determined', + }, + { + status: 'determined', + details: { + install_id: 'unique-install-id-12345', + install_time: '2023-10-15T10:30:00.000Z', + app_launch_count: 42, + payload: 'custom-payload-data', + }, + }, + { + status: 'determined', + details: { + install_time: '2023-10-15T10:30:00.000Z', + app_launch_count: 1, + // Optional fields omitted + }, + }, +]; + +function toModel(mock: MockDef): Model { + if (mock.status === 'determined') { + return { + status: 'determined', + details: { + installTime: new Date(mock.details.install_time), + appLaunchCount: mock.details.app_launch_count, + installId: mock.details.install_id, + payload: mock.details.payload, + }, + }; + } else { + return { + status: mock.status, + }; + } +} + +describe('AdaptyInstallationStatusCoder', () => { + let coder: AdaptyInstallationStatusCoder; + + beforeEach(() => { + coder = new AdaptyInstallationStatusCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-installation-status.ts b/src/coders/adapty-installation-status.ts new file mode 100644 index 0000000..d462193 --- /dev/null +++ b/src/coders/adapty-installation-status.ts @@ -0,0 +1,55 @@ +import { AdaptyInstallationStatus, AdaptyInstallationDetails } from '@/types'; +import type { Def } from '@/types/schema'; +import { Converter } from './types'; +import { DateCoder } from './date'; + +export class AdaptyInstallationStatusCoder + implements + Converter +{ + encode(model: AdaptyInstallationStatus): Def['AdaptyInstallationStatus'] { + if (model.status === 'determined') { + const details: Def['AdaptyInstallationDetails'] = { + install_time: new DateCoder().encode(model.details.installTime), + app_launch_count: model.details.appLaunchCount, + }; + + if (model.details.installId) { + details.install_id = model.details.installId; + } + + if (model.details.payload) { + details.payload = model.details.payload; + } + + return { + status: 'determined', + details, + }; + } + + return { + status: model.status, + }; + } + + decode(json: Def['AdaptyInstallationStatus']): AdaptyInstallationStatus { + if (json.status === 'determined') { + const details: AdaptyInstallationDetails = { + installTime: new DateCoder().decode(json.details.install_time), + appLaunchCount: json.details.app_launch_count, + installId: json.details.install_id, + payload: json.details.payload, + }; + + return { + status: 'determined', + details, + }; + } + + return { + status: json.status, + }; + } +} diff --git a/src/coders/adapty-native-error.test.ts b/src/coders/adapty-native-error.test.ts new file mode 100644 index 0000000..8c25da2 --- /dev/null +++ b/src/coders/adapty-native-error.test.ts @@ -0,0 +1,63 @@ +import { AdaptyNativeErrorCoder } from './adapty-native-error'; +import { AdaptyError } from '@/adapty-error'; +import type { AdaptyNativeError } from '@/types/bridge'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyNativeError; +const mocks: Def['AdaptyError'][] = [ + { + adapty_code: 400, + message: 'The provided input is invalid.', + detail: 'Input XYZ is missing.', + }, + { + adapty_code: 401, + message: 'User is not authorized.', + detail: 'Missing or invalid token.', + }, + { adapty_code: 500, message: 'Internal server error.' }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + adaptyCode: mock.adapty_code, + message: mock.message, + ...(mock.detail && { detail: mock.detail }), + }; +} + +describe('AdaptyNativeErrorCoder', () => { + let coder: AdaptyNativeErrorCoder; + + beforeEach(() => { + coder = new AdaptyNativeErrorCoder(); + }); + + // Important for analytics systems, etc. + it.each(mocks)('should be Error', mock => { + const decoded = coder.decode(mock); + const error = coder.getError(decoded); + + expect(error).toBeInstanceOf(Error); + }); + + it.each(mocks)('should be AdaptyError', mock => { + const decoded = coder.decode(mock); + const error = coder.getError(decoded); + + expect(error).toBeInstanceOf(AdaptyError); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toEqual(mock); + }); +}); diff --git a/src/coders/adapty-native-error.ts b/src/coders/adapty-native-error.ts new file mode 100644 index 0000000..9cda3a4 --- /dev/null +++ b/src/coders/adapty-native-error.ts @@ -0,0 +1,30 @@ +import type { AdaptyNativeError } from '@/types/bridge'; +import type { ErrorConverter } from './error-coder'; +import type { Properties } from './types'; +import type { Def } from '@/types/schema'; +import { SimpleCoder } from './coder'; +import { AdaptyError } from '@/adapty-error'; + +type Model = AdaptyNativeError; +type Serializable = Def['AdaptyError']; + +export class AdaptyNativeErrorCoder + extends SimpleCoder + implements ErrorConverter +{ + public type: 'error' = 'error'; + + protected properties: Properties = { + adaptyCode: { key: 'adapty_code', required: true, type: 'number' }, + message: { key: 'message', required: true, type: 'string' }, + detail: { key: 'detail', required: false, type: 'string' }, + }; + + public getError(data: Model): AdaptyError { + return new AdaptyError({ + adaptyCode: data.adaptyCode as any, + message: data.message, + detail: data.detail, + }); + } +} diff --git a/src/coders/adapty-non-subscription.test.ts b/src/coders/adapty-non-subscription.test.ts new file mode 100644 index 0000000..c23e82c --- /dev/null +++ b/src/coders/adapty-non-subscription.test.ts @@ -0,0 +1,62 @@ +import type { Def } from '@/types/schema'; +import type { AdaptyNonSubscription, VendorStore } from '@/types'; +import { AdaptyNonSubscriptionCoder } from './adapty-non-subscription'; + +type Model = AdaptyNonSubscription; +const mocks: Def['AdaptyProfile.NonSubscription'][] = [ + { + is_consumable: true, + is_refund: false, + is_sandbox: false, + purchase_id: 'purchase123', + purchased_at: '2023-08-08T12:00:00.000Z', + store: 'appstore', + vendor_product_id: 'product1', + vendor_transaction_id: 'transaction1', + }, + { + is_consumable: false, + is_refund: true, + is_sandbox: true, + purchase_id: 'purchase456', + purchased_at: '2023-07-15T14:30:00.000Z', + store: 'google_play', + vendor_product_id: 'product2', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + isConsumable: mock.is_consumable, + isRefund: mock.is_refund, + isSandbox: mock.is_sandbox, + purchaseId: mock.purchase_id, + purchasedAt: new Date(mock.purchased_at), + store: mock.store as VendorStore, + vendorProductId: mock.vendor_product_id, + ...(mock.vendor_transaction_id && { + vendorTransactionId: mock.vendor_transaction_id, + }), + }; +} + +describe('AdaptyNonSubscriptionCoder', () => { + let coder: AdaptyNonSubscriptionCoder; + + beforeEach(() => { + coder = new AdaptyNonSubscriptionCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-non-subscription.ts b/src/coders/adapty-non-subscription.ts new file mode 100644 index 0000000..5a42ec3 --- /dev/null +++ b/src/coders/adapty-non-subscription.ts @@ -0,0 +1,37 @@ +import type { AdaptyNonSubscription } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { DateCoder } from './date'; + +type Model = AdaptyNonSubscription; +type Serializable = Def['AdaptyProfile.NonSubscription']; + +export class AdaptyNonSubscriptionCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + isConsumable: { key: 'is_consumable', required: true, type: 'boolean' }, + isRefund: { key: 'is_refund', required: true, type: 'boolean' }, + isSandbox: { key: 'is_sandbox', required: true, type: 'boolean' }, + purchasedAt: { + key: 'purchased_at', + required: true, + type: 'string', + converter: new DateCoder(), + }, + purchaseId: { key: 'purchase_id', required: true, type: 'string' }, + store: { key: 'store', required: true, type: 'string' }, + vendorProductId: { + key: 'vendor_product_id', + required: true, + type: 'string', + }, + vendorTransactionId: { + key: 'vendor_transaction_id', + required: false, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-onboarding-builder.test.ts b/src/coders/adapty-onboarding-builder.test.ts new file mode 100644 index 0000000..fcd129f --- /dev/null +++ b/src/coders/adapty-onboarding-builder.test.ts @@ -0,0 +1,42 @@ +import { AdaptyOnboardingBuilderCoder } from './adapty-onboarding-builder'; +import type { AdaptyOnboardingBuilder } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyOnboardingBuilder; +type Serializable = Required['onboarding_builder']; + +const mocks: Serializable[] = [ + { + config_url: 'https://config.adapty.io/onboarding', + }, + { + config_url: 'https://config.adapty.io/onboarding-v2', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + url: mock.config_url, + }; +} + +describe('AdaptyOnboardingBuilderCoder', () => { + let coder: AdaptyOnboardingBuilderCoder; + + beforeEach(() => { + coder = new AdaptyOnboardingBuilderCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-onboarding-builder.ts b/src/coders/adapty-onboarding-builder.ts new file mode 100644 index 0000000..0e4a4b6 --- /dev/null +++ b/src/coders/adapty-onboarding-builder.ts @@ -0,0 +1,20 @@ +import type { AdaptyOnboardingBuilder } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptyOnboardingBuilder; +type Serializable = Required['onboarding_builder']; + +export class AdaptyOnboardingBuilderCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + url: { + key: 'config_url', + required: true, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-onboarding.test.ts b/src/coders/adapty-onboarding.test.ts new file mode 100644 index 0000000..9600da2 --- /dev/null +++ b/src/coders/adapty-onboarding.test.ts @@ -0,0 +1,119 @@ +import { AdaptyOnboardingCoder } from './adapty-onboarding'; +import type { AdaptyOnboarding } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyOnboarding; +const mocks: Def['AdaptyOnboarding'][] = [ + { + placement: { + ab_test_name: 'test_ab', + audience_name: 'test_audience', + developer_id: 'placement_123', + revision: 1, + placement_audience_version_id: 'version_123', + is_tracking_purchases: true, + }, + onboarding_id: 'onboarding_123', + onboarding_name: 'Premium Onboarding', + variation_id: 'variation_456', + response_created_at: 1640995200000, + onboarding_builder: { + config_url: 'https://example.com', + }, + remote_config: { + lang: 'en', + data: '{"feature": "premium", "theme": "dark"}', + }, + payload_data: '{"custom": "payload"}', + request_locale: 'en', + }, + { + placement: { + ab_test_name: 'test_ab_2', + audience_name: 'test_audience_2', + developer_id: 'placement_789', + revision: 2, + placement_audience_version_id: 'version_789', + }, + onboarding_id: 'onboarding_789', + onboarding_name: 'Basic Onboarding', + variation_id: 'variation_123', + response_created_at: 1640995300000, + request_locale: 'en', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + placement: { + abTestName: mock.placement.ab_test_name, + audienceName: mock.placement.audience_name, + id: mock.placement.developer_id, + revision: mock.placement.revision, + audienceVersionId: mock.placement.placement_audience_version_id, + ...(mock.placement.is_tracking_purchases !== undefined && { + isTrackingPurchases: mock.placement.is_tracking_purchases, + }), + }, + id: mock.onboarding_id, + name: mock.onboarding_name, + variationId: mock.variation_id, + hasViewConfiguration: mock.onboarding_builder !== undefined, + ...(mock.response_created_at && { version: mock.response_created_at }), + ...(mock.onboarding_builder && { + onboardingBuilder: { + url: mock.onboarding_builder.config_url, + }, + }), + ...(mock.remote_config && { + remoteConfig: { + lang: mock.remote_config.lang, + data: JSON.parse(mock.remote_config.data), + dataString: mock.remote_config.data, + }, + }), + ...(mock.payload_data && { payloadData: mock.payload_data }), + requestLocale: mock.request_locale, + }; +} + +describe('AdaptyOnboardingCoder', () => { + let coder: AdaptyOnboardingCoder; + + beforeEach(() => { + coder = new AdaptyOnboardingCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + const expected = toModel(mock); + + expect(decoded.placement).toStrictEqual(expected.placement); + expect(decoded.id).toBe(expected.id); + expect(decoded.name).toBe(expected.name); + expect(decoded.variationId).toBe(expected.variationId); + expect(decoded.hasViewConfiguration).toBe(expected.hasViewConfiguration); + expect(decoded.version).toBe(expected.version); + expect(decoded.onboardingBuilder).toStrictEqual(expected.onboardingBuilder); + expect(decoded.payloadData).toBe(expected.payloadData); + + if (expected.remoteConfig) { + expect(decoded.remoteConfig?.lang).toBe(expected.remoteConfig.lang); + expect(decoded.remoteConfig?.data).toStrictEqual( + expected.remoteConfig.data, + ); + expect(decoded.remoteConfig?.dataString).toBeTruthy(); + } + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + const { hasViewConfiguration, ...encodableDecoded } = decoded; + const reDecoded = coder.decode(encoded); + const { hasViewConfiguration: _, ...encodableReDecoded } = reDecoded; + + expect(encodableReDecoded).toStrictEqual(encodableDecoded); + }); +}); diff --git a/src/coders/adapty-onboarding.ts b/src/coders/adapty-onboarding.ts new file mode 100644 index 0000000..3adff23 --- /dev/null +++ b/src/coders/adapty-onboarding.ts @@ -0,0 +1,57 @@ +import type { AdaptyOnboarding } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { Coder } from './coder'; +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import { AdaptyOnboardingBuilderCoder } from './adapty-onboarding-builder'; +import { AdaptyPlacementCoder } from '@/coders/adapty-placement'; + +type Model = AdaptyOnboarding; +type CodableModel = Omit; +type Serializable = Def['AdaptyOnboarding']; + +export class AdaptyOnboardingCoder extends Coder< + Model, + CodableModel, + Serializable +> { + protected properties: Properties = { + placement: { + key: 'placement', + required: true, + type: 'object', + converter: new AdaptyPlacementCoder(), + }, + id: { key: 'onboarding_id', required: true, type: 'string' }, + name: { key: 'onboarding_name', required: true, type: 'string' }, + remoteConfig: { + key: 'remote_config', + required: false, + type: 'object', + converter: new AdaptyRemoteConfigCoder(), + }, + variationId: { key: 'variation_id', required: true, type: 'string' }, + version: { key: 'response_created_at', required: false, type: 'number' }, + onboardingBuilder: { + key: 'onboarding_builder', + required: false, + type: 'object', + converter: new AdaptyOnboardingBuilderCoder(), + }, + payloadData: { key: 'payload_data', required: false, type: 'string' }, + requestLocale: { key: 'request_locale', required: true, type: 'string' }, + }; + + override decode(data: Serializable): Model { + const codablePart = super.decode(data); + return { + ...codablePart, + hasViewConfiguration: codablePart.onboardingBuilder !== undefined, + }; + } + + override encode(data: Model): Serializable { + const { hasViewConfiguration, ...codablePart } = data; + return super.encode(codablePart); + } +} diff --git a/src/coders/adapty-paywall-builder.test.ts b/src/coders/adapty-paywall-builder.test.ts new file mode 100644 index 0000000..7a78cf4 --- /dev/null +++ b/src/coders/adapty-paywall-builder.test.ts @@ -0,0 +1,43 @@ +import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; +import type { AdaptyPaywallBuilder } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyPaywallBuilder; +const mocks: Required['paywall_builder'][] = [ + { + paywall_builder_id: 'paywallBuilder1', + lang: 'en', + }, + { + paywall_builder_id: 'paywallBuilder2', + lang: 'fr', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + lang: mock.lang, + id: mock.paywall_builder_id, + }; +} + +describe('AdaptyPaywallBuilderCoder', () => { + let coder: AdaptyPaywallBuilderCoder; + + beforeEach(() => { + coder = new AdaptyPaywallBuilderCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-paywall-builder.ts b/src/coders/adapty-paywall-builder.ts new file mode 100644 index 0000000..840ecfc --- /dev/null +++ b/src/coders/adapty-paywall-builder.ts @@ -0,0 +1,25 @@ +import type { AdaptyPaywallBuilder } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptyPaywallBuilder; +type Serializable = Required['paywall_builder']; + +export class AdaptyPaywallBuilderCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + id: { + key: 'paywall_builder_id', + required: true, + type: 'string', + }, + lang: { + key: 'lang', + required: true, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-paywall-product.test.ts b/src/coders/adapty-paywall-product.test.ts new file mode 100644 index 0000000..5b4a900 --- /dev/null +++ b/src/coders/adapty-paywall-product.test.ts @@ -0,0 +1,169 @@ +import type { AdaptyPaywallProduct } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyPriceCoder } from './adapty-price'; +import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; +import { AdaptySubscriptionDetailsCoder } from './adapty-subscription-details'; + +type Model = AdaptyPaywallProduct; +const mocks: Def['AdaptyPaywallProduct.Response'][] = [ + { + is_family_shareable: false, + localized_description: 'Get premium features with this plan', + localized_title: 'Yearly Premium Plan', + paywall_ab_test_name: 'abTest1', + paywall_name: 'Premium Subscription', + paywall_variation_id: 'variation1', + region_code: 'US', + payload_data: 'examplePayloadData', + vendor_product_id: 'yearly.premium.6999', + adapty_product_id: 'adapty_product_id', + access_level_id: 'access_level_id', + product_type: 'product_type', + paywall_product_index: 0, + web_purchase_url: 'https://example.com/purchase', + price: { + amount: 69.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$69.99', + }, + subscription: { + base_plan_id: 'androidPlan1', + renewal_type: 'autorenewable', + period: { + unit: 'year', + number_of_units: 1, + }, + localized_period: '1 year', + offer: { + offer_identifier: { + type: 'introductory', + id: 'test_intro_offer', + }, + phases: [ + { + price: { + amount: 49.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$49.99', + }, + number_of_periods: 2, + payment_mode: 'pay_as_you_go', + subscription_period: { + unit: 'month', + number_of_units: 3, + }, + localized_subscription_period: '3 months', + localized_number_of_periods: '2', + }, + ], + offer_tags: ['tag1', 'tag2'], + }, + }, + }, + { + is_family_shareable: true, + localized_description: 'Get premium features with this plan', + localized_title: 'Yearly Premium Plan', + paywall_ab_test_name: 'abTest1', + paywall_name: 'Premium Subscription', + paywall_variation_id: 'variation1', + region_code: 'US', + payload_data: 'examplePayloadData', + vendor_product_id: 'yearly.premium.6999', + adapty_product_id: 'adapty_product_id', + access_level_id: 'access_level_id', + product_type: 'product_type', + paywall_product_index: 1, + price: { + amount: 69.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$69.99', + }, + subscription: { + group_identifier: 'group1', + period: { + unit: 'year', + number_of_units: 1, + }, + localized_period: '1 year', + offer: { + offer_identifier: { + type: 'promotional', + id: 'test_promo_offer', + }, + phases: [ + { + price: { + amount: 29.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$29.99', + }, + number_of_periods: 1, + payment_mode: 'free_trial', + subscription_period: { + unit: 'month', + number_of_units: 1, + }, + localized_subscription_period: '1 month', + localized_number_of_periods: '1', + }, + ], + }, + }, + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _price = new AdaptyPriceCoder(); + const _details = new AdaptySubscriptionDetailsCoder(); + + return { + ios: { + isFamilyShareable: mock.is_family_shareable as boolean, + }, + adaptyId: mock.adapty_product_id, + accessLevelId: mock.access_level_id, + productType: mock.product_type, + paywallProductIndex: mock.paywall_product_index, + localizedDescription: mock.localized_description, + localizedTitle: mock.localized_title, + paywallABTestName: mock.paywall_ab_test_name, + paywallName: mock.paywall_name, + variationId: mock.paywall_variation_id, + regionCode: mock.region_code, + ...(mock.web_purchase_url && { webPurchaseUrl: mock.web_purchase_url }), + payloadData: mock.payload_data, + vendorProductId: mock.vendor_product_id, + ...(mock.price && { + price: _price.decode(mock.price), + }), + ...(mock.subscription && { + subscription: _details.decode(mock.subscription), + }), + }; +} + +describe('AdaptyPaywallProductCoder', () => { + let coder: AdaptyPaywallProductCoder; + + beforeEach(() => { + coder = new AdaptyPaywallProductCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-paywall-product.ts b/src/coders/adapty-paywall-product.ts new file mode 100644 index 0000000..c186cb6 --- /dev/null +++ b/src/coders/adapty-paywall-product.ts @@ -0,0 +1,92 @@ +import type { AdaptyPaywallProduct } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyPriceCoder } from './adapty-price'; +import { AdaptySubscriptionDetailsCoder } from './adapty-subscription-details'; + +type Model = AdaptyPaywallProduct; +type Serializable = Def['AdaptyPaywallProduct.Response']; + +export class AdaptyPaywallProductCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + vendorProductId: { + key: 'vendor_product_id', + required: true, + type: 'string', + }, + adaptyId: { + key: 'adapty_product_id', + required: true, + type: 'string', + }, + paywallProductIndex: { + key: 'paywall_product_index', + required: true, + type: 'number', + }, + localizedDescription: { + key: 'localized_description', + required: true, + type: 'string', + }, + localizedTitle: { key: 'localized_title', required: true, type: 'string' }, + regionCode: { key: 'region_code', required: false, type: 'string' }, + variationId: { + key: 'paywall_variation_id', + required: true, + type: 'string', + }, + paywallABTestName: { + key: 'paywall_ab_test_name', + required: true, + type: 'string', + }, + paywallName: { key: 'paywall_name', required: true, type: 'string' }, + accessLevelId: { key: 'access_level_id', required: true, type: 'string' }, + productType: { key: 'product_type', required: true, type: 'string' }, + price: { + key: 'price', + required: false, // Native SDKs require this + type: 'object', + converter: new AdaptyPriceCoder(), + }, + webPurchaseUrl: { + key: 'web_purchase_url', + required: false, + type: 'string', + }, + payloadData: { key: 'payload_data', required: false, type: 'string' }, + subscription: { + key: 'subscription', + required: false, + type: 'object', + converter: new AdaptySubscriptionDetailsCoder(), + }, + ios: { + isFamilyShareable: { + key: 'is_family_shareable', + required: true, + type: 'boolean', + }, + }, + }; + + public getInput(data: Serializable): Def['AdaptyPaywallProduct.Request'] { + return { + adapty_product_id: data.adapty_product_id, + access_level_id: data.access_level_id, + product_type: data.product_type, + paywall_product_index: data.paywall_product_index, + paywall_ab_test_name: data.paywall_ab_test_name, + payload_data: data.payload_data, + paywall_name: data.paywall_name, + paywall_variation_id: data.paywall_variation_id, + subscription_offer_identifier: data.subscription?.offer?.offer_identifier, + vendor_product_id: data.vendor_product_id, + }; + } +} diff --git a/src/coders/adapty-paywall.test.ts b/src/coders/adapty-paywall.test.ts new file mode 100644 index 0000000..149abfc --- /dev/null +++ b/src/coders/adapty-paywall.test.ts @@ -0,0 +1,160 @@ +import type { AdaptyPaywall, ProductReference } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyPaywallCoder } from './adapty-paywall'; +import { ProductReferenceCoder } from './product-reference'; +import { ArrayCoder } from './array'; +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; + +type Model = AdaptyPaywall; +const mocks: Def['AdaptyPaywall'][] = [ + { + placement: { + ab_test_name: 'testA', + audience_name: 'audienceC', + developer_id: 'dev123', + revision: 5, + placement_audience_version_id: 'version_123', + is_tracking_purchases: true, + }, + payload_data: 'additionalData', + paywall_name: 'Paywall1', + paywall_id: '456789o', + response_created_at: 1630458390000, + products: [ + { + vendor_product_id: 'product1', + adapty_product_id: 'adaptyProduct1', + access_level_id: 'premium', + product_type: 'subscription', + promotional_offer_id: 'offer1', // iOS Only + win_back_offer_id: 'offer2', // iOS Only + base_plan_id: 'base1', // Android Only + offer_id: 'androidOffer1', // Android Only + }, + { + vendor_product_id: 'product2', + adapty_product_id: 'adaptyProduct2', + access_level_id: 'premium', + product_type: 'subscription', + }, + ], + remote_config: { + lang: 'en', + data: '{"key":"value"}', // A custom JSON string configured in Adapty Dashboard for this paywall + }, + variation_id: 'var001', + paywall_builder: { + paywall_builder_id: 'paywallBuilder1', + lang: 'en', + }, + request_locale: 'en', + }, + { + placement: { + ab_test_name: 'testB', + audience_name: 'audienceD', + developer_id: 'dev456', + revision: 3, + placement_audience_version_id: 'version_456', + }, + paywall_id: 'instanceId267', + variation_id: 'var002', + paywall_name: 'Paywall2', + products: [ + { + vendor_product_id: 'product3', + adapty_product_id: 'adaptyProduct3', + access_level_id: 'vip', + product_type: 'subscription', + }, + ], + remote_config: { lang: 'fr', data: '' }, + web_purchase_url: 'https://example.com/purchase', + response_created_at: 1632458390000, + request_locale: 'fr', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _products = new ArrayCoder( + ProductReferenceCoder, + ); + const _remoteConfig = new AdaptyRemoteConfigCoder(); + const _paywallBuilder = new AdaptyPaywallBuilderCoder(); + + return { + placement: { + abTestName: mock.placement.ab_test_name, + audienceName: mock.placement.audience_name, + id: mock.placement.developer_id, + revision: mock.placement.revision, + audienceVersionId: mock.placement.placement_audience_version_id, + ...(mock.placement.is_tracking_purchases !== undefined && { + isTrackingPurchases: mock.placement.is_tracking_purchases, + }), + }, + id: mock.paywall_id, + ...(mock.payload_data && { payloadData: mock.payload_data }), + name: mock.paywall_name, + products: _products.decode(mock.products), + productIdentifiers: mock.products.map(product => ({ + vendorProductId: product.vendor_product_id, + adaptyProductId: product.adapty_product_id, + basePlanId: product.base_plan_id, + })), + ...(mock.remote_config && { + remoteConfig: _remoteConfig.decode(mock.remote_config), + }), + variationId: mock.variation_id, + ...(mock.response_created_at && { version: mock.response_created_at }), + ...(mock.paywall_builder && { + paywallBuilder: _paywallBuilder.decode(mock.paywall_builder), + }), + ...(mock.web_purchase_url && { webPurchaseUrl: mock.web_purchase_url }), + requestLocale: mock.request_locale, + hasViewConfiguration: mock.paywall_builder !== undefined, + }; +} + +describe('AdaptyPaywallCoder', () => { + let coder: AdaptyPaywallCoder; + + beforeEach(() => { + coder = new AdaptyPaywallCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); + + it('should derive productIdentifiers from products', () => { + const mock = mocks[0]; // Use the first mock that has both iOS and Android data + expect(mock).toBeDefined(); + const decoded = coder.decode(mock!); + + expect(decoded.productIdentifiers).toBeDefined(); + expect(decoded.productIdentifiers).toHaveLength(2); + + expect(decoded.productIdentifiers[0]).toEqual({ + vendorProductId: 'product1', + adaptyProductId: 'adaptyProduct1', + basePlanId: 'base1', + }); + + expect(decoded.productIdentifiers[1]).toEqual({ + vendorProductId: 'product2', + adaptyProductId: 'adaptyProduct2', + basePlanId: undefined, + }); + }); +}); diff --git a/src/coders/adapty-paywall.ts b/src/coders/adapty-paywall.ts new file mode 100644 index 0000000..93c6a19 --- /dev/null +++ b/src/coders/adapty-paywall.ts @@ -0,0 +1,75 @@ +import type { AdaptyPaywall } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { ProductReferenceCoder } from './product-reference'; +import { ArrayCoder } from './array'; +import { Coder } from './coder'; +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; +import { AdaptyPlacementCoder } from '@/coders/adapty-placement'; + +type Model = AdaptyPaywall; +type CodableModel = Omit; +type Serializable = Def['AdaptyPaywall']; + +export class AdaptyPaywallCoder extends Coder< + Model, + CodableModel, + Serializable +> { + protected properties: Properties = { + placement: { + key: 'placement', + required: true, + type: 'object', + converter: new AdaptyPlacementCoder(), + }, + id: { key: 'paywall_id', required: true, type: 'string' }, + name: { key: 'paywall_name', required: true, type: 'string' }, + products: { + key: 'products', + required: true, + type: 'array', + converter: new ArrayCoder(ProductReferenceCoder), + }, + remoteConfig: { + key: 'remote_config', + required: false, + type: 'object', + converter: new AdaptyRemoteConfigCoder(), + }, + variationId: { key: 'variation_id', required: true, type: 'string' }, + version: { key: 'response_created_at', required: false, type: 'number' }, + paywallBuilder: { + key: 'paywall_builder', + required: false, + type: 'object', + converter: new AdaptyPaywallBuilderCoder(), + }, + webPurchaseUrl: { + key: 'web_purchase_url', + required: false, + type: 'string', + }, + payloadData: { key: 'payload_data', required: false, type: 'string' }, + requestLocale: { key: 'request_locale', required: true, type: 'string' }, + }; + + override decode(data: Serializable): Model { + const codablePart = super.decode(data); + return { + ...codablePart, + hasViewConfiguration: codablePart.paywallBuilder !== undefined, + productIdentifiers: codablePart.products.map(product => ({ + vendorProductId: product.vendorId, + adaptyProductId: product.adaptyId, + basePlanId: product.android?.basePlanId, + })), + }; + } + + override encode(data: Model): Serializable { + const { hasViewConfiguration, productIdentifiers, ...codablePart } = data; + return super.encode(codablePart); + } +} diff --git a/src/coders/adapty-placement.test.ts b/src/coders/adapty-placement.test.ts new file mode 100644 index 0000000..c52008b --- /dev/null +++ b/src/coders/adapty-placement.test.ts @@ -0,0 +1,56 @@ +import { AdaptyPlacementCoder } from './adapty-placement'; +import type { AdaptyPlacement } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyPlacement; +const mocks: Def['AdaptyPlacement'][] = [ + { + ab_test_name: 'test_ab', + audience_name: 'test_audience', + developer_id: 'placement_123', + revision: 1, + placement_audience_version_id: 'version_123', + is_tracking_purchases: true, + }, + { + ab_test_name: 'another_ab', + audience_name: 'another_audience', + developer_id: 'placement_456', + revision: 2, + placement_audience_version_id: 'version_456', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + abTestName: mock.ab_test_name, + audienceName: mock.audience_name, + id: mock.developer_id, + revision: mock.revision, + audienceVersionId: mock.placement_audience_version_id, + ...(mock.is_tracking_purchases !== undefined && { + isTrackingPurchases: mock.is_tracking_purchases, + }), + }; +} + +describe('AdaptyPlacementCoder', () => { + let coder: AdaptyPlacementCoder; + + beforeEach(() => { + coder = new AdaptyPlacementCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-placement.ts b/src/coders/adapty-placement.ts new file mode 100644 index 0000000..a2a7884 --- /dev/null +++ b/src/coders/adapty-placement.ts @@ -0,0 +1,26 @@ +import type { AdaptyPlacement } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptyPlacement; +type Serializable = Def['AdaptyPlacement']; + +export class AdaptyPlacementCoder extends SimpleCoder { + protected properties: Properties = { + abTestName: { key: 'ab_test_name', required: true, type: 'string' }, + audienceName: { key: 'audience_name', required: true, type: 'string' }, + id: { key: 'developer_id', required: true, type: 'string' }, + revision: { key: 'revision', required: true, type: 'number' }, + audienceVersionId: { + key: 'placement_audience_version_id', + required: true, + type: 'string', + }, + isTrackingPurchases: { + key: 'is_tracking_purchases', + required: false, + type: 'boolean', + }, + }; +} diff --git a/src/coders/adapty-price.test.ts b/src/coders/adapty-price.test.ts new file mode 100644 index 0000000..a899bdb --- /dev/null +++ b/src/coders/adapty-price.test.ts @@ -0,0 +1,44 @@ +import { AdaptyPriceCoder } from './adapty-price'; +import type { AdaptyPrice } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyPrice; +const mocks: Def['AdaptyPrice'][] = [ + { + amount: 9.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$9.99', + }, + { amount: 9.99 }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + amount: mock.amount, + ...(mock.currency_code && { currencyCode: mock.currency_code }), + ...(mock.currency_symbol && { currencySymbol: mock.currency_symbol }), + ...(mock.localized_string && { localizedString: mock.localized_string }), + }; +} + +describe('AdaptyPriceCoder', () => { + let coder: AdaptyPriceCoder; + + beforeEach(() => { + coder = new AdaptyPriceCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-price.ts b/src/coders/adapty-price.ts new file mode 100644 index 0000000..d6be140 --- /dev/null +++ b/src/coders/adapty-price.ts @@ -0,0 +1,32 @@ +import type { AdaptyPrice } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptyPrice; +type Serializable = Def['AdaptyPrice']; + +export class AdaptyPriceCoder extends SimpleCoder { + protected properties: Properties = { + amount: { + key: 'amount', + required: true, + type: 'number', + }, + currencyCode: { + key: 'currency_code', + required: false, + type: 'string', + }, + currencySymbol: { + key: 'currency_symbol', + required: false, + type: 'string', + }, + localizedString: { + key: 'localized_string', + required: false, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-profile-parameters.test.ts b/src/coders/adapty-profile-parameters.test.ts new file mode 100644 index 0000000..c6d49fb --- /dev/null +++ b/src/coders/adapty-profile-parameters.test.ts @@ -0,0 +1,27 @@ +import { Def } from '@/types/schema'; +import { AdaptyProfileParametersCoder } from './adapty-profile-parameters'; + +const mocks: Def['AdaptyProfileParameters'][] = [ + { + first_name: 'John', + last_name: 'Doe', + gender: 'm', + birthday: '1990-01-01', + email: 'john.doe@example.com', + phone_number: '+123456789', + att_status: 2, + custom_attributes: { attr1: 'value1', attr2: 'value2' }, + analytics_disabled: false, + }, +]; + +describe('AdaptyProfileParametersCoder', () => { + const coder = new AdaptyProfileParametersCoder(); + + it.each(mocks)('should encode/decode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toEqual(mock); + }); +}); diff --git a/src/coders/adapty-profile-parameters.ts b/src/coders/adapty-profile-parameters.ts new file mode 100644 index 0000000..4eea9ea --- /dev/null +++ b/src/coders/adapty-profile-parameters.ts @@ -0,0 +1,36 @@ +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import type { AdaptyProfileParameters } from '@/types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptyProfileParameters; +type Serializable = Def['AdaptyProfileParameters']; + +export class AdaptyProfileParametersCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + firstName: { key: 'first_name', required: false, type: 'string' }, + lastName: { key: 'last_name', required: false, type: 'string' }, + gender: { key: 'gender', required: false, type: 'string' }, + birthday: { key: 'birthday', required: false, type: 'string' }, + email: { key: 'email', required: false, type: 'string' }, + phoneNumber: { key: 'phone_number', required: false, type: 'string' }, + appTrackingTransparencyStatus: { + key: 'att_status', + required: false, + type: 'number', + }, + codableCustomAttributes: { + key: 'custom_attributes', + required: false, + type: 'object', + }, + analyticsDisabled: { + key: 'analytics_disabled', + required: false, + type: 'boolean', + }, + }; +} diff --git a/src/coders/adapty-profile.test.ts b/src/coders/adapty-profile.test.ts new file mode 100644 index 0000000..79312e4 --- /dev/null +++ b/src/coders/adapty-profile.test.ts @@ -0,0 +1,230 @@ +import type { Def } from '@/types/schema'; +import type { AdaptyNonSubscription, AdaptyProfile } from '@/types'; +import { AdaptyProfileCoder } from './adapty-profile'; +import { AdaptyAccessLevelCoder } from './adapty-access-level'; +import { HashmapCoder } from './hashmap'; +import { AdaptySubscriptionCoder } from './adapty-subscription'; +import { AdaptyNonSubscriptionCoder } from './adapty-non-subscription'; +import { ArrayCoder } from './array'; + +type Model = AdaptyProfile; +const mocks: Omit< + Def['AdaptyProfile'], + 'segment_hash' | 'is_test_user' | 'timestamp' +>[] = [ + { + customer_user_id: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + paid_access_levels: { + premium: { + id: 'premium', + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + is_active: false, + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + cancellation_reason: 'voluntarily_cancelled', + store: 'app_store', + }, + }, + custom_attributes: {}, + subscriptions: { + 'monthly.premium.999': { + is_lifetime: false, + vendor_product_id: 'monthly.premium.999', + is_sandbox: true, + unsubscribed_at: '2023-01-12T11:36:38.000Z', + expires_at: '2023-01-12T11:36:38.000Z', + will_renew: false, + vendor_transaction_id: '2000000248420224', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-01-12T11:31:38.000Z', + is_refund: false, + store: 'app_store', + }, + 'weekly.premium.599': { + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + is_sandbox: true, + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + vendor_transaction_id: '2000000378024239', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + store: 'app_store', + cancellation_reason: 'voluntarily_cancelled', + }, + }, + profile_id: '69a4be0c-7ee2-4669-b637-814a60494346', + }, + { + non_subscriptions: { + adapty_product_1: [ + { + is_sandbox: true, + vendor_product_id: 'adapty_product_1', + is_consumable: false, + purchased_at: '2020-10-23T07:42:00.000Z', + vendor_transaction_id: 'GPA.3316-9977-7087-23521', + purchase_id: 'ec4323b1-a83e-4ed2-be3f-370300323979', + is_refund: false, + store: 'play_store', + }, + ], + consumable_apples_99: [ + { + is_sandbox: true, + vendor_product_id: 'consumable_apples_99', + is_consumable: true, + purchased_at: '2023-02-24T12:14:27.000Z', + vendor_transaction_id: 'GPA.3385-8493-4264-13908', + purchase_id: 'dba87b14-9d8e-442e-8562-e647b4c6143d', + is_refund: false, + store: 'play_store', + }, + ], + adapty_product_2: [ + { + is_sandbox: true, + vendor_product_id: 'adapty_product_2', + is_consumable: false, + purchased_at: '2020-10-23T08:00:00.000Z', + vendor_transaction_id: 'GPA.3311-5363-7214-69403', + purchase_id: '269363f6-5617-4c99-ad2e-c1213f403e60', + is_refund: false, + store: 'play_store', + }, + ], + }, + customer_user_id: 'divan', + paid_access_levels: { + premium: { + id: 'premium', + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + unsubscribed_at: '2023-08-06T06:56:22.000Z', + expires_at: '2023-08-06T06:56:22.000Z', + will_renew: false, + is_active: false, + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + renewed_at: '2023-08-06T06:53:22.000Z', + is_refund: false, + cancellation_reason: 'voluntarily_cancelled', + store: 'app_store', + }, + }, + custom_attributes: {}, + subscriptions: { + trial_sub_12: { + is_lifetime: false, + vendor_product_id: 'trial_sub_12', + is_sandbox: true, + unsubscribed_at: '2023-06-12T07:56:29.483Z', + expires_at: '2023-06-12T07:56:29.483Z', + will_renew: false, + vendor_transaction_id: 'GPA.3351-8241-0272-90828..5', + vendor_original_transaction_id: 'GPA.3351-8241-0272-90828', + is_in_grace_period: false, + activated_at: '2023-06-12T07:21:36.251Z', + is_active: false, + renewed_at: '2023-06-12T07:51:29.483Z', + is_refund: false, + store: 'play_store', + cancellation_reason: 'billing_error', + }, + 'weekly.premium.599': { + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + is_sandbox: true, + unsubscribed_at: '2023-08-06T06:56:22.000Z', + expires_at: '2023-08-06T06:56:22.000Z', + will_renew: false, + vendor_transaction_id: '2000000383944152', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-08-06T06:53:22.000Z', + is_refund: false, + store: 'app_store', + cancellation_reason: 'voluntarily_cancelled', + }, + 'monthly.premium.999': { + is_lifetime: false, + vendor_product_id: 'monthly.premium.999', + is_sandbox: true, + unsubscribed_at: '2023-01-12T11:36:38.000Z', + expires_at: '2023-01-12T11:36:38.000Z', + will_renew: false, + vendor_transaction_id: '2000000248420224', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-01-12T11:31:38.000Z', + is_refund: false, + store: 'app_store', + }, + }, + profile_id: 'a2d52e41-3a79-4e8f-9872-63a0545d4711', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _levels = new HashmapCoder(new AdaptyAccessLevelCoder()); + const _subs = new HashmapCoder(new AdaptySubscriptionCoder()); + const _nonsubs = new HashmapCoder( + new ArrayCoder( + AdaptyNonSubscriptionCoder, + ), + ); + + return { + ...(mock.paid_access_levels && { + accessLevels: _levels.decode(mock.paid_access_levels), + }), + ...(mock.subscriptions && { + subscriptions: _subs.decode(mock.subscriptions), + }), + ...(mock.non_subscriptions && { + nonSubscriptions: _nonsubs.decode(mock.non_subscriptions), + }), + customAttributes: mock.custom_attributes, + customerUserId: mock.customer_user_id, + profileId: mock.profile_id, + }; +} + +describe('AdaptyProfileCoder', () => { + let coder: AdaptyProfileCoder; + + beforeEach(() => { + coder = new AdaptyProfileCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock as any); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock as any); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-profile.ts b/src/coders/adapty-profile.ts new file mode 100644 index 0000000..dded29b --- /dev/null +++ b/src/coders/adapty-profile.ts @@ -0,0 +1,54 @@ +import type { AdaptyNonSubscription, AdaptyProfile } from '../types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyAccessLevelCoder } from './adapty-access-level'; +import { AdaptyNonSubscriptionCoder } from './adapty-non-subscription'; +import { AdaptySubscriptionCoder } from './adapty-subscription'; +import { HashmapCoder } from './hashmap'; +import { ArrayCoder } from './array'; + +type Model = AdaptyProfile; +type Serializable = Def['AdaptyProfile']; + +export class AdaptyProfileCoder extends SimpleCoder { + protected properties: Properties = { + accessLevels: { + key: 'paid_access_levels', + required: false, + type: 'object', + converter: new HashmapCoder(new AdaptyAccessLevelCoder()), + }, + customAttributes: { + key: 'custom_attributes', + required: false, + type: 'object', + }, + customerUserId: { + key: 'customer_user_id', + required: false, + type: 'string', + }, + nonSubscriptions: { + key: 'non_subscriptions', + required: false, + type: 'object', + converter: new HashmapCoder( + new ArrayCoder( + AdaptyNonSubscriptionCoder, + ), + ), + }, + profileId: { + key: 'profile_id', + required: true, + type: 'string', + }, + subscriptions: { + key: 'subscriptions', + required: false, + type: 'object', + converter: new HashmapCoder(new AdaptySubscriptionCoder()), + }, + }; +} diff --git a/src/coders/adapty-purchase-params.test.ts b/src/coders/adapty-purchase-params.test.ts new file mode 100644 index 0000000..bb18fc9 --- /dev/null +++ b/src/coders/adapty-purchase-params.test.ts @@ -0,0 +1,91 @@ +import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; +import * as Input from '@/types/inputs'; + +jest.mock('react-native', () => ({ + Platform: { + OS: 'android', + }, +})); + +describe('AdaptyPurchaseParamsCoder', () => { + let coder: AdaptyPurchaseParamsCoder; + + beforeEach(() => { + coder = new AdaptyPurchaseParamsCoder(); + }); + + describe('encode', () => { + it('should handle deprecated type', () => { + const params: Input.MakePurchaseParamsInput = { + android: { + oldSubVendorProductId: 'old_product_id', + prorationMode: + Input.AdaptyAndroidSubscriptionUpdateReplacementMode + .ChargeProratedPrice, + isOfferPersonalized: true, + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + subscription_update_params: { + replacement_mode: 'charge_prorated_price', + old_sub_vendor_product_id: 'old_product_id', + }, + is_offer_personalized: true, + }); + }); + + it('should handle new type', () => { + const params: Input.MakePurchaseParamsInput = { + android: { + subscriptionUpdateParams: { + oldSubVendorProductId: 'old_product_id', + prorationMode: + Input.AdaptyAndroidSubscriptionUpdateReplacementMode + .ChargeProratedPrice, + }, + isOfferPersonalized: true, + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({ + subscription_update_params: { + replacement_mode: 'charge_prorated_price', + old_sub_vendor_product_id: 'old_product_id', + }, + is_offer_personalized: true, + }); + }); + + it('should handle empty params', () => { + const params: Input.MakePurchaseParamsInput = {}; + + const result = coder.encode(params); + expect(result).toEqual({}); + }); + + it('should return empty object for non-Android platforms', () => { + const originalPlatform = require('react-native').Platform; + require('react-native').Platform = { OS: 'ios' }; + + const params: Input.MakePurchaseParamsInput = { + android: { + subscriptionUpdateParams: { + oldSubVendorProductId: 'old_product_id', + prorationMode: + Input.AdaptyAndroidSubscriptionUpdateReplacementMode + .ChargeProratedPrice, + }, + isOfferPersonalized: true, + }, + }; + + const result = coder.encode(params); + expect(result).toEqual({}); + + require('react-native').Platform = originalPlatform; + }); + }); +}); diff --git a/src/coders/adapty-purchase-params.ts b/src/coders/adapty-purchase-params.ts new file mode 100644 index 0000000..c36b318 --- /dev/null +++ b/src/coders/adapty-purchase-params.ts @@ -0,0 +1,58 @@ +import * as Input from '@/types/inputs'; +import { Platform } from 'react-native'; + +type Model = Input.MakePurchaseParamsInput; +type Serializable = Record; + +function isDeprecatedType( + data: any, +): data is { android?: Input.AdaptyAndroidSubscriptionUpdateParameters } { + return ( + data && + data.android && + 'oldSubVendorProductId' in data.android && + 'prorationMode' in data.android + ); +} + +export class AdaptyPurchaseParamsCoder { + encode(data: Model): Serializable { + if (Platform.OS !== 'android') { + return {}; + } + + const purchaseParams: Serializable = {}; + + if (isDeprecatedType(data)) { + if (data.android) { + purchaseParams['subscription_update_params'] = { + replacement_mode: data.android.prorationMode, + old_sub_vendor_product_id: data.android.oldSubVendorProductId, + }; + + if (data.android.isOfferPersonalized) { + purchaseParams['is_offer_personalized'] = + data.android.isOfferPersonalized; + } + } + return purchaseParams; + } + + if (data.android) { + if (data.android.subscriptionUpdateParams) { + purchaseParams['subscription_update_params'] = { + replacement_mode: data.android.subscriptionUpdateParams.prorationMode, + old_sub_vendor_product_id: + data.android.subscriptionUpdateParams.oldSubVendorProductId, + }; + } + + if (data.android.isOfferPersonalized !== undefined) { + purchaseParams['is_offer_personalized'] = + data.android.isOfferPersonalized; + } + } + + return purchaseParams; + } +} diff --git a/src/coders/adapty-purchase-result.test.ts b/src/coders/adapty-purchase-result.test.ts new file mode 100644 index 0000000..f5a9610 --- /dev/null +++ b/src/coders/adapty-purchase-result.test.ts @@ -0,0 +1,271 @@ +import type { AdaptyPurchaseResult } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyPurchaseResultCoder } from '@/coders/adapty-purchase-result'; +import { AdaptyProfileCoder } from '@/coders/adapty-profile'; +import { Platform } from 'react-native'; + +type Model = AdaptyPurchaseResult; +type TestAdaptyPurchaseResultDef = + | Exclude + | { + type: 'success'; + profile: Omit< + Def['AdaptyProfile'], + 'segment_hash' | 'is_test_user' | 'timestamp' + >; + apple_jws_transaction?: string; + google_purchase_token?: string; + }; +const mocks: TestAdaptyPurchaseResultDef[] = [ + { + type: 'success', + profile: { + customer_user_id: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + paid_access_levels: { + premium: { + id: 'premium', + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + is_active: false, + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + cancellation_reason: 'voluntarily_cancelled', + store: 'app_store', + }, + }, + custom_attributes: {}, + subscriptions: { + 'monthly.premium.999': { + is_lifetime: false, + vendor_product_id: 'monthly.premium.999', + is_sandbox: true, + unsubscribed_at: '2023-01-12T11:36:38.000Z', + expires_at: '2023-01-12T11:36:38.000Z', + will_renew: false, + vendor_transaction_id: '2000000248420224', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-01-12T11:31:38.000Z', + is_refund: false, + store: 'app_store', + }, + 'weekly.premium.599': { + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + is_sandbox: true, + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + vendor_transaction_id: '2000000378024239', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + store: 'app_store', + cancellation_reason: 'voluntarily_cancelled', + }, + }, + profile_id: '69a4be0c-7ee2-4669-b637-814a60494346', + }, + }, + { + type: 'success', + profile: { + customer_user_id: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + paid_access_levels: { + premium: { + id: 'premium', + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + is_active: false, + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + cancellation_reason: 'voluntarily_cancelled', + store: 'app_store', + }, + }, + custom_attributes: {}, + subscriptions: { + 'monthly.premium.999': { + is_lifetime: false, + vendor_product_id: 'monthly.premium.999', + is_sandbox: true, + unsubscribed_at: '2023-01-12T11:36:38.000Z', + expires_at: '2023-01-12T11:36:38.000Z', + will_renew: false, + vendor_transaction_id: '2000000248420224', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-01-12T11:31:38.000Z', + is_refund: false, + store: 'app_store', + }, + 'weekly.premium.599': { + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + is_sandbox: true, + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + vendor_transaction_id: '2000000378024239', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + store: 'app_store', + cancellation_reason: 'voluntarily_cancelled', + }, + }, + profile_id: '69a4be0c-7ee2-4669-b637-814a60494346', + }, + apple_jws_transaction: 'test_apple_jws_transaction', + }, + { + type: 'success', + profile: { + customer_user_id: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + paid_access_levels: { + premium: { + id: 'premium', + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + is_active: false, + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + cancellation_reason: 'voluntarily_cancelled', + store: 'app_store', + }, + }, + custom_attributes: {}, + subscriptions: { + 'monthly.premium.999': { + is_lifetime: false, + vendor_product_id: 'monthly.premium.999', + is_sandbox: true, + unsubscribed_at: '2023-01-12T11:36:38.000Z', + expires_at: '2023-01-12T11:36:38.000Z', + will_renew: false, + vendor_transaction_id: '2000000248420224', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-01-12T11:31:38.000Z', + is_refund: false, + store: 'app_store', + }, + 'weekly.premium.599': { + is_lifetime: false, + vendor_product_id: 'weekly.premium.599', + is_sandbox: true, + unsubscribed_at: '2023-07-28T08:16:19.000Z', + expires_at: '2023-07-28T08:16:19.000Z', + will_renew: false, + vendor_transaction_id: '2000000378024239', + vendor_original_transaction_id: '2000000244587785', + is_in_grace_period: false, + activated_at: '2023-01-08T12:05:59.000Z', + is_active: false, + renewed_at: '2023-07-28T08:13:19.000Z', + is_refund: false, + store: 'app_store', + cancellation_reason: 'voluntarily_cancelled', + }, + }, + profile_id: '69a4be0c-7ee2-4669-b637-814a60494346', + }, + google_purchase_token: 'test_google_purchase_token', + }, + { type: 'pending' }, + { type: 'user_cancelled' }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _profile = new AdaptyProfileCoder(); + if (mock.type === 'success') { + return { + type: mock.type, + profile: _profile.decode(mock.profile as any), + ...(Platform.OS === 'ios' && mock.apple_jws_transaction + ? { ios: { jwsTransaction: mock.apple_jws_transaction } } + : {}), + ...(Platform.OS === 'android' && mock.google_purchase_token + ? { android: { purchaseToken: mock.google_purchase_token } } + : {}), + }; + } else { + return { + type: mock.type, + }; + } +} + +jest.mock('react-native', () => ({ + Platform: { + OS: 'ios', + }, +})); + +describe('AdaptyPurchaseResultCoder', () => { + let coder: AdaptyPurchaseResultCoder; + + beforeEach(() => { + coder = new AdaptyPurchaseResultCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const originalPlatform = require('react-native').Platform; + if (mock.apple_jws_transaction) { + require('react-native').Platform = { OS: 'ios' }; + } + if (mock.google_purchase_token) { + require('react-native').Platform = { OS: 'android' }; + } + + const decoded = coder.decode(mock as any); + + expect(decoded).toStrictEqual(toModel(mock)); + + require('react-native').Platform = originalPlatform; + }); + + it.each(mocks)('should decode/encode', mock => { + const originalPlatform = require('react-native').Platform; + if (mock.apple_jws_transaction) { + require('react-native').Platform = { OS: 'ios' }; + } + if (mock.google_purchase_token) { + require('react-native').Platform = { OS: 'android' }; + } + + const decoded = coder.decode(mock as any); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + + require('react-native').Platform = originalPlatform; + }); +}); diff --git a/src/coders/adapty-purchase-result.ts b/src/coders/adapty-purchase-result.ts new file mode 100644 index 0000000..ce54376 --- /dev/null +++ b/src/coders/adapty-purchase-result.ts @@ -0,0 +1,69 @@ +import type { AdaptyPurchaseResult } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyProfileCoder } from '@/coders/adapty-profile'; +import { Platform } from 'react-native'; + +type Model = AdaptyPurchaseResult; +type Serializable = Def['AdaptyPurchaseResult']; + +export class AdaptyPurchaseResultCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + type: { + key: 'type', + required: true, + type: 'string', + }, + }; + + override decode(data: Serializable): Model { + const baseResult = super.decode(data); + if (baseResult.type === 'success') { + if (!data.profile) { + throw new Error( + 'Profile is required for success type of purchase result', + ); + } + return { + ...baseResult, + profile: new AdaptyProfileCoder().decode(data.profile), + ...(Platform.OS === 'ios' && data.apple_jws_transaction + ? { ios: { jwsTransaction: data.apple_jws_transaction } } + : {}), + ...(Platform.OS === 'android' && data.google_purchase_token + ? { android: { purchaseToken: data.google_purchase_token } } + : {}), + }; + } + return baseResult; + } + + override encode(data: Model): Serializable { + const { type } = data; + + if (type === 'success') { + if (!('profile' in data)) { + throw new Error( + 'Profile is required for success type of purchase result', + ); + } + + return { + type: 'success', + profile: new AdaptyProfileCoder().encode(data.profile), + ...(Platform.OS === 'ios' && data.ios?.jwsTransaction + ? { apple_jws_transaction: data.ios.jwsTransaction } + : {}), + ...(Platform.OS === 'android' && data.android?.purchaseToken + ? { google_purchase_token: data.android.purchaseToken } + : {}), + }; + } + + return super.encode({ type }); + } +} diff --git a/src/coders/adapty-remote-config.test.ts b/src/coders/adapty-remote-config.test.ts new file mode 100644 index 0000000..8608e08 --- /dev/null +++ b/src/coders/adapty-remote-config.test.ts @@ -0,0 +1,44 @@ +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import type { AdaptyRemoteConfig } from '@/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyRemoteConfig; +const mocks: Required['remote_config'][] = [ + { + lang: 'en', + data: '{"key":"value"}', // A custom JSON string configured in Adapty Dashboard + }, + { + lang: 'en', + data: '', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + lang: mock.lang, + data: mock.data ? JSON.parse(mock.data) : {}, + dataString: mock.data, + }; +} + +describe('AdaptyRemoteConfigCoder', () => { + let coder: AdaptyRemoteConfigCoder; + + beforeEach(() => { + coder = new AdaptyRemoteConfigCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-remote-config.ts b/src/coders/adapty-remote-config.ts new file mode 100644 index 0000000..ac53f41 --- /dev/null +++ b/src/coders/adapty-remote-config.ts @@ -0,0 +1,43 @@ +import type { AdaptyRemoteConfig } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { Coder } from './coder'; +import { JSONCoder } from './json'; + +type Model = AdaptyRemoteConfig; +type CodableModel = Omit; +type Serializable = Required['remote_config']; + +export class AdaptyRemoteConfigCoder extends Coder< + Model, + CodableModel, + Serializable +> { + protected properties: Properties = { + data: { + key: 'data', + required: true, + type: 'string' as any, + converter: new JSONCoder(), + }, + lang: { + key: 'lang', + required: true, + type: 'string', + }, + }; + + override decode(data: Serializable): Model { + const codablePart = super.decode(data); + const dataString = JSON.stringify(codablePart.data); + return { + ...codablePart, + dataString: dataString.length < 4 ? '' : dataString, + }; + } + + override encode(data: Model): Serializable { + const { dataString, ...codablePart } = data; + return super.encode(codablePart); + } +} diff --git a/src/coders/adapty-subscription-details.test.ts b/src/coders/adapty-subscription-details.test.ts new file mode 100644 index 0000000..e68c42c --- /dev/null +++ b/src/coders/adapty-subscription-details.test.ts @@ -0,0 +1,123 @@ +import type { AdaptySubscriptionDetails } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptySubscriptionDetailsCoder } from './adapty-subscription-details'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; +import { AdaptySubscriptionOfferCoder } from '@/coders/adapty-subscription-offer'; + +type Model = AdaptySubscriptionDetails; +const mocks: Def['AdaptyPaywallProduct.Subscription'][] = [ + { + base_plan_id: 'androidPlan1', + renewal_type: 'prepaid', + period: { + unit: 'year', + number_of_units: 1, + }, + localized_period: '1 year', + offer: { + offer_identifier: { + type: 'introductory', + id: 'test_intro_offer', + }, + phases: [ + { + price: { + amount: 49.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$49.99', + }, + number_of_periods: 2, + payment_mode: 'pay_as_you_go', + subscription_period: { + unit: 'month', + number_of_units: 3, + }, + localized_subscription_period: '3 months', + localized_number_of_periods: '2', + }, + ], + offer_tags: ['tag1', 'tag2'], + }, + }, + { + group_identifier: 'group1', + period: { + unit: 'year', + number_of_units: 1, + }, + localized_period: '1 year', + offer: { + offer_identifier: { + type: 'promotional', + id: 'test_promo_offer', + }, + phases: [ + { + price: { + amount: 29.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$29.99', + }, + number_of_periods: 1, + payment_mode: 'free_trial', + subscription_period: { + unit: 'month', + number_of_units: 1, + }, + localized_subscription_period: '1 month', + localized_number_of_periods: '1', + }, + ], + }, + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _period = new AdaptySubscriptionPeriodCoder(); + const _offer = new AdaptySubscriptionOfferCoder(); + + return { + subscriptionPeriod: _period.decode(mock.period), + ...(mock.localized_period && { + localizedSubscriptionPeriod: mock.localized_period, + }), + ...(mock.offer && { + offer: _offer.decode(mock.offer), + }), + ...(mock.base_plan_id + ? { + android: { + basePlanId: mock.base_plan_id, + renewalType: mock.renewal_type, + }, + } + : { + ios: { + subscriptionGroupIdentifier: mock.group_identifier, + }, + }), + }; +} + +describe('AdaptySubscriptionDetailsCoder', () => { + let coder: AdaptySubscriptionDetailsCoder; + + beforeEach(() => { + coder = new AdaptySubscriptionDetailsCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-subscription-details.ts b/src/coders/adapty-subscription-details.ts new file mode 100644 index 0000000..d19c4e4 --- /dev/null +++ b/src/coders/adapty-subscription-details.ts @@ -0,0 +1,60 @@ +import type { AdaptySubscriptionDetails } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; +import { AdaptySubscriptionOfferCoder } from '@/coders/adapty-subscription-offer'; + +type Model = AdaptySubscriptionDetails; +type Serializable = Def['AdaptyPaywallProduct.Subscription']; + +export class AdaptySubscriptionDetailsCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + subscriptionPeriod: { + key: 'period', + required: true, + type: 'object', + converter: new AdaptySubscriptionPeriodCoder(), + }, + localizedSubscriptionPeriod: { + key: 'localized_period', + required: false, + type: 'string', + }, + offer: { + key: 'offer', + required: false, + type: 'object', + converter: new AdaptySubscriptionOfferCoder(), + }, + ios: { + subscriptionGroupIdentifier: { + key: 'group_identifier', + required: false, + type: 'string', + }, + }, + android: { + basePlanId: { + key: 'base_plan_id', + required: true, + type: 'string', + }, + renewalType: { + key: 'renewal_type', + required: false, + type: 'string', + }, + }, + }; + + override decode(data: Serializable): Model { + const baseResult = super.decode(data); + const propToRemove = data.base_plan_id ? 'ios' : 'android'; + const { [propToRemove]: _, ...partialData } = baseResult; + return partialData; + } +} diff --git a/src/coders/adapty-subscription-offer-identifier.test.ts b/src/coders/adapty-subscription-offer-identifier.test.ts new file mode 100644 index 0000000..0ae3d79 --- /dev/null +++ b/src/coders/adapty-subscription-offer-identifier.test.ts @@ -0,0 +1,57 @@ +import type { AdaptySubscriptionOfferId } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptySubscriptionOfferIdCoder } from '@/coders/adapty-subscription-offer-identifier'; + +type Model = AdaptySubscriptionOfferId; +const mocks: Def['AdaptySubscriptionOffer.Identifier'][] = [ + { + type: 'introductory', + }, + { + type: 'introductory', + id: 'test_intro_offer', + }, + { + type: 'promotional', + id: 'test_promo_offer', + }, + { + type: 'win_back', + id: 'test_win_back_offer', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + if (mock.type === 'introductory') { + return { + type: mock.type, + ...(mock.id && { id: mock.id }), + }; + } else { + return { + type: mock.type, + id: mock.id, + }; + } +} + +describe('AdaptySubscriptionOfferIdCoder', () => { + let coder: AdaptySubscriptionOfferIdCoder; + + beforeEach(() => { + coder = new AdaptySubscriptionOfferIdCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-subscription-offer-identifier.ts b/src/coders/adapty-subscription-offer-identifier.ts new file mode 100644 index 0000000..96bd73b --- /dev/null +++ b/src/coders/adapty-subscription-offer-identifier.ts @@ -0,0 +1,25 @@ +import { AdaptySubscriptionOfferId } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptySubscriptionOfferId; +type Serializable = Def['AdaptySubscriptionOffer.Identifier']; + +export class AdaptySubscriptionOfferIdCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + type: { + key: 'type', + required: true, + type: 'string', + }, + id: { + key: 'id', + required: false, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-subscription-offer.test.ts b/src/coders/adapty-subscription-offer.test.ts new file mode 100644 index 0000000..adddf23 --- /dev/null +++ b/src/coders/adapty-subscription-offer.test.ts @@ -0,0 +1,98 @@ +import type { AdaptyDiscountPhase, AdaptySubscriptionOffer } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptyDiscountPhaseCoder } from './adapty-discount-phase'; +import { ArrayCoder } from './array'; +import { AdaptySubscriptionOfferCoder } from '@/coders/adapty-subscription-offer'; +import { AdaptySubscriptionOfferIdCoder } from '@/coders/adapty-subscription-offer-identifier'; + +type Model = AdaptySubscriptionOffer; +const mocks: Def['AdaptySubscriptionOffer'][] = [ + { + offer_identifier: { + type: 'introductory', + id: 'test_intro_offer', + }, + phases: [ + { + price: { + amount: 49.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$49.99', + }, + number_of_periods: 2, + payment_mode: 'pay_as_you_go', + subscription_period: { + unit: 'month', + number_of_units: 3, + }, + localized_subscription_period: '3 months', + localized_number_of_periods: '2', + }, + ], + offer_tags: ['tag1', 'tag2'], + }, + { + offer_identifier: { + type: 'promotional', + id: 'test_promo_offer', + }, + phases: [ + { + price: { + amount: 29.99, + currency_code: 'USD', + currency_symbol: '$', + localized_string: '$29.99', + }, + number_of_periods: 1, + payment_mode: 'free_trial', + subscription_period: { + unit: 'month', + number_of_units: 1, + }, + localized_subscription_period: '1 month', + localized_number_of_periods: '1', + }, + ], + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + const _offerId = new AdaptySubscriptionOfferIdCoder(); + const _discounts = new ArrayCoder< + AdaptyDiscountPhase, + AdaptyDiscountPhaseCoder + >(AdaptyDiscountPhaseCoder); + + return { + identifier: _offerId.decode(mock.offer_identifier), + phases: _discounts.decode(mock.phases), + ...(mock.offer_tags && { + android: { + offerTags: mock.offer_tags, + }, + }), + }; +} + +describe('AdaptySubscriptionOfferCoder', () => { + let coder: AdaptySubscriptionOfferCoder; + + beforeEach(() => { + coder = new AdaptySubscriptionOfferCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-subscription-offer.ts b/src/coders/adapty-subscription-offer.ts new file mode 100644 index 0000000..2d409f5 --- /dev/null +++ b/src/coders/adapty-subscription-offer.ts @@ -0,0 +1,46 @@ +import type { AdaptySubscriptionOffer } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptySubscriptionOfferIdCoder } from '@/coders/adapty-subscription-offer-identifier'; +import { ArrayCoder } from '@/coders/array'; +import { AdaptyDiscountPhaseCoder } from '@/coders/adapty-discount-phase'; + +type Model = AdaptySubscriptionOffer; +type Serializable = Def['AdaptySubscriptionOffer']; + +export class AdaptySubscriptionOfferCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + identifier: { + key: 'offer_identifier', + required: true, + type: 'object', + converter: new AdaptySubscriptionOfferIdCoder(), + }, + phases: { + key: 'phases', + required: true, + type: 'array', + converter: new ArrayCoder(AdaptyDiscountPhaseCoder), + }, + android: { + offerTags: { + key: 'offer_tags', + required: false, + type: 'array', + }, + }, + }; + + override decode(data: Serializable): Model { + const baseResult = super.decode(data); + if (!data.offer_tags) { + const { android, ...partialData } = baseResult; + return partialData; + } + return baseResult; + } +} diff --git a/src/coders/adapty-subscription-period.test.ts b/src/coders/adapty-subscription-period.test.ts new file mode 100644 index 0000000..d69982c --- /dev/null +++ b/src/coders/adapty-subscription-period.test.ts @@ -0,0 +1,40 @@ +import type { AdaptySubscriptionPeriod, ProductPeriod } from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; + +type Model = AdaptySubscriptionPeriod; +const mocks: Def['AdaptySubscriptionPeriod'][] = [ + { unit: 'day', number_of_units: 5 }, + { unit: 'week', number_of_units: 2 }, + { unit: 'month', number_of_units: 6 }, + { unit: 'year', number_of_units: 1 }, + { unit: 'unknown', number_of_units: 0 }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + numberOfUnits: mock.number_of_units, + unit: mock.unit as ProductPeriod, + }; +} + +describe('AdaptySubscriptionPeriodCoder', () => { + let coder: AdaptySubscriptionPeriodCoder; + + beforeEach(() => { + coder = new AdaptySubscriptionPeriodCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toEqual(mock); + }); +}); diff --git a/src/coders/adapty-subscription-period.ts b/src/coders/adapty-subscription-period.ts new file mode 100644 index 0000000..d6e391e --- /dev/null +++ b/src/coders/adapty-subscription-period.ts @@ -0,0 +1,17 @@ +import type { AdaptySubscriptionPeriod } from '@/types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; + +type Model = AdaptySubscriptionPeriod; +type Serializable = Def['AdaptySubscriptionPeriod']; + +export class AdaptySubscriptionPeriodCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + unit: { key: 'unit', required: true, type: 'string' }, + numberOfUnits: { key: 'number_of_units', required: true, type: 'number' }, + }; +} diff --git a/src/coders/adapty-subscription.test.ts b/src/coders/adapty-subscription.test.ts new file mode 100644 index 0000000..5a5af95 --- /dev/null +++ b/src/coders/adapty-subscription.test.ts @@ -0,0 +1,98 @@ +import type { + AdaptySubscription, + CancellationReason, + VendorStore, +} from '@/types'; +import type { Def } from '@/types/schema'; +import { AdaptySubscriptionCoder } from './adapty-subscription'; + +type Model = AdaptySubscription; +const mocks: Def['AdaptyProfile.Subscription'][] = [ + { + activated_at: '2023-08-09T12:34:56.789Z', + is_active: true, + is_in_grace_period: false, + is_lifetime: false, + is_refund: false, + is_sandbox: false, + store: 'App Store', + vendor_original_transaction_id: 'originalTransaction123', + vendor_product_id: 'product123', + vendor_transaction_id: 'transaction123', + will_renew: true, + }, + { + activated_at: '2022-07-09T12:34:56.789Z', + billing_issue_detected_at: '2023-08-09T12:34:56.789Z', + cancellation_reason: 'User Choice', + expires_at: '2023-08-09T12:34:56.789Z', + is_active: false, + is_in_grace_period: true, + is_lifetime: true, + is_refund: true, + is_sandbox: true, + renewed_at: '2023-07-09T12:34:56.789Z', + starts_at: '2023-08-09T12:34:56.789Z', + store: 'Google Play', + unsubscribed_at: '2023-08-09T12:34:56.789Z', + vendor_original_transaction_id: 'originalTransaction456', + vendor_product_id: 'product456', + vendor_transaction_id: 'transaction456', + will_renew: false, + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + activatedAt: new Date(mock.activated_at), + ...(mock.billing_issue_detected_at && { + billingIssueDetectedAt: new Date(mock.billing_issue_detected_at), + }), + ...(mock.expires_at && { + expiresAt: new Date(mock.expires_at), + }), + ...(mock.renewed_at && { + renewedAt: new Date(mock.renewed_at), + }), + ...(mock.starts_at && { + startsAt: new Date(mock.starts_at), + }), + ...(mock.unsubscribed_at && { + unsubscribedAt: new Date(mock.unsubscribed_at), + }), + ...(mock.cancellation_reason && { + cancellationReason: mock.cancellation_reason as CancellationReason, + }), + isActive: mock.is_active, + isInGracePeriod: mock.is_in_grace_period, + isLifetime: mock.is_lifetime, + isRefund: mock.is_refund, + isSandbox: mock.is_sandbox, + store: mock.store as VendorStore, + vendorOriginalTransactionId: mock.vendor_original_transaction_id, + vendorProductId: mock.vendor_product_id, + vendorTransactionId: mock.vendor_transaction_id, + willRenew: mock.will_renew, + }; +} + +describe('AdaptySubscriptionCoder', () => { + let coder: AdaptySubscriptionCoder; + + beforeEach(() => { + coder = new AdaptySubscriptionCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-subscription.ts b/src/coders/adapty-subscription.ts new file mode 100644 index 0000000..1d85d2d --- /dev/null +++ b/src/coders/adapty-subscription.ts @@ -0,0 +1,119 @@ +import type { AdaptySubscription } from '../types'; +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { DateCoder } from './date'; + +type Model = AdaptySubscription; +type Serializable = Def['AdaptyProfile.Subscription']; + +export class AdaptySubscriptionCoder extends SimpleCoder { + protected properties: Properties = { + isActive: { + key: 'is_active', + required: true, + type: 'boolean', + }, + isLifetime: { + key: 'is_lifetime', + required: true, + type: 'boolean', + }, + vendorProductId: { + key: 'vendor_product_id', + required: true, + type: 'string', + }, + store: { + key: 'store', + required: true, + type: 'string', + }, + vendorTransactionId: { + key: 'vendor_transaction_id', + required: true, + type: 'string', + }, + vendorOriginalTransactionId: { + key: 'vendor_original_transaction_id', + required: true, + type: 'string', + }, + activatedAt: { + key: 'activated_at', + required: true, + type: 'string', + converter: new DateCoder(), + }, + willRenew: { + key: 'will_renew', + required: true, + type: 'boolean', + }, + isInGracePeriod: { + key: 'is_in_grace_period', + required: true, + type: 'boolean', + }, + isRefund: { + key: 'is_refund', + required: true, + type: 'boolean', + }, + isSandbox: { + key: 'is_sandbox', + required: true, + type: 'boolean', + }, + renewedAt: { + key: 'renewed_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + expiresAt: { + key: 'expires_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + startsAt: { + key: 'starts_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + unsubscribedAt: { + key: 'unsubscribed_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + billingIssueDetectedAt: { + key: 'billing_issue_detected_at', + required: false, + type: 'string', + converter: new DateCoder(), + }, + activeIntroductoryOfferType: { + key: 'active_introductory_offer_type', + required: false, + type: 'string', + }, + activePromotionalOfferType: { + key: 'active_promotional_offer_type', + required: false, + type: 'string', + }, + activePromotionalOfferId: { + key: 'active_promotional_offer_id', + required: false, + type: 'string', + }, + cancellationReason: { + key: 'cancellation_reason', + required: false, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-ui-create-onboarding-view-params.test.ts b/src/coders/adapty-ui-create-onboarding-view-params.test.ts new file mode 100644 index 0000000..2a621c3 --- /dev/null +++ b/src/coders/adapty-ui-create-onboarding-view-params.test.ts @@ -0,0 +1,35 @@ +import { AdaptyUICreateOnboardingViewParamsCoder } from './adapty-ui-create-onboarding-view-params'; +import { CreateOnboardingViewParamsInput } from '@/ui/types'; +import { WebPresentation } from '@/types'; + +describe('AdaptyUICreateOnboardingViewParamsCoder', () => { + let coder: AdaptyUICreateOnboardingViewParamsCoder; + + beforeEach(() => { + coder = new AdaptyUICreateOnboardingViewParamsCoder(); + }); + + it('encodes empty object to empty object', () => { + const input: CreateOnboardingViewParamsInput = {}; + const result = coder.encode(input); + expect(result).toEqual({}); + }); + + it('encodes with externalUrlsPresentation', () => { + const input: CreateOnboardingViewParamsInput = { + externalUrlsPresentation: WebPresentation.BrowserInApp, + }; + const result = coder.encode(input); + expect(result).toEqual({ + external_urls_presentation: 'browser_in_app', + }); + }); + + it('encodes with undefined externalUrlsPresentation', () => { + const input: CreateOnboardingViewParamsInput = { + externalUrlsPresentation: undefined, + }; + const result = coder.encode(input); + expect(result).toEqual({}); + }); +}); diff --git a/src/coders/adapty-ui-create-onboarding-view-params.ts b/src/coders/adapty-ui-create-onboarding-view-params.ts new file mode 100644 index 0000000..c681729 --- /dev/null +++ b/src/coders/adapty-ui-create-onboarding-view-params.ts @@ -0,0 +1,19 @@ +import { CreateOnboardingViewParamsInput } from '@/ui/types'; +import { Req } from '@/types/schema'; + +type Serializable = Pick< + Req['AdaptyUICreateOnboardingView.Request'], + 'external_urls_presentation' +>; + +export class AdaptyUICreateOnboardingViewParamsCoder { + encode(data: CreateOnboardingViewParamsInput): Serializable { + const result: Serializable = {}; + + if (data.externalUrlsPresentation) { + result.external_urls_presentation = data.externalUrlsPresentation; + } + + return result; + } +} diff --git a/src/coders/adapty-ui-create-paywall-view-params.test.ts b/src/coders/adapty-ui-create-paywall-view-params.test.ts new file mode 100644 index 0000000..fb034ed --- /dev/null +++ b/src/coders/adapty-ui-create-paywall-view-params.test.ts @@ -0,0 +1,306 @@ +import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; +import { Platform } from 'react-native'; +import type { CreatePaywallViewParamsInput } from '@/ui/types'; +import type { AdaptyProductIdentifier } from '@/types'; + +describe('AdaptyUICreatePaywallViewParamsCoder', () => { + let coder: AdaptyUICreatePaywallViewParamsCoder; + + beforeEach(() => { + coder = new AdaptyUICreatePaywallViewParamsCoder(); + }); + + it('should encode basic params', () => { + const input: CreatePaywallViewParamsInput = { + prefetchProducts: true, + loadTimeoutMs: 10000, + }; + + const result = coder.encode(input); + + expect(result).toEqual({ + preload_products: true, + load_timeout: 10, + }); + }); + + it('should encode custom tags', () => { + const input: CreatePaywallViewParamsInput = { + customTags: { + USERNAME: 'John', + CITY: 'New York', + }, + }; + + const result = coder.encode(input); + + expect(result).toEqual({ + custom_tags: { + USERNAME: 'John', + CITY: 'New York', + }, + }); + }); + + it('should encode custom timers', () => { + const date = new Date('2024-01-01T12:00:00.000Z'); + const input: CreatePaywallViewParamsInput = { + customTimers: { + TIMER1: date, + }, + }; + + const result = coder.encode(input); + + expect(result).toEqual({ + custom_timers: { + TIMER1: '2024-01-01T12:00:00.000Z', + }, + }); + }); + + it('should encode color assets', () => { + const input: CreatePaywallViewParamsInput = { + customAssets: { + primaryColor: { + type: 'color', + argb: 0xffff0000, // Red + }, + secondaryColor: { + type: 'color', + rgba: 0x00ff00ff, // Green + }, + tertiaryColor: { + type: 'color', + rgb: 0x0000ff, // Blue + }, + }, + }; + + const result = coder.encode(input); + + expect(result.custom_assets).toEqual([ + { + id: 'primaryColor', + type: 'color', + value: '#ff0000ff', + }, + { + id: 'secondaryColor', + type: 'color', + value: '#00ff00ff', + }, + { + id: 'tertiaryColor', + type: 'color', + value: '#0000ffff', + }, + ]); + }); + + it('should encode image assets', () => { + const input: CreatePaywallViewParamsInput = { + customAssets: { + base64Image: { + type: 'image', + base64: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', + }, + assetImage: { + type: 'image', + relativeAssetPath: 'images/test.png', + }, + }, + }; + + const result = coder.encode(input); + + expect(result.custom_assets).toEqual([ + { + id: 'base64Image', + type: 'image', + value: + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', + }, + { + id: 'assetImage', + type: 'image', + asset_id: 'images/test.png', + }, + ]); + }); + + it('should encode gradient assets', () => { + const input: CreatePaywallViewParamsInput = { + customAssets: { + gradient1: { + type: 'linear-gradient', + values: [ + { p: 0, argb: 0xffff0000 }, + { p: 1, argb: 0xff0000ff }, + ], + points: { x0: 0, y0: 0, x1: 1, y1: 1 }, + }, + }, + }; + + const result = coder.encode(input); + + expect(result.custom_assets).toEqual([ + { + id: 'gradient1', + type: 'linear-gradient', + values: [ + { color: '#ff0000ff', p: 0 }, + { color: '#0000ffff', p: 1 }, + ], + points: { x0: 0, y0: 0, x1: 1, y1: 1 }, + }, + ]); + }); + + it('should encode product purchase parameters', () => { + const productId: AdaptyProductIdentifier = { + vendorProductId: 'com.example.product', + adaptyProductId: 'adapty_product_id', + }; + + const input: CreatePaywallViewParamsInput = { + productPurchaseParams: [ + { + productId, + params: { + android: { + isOfferPersonalized: true, + }, + }, + }, + ], + }; + + const result = coder.encode(input); + + // AdaptyPurchaseParamsCoder only processes Android params on Android platform + // On other platforms it returns empty object + expect(result.product_purchase_parameters).toEqual({ + adapty_product_id: {}, + }); + }); + + it('should encode product purchase parameters on Android', () => { + const original = Platform.OS; + // mock Platform.OS to android + Object.defineProperty(Platform, 'OS', { + configurable: true, + get() { + return 'android'; + }, + } as any); + + try { + const productId: AdaptyProductIdentifier = { + vendorProductId: 'com.example.product', + adaptyProductId: 'adapty_product_id', + }; + + const input: CreatePaywallViewParamsInput = { + productPurchaseParams: [ + { + productId, + params: { + android: { + isOfferPersonalized: true, + }, + }, + }, + ], + }; + + const result = coder.encode(input); + + expect(result.product_purchase_parameters).toEqual({ + adapty_product_id: { + is_offer_personalized: true, + }, + }); + } finally { + Object.defineProperty(Platform, 'OS', { + configurable: true, + get() { + return original as any; + }, + } as any); + } + }); + + it('should encode asset_id with android suffixes on Android', () => { + const originalOS = Platform.OS; + const originalSelect = Platform.select; + Object.defineProperty(Platform, 'OS', { + configurable: true, + get() { + return 'android'; + }, + } as any); + const selectSpy = jest + .spyOn(Platform, 'select') + .mockImplementation((spec: any) => spec.android); + + try { + const input: CreatePaywallViewParamsInput = { + customAssets: { + imgRel: { type: 'image', relativeAssetPath: 'images/test.png' }, + videoRel: { type: 'video', relativeAssetPath: 'videos/intro.mp4' }, + imgFL: { + type: 'image', + fileLocation: { + ios: { fileName: 'ios_name.png' }, + android: { relativeAssetPath: 'images/rel.png' }, + }, + }, + videoFL: { + type: 'video', + fileLocation: { + ios: { fileName: 'ios_video.mp4' }, + android: { rawResName: 'intro' }, + }, + }, + }, + }; + + const result = coder.encode(input); + expect(result.custom_assets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'imgRel', + asset_id: 'images/test.pnga', + }), + expect.objectContaining({ + id: 'videoRel', + asset_id: 'videos/intro.mp4a', + }), + expect.objectContaining({ id: 'imgFL', asset_id: 'images/rel.pnga' }), + expect.objectContaining({ id: 'videoFL', asset_id: 'intror' }), + ]), + ); + } finally { + Object.defineProperty(Platform, 'OS', { + configurable: true, + get() { + return originalOS as any; + }, + } as any); + selectSpy.mockRestore(); + // restore just in case + (Platform as any).select = originalSelect; + } + }); + + it('should handle empty input', () => { + const input: CreatePaywallViewParamsInput = {}; + + const result = coder.encode(input); + + expect(result).toEqual({}); + }); +}); diff --git a/src/coders/adapty-ui-create-paywall-view-params.ts b/src/coders/adapty-ui-create-paywall-view-params.ts new file mode 100644 index 0000000..09caecb --- /dev/null +++ b/src/coders/adapty-ui-create-paywall-view-params.ts @@ -0,0 +1,178 @@ +import { Platform } from 'react-native'; +import type { + CreatePaywallViewParamsInput, + AdaptyCustomAsset, +} from '@/ui/types'; +import type { Def } from '@/types/schema'; +import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; +import { + colorToHex, + extractBase64Data, + formatDateUTC, + resolveAssetId, +} from './utils'; + +type Model = CreatePaywallViewParamsInput; +type Serializable = { + preload_products?: boolean; + load_timeout?: number; + custom_tags?: Def['AdaptyUI.CustomTagsValues']; + custom_timers?: Def['AdaptyUI.CustomTimersValues']; + custom_assets?: Def['AdaptyUI.CustomAssets']; + product_purchase_parameters?: Def['AdaptyUI.ProductPurchaseParameters']; +}; + +export class AdaptyUICreatePaywallViewParamsCoder { + encode(data: Model): Serializable { + const result: Serializable = {}; + + if (data.prefetchProducts !== undefined) { + result.preload_products = data.prefetchProducts; + } + + if (data.loadTimeoutMs !== undefined) { + result.load_timeout = data.loadTimeoutMs / 1000; + } + + if (data.customTags) { + result.custom_tags = data.customTags; + } + + if (data.customTimers) { + result.custom_timers = this.encodeCustomTimers(data.customTimers); + } + + if (data.customAssets) { + result.custom_assets = this.encodeCustomAssets(data.customAssets); + } + + if (data.productPurchaseParams) { + result.product_purchase_parameters = this.encodeProductPurchaseParams( + data.productPurchaseParams, + ); + } + + return result; + } + + private encodeCustomTimers( + timers: Record, + ): Def['AdaptyUI.CustomTimersValues'] { + const result: Record = {}; + for (const key in timers) { + if (timers.hasOwnProperty(key)) { + const date = timers[key]; + if (date instanceof Date) { + result[key] = formatDateUTC(date); + } + } + } + return result; + } + + private encodeCustomAssets( + assets: Record, + ): Def['AdaptyUI.CustomAssets'] { + const getAssetId = (asset: any): string => { + return resolveAssetId(asset, spec => Platform.select(spec)) || ''; + }; + + return Object.entries(assets) + .map(([id, asset]): Def['AdaptyUI.CustomAssets'][number] | undefined => { + switch (asset.type) { + case 'image': + return 'base64' in asset + ? { + id, + type: 'image', + value: extractBase64Data(asset.base64), + } + : { + id, + type: 'image', + asset_id: getAssetId(asset), + }; + + case 'video': + return { + id, + type: 'video', + asset_id: getAssetId(asset), + }; + + case 'color': + let value: string; + + if ('argb' in asset) { + value = colorToHex.fromARGB(asset.argb); + } else if ('rgba' in asset) { + value = colorToHex.fromRGBA(asset.rgba); + } else if ('rgb' in asset) { + value = colorToHex.fromRGB(asset.rgb); + } else { + return undefined; + } + + return { + id, + type: 'color', + value, + }; + + case 'linear-gradient': + const { values, points = {} } = asset; + const { x0 = 0, y0 = 0, x1 = 1, y1 = 0 } = points; + + const colorStops = values + .map(({ p, ...colorInput }) => { + let color: string; + + if ('argb' in colorInput) { + color = colorToHex.fromARGB(colorInput.argb); + } else if ('rgba' in colorInput) { + color = colorToHex.fromRGBA(colorInput.rgba); + } else if ('rgb' in colorInput) { + color = colorToHex.fromRGB(colorInput.rgb); + } else { + return undefined; + } + + return { color, p }; + }) + .filter( + (v): v is { color: string; p: number } => v !== undefined, + ); + + if (colorStops.length !== values.length) return undefined; + + return { + id, + type: 'linear-gradient', + values: colorStops, + points: { x0, y0, x1, y1 }, + }; + + default: + return undefined; + } + }) + .filter( + (item): item is Def['AdaptyUI.CustomAssets'][number] => + item !== undefined, + ); + } + + private encodeProductPurchaseParams( + params: CreatePaywallViewParamsInput['productPurchaseParams'], + ): Def['AdaptyUI.ProductPurchaseParameters'] { + if (!params) return {}; + + const purchaseParamsCoder = new AdaptyPurchaseParamsCoder(); + return Object.fromEntries( + params.map(({ productId, params }) => [ + productId.adaptyProductId, + purchaseParamsCoder.encode(params), + ]), + ); + } +} diff --git a/src/coders/adapty-ui-dialog-config.test.ts b/src/coders/adapty-ui-dialog-config.test.ts new file mode 100644 index 0000000..3eb3b3c --- /dev/null +++ b/src/coders/adapty-ui-dialog-config.test.ts @@ -0,0 +1,25 @@ +import { Def } from '@/types/schema'; +import { AdaptyUiDialogConfigCoder } from '@/coders/adapty-ui-dialog-config'; + +const mocks: Def['AdaptyUI.DialogConfiguration'][] = [ + { + default_action_title: 'default_action_title', + secondary_action_title: 'secondary_action_title', + title: 'title', + content: 'content', + }, + { + default_action_title: 'default_action_title2', + }, +]; + +describe('AdaptyUiDialogConfigCoder', () => { + const coder = new AdaptyUiDialogConfigCoder(); + + it.each(mocks)('should encode/decode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toEqual(mock); + }); +}); diff --git a/src/coders/adapty-ui-dialog-config.ts b/src/coders/adapty-ui-dialog-config.ts new file mode 100644 index 0000000..3029b99 --- /dev/null +++ b/src/coders/adapty-ui-dialog-config.ts @@ -0,0 +1,35 @@ +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyUiDialogConfig } from '@/ui/types'; + +type Model = AdaptyUiDialogConfig; +type Serializable = Def['AdaptyUI.DialogConfiguration']; + +export class AdaptyUiDialogConfigCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + primaryActionTitle: { + key: 'default_action_title', + required: true, + type: 'string', + }, + secondaryActionTitle: { + key: 'secondary_action_title', + required: false, + type: 'string', + }, + title: { + key: 'title', + required: false, + type: 'string', + }, + content: { + key: 'content', + required: false, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-ui-media-cache.test.ts b/src/coders/adapty-ui-media-cache.test.ts new file mode 100644 index 0000000..e908119 --- /dev/null +++ b/src/coders/adapty-ui-media-cache.test.ts @@ -0,0 +1,21 @@ +import { Def } from '@/types/schema'; +import { AdaptyUiMediaCacheCoder } from '@/coders/adapty-ui-media-cache'; + +const mocks: Required['media_cache'][] = [ + { + memory_storage_total_cost_limit: 100 * 1024 * 1024, + memory_storage_count_limit: 2147483647, + disk_storage_size_limit: 100 * 1024 * 1024, + }, +]; + +describe('AdaptyUiMediaCacheCoder', () => { + const coder = new AdaptyUiMediaCacheCoder(); + + it.each(mocks)('should encode/decode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toEqual(mock); + }); +}); diff --git a/src/coders/adapty-ui-media-cache.ts b/src/coders/adapty-ui-media-cache.ts new file mode 100644 index 0000000..03fdf8a --- /dev/null +++ b/src/coders/adapty-ui-media-cache.ts @@ -0,0 +1,27 @@ +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyUiMediaCache } from '@/ui/types'; + +type Model = AdaptyUiMediaCache; +type Serializable = Required['media_cache']; + +export class AdaptyUiMediaCacheCoder extends SimpleCoder { + protected properties: Properties = { + memoryStorageTotalCostLimit: { + key: 'memory_storage_total_cost_limit', + required: false, + type: 'number', + }, + memoryStorageCountLimit: { + key: 'memory_storage_count_limit', + required: false, + type: 'number', + }, + diskStorageSizeLimit: { + key: 'disk_storage_size_limit', + required: false, + type: 'number', + }, + }; +} diff --git a/src/coders/adapty-ui-onboarding-meta.test.ts b/src/coders/adapty-ui-onboarding-meta.test.ts new file mode 100644 index 0000000..2fbad5c --- /dev/null +++ b/src/coders/adapty-ui-onboarding-meta.test.ts @@ -0,0 +1,49 @@ +import { AdaptyUiOnboardingMetaCoder } from './adapty-ui-onboarding-meta'; +import type { AdaptyUiOnboardingMeta } from '@/ui/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyUiOnboardingMeta; +const mocks: Def['AdaptyUI.OnboardingMeta'][] = [ + { + onboarding_id: 'onboarding_123', + screen_cid: 'screen_456', + screen_index: 0, + total_screens: 3, + }, + { + onboarding_id: 'onboarding_789', + screen_cid: 'screen_abc', + screen_index: 2, + total_screens: 5, + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + onboardingId: mock.onboarding_id, + screenClientId: mock.screen_cid, + screenIndex: mock.screen_index, + totalScreens: mock.total_screens, + }; +} + +describe('AdaptyUiOnboardingMetaCoder', () => { + let coder: AdaptyUiOnboardingMetaCoder; + + beforeEach(() => { + coder = new AdaptyUiOnboardingMetaCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-ui-onboarding-meta.ts b/src/coders/adapty-ui-onboarding-meta.ts new file mode 100644 index 0000000..0e91407 --- /dev/null +++ b/src/coders/adapty-ui-onboarding-meta.ts @@ -0,0 +1,35 @@ +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyUiOnboardingMeta } from '@/ui/types'; + +type Model = AdaptyUiOnboardingMeta; +type Serializable = Def['AdaptyUI.OnboardingMeta']; + +export class AdaptyUiOnboardingMetaCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + onboardingId: { + key: 'onboarding_id', + required: true, + type: 'string', + }, + screenClientId: { + key: 'screen_cid', + required: true, + type: 'string', + }, + screenIndex: { + key: 'screen_index', + required: true, + type: 'number', + }, + totalScreens: { + key: 'total_screens', + required: true, + type: 'number', + }, + }; +} diff --git a/src/coders/adapty-ui-onboarding-state-params.test.ts b/src/coders/adapty-ui-onboarding-state-params.test.ts new file mode 100644 index 0000000..66b3345 --- /dev/null +++ b/src/coders/adapty-ui-onboarding-state-params.test.ts @@ -0,0 +1,46 @@ +import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state-params'; +import type { AdaptyUiOnboardingStateParams } from '@/ui/types'; +import type { Def } from '@/types/schema'; + +type Model = AdaptyUiOnboardingStateParams; +const mocks: Def['AdaptyUI.OnboardingsStateParams'][] = [ + { + id: 'param_1', + value: 'option_a', + label: 'Option A', + }, + { + id: 'param_2', + value: 'option_b', + label: 'Option B', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + id: mock.id, + value: mock.value, + label: mock.label, + }; +} + +describe('AdaptyUiOnboardingStateParamsCoder', () => { + let coder: AdaptyUiOnboardingStateParamsCoder; + + beforeEach(() => { + coder = new AdaptyUiOnboardingStateParamsCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/adapty-ui-onboarding-state-params.ts b/src/coders/adapty-ui-onboarding-state-params.ts new file mode 100644 index 0000000..1f905c7 --- /dev/null +++ b/src/coders/adapty-ui-onboarding-state-params.ts @@ -0,0 +1,30 @@ +import type { Def } from '@/types/schema'; +import type { Properties } from './types'; +import { SimpleCoder } from './coder'; +import { AdaptyUiOnboardingStateParams } from '@/ui/types'; + +type Model = AdaptyUiOnboardingStateParams; +type Serializable = Def['AdaptyUI.OnboardingsStateParams']; + +export class AdaptyUiOnboardingStateParamsCoder extends SimpleCoder< + Model, + Serializable +> { + protected properties: Properties = { + id: { + key: 'id', + required: true, + type: 'string', + }, + value: { + key: 'value', + required: true, + type: 'string', + }, + label: { + key: 'label', + required: true, + type: 'string', + }, + }; +} diff --git a/src/coders/adapty-ui-onboarding-state-updated-action.test.ts b/src/coders/adapty-ui-onboarding-state-updated-action.test.ts new file mode 100644 index 0000000..3d442ea --- /dev/null +++ b/src/coders/adapty-ui-onboarding-state-updated-action.test.ts @@ -0,0 +1,128 @@ +import { AdaptyUiOnboardingStateUpdatedActionCoder } from './adapty-ui-onboarding-state-updated-action'; +import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import type { Event } from '@/types/schema'; + +type Serializable = Event['OnboardingViewEvent.OnStateUpdatedAction']['action']; + +const selectMock: Serializable = { + element_id: 'select-1', + element_type: 'select', + value: { + id: 'option_1', + value: 'option_value_1', + label: 'Option 1', + }, +}; + +const multiSelectMock: Serializable = { + element_id: 'multi-select-1', + element_type: 'multi_select', + value: [ + { + id: 'option_1', + value: 'option_value_1', + label: 'Option 1', + }, + { + id: 'option_2', + value: 'option_value_2', + label: 'Option 2', + }, + ], +}; + +const inputMock: Serializable = { + element_id: 'input-1', + element_type: 'input', + value: { type: 'text', value: 'user input text' }, +}; + +const datePickerMock: Serializable = { + element_id: 'date-picker-1', + element_type: 'date_picker', + value: { day: 25, month: 12, year: 2023 }, +}; + +const mocks = [selectMock, multiSelectMock, inputMock, datePickerMock]; + +const expectedSelect: OnboardingStateUpdatedAction = { + elementId: 'select-1', + elementType: 'select', + value: { + id: 'option_1', + value: 'option_value_1', + label: 'Option 1', + }, +}; + +const expectedMultiSelect: OnboardingStateUpdatedAction = { + elementId: 'multi-select-1', + elementType: 'multi_select', + value: [ + { + id: 'option_1', + value: 'option_value_1', + label: 'Option 1', + }, + { + id: 'option_2', + value: 'option_value_2', + label: 'Option 2', + }, + ], +}; + +const expectedInput: OnboardingStateUpdatedAction = { + elementId: 'input-1', + elementType: 'input', + value: { type: 'text', value: 'user input text' }, +}; + +const expectedDatePicker: OnboardingStateUpdatedAction = { + elementId: 'date-picker-1', + elementType: 'date_picker', + value: { day: 25, month: 12, year: 2023 }, +}; + +const expectedResults = [ + expectedSelect, + expectedMultiSelect, + expectedInput, + expectedDatePicker, +]; + +describe('AdaptyUiOnboardingStateUpdatedActionCoder', () => { + let coder: AdaptyUiOnboardingStateUpdatedActionCoder; + + beforeEach(() => { + coder = new AdaptyUiOnboardingStateUpdatedActionCoder(); + }); + + it.each(mocks.map((mock, idx) => ({ mock, expected: expectedResults[idx] })))( + 'should decode $mock.element_type to expected result', + ({ mock, expected }) => { + const decoded = coder.decode(mock); + expect(decoded).toStrictEqual(expected); + }, + ); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + + expect(decoded.elementId).toBe(mock.element_id); + expect(decoded.elementType).toBe(mock.element_type); + + if (mock.element_type === 'select') { + expect(decoded.value).toEqual({ + id: (mock.value as any).id, + value: (mock.value as any).value, + label: (mock.value as any).label, + }); + } else if (mock.element_type === 'multi_select') { + expect(Array.isArray(decoded.value)).toBe(true); + expect(decoded.value).toHaveLength((mock.value as any[]).length); + } else { + expect(decoded.value).toEqual(mock.value); + } + }); +}); diff --git a/src/coders/adapty-ui-onboarding-state-updated-action.ts b/src/coders/adapty-ui-onboarding-state-updated-action.ts new file mode 100644 index 0000000..76e8ad0 --- /dev/null +++ b/src/coders/adapty-ui-onboarding-state-updated-action.ts @@ -0,0 +1,97 @@ +import type { Def, Event } from '@/types/schema'; +import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import { SimpleCoder } from './coder'; +import type { Properties } from './types'; +import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state-params'; + +type Model = OnboardingStateUpdatedAction; +type BaseModel = Omit; +type Serializable = Event['OnboardingViewEvent.OnStateUpdatedAction']['action']; +type SerializableStateParam = Def['AdaptyUI.OnboardingsStateParams']; + +export class AdaptyUiOnboardingStateUpdatedActionCoder extends SimpleCoder< + BaseModel, + Serializable +> { + private readonly paramCoder = new AdaptyUiOnboardingStateParamsCoder(); + + protected properties: Properties = { + elementId: { + key: 'element_id', + required: true, + type: 'string', + }, + elementType: { + key: 'element_type', + required: true, + type: 'string', + }, + }; + + override decode(data: Serializable): OnboardingStateUpdatedAction { + const base = super.decode(data); + const { elementType } = base; + + switch (elementType) { + case 'select': + return { + ...base, + elementType: 'select', + value: this.paramCoder.decode(data.value as SerializableStateParam), + }; + case 'multi_select': + return { + ...base, + elementType: 'multi_select', + value: Array.isArray(data.value) + ? data.value.map(v => this.paramCoder.decode(v)) + : [], + }; + case 'input': + return { + ...base, + value: data.value as any, + }; + case 'date_picker': + return { + ...base, + value: data.value as any, + }; + default: + throw new Error(`Unknown element_type: ${elementType}`); + } + } + + override encode(data: OnboardingStateUpdatedAction): Serializable { + const base = super.encode(data); + const { elementType } = data; + switch (elementType) { + case 'select': + return { + ...base, + element_type: 'select', + value: this.paramCoder.encode(data.value), + }; + case 'multi_select': + return { + ...base, + element_type: 'multi_select', + value: data.value.map(v => this.paramCoder.encode(v)), + }; + case 'input': + return { + ...base, + element_type: 'input', + value: data.value, + }; + case 'date_picker': + return { + ...base, + element_type: 'date_picker', + value: data.value, + }; + default: + throw new Error(`Unknown elementType: ${elementType}`); + } + } +} diff --git a/src/coders/array.ts b/src/coders/array.ts new file mode 100644 index 0000000..2178aad --- /dev/null +++ b/src/coders/array.ts @@ -0,0 +1,34 @@ +import { SimpleCoder } from './coder'; +import { Converter } from './types'; + +// Coder for Array +export class ArrayCoder< + Model extends Record, + ModelCoder extends SimpleCoder, +> implements Converter +{ + private coder: ModelCoder; + + constructor(coder: new () => ModelCoder) { + this.coder = new coder(); + } + + decode(input: any[]): Model[] { + const result: Model[] = []; + + input.forEach(value => { + result.push(this.coder.decode(value)); + }); + + return result; + } + + encode(value: Model[]): any[] { + const result: any[] = []; + value.forEach(model => { + result.push(this.coder.encode(model)); + }); + + return result; + } +} diff --git a/src/coders/bridge-error.test.ts b/src/coders/bridge-error.test.ts new file mode 100644 index 0000000..a57fea7 --- /dev/null +++ b/src/coders/bridge-error.test.ts @@ -0,0 +1,42 @@ +import { AdaptyBridgeError } from '@/types/bridge'; +import { BridgeErrorCoder } from './bridge-error'; +import { AdaptyError } from '@/adapty-error'; + +describe('BridgeErrorCoder', () => { + let coder: BridgeErrorCoder; + + const input = { + name: 'source', + error_type: 'typeMismatch', + type: 'JSON-encoded AdaptyAttributionSource', + }; + + beforeEach(() => { + coder = new BridgeErrorCoder(); + }); + + it('should be Error', () => { + const data = coder.decode(input); + const error = coder.getError(data); + + expect(error).toBeInstanceOf(Error); + }); + it('should be AdaptyError', () => { + const data = coder.decode(input); + const error = coder.getError(data); + + expect(error).toBeInstanceOf(AdaptyError); + }); + + it('should encode/decode', () => { + const result = coder.decode(input); + + expect(result).toStrictEqual({ + errorType: 'typeMismatch', + name: 'source', + type: 'JSON-encoded AdaptyAttributionSource', + } satisfies AdaptyBridgeError); + + expect(coder.encode(result)).toStrictEqual(input); + }); +}); diff --git a/src/coders/bridge-error.ts b/src/coders/bridge-error.ts new file mode 100644 index 0000000..1ca4647 --- /dev/null +++ b/src/coders/bridge-error.ts @@ -0,0 +1,93 @@ +import { AdaptyBridgeError } from '@/types/bridge'; +import { SimpleCoder } from './coder'; +import { Properties } from './types'; +import { ErrorConverter } from './error-coder'; +import { AdaptyError } from '@/adapty-error'; + +type Model = AdaptyBridgeError; +type Serializable = Record; + +export class BridgeErrorCoder + extends SimpleCoder + implements ErrorConverter +{ + public type: 'error' = 'error'; + + protected properties: Properties = { + errorType: { + key: 'error_type', + required: true, + type: 'string', + }, + name: { + key: 'name', + required: false, + type: 'string', + }, + type: { + key: 'type', + required: false, + type: 'string', + }, + underlyingError: { + key: 'parent_error', + required: false, + type: 'string', + }, + description: { + key: 'description', + required: false, + type: 'string', + }, + }; + + public getError(data: Model): AdaptyError { + switch (data.errorType) { + case 'missingRequiredArgument': + return new AdaptyError({ + adaptyCode: 3001, + message: `Required parameter "${data.name} was not passed to a native module"`, + }); + case 'typeMismatch': + return new AdaptyError({ + adaptyCode: 3001, + message: `Passed parameter "${data.name}" has invalid type. Expected type: ${data.type}"`, + }); + case 'encodingFailed': + return new AdaptyError({ + adaptyCode: 2009, + message: `Bridge layer failed to encode response. Bridge error: ${JSON.stringify( + data.underlyingError ?? {}, + )}"`, + }); + case 'wrongParam': + case 'WRONG_PARAMETER': + return new AdaptyError({ + adaptyCode: 3001, + message: + data.name ?? + `Wrong parameter. Bridge error: ${JSON.stringify( + data.underlyingError ?? {}, + )}"`, + }); + case 'methodNotImplemented': + return new AdaptyError({ + adaptyCode: 2003, + message: 'Requested bridge handle not found', + }); + case 'unsupportedIosVersion': + return new AdaptyError({ + adaptyCode: 2003, + message: 'Unsupported iOS version', + }); + case 'unexpectedError': + default: + return new AdaptyError({ + adaptyCode: 0, + message: `Unexpected error occurred: ${JSON.stringify( + data.underlyingError ?? {}, + )}`, + }); + } + } +} diff --git a/src/coders/coder.ts b/src/coders/coder.ts new file mode 100644 index 0000000..e870550 --- /dev/null +++ b/src/coders/coder.ts @@ -0,0 +1,184 @@ +import { AdaptyError } from '@/adapty-error'; +import { Converter, Properties, StrType } from './types'; +import { Platform } from 'react-native'; + +export abstract class Coder< + Model extends Record, + CodableModel extends Partial, + Serializable extends Record = Record, +> implements Converter +{ + protected abstract properties: Properties; + + encode(data: CodableModel): Serializable { + return this.encodeWithProperties(data, this.properties); + } + // From vendor_product_id to productId + decode(data: Serializable): CodableModel { + return this.decodeWithProperties(data, this.properties); + } + + protected isType(value: unknown, type: StrType): boolean { + switch (type) { + case 'string': + case 'boolean': + case 'number': + return typeof value === type; + case 'object': + return value !== null && typeof value === 'object'; + case 'array': + return Array.isArray(value); + } + } + + private getNestedValue(obj: Record, key: string): any { + const keys = key.split('.'); + let current; + if (typeof obj === 'string') { + try { + current = JSON.parse(obj); + } catch (error) { + return undefined; + } + } else { + current = obj; + } + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]!; + + if (current[key] !== undefined) { + current = current[key]; + } else { + return undefined; + } + } + + return current; + } + + private assignNestedValue>( + obj: T, + key: string, + value: any, + ): T { + const keys = String(key).split('.'); + let currentObj: Partial = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i] as keyof Partial; + + if (currentObj[key] === undefined) { + currentObj[key] = {} as any; + } + + currentObj = currentObj[key] as any; + } + + currentObj[keys[keys.length - 1] as keyof Partial] = value; + return obj; + } + + private encodeWithProperties, Serializable>( + data: Model, + properties: Properties, + ): Serializable { + const result: Partial = {}; + + for (const key in data) { + if (key === 'ios' || key === 'android') { + // Read properties from ios/android platform keys and flatten them + const platformResult = this.encodeWithProperties( + data[key], + (properties as Record)[key], + ) as any; + + Object.assign(result, platformResult); + continue; + } + + const property = properties[key as string]; + if (!property) { + throw AdaptyError.failedToEncode( + `Failed to find encoder for property "${key}"`, + ); + } + + const converter = property.converter; + + this.assignNestedValue( + result, + property.key as string, + converter ? converter.encode(data[key]) : data[key as keyof Model], + ); + } + + return result as Serializable; + } + + private decodeWithProperties>( + data: Serializable, + properties: Properties, + platform?: 'ios' | 'android', + ): Model { + const result: Partial = {}; + + for (const key in properties) { + if (key === 'android' || key === 'ios') { + // Add ios/android property and fill platform data there + result[key as unknown as keyof typeof result] = + this.decodeWithProperties( + data, + (properties as Record)[key], + key, + ) as any; + continue; + } + + const property = properties[key]; + if (!property) { + throw AdaptyError.failedToDecode( + `Failed to find decoder for property "${key}"`, + ); + } + + const value = this.getNestedValue( + data as Record, + property.key as string, + ); + + if ( + property.required && + value === undefined && + (!platform || platform == Platform.OS) + ) { + throw AdaptyError.failedToDecode( + `Failed to decode native response, because it is missing required property "${key}"`, + ); + } + + // If value is null or undefined and property is not required, continue + if (value == null) continue; + + if (!this.isType(value, property.type)) { + throw AdaptyError.failedToDecode( + `Failed to decode native response, because its property "${key}" has invalid type. Expected type: ${ + property.type + }. Received type: ${typeof value}`, + ); + } + + // If a converter is provided, use it to convert the value + result[key as keyof Model] = property.converter + ? property.converter.decode(value) + : (value as any); + } + + return result as Model; + } +} + +export abstract class SimpleCoder< + Model extends Record, + Serializable extends Record = Record, +> extends Coder {} diff --git a/src/coders/date.ts b/src/coders/date.ts new file mode 100644 index 0000000..0298cf5 --- /dev/null +++ b/src/coders/date.ts @@ -0,0 +1,53 @@ +import { AdaptyError } from '@/adapty-error'; +import type { Converter } from './types'; + +/** + * Format: yyyy-MM-dd'T'HH:mm:ss.SSSZ + * OpenAPI: Output.Date + */ +export class DateCoder implements Converter { + decode(input: string): Date { + let pureValue = input; + + if (!input.endsWith('Z')) { + // React Native seems to have an inconsistent behaviour + // with dates with timezone offset + // It is not handled with custom libs like dayjs + // So we just remove the timezone offset if possible + + // Android SDK returns the following format, + // that resulted in an invalid date for some customers + // `2023-02-24T07:16:28.000000+0000` + // solution is: + // 1. Replace "+0000" with "Z" + // 2. Replace microsecs with 0 millisecs + + // with the timezone offset removed + const dateParts = input.split(/[-T:.Z]/); + const date = new Date( + Date.UTC( + parseInt(dateParts[0] ?? '0', 10), + parseInt(dateParts[1] ?? '0', 10) - 1, + parseInt(dateParts[2] ?? '0', 10), + parseInt(dateParts[3] ?? '0', 10), + parseInt(dateParts[4] ?? '0', 10), + parseInt(dateParts[5] ?? '0', 10), + ), + ); + pureValue = date.toISOString(); + } + + const parsedValue = Date.parse(pureValue); + if (isNaN(parsedValue)) { + throw AdaptyError.failedToDecode( + `Failed to decode a date string into JS Date. String value: ${pureValue}`, + ); + } + + return new Date(parsedValue); + } + + encode(value: Date): string { + return value.toISOString(); + } +} diff --git a/src/coders/error-coder.ts b/src/coders/error-coder.ts new file mode 100644 index 0000000..1cb59e9 --- /dev/null +++ b/src/coders/error-coder.ts @@ -0,0 +1,8 @@ +import { AdaptyError } from '../adapty-error'; +import { Converter } from './types'; + +export interface ErrorConverter> + extends Converter { + type: 'error'; + getError: (data: Model) => AdaptyError; +} diff --git a/src/coders/hashmap.ts b/src/coders/hashmap.ts new file mode 100644 index 0000000..7d31512 --- /dev/null +++ b/src/coders/hashmap.ts @@ -0,0 +1,35 @@ +import type { Converter } from './types'; + +// Coder for Record +export class HashmapCoder> + implements Converter, Record> +{ + private coder: T | null; + + constructor(coder: T | null) { + this.coder = coder; + } + + decode(input: Record): Record { + const result: Record = {}; + + Object.keys(input).forEach(key => { + const property = input[key as string]; + + result[key] = this.coder?.decode(property) ?? property; + }); + + return result; + } + + encode(value: Record): Record { + const result: Record = {}; + + Object.keys(value).forEach(key => { + const property = value[key as string]; + result[key] = this.coder?.encode(property) ?? property; + }); + + return result; + } +} diff --git a/src/coders/index.ts b/src/coders/index.ts new file mode 100644 index 0000000..3617dae --- /dev/null +++ b/src/coders/index.ts @@ -0,0 +1,3 @@ +export { parseMethodResult } from './parse'; +export { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; +export { AdaptyUICreateOnboardingViewParamsCoder } from './adapty-ui-create-onboarding-view-params'; diff --git a/src/coders/json.ts b/src/coders/json.ts new file mode 100644 index 0000000..865b6d2 --- /dev/null +++ b/src/coders/json.ts @@ -0,0 +1,20 @@ +import { Converter } from './types'; + +type Model = any; +type Serialized = string; + +export class JSONCoder implements Converter { + decode(input: Serialized): Model { + if (!input) { + return {}; + } + + return JSON.parse(input); + } + encode(value: Model): Serialized { + if (Object.keys(value).length === 0) { + return ''; + } + return JSON.stringify(value); + } +} diff --git a/src/coders/parse-onboarding.ts b/src/coders/parse-onboarding.ts new file mode 100644 index 0000000..5341b34 --- /dev/null +++ b/src/coders/parse-onboarding.ts @@ -0,0 +1,135 @@ +import { AdaptyError } from '@/adapty-error'; +import { LogContext } from '../logger'; +import { ErrorConverter } from './error-coder'; +import type { Converter } from './types'; +import { AdaptyNativeErrorCoder } from './adapty-native-error'; +import { AdaptyUiOnboardingMetaCoder } from '@/coders/adapty-ui-onboarding-meta'; +import { AdaptyUiOnboardingStateUpdatedActionCoder } from '@/coders/adapty-ui-onboarding-state-updated-action'; +import type { AdaptyUiOnboardingMeta } from '@/ui/types'; +import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import { + OnboardingEventId, + type OnboardingEventView, + type ParsedOnboardingEvent, +} from '@/types/onboarding-events'; + +// Re-export types for convenience +export { + OnboardingEventId, + type OnboardingEventIdType, + type OnboardingEventView, + type OnboardingCloseEvent, + type OnboardingCustomEvent, + type OnboardingPaywallEvent, + type OnboardingStateUpdatedEvent, + type OnboardingFinishedLoadingEvent, + type OnboardingAnalyticsEvent, + type OnboardingErrorEvent, + type ParsedOnboardingEvent, +} from '@/types/onboarding-events'; + +// Parser +export function parseOnboardingEvent( + input: string, + ctx?: LogContext, +): ParsedOnboardingEvent | null { + let obj: Record; + try { + obj = JSON.parse(input); + } catch (error) { + throw AdaptyError.failedToDecode( + `Failed to decode event: ${(error as Error)?.message}`, + ); + } + + const eventId = obj['id'] as string | undefined; + if (!eventId?.startsWith('onboarding_')) { + return null; + } + + const viewObj = obj['view'] as Record; + const view: OnboardingEventView = { + id: viewObj['id'] as string, + placementId: viewObj['placement_id'] as string | undefined, + variationId: viewObj['variation_id'] as string | undefined, + }; + const decodeMeta = () => + getOnboardingCoder('meta', ctx)!.decode( + obj['meta'], + ) as AdaptyUiOnboardingMeta; + + switch (eventId) { + case OnboardingEventId.Close: + case OnboardingEventId.Custom: + case OnboardingEventId.Paywall: + return { + id: eventId, + view, + actionId: (obj['action_id'] as string) ?? '', + meta: decodeMeta(), + }; + + case OnboardingEventId.StateUpdated: + return { + id: eventId, + view, + action: getOnboardingCoder('action', ctx)!.decode( + obj['action'], + ) as OnboardingStateUpdatedAction, + meta: decodeMeta(), + }; + + case OnboardingEventId.FinishedLoading: + return { + id: eventId, + view, + meta: decodeMeta(), + }; + + case OnboardingEventId.Analytics: { + const eventObj = obj['event'] as Record; + return { + id: eventId, + view, + event: { + name: eventObj['name'] as string, + elementId: eventObj['element_id'] as string | undefined, + reply: eventObj['reply'] as string | undefined, + }, + meta: decodeMeta(), + }; + } + + case OnboardingEventId.Error: { + const errorCoder = getOnboardingCoder( + 'error', + ctx, + ) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return { + id: eventId, + view, + error: errorCoder.getError(decodedError), + }; + } + + default: + return null; + } +} + +type OnboardingCoderType = 'meta' | 'action' | 'error'; + +function getOnboardingCoder( + type: OnboardingCoderType, + _ctx?: LogContext, +): Converter | ErrorConverter { + switch (type) { + case 'meta': + return new AdaptyUiOnboardingMetaCoder(); + case 'action': + return new AdaptyUiOnboardingStateUpdatedActionCoder(); + case 'error': + return new AdaptyNativeErrorCoder(); + } +} diff --git a/src/coders/parse-paywall.ts b/src/coders/parse-paywall.ts new file mode 100644 index 0000000..c1a8b59 --- /dev/null +++ b/src/coders/parse-paywall.ts @@ -0,0 +1,224 @@ +import { AdaptyError } from '@/adapty-error'; +import { LogContext } from '../logger'; +import { ErrorConverter } from './error-coder'; +import type { Converter } from './types'; +import { AdaptyNativeErrorCoder } from './adapty-native-error'; +import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; +import { AdaptyProfileCoder } from './adapty-profile'; +import { AdaptyPurchaseResultCoder } from './adapty-purchase-result'; +import type { + AdaptyPaywallProduct, + AdaptyProfile, + AdaptyPurchaseResult, +} from '@/types'; +import { + PaywallEventId, + type PaywallEventView, + type ParsedPaywallEvent, +} from '@/types/paywall-events'; + +// Re-export types for convenience +export { + PaywallEventId, + type PaywallEventIdType, + type PaywallEventView, + type PaywallDidAppearEvent, + type PaywallDidDisappearEvent, + type PaywallDidPerformActionEvent, + type PaywallDidSelectProductEvent, + type PaywallDidStartPurchaseEvent, + type PaywallDidFinishPurchaseEvent, + type PaywallDidFailPurchaseEvent, + type PaywallDidStartRestoreEvent, + type PaywallDidFinishRestoreEvent, + type PaywallDidFailRestoreEvent, + type PaywallDidFailRenderingEvent, + type PaywallDidFailLoadingProductsEvent, + type PaywallDidFinishWebPaymentNavigationEvent, + type ParsedPaywallEvent, +} from '@/types/paywall-events'; + +// Parser +export function parsePaywallEvent( + input: string, + ctx?: LogContext, +): ParsedPaywallEvent | null { + let obj: Record; + try { + obj = JSON.parse(input); + } catch (error) { + throw AdaptyError.failedToDecode( + `Failed to decode event: ${(error as Error)?.message}`, + ); + } + + const eventId = obj['id'] as string | undefined; + if (!eventId?.startsWith('paywall_view_')) { + return null; + } + + const viewObj = obj['view'] as Record; + const view: PaywallEventView = { + id: viewObj['id'] as string, + placementId: viewObj['placement_id'] as string | undefined, + variationId: viewObj['variation_id'] as string | undefined, + }; + + switch (eventId) { + case PaywallEventId.DidAppear: + return { + id: eventId, + view, + }; + + case PaywallEventId.DidDisappear: + return { + id: eventId, + view, + }; + + case PaywallEventId.DidPerformAction: { + const actionObj = obj['action'] as Record; + return { + id: eventId, + view, + action: { + type: actionObj['type'] as + | 'close' + | 'system_back' + | 'open_url' + | 'custom', + value: actionObj['value'] as string | undefined, + }, + }; + } + + case PaywallEventId.DidSelectProduct: + return { + id: eventId, + view, + productId: (obj['product_id'] as string) ?? '', + }; + + case PaywallEventId.DidStartPurchase: + return { + id: eventId, + view, + product: getPaywallCoder('product', ctx)!.decode( + obj['product'], + ) as AdaptyPaywallProduct, + }; + + case PaywallEventId.DidFinishPurchase: + return { + id: eventId, + view, + purchaseResult: getPaywallCoder('purchaseResult', ctx)!.decode( + obj['purchased_result'], + ) as AdaptyPurchaseResult, + product: getPaywallCoder('product', ctx)!.decode( + obj['product'], + ) as AdaptyPaywallProduct, + }; + + case PaywallEventId.DidFailPurchase: { + const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return { + id: eventId, + view, + error: errorCoder.getError(decodedError), + product: getPaywallCoder('product', ctx)!.decode( + obj['product'], + ) as AdaptyPaywallProduct, + }; + } + + case PaywallEventId.DidStartRestore: + return { + id: eventId, + view, + }; + + case PaywallEventId.DidFinishRestore: + return { + id: eventId, + view, + profile: getPaywallCoder('profile', ctx)!.decode( + obj['profile'], + ) as AdaptyProfile, + }; + + case PaywallEventId.DidFailRestore: { + const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return { + id: eventId, + view, + error: errorCoder.getError(decodedError), + }; + } + + case PaywallEventId.DidFailRendering: { + const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return { + id: eventId, + view, + error: errorCoder.getError(decodedError), + }; + } + + case PaywallEventId.DidFailLoadingProducts: { + const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return { + id: eventId, + view, + error: errorCoder.getError(decodedError), + }; + } + + case PaywallEventId.DidFinishWebPaymentNavigation: + return { + id: eventId, + view, + product: obj['product'] + ? (getPaywallCoder('product', ctx)!.decode( + obj['product'], + ) as AdaptyPaywallProduct) + : undefined, + error: obj['error'] + ? (() => { + const errorCoder = getPaywallCoder( + 'error', + ctx, + ) as ErrorConverter; + const decodedError = errorCoder.decode(obj['error']); + return errorCoder.getError(decodedError); + })() + : undefined, + }; + + default: + return null; + } +} + +type PaywallCoderType = 'product' | 'profile' | 'purchaseResult' | 'error'; + +function getPaywallCoder( + type: PaywallCoderType, + _ctx?: LogContext, +): Converter | ErrorConverter { + switch (type) { + case 'product': + return new AdaptyPaywallProductCoder(); + case 'profile': + return new AdaptyProfileCoder(); + case 'purchaseResult': + return new AdaptyPurchaseResultCoder(); + case 'error': + return new AdaptyNativeErrorCoder(); + } +} diff --git a/src/coders/parse.test.ts b/src/coders/parse.test.ts new file mode 100644 index 0000000..064a9e5 --- /dev/null +++ b/src/coders/parse.test.ts @@ -0,0 +1,242 @@ +// import type { AdaptyNativeError } from '@/types/bridge'; +// import type { AdaptyPaywall, AdaptyPaywallProduct, AdaptyProfile } from '@/types'; +// import { AdaptyError } from '@/adapty-error'; + +// import { AdaptyPaywallCoder } from './adapty-paywall'; +// import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; +// import { AdaptyProfileCoder } from './adapty-profile'; +// import { ArrayCoder } from './array'; +// import { parse } from './parse'; + +describe('parse SDK responses', () => { + // it('AdaptyPaywallCoder', () => { + // const input = { + // type: 'AdaptyPaywall', + // data: { + // revision: 1, + // use_paywall_builder: true, + // products: [{ vendor_product_id: 'weekly.premium.599' }], + // variation_id: 'bdaca0ea-04ea-47a8-8f5f-8a6f65edbd51', + // developer_id: '111', + // paywall_name: '111', + // ab_test_name: '111', + // remote_config: { lang: 'en', data: '' }, + // paywall_updated_at: 1676361063649, + // }, + // }; + // const result = parse(JSON.stringify(input)); + // expect(result).toEqual({ + // revision: 1, + // hasViewConfiguration: true, + // products: [{ vendorId: 'weekly.premium.599', ios: {} }], + // variationId: 'bdaca0ea-04ea-47a8-8f5f-8a6f65edbd51', + // id: '111', + // name: '111', + // abTestName: '111', + // locale: 'en', + // remoteConfigString: '', + // remoteConfig: {}, + // version: 1676361063649, + // } satisfies AdaptyPaywall); + // const coder = new AdaptyPaywallCoder(); + // const recoded = coder.encode(result); + // expect(input.data).toEqual(recoded); + // }); + // it('AdaptyProfileCoder', () => { + // const input = { + // type: 'AdaptyProfile', + // data: { + // customer_user_id: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + // paid_access_levels: { + // premium: { + // id: 'premium', + // is_lifetime: false, + // vendor_product_id: 'weekly.premium.599', + // unsubscribed_at: '2023-07-28T08:16:19.000Z', + // expires_at: '2023-07-28T08:16:19.000Z', + // will_renew: false, + // is_active: false, + // is_in_grace_period: false, + // activated_at: '2023-01-08T12:05:59.000Z', + // renewed_at: '2023-07-28T08:13:19.000Z', + // is_refund: false, + // cancellation_reason: 'voluntarily_cancelled', + // store: 'app_store', + // }, + // }, + // custom_attributes: {}, + // non_subscriptions: {}, + // subscriptions: { + // 'monthly.premium.999': { + // is_lifetime: false, + // vendor_product_id: 'monthly.premium.999', + // is_sandbox: true, + // unsubscribed_at: '2023-01-12T11:36:38.000Z', + // expires_at: '2023-01-12T11:36:38.000Z', + // will_renew: false, + // vendor_transaction_id: '2000000248420224', + // vendor_original_transaction_id: '2000000244587785', + // is_in_grace_period: false, + // activated_at: '2023-01-08T12:05:59.000Z', + // is_active: false, + // renewed_at: '2023-01-12T11:31:38.000Z', + // is_refund: false, + // store: 'app_store', + // }, + // 'weekly.premium.599': { + // is_lifetime: false, + // vendor_product_id: 'weekly.premium.599', + // is_sandbox: true, + // unsubscribed_at: '2023-07-28T08:16:19.000Z', + // expires_at: '2023-07-28T08:16:19.000Z', + // will_renew: false, + // vendor_transaction_id: '2000000378024239', + // vendor_original_transaction_id: '2000000244587785', + // is_in_grace_period: false, + // activated_at: '2023-01-08T12:05:59.000Z', + // is_active: false, + // renewed_at: '2023-07-28T08:13:19.000Z', + // is_refund: false, + // store: 'app_store', + // cancellation_reason: 'voluntarily_cancelled', + // }, + // }, + // profile_id: '69a4be0c-7ee2-4669-b637-814a60494346', + // }, + // }; + // const result = parse(JSON.stringify(input)); + // expect(result).toEqual({ + // profileId: '69a4be0c-7ee2-4669-b637-814a60494346', + // customAttributes: {}, + // customerUserId: '57739865-5F09-45FF-8A95-BBB5AB0B4276', + // accessLevels: { + // premium: { + // id: 'premium', + // isLifetime: false, + // vendorProductId: 'weekly.premium.599', + // unsubscribedAt: new Date('2023-07-28T08:16:19.000Z'), + // expiresAt: new Date('2023-07-28T08:16:19.000Z'), + // willRenew: false, + // isActive: false, + // isInGracePeriod: false, + // activatedAt: new Date('2023-01-08T12:05:59.000Z'), + // renewedAt: new Date('2023-07-28T08:13:19.000Z'), + // isRefund: false, + // cancellationReason: 'voluntarily_cancelled', + // store: 'app_store', + // }, + // }, + // subscriptions: { + // 'monthly.premium.999': { + // isLifetime: false, + // vendorProductId: 'monthly.premium.999', + // isSandbox: true, + // unsubscribedAt: new Date('2023-01-12T11:36:38.000Z'), + // expiresAt: new Date('2023-01-12T11:36:38.000Z'), + // willRenew: false, + // vendorTransactionId: '2000000248420224', + // vendorOriginalTransactionId: '2000000244587785', + // isInGracePeriod: false, + // activatedAt: new Date('2023-01-08T12:05:59.000Z'), + // isActive: false, + // renewedAt: new Date('2023-01-12T11:31:38.000Z'), + // isRefund: false, + // store: 'app_store', + // }, + // 'weekly.premium.599': { + // isLifetime: false, + // vendorProductId: 'weekly.premium.599', + // isSandbox: true, + // unsubscribedAt: new Date('2023-07-28T08:16:19.000Z'), + // expiresAt: new Date('2023-07-28T08:16:19.000Z'), + // willRenew: false, + // vendorTransactionId: '2000000378024239', + // vendorOriginalTransactionId: '2000000244587785', + // isInGracePeriod: false, + // activatedAt: new Date('2023-01-08T12:05:59.000Z'), + // isActive: false, + // renewedAt: new Date('2023-07-28T08:13:19.000Z'), + // isRefund: false, + // store: 'app_store', + // cancellationReason: 'voluntarily_cancelled', + // }, + // }, + // nonSubscriptions: {}, + // } satisfies AdaptyProfile); + // const coder = new AdaptyProfileCoder(); + // const recoded = coder.encode(result); + // expect(input.data).toEqual(recoded); + // }); + // it('Array', () => { + // const input = { + // type: 'Array', + // data: [ + // { + // vendor_product_id: 'weekly.premium.599', + // subscription_period: { + // number_of_units: 7, + // unit: 'day', + // }, + // region_code: 'US', + // is_family_shareable: false, + // localized_subscription_period: '1 week', + // currency_code: 'USD', + // localized_title: '1 Week Premium', + // paywall_name: '111', + // price: 5.99, + // paywall_ab_test_name: '111', + // subscription_group_identifier: '20770576', + // discounts: [], + // localized_price: '$5.99', + // localized_description: '1 Month Premium Description', + // currency_symbol: '$', + // variation_id: 'bdaca0ea-04ea-47a8-8f5f-8a6f65edbd51', + // }, + // ], + // }; + // const products = parse(input); + // expect(products).toEqual([ + // { + // vendorProductId: 'weekly.premium.599', + // subscriptionPeriod: { + // numberOfUnits: 7, + // unit: 'day', + // }, + // localizedSubscriptionPeriod: '1 week', + // currencyCode: 'USD', + // localizedTitle: '1 Week Premium', + // paywallName: '111', + // price: 5.99, + // paywallABTestName: '111', + // localizedPrice: '$5.99', + // localizedDescription: '1 Month Premium Description', + // currencySymbol: '$', + // variationId: 'bdaca0ea-04ea-47a8-8f5f-8a6f65edbd51', + // ios: { + // isFamilyShareable: false, + // discounts: [], + // regionCode: 'US', + // subscriptionGroupIdentifier: '20770576', + // }, + // android: {}, + // }, + // ] satisfies AdaptyPaywallProduct[]); + // const coder = new ArrayCoder(AdaptyPaywallProductCoder as any); + // const recoded = coder.encode(products); + // expect(input.data).toEqual(recoded); + // }); + // it('AdaptyError', () => { + // const input = { + // type: 'AdaptyError', + // data: { + // adapty_code: 0, + // message: 'Product purchase failed', + // }, + // }; + // const result = parse(input); + // expect(result).toBeInstanceOf(AdaptyError); + // }); + it('falsy', () => { + expect(2).toStrictEqual(2); + }); +}); diff --git a/src/coders/parse.ts b/src/coders/parse.ts new file mode 100644 index 0000000..bdfe555 --- /dev/null +++ b/src/coders/parse.ts @@ -0,0 +1,178 @@ +import { AdaptyError } from '@/adapty-error'; +import { LogContext } from '../logger'; +import { AdaptyNativeErrorCoder } from './adapty-native-error'; +import { AdaptyPaywallCoder } from './adapty-paywall'; +import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; +import { AdaptyProfileCoder } from './adapty-profile'; +import { ArrayCoder } from './array'; +import { BridgeErrorCoder } from './bridge-error'; +import { ErrorConverter } from './error-coder'; +import type { Converter } from './types'; +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; +import { AdaptyPurchaseResultCoder } from '@/coders/adapty-purchase-result'; +import { AdaptyOnboardingCoder } from '@/coders/adapty-onboarding'; +import { AdaptyUiOnboardingMetaCoder } from '@/coders/adapty-ui-onboarding-meta'; +import { AdaptyUiOnboardingStateParamsCoder } from '@/coders/adapty-ui-onboarding-state-params'; +import { AdaptyUiOnboardingStateUpdatedActionCoder } from '@/coders/adapty-ui-onboarding-state-updated-action'; +import { AdaptyInstallationStatusCoder } from '@/coders/adapty-installation-status'; +import { AdaptyInstallationDetailsCoder } from '@/coders/adapty-installation-details'; + +const AdaptyTypes = [ + 'AdaptyError', + 'AdaptyProfile', + 'AdaptyPurchaseResult', + 'AdaptyPaywall', + 'AdaptyPaywallProduct', + 'AdaptyOnboarding', + 'AdaptyRemoteConfig', + 'AdaptyPaywallBuilder', + 'AdaptyInstallationStatus', + 'AdaptyInstallationDetails', + 'AdaptyUiView', + 'AdaptyUiDialogActionType', + 'AdaptyUiOnboardingMeta', + 'AdaptyUiOnboardingStateParams', + 'AdaptyUiOnboardingStateUpdatedAction', + 'Array', + 'BridgeError', + 'String', + 'Boolean', + 'Void', +] as const; + +export type AdaptyType = (typeof AdaptyTypes)[number]; + +interface AdaptyResult { + success?: T; + error?: AdaptyResultError; +} + +interface AdaptyResultError { + adaptyCode: number; + message: string; + detail?: string; +} + +export function parseMethodResult( + input: string, + resultType: AdaptyType, + ctx?: LogContext, +): T { + const log = ctx?.decode({ methodName: 'parseMethodResult' }); + log?.start({ input }); + + let obj: AdaptyResult; + + // Attempt to parse the input into a JSON object + try { + obj = JSON.parse(input); + } catch (error) { + const adaptyError = AdaptyError.failedToDecode( + `Failed to decode native response. JSON.parse raised an error: ${ + (error as Error)?.message || '' + }`, + ); + + log?.failed(adaptyError.message); + throw adaptyError; + } + + if (obj.hasOwnProperty('success')) { + if ( + [ + 'String', + 'Boolean', + 'Void', + 'AdaptyUiView', + 'AdaptyUiDialogActionType', + ].includes(resultType) + ) { + return obj.success as T; + } + + const coder = getCoder(resultType, ctx); + return coder?.decode(obj.success); + } else if (obj.hasOwnProperty('error')) { + const coder = getCoder('AdaptyError', ctx); + const errorData = coder?.decode(obj.error); + throw (coder as ErrorConverter).getError(errorData); + } else { + const adaptyError = AdaptyError.failedToDecode( + `Failed to decode native response. Response does not have expected "success" or "error" property`, + ); + log?.failed(adaptyError.message); + throw adaptyError; + } +} + +export function parseCommonEvent( + event: string, + input: string, + ctx?: LogContext, +) { + let obj: Record; + try { + obj = JSON.parse(input); + } catch (error) { + throw AdaptyError.failedToDecode( + `Failed to decode event: ${(error as Error)?.message}`, + ); + } + switch (event) { + case 'did_load_latest_profile': + return getCoder('AdaptyProfile', ctx)?.decode(obj['profile']); + case 'on_installation_details_success': + return getCoder('AdaptyInstallationDetails', ctx)?.decode(obj['details']); + case 'on_installation_details_fail': + return getCoder('AdaptyError', ctx)?.decode(obj['error']); + default: + return null; + } +} + +function getCoder( + type: AdaptyType, + ctx?: LogContext, +): Converter | ErrorConverter | null { + ctx?.stack; + + switch (type as AdaptyType) { + case 'AdaptyError': + return new AdaptyNativeErrorCoder(); + case 'AdaptyProfile': + return new AdaptyProfileCoder(); + case 'AdaptyPaywall': + return new AdaptyPaywallCoder(); + case 'AdaptyPaywallProduct': + return new AdaptyPaywallProductCoder(); + case 'AdaptyRemoteConfig': + return new AdaptyRemoteConfigCoder(); + case 'AdaptyPaywallBuilder': + return new AdaptyPaywallBuilderCoder(); + case 'AdaptyOnboarding': + return new AdaptyOnboardingCoder(); + case 'AdaptyPurchaseResult': + return new AdaptyPurchaseResultCoder(); + case 'AdaptyInstallationStatus': + return new AdaptyInstallationStatusCoder(); + case 'AdaptyInstallationDetails': + return new AdaptyInstallationDetailsCoder(); + case 'AdaptyUiOnboardingMeta': + return new AdaptyUiOnboardingMetaCoder(); + case 'AdaptyUiOnboardingStateParams': + return new AdaptyUiOnboardingStateParamsCoder(); + case 'AdaptyUiOnboardingStateUpdatedAction': + return new AdaptyUiOnboardingStateUpdatedActionCoder(); + case 'BridgeError': + return new BridgeErrorCoder(); + case 'Array': + return new ArrayCoder(AdaptyPaywallProductCoder as any); + case 'String': + return null; + } + // @ts-ignore + throw AdaptyError.failedToDecode( + `Failed to decode native response. Response has unexpected "type" property: ${type}`, + ); +} diff --git a/src/coders/product-reference.test.ts b/src/coders/product-reference.test.ts new file mode 100644 index 0000000..f915aed --- /dev/null +++ b/src/coders/product-reference.test.ts @@ -0,0 +1,81 @@ +import type { ProductReference } from '@/types'; +import type { Def } from '@/types/schema'; +import { ProductReferenceCoder } from './product-reference'; + +type Model = ProductReference; +const mocks: Def['AdaptyPaywall.ProductReference'][] = [ + { + vendor_product_id: 'product123', + adapty_product_id: 'adaptyProduct123', + access_level_id: 'premium', + product_type: 'subscription', + }, + { + vendor_product_id: 'product456', + adapty_product_id: 'adaptyProduct456', + access_level_id: 'premium', + product_type: 'subscription', + promotional_offer_id: 'offer789', + win_back_offer_id: 'offer456', + }, + { + vendor_product_id: 'product111', + adapty_product_id: 'adaptyProduct111', + access_level_id: 'vip', + product_type: 'subscription', + base_plan_id: 'base222', + offer_id: 'offer333', + }, + { + vendor_product_id: 'productXYZ', + adapty_product_id: 'adaptyProductXYZ', + access_level_id: 'premium', + product_type: 'non_subscription', + promotional_offer_id: 'promoOfferIOS', + win_back_offer_id: 'winBackOfferIOS', + base_plan_id: 'baseAndroid', + offer_id: 'offerAndroid', + }, +]; + +function toModel(mock: (typeof mocks)[number]): Model { + return { + vendorId: mock.vendor_product_id, + adaptyId: mock.adapty_product_id, + accessLevelId: mock.access_level_id, + productType: mock.product_type, + ios: { + ...(mock.promotional_offer_id && { + promotionalOfferId: mock.promotional_offer_id, + }), + ...(mock.win_back_offer_id && { + winBackOfferId: mock.win_back_offer_id, + }), + }, + android: { + ...(mock.base_plan_id && { basePlanId: mock.base_plan_id }), + ...(mock.offer_id && { offerId: mock.offer_id }), + } as any, + }; +} + +describe('ProductReferenceCoder', () => { + let coder: ProductReferenceCoder; + + beforeEach(() => { + coder = new ProductReferenceCoder(); + }); + + it.each(mocks)('should decode to expected result', mock => { + const decoded = coder.decode(mock); + + expect(decoded).toStrictEqual(toModel(mock)); + }); + + it.each(mocks)('should decode/encode', mock => { + const decoded = coder.decode(mock); + const encoded = coder.encode(decoded); + + expect(encoded).toStrictEqual(mock); + }); +}); diff --git a/src/coders/product-reference.ts b/src/coders/product-reference.ts new file mode 100644 index 0000000..80da33c --- /dev/null +++ b/src/coders/product-reference.ts @@ -0,0 +1,56 @@ +import type { Properties } from './types'; +import type { ProductReference } from '@/types'; +import type { Def } from '@/types/schema'; +import { SimpleCoder } from './coder'; + +type Model = ProductReference; +type Serializable = Def['AdaptyPaywall.ProductReference']; + +export class ProductReferenceCoder extends SimpleCoder { + protected properties: Properties = { + vendorId: { + key: 'vendor_product_id', + required: true, + type: 'string', + }, + adaptyId: { + key: 'adapty_product_id', + required: true, + type: 'string', + }, + accessLevelId: { + key: 'access_level_id', + required: true, + type: 'string', + }, + productType: { + key: 'product_type', + required: true, + type: 'string', + }, + ios: { + promotionalOfferId: { + key: 'promotional_offer_id', + required: false, + type: 'string', + }, + winBackOfferId: { + key: 'win_back_offer_id', + required: false, + type: 'string', + }, + }, + android: { + basePlanId: { + key: 'base_plan_id', + required: false, + type: 'string', + }, + offerId: { + key: 'offer_id', + required: false, + type: 'string', + }, + }, + }; +} diff --git a/src/coders/types.ts b/src/coders/types.ts new file mode 100644 index 0000000..6fd6637 --- /dev/null +++ b/src/coders/types.ts @@ -0,0 +1,62 @@ +export type StrType = T extends string + ? 'string' + : T extends Date + ? 'string' // endoded JSON + : T extends boolean + ? 'boolean' + : T extends number + ? 'number' + : T extends any[] + ? 'array' + : 'object'; + +export interface Converter { + type?: 'data' | 'error'; // identify error converters via type + decode: (input: Serialized) => Model; + encode: (value: Model) => Serialized; +} + +// nests object keys like "obj.obj2.value" +// type NestedKeys = { +// [K in keyof T]: T[K] extends object ? `${K}.${NestedKeys}` | K : K; +// }[keyof T]; + +export interface PropertyMeta< + Model = unknown, + Key extends string | number | symbol = string, +> { + converter?: Converter>; + key: Key; + required: undefined extends Model ? false : true; + type: StrType; +} + +// Trust me +export type Properties< + Model extends Record, + SerializableData = Record, +> = { + [K in keyof Omit, 'ios' | 'android'>]: PropertyMeta< + Model[K], + keyof SerializableData + >; +} & ('ios' extends keyof Model + ? { + ios: { + [K in keyof Required['ios']>]: PropertyMeta< + Required['ios'][K], + keyof SerializableData + >; + }; + } + : {}) & + ('android' extends keyof Model + ? { + android: { + [K in keyof Required['android']>]: PropertyMeta< + Required['android'][K], + keyof SerializableData + >; + }; + } + : {}); diff --git a/src/coders/utils.ts b/src/coders/utils.ts new file mode 100644 index 0000000..0cdf213 --- /dev/null +++ b/src/coders/utils.ts @@ -0,0 +1,67 @@ +export const formatDateUTC = (date: Date): string => { + const pad = (num: number, digits: number = 2): string => { + const str = num.toString(); + const paddingLength = digits - str.length; + return paddingLength > 0 ? '0'.repeat(paddingLength) + str : str; + }; + + const year = date.getUTCFullYear(); + const month = pad(date.getUTCMonth() + 1); + const day = pad(date.getUTCDate()); + const hours = pad(date.getUTCHours()); + const minutes = pad(date.getUTCMinutes()); + const seconds = pad(date.getUTCSeconds()); + const millis = pad(date.getUTCMilliseconds(), 3); + + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${millis}Z`; +}; + +export const colorToHex = { + fromARGB(value: number): string { + const hex = value.toString(16).padStart(8, '0'); + return `#${hex.slice(2)}${hex.slice(0, 2)}`; + }, + fromRGBA(value: number): string { + return `#${value.toString(16).padStart(8, '0')}`; + }, + fromRGB(value: number): string { + return `#${value.toString(16).padStart(6, '0')}ff`; + }, +}; + +export const extractBase64Data = (input: string): string => { + const commaIndex = input.indexOf(','); + if (input.startsWith('data:') && commaIndex !== -1) { + return input.slice(commaIndex + 1); + } + return input; +}; + +import type { FileLocation } from '@/types/inputs'; + +type PlatformSelector = { ios: T; android: T }; + +export const resolveAssetId = ( + asset: { relativeAssetPath: string } | { fileLocation: FileLocation }, + select: (spec: PlatformSelector) => T | undefined, +): string => { + if ('relativeAssetPath' in asset) { + return ( + select({ + ios: asset.relativeAssetPath, + android: `${asset.relativeAssetPath}a`, + }) || '' + ); + } + + const fileLocation = asset.fileLocation; + return ( + select({ + ios: fileLocation.ios.fileName, + android: + 'relativeAssetPath' in fileLocation.android + ? `${fileLocation.android.relativeAssetPath}a` + : `${(fileLocation.android as any).rawResName}r`, + }) || '' + ); +}; diff --git a/src/types/api.d.ts b/src/types/api.d.ts new file mode 100644 index 0000000..8607d1d --- /dev/null +++ b/src/types/api.d.ts @@ -0,0 +1,1061 @@ +/** OneOf type helpers */ +type Without = { [P in Exclude]?: never }; +type XOR = T | U extends object + ? (Without & U) | (Without & T) + : T | U; +type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never; + +export type paths = Record; + +export type webhooks = Record; + +export interface components { + requests: { + 'Activate.Request': { + method: 'activate'; + configuration: components['defs']['AdaptyConfiguration']; + }; + + 'Activate.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'AdaptyUICreatePaywallView.Request': { + method: 'adapty_ui_create_paywall_view'; + paywall: components['defs']['AdaptyPaywall']; + load_timeout?: number; + preload_products?: boolean; + custom_tags?: components['defs']['AdaptyUI.CustomTagsValues']; + custom_timers?: components['defs']['AdaptyUI.CustomTimersValues']; + custom_assets?: components['defs']['AdaptyUI.CustomAssets']; + product_purchase_parameters?: components['defs']['AdaptyUI.ProductPurchaseParameters']; + }; + + 'AdaptyUICreatePaywallView.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyUI.PaywallView'] }, + ] + >; + + 'AdaptyUIDismissPaywallView.Request': { + method: 'adapty_ui_dismiss_paywall_view'; + id: string; + destroy?: boolean; + }; + + 'AdaptyUIDismissPaywallView.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'AdaptyUIPresentPaywallView.Request': { + method: 'adapty_ui_present_paywall_view'; + id: string; + ios_presentation_style?: components['defs']['AdaptyUI.IOSPresentationStyle']; + }; + + 'AdaptyUIPresentPaywallView.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'AdaptyUIShowDialog.Request': { + method: 'adapty_ui_show_dialog'; + id: string; + configuration: components['defs']['AdaptyUI.DialogConfiguration']; + }; + + 'AdaptyUIShowDialog.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyUI.DialogActionType'] }, + ] + >; + + 'AdaptyUICreateOnboardingView.Request': { + method: 'adapty_ui_create_onboarding_view'; + onboarding: components['defs']['AdaptyOnboarding']; + external_urls_presentation?: components['defs']['AdaptyWebPresentation']; + }; + + 'AdaptyUICreateOnboardingView.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyUI.OnboardingView'] }, + ] + >; + + 'AdaptyUIDismissOnboardingView.Request': { + method: 'adapty_ui_dismiss_onboarding_view'; + id: string; + destroy?: boolean; + }; + + 'AdaptyUIDismissOnboardingView.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'AdaptyUIPresentOnboardingView.Request': { + method: 'adapty_ui_present_onboarding_view'; + id: string; + ios_presentation_style?: components['defs']['AdaptyUI.IOSPresentationStyle']; + }; + + 'AdaptyUIPresentOnboardingView.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'GetPaywall.Request': { + method: 'get_paywall'; + placement_id: string; + locale?: components['defs']['AdaptyLocale']; + fetch_policy?: components['defs']['AdaptyPlacementFetchPolicy']; + load_timeout?: number; + }; + + 'GetPaywall.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyPaywall'] }, + ] + >; + + 'GetPaywallForDefaultAudience.Request': { + method: 'get_paywall_for_default_audience'; + placement_id: string; + locale?: components['defs']['AdaptyLocale']; + fetch_policy?: components['defs']['AdaptyPlacementFetchPolicy']; + }; + + 'GetPaywallForDefaultAudience.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyPaywall'] }, + ] + >; + + 'GetPaywallProducts.Request': { + method: 'get_paywall_products'; + paywall: components['defs']['AdaptyPaywall']; + }; + + 'GetPaywallProducts.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyPaywallProduct.Response'][] }, + ] + >; + + 'GetOnboarding.Request': { + method: 'get_onboarding'; + placement_id: string; + locale?: components['defs']['AdaptyLocale']; + fetch_policy?: components['defs']['AdaptyPlacementFetchPolicy']; + load_timeout?: number; + }; + + 'GetOnboarding.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyOnboarding'] }, + ] + >; + + 'GetOnboardingForDefaultAudience.Request': { + method: 'get_onboarding_for_default_audience'; + placement_id: string; + locale?: components['defs']['AdaptyLocale']; + fetch_policy?: components['defs']['AdaptyPlacementFetchPolicy']; + }; + + 'GetOnboardingForDefaultAudience.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyOnboarding'] }, + ] + >; + + 'GetProfile.Request': { + method: 'get_profile'; + }; + + 'GetProfile.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyProfile'] }, + ] + >; + + 'Identify.Request': { + method: 'identify'; + customer_user_id: string; + parameters?: components['defs']['CustomerIdentityParameters']; + }; + + 'Identify.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'IsActivated.Request': { + method: 'is_activated'; + }; + + 'IsActivated.Response': { + success: boolean; + }; + + 'GetLogLevel.Request': { + method: 'get_log_level'; + }; + + 'GetLogLevel.Response': { + success: components['defs']['AdaptyLog.Level']; + }; + + 'SetLogLevel.Request': { + method: 'set_log_level'; + value: components['defs']['AdaptyLog.Level']; + }; + + 'SetLogLevel.Response': { + success: true; + }; + + 'Logout.Request': { + method: 'logout'; + }; + + 'Logout.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'LogShowPaywall.Request': { + method: 'log_show_paywall'; + paywall: components['defs']['AdaptyPaywall']; + }; + + 'LogShowPaywall.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'MakePurchase.Request': { + method: 'make_purchase'; + product: components['defs']['AdaptyPaywallProduct.Request']; + parameters?: components['defs']['AdaptyPurchaseParameters']; + }; + + 'MakePurchase.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyPurchaseResult'] }, + ] + >; + + 'OpenWebPaywall.Request': OneOf< + [ + { + method: 'open_web_paywall'; + product: components['defs']['AdaptyPaywallProduct.Request']; + open_in?: components['defs']['AdaptyWebPresentation']; + }, + { + method: 'open_web_paywall'; + paywall: components['defs']['AdaptyPaywall']; + open_in?: components['defs']['AdaptyWebPresentation']; + }, + ] + >; + + 'OpenWebPaywall.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'CreateWebPaywallUrl.Request': OneOf< + [ + { + method: 'create_web_paywall_url'; + product: components['defs']['AdaptyPaywallProduct.Request']; + }, + { + method: 'create_web_paywall_url'; + paywall: components['defs']['AdaptyPaywall']; + }, + ] + >; + + 'CreateWebPaywallUrl.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: string }] + >; + + 'PresentCodeRedemptionSheet.Request': { + method: 'present_code_redemption_sheet'; + }; + + 'PresentCodeRedemptionSheet.Response': { + success: true; + }; + + 'ReportTransaction.Request': { + method: 'report_transaction'; + transaction_id: string; + variation_id?: string; + }; + + 'ReportTransaction.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'RestorePurchases.Request': { + method: 'restore_purchases'; + }; + + 'RestorePurchases.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyProfile'] }, + ] + >; + + 'GetSDKVersion.Request': { + method: 'get_sdk_version'; + }; + + 'GetSDKVersion.Response': { + success: string; + }; + + 'GetCurrentInstallationStatus.Request': { + method: 'get_current_installation_status'; + }; + + 'GetCurrentInstallationStatus.Response': OneOf< + [ + { error: components['defs']['AdaptyError'] }, + { success: components['defs']['AdaptyInstallationStatus'] }, + ] + >; + + 'SetFallback.Request': { + method: 'set_fallback'; + } & OneOf<[{ asset_id: string }, { path: string }]>; + + 'SetFallback.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'SetIntegrationIdentifier.Request': { + method: 'set_integration_identifiers'; + key_values: Record; + }; + + 'SetIntegrationIdentifier.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'UpdateAttributionData.Request': { + method: 'update_attribution_data'; + attribution: string; + source: string; + }; + + 'UpdateAttributionData.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'UpdateCollectingRefundDataConsent.Request': { + method: 'update_collecting_refund_data_consent'; + consent: boolean; + }; + + 'UpdateCollectingRefundDataConsent.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'UpdateProfile.Request': { + method: 'update_profile'; + params: components['defs']['AdaptyProfileParameters']; + }; + + 'UpdateProfile.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + + 'UpdateRefundPreference.Request': { + method: 'update_refund_preference'; + refund_preference: components['defs']['AdaptyRefundPreference']; + }; + + 'UpdateRefundPreference.Response': OneOf< + [{ error: components['defs']['AdaptyError'] }, { success: true }] + >; + }; + events: { + 'Event.DidLoadLatestProfile': { + id: 'did_load_latest_profile'; + profile: components['defs']['AdaptyProfile']; + }; + + 'Event.OnInstallationDetailsSuccess': { + id: 'on_installation_details_success'; + details: components['defs']['AdaptyInstallationDetails']; + }; + + 'Event.OnInstallationDetailsFail': { + id: 'on_installation_details_fail'; + error: components['defs']['AdaptyError']; + }; + + 'PaywallViewEvent.DidAppear': { + id: 'paywall_view_did_appear'; + view: components['defs']['AdaptyUI.PaywallView']; + }; + + 'PaywallViewEvent.DidDisappear': { + id: 'paywall_view_did_disappear'; + view: components['defs']['AdaptyUI.PaywallView']; + }; + + 'PaywallViewEvent.DidUserAction': { + id: 'paywall_view_did_perform_action'; + view: components['defs']['AdaptyUI.PaywallView']; + action: components['defs']['AdaptyUI.UserAction']; + }; + + 'PaywallViewEvent.DidSelectProduct': { + id: 'paywall_view_did_select_product'; + view: components['defs']['AdaptyUI.PaywallView']; + product_id: string; + }; + + 'PaywallViewEvent.WillPurchase': { + id: 'paywall_view_did_start_purchase'; + view: components['defs']['AdaptyUI.PaywallView']; + product: components['defs']['AdaptyPaywallProduct.Response']; + }; + + 'PaywallViewEvent.DidPurchase': { + id: 'paywall_view_did_finish_purchase'; + view: components['defs']['AdaptyUI.PaywallView']; + product: components['defs']['AdaptyPaywallProduct.Response']; + purchased_result: components['defs']['AdaptyPurchaseResult']; + }; + + 'PaywallViewEvent.DidFailPurchase': { + id: 'paywall_view_did_fail_purchase'; + view: components['defs']['AdaptyUI.PaywallView']; + product: components['defs']['AdaptyPaywallProduct.Response']; + error: components['defs']['AdaptyError']; + }; + + 'PaywallViewEvent.WillRestorePurchase': { + id: 'paywall_view_did_start_restore'; + view: components['defs']['AdaptyUI.PaywallView']; + }; + + 'PaywallViewEvent.DidRestorePurchase': { + id: 'paywall_view_did_finish_restore'; + view: components['defs']['AdaptyUI.PaywallView']; + profile: components['defs']['AdaptyProfile']; + }; + + 'PaywallViewEvent.DidFailRestorePurchase': { + id: 'paywall_view_did_fail_restore'; + view: components['defs']['AdaptyUI.PaywallView']; + error: components['defs']['AdaptyError']; + }; + + 'PaywallViewEvent.DidFailRendering': { + id: 'paywall_view_did_fail_rendering'; + view: components['defs']['AdaptyUI.PaywallView']; + error: components['defs']['AdaptyError']; + }; + + 'PaywallViewEvent.DidFailLoadingProducts': { + id: 'paywall_view_did_fail_loading_products'; + view: components['defs']['AdaptyUI.PaywallView']; + error: components['defs']['AdaptyError']; + }; + + 'PaywallViewEvent.DidFinishWebPaymentNavigation': { + id: 'paywall_view_did_finish_web_payment_navigation'; + view: components['defs']['AdaptyUI.PaywallView']; + product?: components['defs']['AdaptyPaywallProduct.Response']; + error?: components['defs']['AdaptyError']; + }; + + 'OnboardingViewEvent.DidFailWithError': { + id: 'onboarding_did_fail_with_error'; + view: components['defs']['AdaptyUI.OnboardingView']; + error: components['defs']['AdaptyError']; + }; + + 'OnboardingViewEvent.OnAnalyticsEvent': { + id: 'onboarding_on_analytics_action'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + event: { + name: + | 'onboarding_started' + | 'screen_presented' + | 'screen_completed' + | 'second_screen_presented' + | 'registration_screen_presented' + | 'products_screen_presented' + | 'user_email_collected' + | 'onboarding_completed' + | string; + element_id?: string; + reply?: string; + }; + }; + + 'OnboardingViewEvent.DidFinishLoading': { + id: 'onboarding_did_finish_loading'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + }; + + 'OnboardingViewEvent.OnCloseAction': { + id: 'onboarding_on_close_action'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + action_id: string; + }; + + 'OnboardingViewEvent.OnCustomAction': { + id: 'onboarding_on_custom_action'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + action_id: string; + }; + + 'OnboardingViewEvent.OnPaywallAction': { + id: 'onboarding_on_paywall_action'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + action_id: string; + }; + + 'OnboardingViewEvent.OnStateUpdatedAction': { + id: 'onboarding_on_state_updated_action'; + view: components['defs']['AdaptyUI.OnboardingView']; + meta: components['defs']['AdaptyUI.OnboardingMeta']; + action: + | { + element_id: string; + element_type: 'select'; + value: components['defs']['AdaptyUI.OnboardingsStateParams']; + } + | { + element_id: string; + element_type: 'multi_select'; + value: components['defs']['AdaptyUI.OnboardingsStateParams'][]; + } + | { + element_id: string; + element_type: 'input'; + value: + | { type: 'text' | 'email'; value: string } + | { type: 'number'; value: number }; + } + | { + element_id: string; + element_type: 'date_picker'; + value: { + day?: number; + month?: number; + year?: number; + }; + }; + }; + }; + defs: { + AdaptyError: { + adapty_code: number; + message: string; + detail?: string; + }; + + 'AdaptyLog.Level': 'error' | 'warn' | 'info' | 'verbose' | 'debug'; + + AdaptyLocale: string; + + Date: string; + + AdaptyConfiguration: { + api_key: string; + customer_user_id?: string; + customer_identity_parameters?: components['defs']['CustomerIdentityParameters']; + observer_mode?: boolean; + apple_idfa_collection_disabled?: boolean; + google_adid_collection_disabled?: boolean; + google_enable_pending_prepaid_plans?: boolean; + google_local_access_level_allowed?: boolean; + ip_address_collection_disabled?: boolean; + clear_data_on_backup?: boolean; + server_cluster?: 'default' | 'eu' | 'cn'; + backend_proxy_host?: string; + backend_proxy_port?: number; + log_level?: components['defs']['AdaptyLog.Level']; + cross_platform_sdk_name?: string; + cross_platform_sdk_version?: string; + activate_ui?: boolean; + media_cache?: { + memory_storage_total_cost_limit?: number; + memory_storage_count_limit?: number; + disk_storage_size_limit?: number; + }; + }; + + CustomerIdentityParameters: { + app_account_token?: string; + obfuscated_account_id?: string; + obfuscated_profile_id?: string; + }; + + 'AdaptyPaywallProduct.Request': { + vendor_product_id: string; + adapty_product_id: string; + access_level_id: string; + product_type: string; + paywall_product_index: number; + subscription_offer_identifier?: components['defs']['AdaptySubscriptionOffer.Identifier']; + paywall_variation_id: string; + paywall_ab_test_name: string; + paywall_name: string; + web_purchase_url?: string; + payload_data?: string; + }; + + 'AdaptyPaywallProduct.Response': { + vendor_product_id: string; + adapty_product_id: string; + access_level_id: string; + product_type: string; + paywall_product_index: number; + paywall_variation_id: string; + paywall_ab_test_name: string; + paywall_name: string; + web_purchase_url?: string; + localized_description: string; + localized_title: string; + is_family_shareable?: boolean; + region_code?: string; + price: components['defs']['AdaptyPrice']; + subscription?: components['defs']['AdaptyPaywallProduct.Subscription']; + payload_data?: string; + }; + + AdaptyPrice: { + amount: number; + currency_code?: string; + currency_symbol?: string; + localized_string?: string; + }; + + 'AdaptyPaywallProduct.Subscription': { + group_identifier?: string; + period: components['defs']['AdaptySubscriptionPeriod']; + localized_period?: string; + offer?: components['defs']['AdaptySubscriptionOffer']; + renewal_type?: 'prepaid' | 'autorenewable'; + base_plan_id?: string; + }; + + AdaptySubscriptionOffer: { + offer_identifier: components['defs']['AdaptySubscriptionOffer.Identifier']; + phases: components['defs']['AdaptySubscriptionOffer.Phase'][]; + offer_tags?: string[]; + }; + + 'AdaptySubscriptionOffer.Phase': { + price: components['defs']['AdaptyPrice']; + subscription_period: components['defs']['AdaptySubscriptionPeriod']; + number_of_periods: number; + payment_mode: components['defs']['AdaptySubscriptionOffer.PaymentMode']; + localized_subscription_period?: string; + localized_number_of_periods?: string; + }; + + AdaptyPlacement: { + developer_id: string; + ab_test_name: string; + audience_name: string; + revision: number; + is_tracking_purchases?: boolean; + placement_audience_version_id: string; + }; + + AdaptyPaywall: { + placement: components['defs']['AdaptyPlacement']; + paywall_id: string; + paywall_name: string; + variation_id: string; + products: components['defs']['AdaptyPaywall.ProductReference'][]; + response_created_at: number; + remote_config?: components['defs']['AdaptyRemoteConfig']; + paywall_builder?: components['defs']['AdaptyPaywall.ViewConfiguration']; + web_purchase_url?: string; + payload_data?: string; + request_locale: components['defs']['AdaptyLocale']; + }; + + AdaptyOnboarding: { + placement: components['defs']['AdaptyPlacement']; + onboarding_id: string; + onboarding_name: string; + variation_id: string; + response_created_at: number; + remote_config?: components['defs']['AdaptyRemoteConfig']; + onboarding_builder?: { + config_url: string; + }; + payload_data?: string; + request_locale: components['defs']['AdaptyLocale']; + }; + + AdaptyPlacementFetchPolicy: OneOf< + [ + { + type: + | 'reload_revalidating_cache_data' + | 'return_cache_data_else_load'; + }, + { + type: 'return_cache_data_if_not_expired_else_load'; + max_age: number; + }, + ] + >; + + AdaptyRemoteConfig: { + lang: components['defs']['AdaptyLocale']; + data: string; + }; + + 'AdaptyPaywall.ProductReference': { + vendor_product_id: string; + adapty_product_id: string; + access_level_id: string; + product_type: string; + promotional_offer_id?: string; + win_back_offer_id?: string; + base_plan_id?: string; + offer_id?: string; + }; + + 'AdaptyPaywall.ViewConfiguration': { + paywall_builder_id: string; + lang: components['defs']['AdaptyLocale']; + }; + + AdaptySubscriptionPeriod: { + unit: components['defs']['AdaptySubscriptionPeriod.Unit']; + number_of_units: number; + }; + + 'AdaptySubscriptionPeriod.Unit': + | 'day' + | 'week' + | 'month' + | 'year' + | 'unknown'; + + AdaptyProfile: { + profile_id: string; + customer_user_id?: string; + segment_hash: string; + is_test_user: boolean; + timestamp: number; + custom_attributes?: components['defs']['AdaptyProfile.CustomAttributes']; + paid_access_levels?: { + [key: string]: components['defs']['AdaptyProfile.AccessLevel']; + }; + subscriptions?: { + [key: string]: components['defs']['AdaptyProfile.Subscription']; + }; + non_subscriptions?: { + [key: string]: components['defs']['AdaptyProfile.NonSubscription'][]; + }; + }; + + 'AdaptyProfile.AccessLevel': { + id: string; + is_active: boolean; + vendor_product_id: string; + store: string; + activated_at: components['defs']['Date']; + renewed_at?: components['defs']['Date']; + expires_at?: components['defs']['Date']; + is_lifetime: boolean; + active_introductory_offer_type?: string; + active_promotional_offer_type?: string; + active_promotional_offer_id?: string; + offer_id?: string; + will_renew: boolean; + is_in_grace_period: boolean; + unsubscribed_at?: components['defs']['Date']; + billing_issue_detected_at?: components['defs']['Date']; + starts_at?: components['defs']['Date']; + cancellation_reason?: string; + is_refund: boolean; + }; + + 'AdaptyProfile.NonSubscription': { + purchase_id: string; + store: string; + vendor_product_id: string; + vendor_transaction_id?: string; + purchased_at: components['defs']['Date']; + is_sandbox: boolean; + is_refund: boolean; + is_consumable: boolean; + }; + + 'AdaptyProfile.Subscription': { + store: string; + vendor_product_id: string; + vendor_transaction_id: string; + vendor_original_transaction_id: string; + is_active: boolean; + is_lifetime: boolean; + activated_at: components['defs']['Date']; + renewed_at?: components['defs']['Date']; + expires_at?: components['defs']['Date']; + starts_at?: components['defs']['Date']; + unsubscribed_at?: components['defs']['Date']; + billing_issue_detected_at?: components['defs']['Date']; + is_in_grace_period: boolean; + is_refund: boolean; + is_sandbox: boolean; + will_renew: boolean; + active_introductory_offer_type?: string; + active_promotional_offer_type?: string; + active_promotional_offer_id?: string; + offer_id?: string; + cancellation_reason?: string; + }; + + 'AdaptyProfile.CustomAttributes': { + [key: string]: string | number; + }; + + 'AdaptyProfile.Gender': 'f' | 'm' | 'o'; + + AdaptyProfileParameters: { + first_name?: string; + last_name?: string; + gender?: components['defs']['AdaptyProfile.Gender']; + birthday?: string; + email?: string; + phone_number?: string; + att_status?: number; + custom_attributes?: components['defs']['AdaptyProfile.CustomAttributes']; + analytics_disabled?: boolean; + }; + + 'AdaptySubscriptionOffer.Identifier': OneOf< + [ + { + type: 'introductory'; + id?: string; + }, + { + type: 'promotional' | 'win_back'; + id: string; + }, + ] + >; + + 'AdaptySubscriptionOffer.PaymentMode': + | 'pay_as_you_go' + | 'pay_up_front' + | 'free_trial' + | 'unknown'; + + AdaptyPurchaseResult: OneOf< + [ + { + type: 'pending' | 'user_cancelled'; + }, + { + type: 'success'; + profile: components['defs']['AdaptyProfile']; + apple_jws_transaction?: string; + google_purchase_token?: string; + }, + ] + >; + + 'AdaptyUI.CustomAssets': ( + | components['assets']['Color'] + | components['assets']['ColorGradient'] + | components['assets']['Image'] + | components['assets']['Video'] + )[]; + + 'AdaptyUI.PaywallView': { + id: string; + placement_id: string; + variation_id: string; + }; + + 'AdaptyUI.OnboardingView': { + id: string; + placement_id: string; + variation_id: string; + }; + + 'AdaptyUI.OnboardingMeta': { + onboarding_id: string; + screen_cid: string; + screen_index: number; + total_screens: number; + }; + + 'AdaptyUI.OnboardingsStateParams': { + id: string; + value: string; + label: string; + }; + + 'AdaptyUI.UserAction': OneOf< + [ + { + type: 'close' | 'system_back'; + }, + { + type: 'open_url' | 'custom'; + value: string; + }, + ] + >; + + 'AdaptyUI.CustomTagsValues': { + [key: string]: string; + }; + + 'AdaptyUI.CustomTimersValues': { + [key: string]: components['defs']['Date']; + }; + + 'AdaptyUI.ProductPurchaseParameters': { + [key: string]: components['defs']['AdaptyPurchaseParameters']; + }; + + 'AdaptyUI.IOSPresentationStyle': 'full_screen' | 'page_sheet'; + + AdaptyWebPresentation: 'browser_out_app' | 'browser_in_app'; + + 'AdaptyUI.DialogConfiguration': { + default_action_title: string; + secondary_action_title?: string; + title?: string; + content?: string; + }; + + 'AdaptyUI.DialogActionType': 'primary' | 'secondary'; + + AdaptyPurchaseParameters: { + subscription_update_params?: components['defs']['AdaptySubscriptionUpdateParameters']; + is_offer_personalized?: boolean; + }; + + AdaptySubscriptionUpdateParameters: { + old_sub_vendor_product_id: string; + replacement_mode: + | 'charge_full_price' + | 'deferred' + | 'without_proration' + | 'charge_prorated_price' + | 'with_time_proration'; + }; + + AdaptyRefundPreference: 'no_preference' | 'grant' | 'decline'; + + AdaptyInstallationStatus: OneOf< + [ + { + status: 'not_available' | 'not_determined'; + }, + { + status: 'determined'; + details: components['defs']['AdaptyInstallationDetails']; + }, + ] + >; + + AdaptyInstallationDetails: { + install_id?: string; + install_time: components['defs']['Date']; + app_launch_count: number; + payload?: string; + }; + }; + assets: { + Color: { + id: string; + type: 'color'; + value: string; // Hex string: ^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$ + }; + + ColorGradient: { + id: string; + type: 'linear-gradient'; + values: { + color: string; + p: number; + }[]; + points: { + x0: number; + y0: number; + x1: number; + y1: number; + }; + }; + + Image: OneOf< + [ + { + id: string; + type: 'image'; + value: string; // base64 + }, + { + id: string; + type: 'image'; + asset_id: string; + }, + { + id: string; + type: 'image'; + path: string; + }, + ] + >; + + Video: OneOf< + [ + { + id: string; + type: 'video'; + asset_id: string; + }, + { + id: string; + type: 'video'; + path: string; + }, + ] + >; + }; +} + +export type $defs = Record; + +export type external = Record; + +export type operations = Record; diff --git a/src/types/bridge.ts b/src/types/bridge.ts new file mode 100644 index 0000000..21f0e0c --- /dev/null +++ b/src/types/bridge.ts @@ -0,0 +1,99 @@ +import type { EmitterSubscription } from 'react-native'; +import type { AdaptyProfile, AdaptyInstallationDetails } from '@/types'; +import type { AdaptyError } from '@/adapty-error'; + +/** + * Valid list of callable bridge handlers + * Must be the same as + * - iOS RNAConstants.MethodName + * @internal + */ +export const MethodNames = [ + 'activate', + 'adapty_ui_activate', + 'adapty_ui_create_paywall_view', + 'adapty_ui_dismiss_paywall_view', + 'adapty_ui_present_paywall_view', + 'adapty_ui_show_dialog', + 'adapty_ui_create_onboarding_view', + 'adapty_ui_dismiss_onboarding_view', + 'adapty_ui_present_onboarding_view', + 'create_web_paywall_url', + 'get_current_installation_status', + 'is_activated', + 'get_paywall', + 'get_paywall_for_default_audience', + 'get_paywall_products', + 'get_onboarding', + 'get_onboarding_for_default_audience', + 'get_profile', + 'identify', + 'log_show_paywall', + 'logout', + 'make_purchase', + 'open_web_paywall', + 'present_code_redemption_sheet', + 'report_transaction', + 'restore_purchases', + 'set_fallback', + 'set_integration_identifiers', + 'set_log_level', + 'update_attribution_data', + 'update_collecting_refund_data_consent', + 'update_profile', + 'update_refund_preference', +] as const; +export type MethodName = (typeof MethodNames)[number]; + +/** + * Types of values that can be passed + * to the bridge without corruption + */ +export type Serializable = + | string + | number + | boolean + | string[] + | null + | undefined; + +/** + * Interface of error that emit from native SDK + */ +export interface AdaptyNativeError { + adaptyCode: number; + message: string; + detail?: string | undefined; +} + +/** + * Interface of error that was raised by native bridge + */ +export interface AdaptyBridgeError { + errorType: string; + name?: string; + type?: string; + underlyingError?: string; + description?: string; +} + +interface EventMap { + onLatestProfileLoad: string; + onInstallationDetailsSuccess: string; + onInstallationDetailsFail: string; +} + +export type UserEventName = keyof EventMap; + +export type AddListenerGeneric = ( + event: E, + callback: (data: Data) => void | Promise, +) => EmitterSubscription; + +export type AddListenerFn = + | AddListenerGeneric<'onLatestProfileLoad', AdaptyProfile> + | AddListenerGeneric< + 'onInstallationDetailsSuccess', + AdaptyInstallationDetails + > + | AddListenerGeneric<'onInstallationDetailsFail', AdaptyError>; diff --git a/src/types/error.ts b/src/types/error.ts new file mode 100644 index 0000000..559bb9c --- /dev/null +++ b/src/types/error.ts @@ -0,0 +1,152 @@ +// import SDK type to link to methods in docs. +// import { AdaptyError } from '../sdk2/error'; + +export const ErrorCode = Object.freeze({ + /** + * System StoreKit codes + */ + 0: 'unknown', + /** + * Client is not allowed to make a request, etc. + */ + 1: 'clientInvalid', + /** + * Invalid purchase identifier, etc. + */ + 3: 'paymentInvalid', + /** + * This device is not allowed to make the payment. + */ + 4: 'paymentNotAllowed', + /** + * Product is not available in the current storefront. + */ + 5: 'storeProductNotAvailable', + + /** + * User has not allowed access to cloud service information. + */ + 6: 'cloudServicePermissionDenied', + + /** + * The device could not connect to the network. + */ + 7: 'cloudServiceNetworkConnectionFailed', + + /** + * User has revoked permission to use this cloud service. + */ + 8: 'cloudServiceRevoked', + + /** + * The user needs to acknowledge Apple's privacy policy. + */ + 9: 'privacyAcknowledgementRequired', + + /** + * The app is attempting to use SKPayment's requestData property, + * but does not have the appropriate entitlement. + */ + 10: 'unauthorizedRequestData', + + /** + * The specified subscription offer identifier is not valid. + */ + 11: 'invalidOfferIdentifier', + + /** + * The cryptographic signature provided is not valid. + */ + 12: 'invalidSignature', + + /** + * One or more parameters from SKPaymentDiscount is missing. + */ + 13: 'missingOfferParams', + + 14: 'invalidOfferPrice', + + /** + * Custom Android codes. + */ + 20: 'adaptyNotInitialized', + 22: 'productNotFound', + 24: 'currentSubscriptionToUpdateNotFoundInHistory', + 97: 'billingServiceTimeout', + 98: 'featureNotSupported', + 99: 'billingServiceDisconnected', + 102: 'billingServiceUnavailable', + 103: 'billingUnavailable', + 105: 'developerError', + 106: 'billingError', + 107: 'itemAlreadyOwned', + 108: 'itemNotOwned', + 112: 'billingNetworkError', + + 1000: 'noProductIDsFound', + 1002: 'productRequestFailed', + + /** + * In-App Purchases are not allowed on this device. + */ + 1003: 'cantMakePayments', + 1004: 'noPurchasesToRestore', + 1005: 'cantReadReceipt', + 1006: 'productPurchaseFailed', + 1010: 'refreshReceiptFailed', + 1011: 'receiveRestoredTransactionsFailed', + + /** + * You need to be authenticated to perform requests. + */ + 2002: 'notActivated', + 2003: 'badRequest', + 2004: 'serverError', + 2005: 'networkFailed', + 2006: 'decodingFailed', + 2009: 'encodingFailed', + + 3000: 'analyticsDisabled', + /** + * Wrong parameter was passed. + */ + 3001: 'wrongParam', + /** + * It is not possible to call `.activate` method more than once. + */ + 3005: 'activateOnceError', + /** + * The user profile was changed during the operation. + */ + 3006: 'profileWasChanged', + 3007: 'unsupportedData', + 3100: 'persistingDataError', + 3101: 'fetchTimeoutError', + 9000: 'operationInterrupted', +}); +export type ErrorCode = keyof typeof ErrorCode; + +export function getErrorCode( + error: (typeof ErrorCode)[ErrorCode], +): ErrorCode | undefined { + const errorCode = Object.keys(ErrorCode).find(keyStr => { + const key = Number(keyStr) as ErrorCode; + return ErrorCode[key] === error; + }); + + if (!errorCode) { + return undefined; + } + + return Number(errorCode) as ErrorCode; +} + +export function getErrorPrompt(code: ErrorCode): (typeof ErrorCode)[ErrorCode] { + const prompt = ErrorCode[code]; + + if (!prompt) { + return `Unknown code: ${code}` as (typeof ErrorCode)[ErrorCode]; + } + + return prompt; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..21be3a4 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,847 @@ +// import SDK type to link to methods in docs. +import type { Adapty } from '@/adapty-handler'; + +export const VendorStore = Object.freeze({ + AppStore: 'app_store', + PlayStore: 'play_store', + Adapty: 'adapty', +}); +export type VendorStore = (typeof VendorStore)[keyof typeof VendorStore]; + +export const OfferType = Object.freeze({ + FreeTrial: 'free_trial', + PayAsYouGo: 'pay_as_you_go', + PayUpFront: 'pay_up_front', +}); +export type OfferType = (typeof OfferType)[keyof typeof OfferType]; + +export const CancellationReason = Object.freeze({ + VolountarilyCancelled: 'voluntarily_cancelled', + BillingError: 'billing_error', + Refund: 'refund', + PriceIncrease: 'price_increase', + ProductWasNotAvailable: 'product_was_not_available', + Unknown: 'unknown', +}); +export type CancellationReason = + (typeof CancellationReason)[keyof typeof CancellationReason]; + +export const Gender = Object.freeze({ + Female: 'f', + Male: 'm', + Other: 'o', +}); +export type Gender = (typeof Gender)[keyof typeof Gender]; + +export const AppTrackingTransparencyStatus = Object.freeze({ + NotDetermined: 0, + Restricted: 1, + Denied: 2, + Authorized: 3, + Unknown: 4, +}); +export type AppTrackingTransparencyStatus = + (typeof AppTrackingTransparencyStatus)[keyof typeof AppTrackingTransparencyStatus]; + +export const ProductPeriod = Object.freeze({ + Day: 'day', + Week: 'week', + Month: 'month', + Year: 'year', +}); +export type ProductPeriod = (typeof ProductPeriod)[keyof typeof ProductPeriod]; + +export interface AdaptyPrice { + /** + * Price as number + */ + amount: number; + /** + * The currency code of the locale + * used to format the price of the product. + * The ISO 4217 (USD, EUR). + */ + currencyCode?: string; + /** + * The currency symbol of the locale + * used to format the price of the product. + * ($, €). + */ + currencySymbol?: string; + /** + * A price’s language is determined + * by the preferred language set on the device. + * On Android, the formatted price from Google Play as is. + */ + localizedString?: string; +} + +/** + * Describes an object that represents a paywall. + * Used in {@link Adapty.getPaywall} method. + * @public + */ +export interface AdaptyPaywall { + readonly placement: AdaptyPlacement; + + /** + * If `true`, it is possible to fetch the view object + * and use it with AdaptyUI library. + * @readonly + */ + readonly hasViewConfiguration: boolean; + + /** + * A paywall name. + * @readonly + */ + readonly name: string; + /** + * A remote config configured in Adapty Dashboard for this paywall. + * @readonly + */ + readonly remoteConfig?: AdaptyRemoteConfig; + /** + * An identifier of a variation, + * used to attribute purchases to this paywall. + * @readonly + */ + readonly variationId: string; + /** + * Array of initial products info + * @readonly + * @deprecated Use {@link AdaptyPaywall.productIdentifiers} instead + */ + readonly products: ProductReference[]; + + /** + * Array of product identifiers for this paywall + * @readonly + */ + readonly productIdentifiers: AdaptyProductIdentifier[]; + + id: string; + version?: number; + webPurchaseUrl?: string; + payloadData?: string; + requestLocale: string; + paywallBuilder?: AdaptyPaywallBuilder; +} + +export interface AdaptyOnboarding { + readonly placement: AdaptyPlacement; + /** + * If `true`, it is possible to fetch the view object + * and use it with AdaptyUI library. + * @readonly + */ + readonly hasViewConfiguration: boolean; + + /** + * A paywall name. + * @readonly + */ + readonly name: string; + /** + * A remote config configured in Adapty Dashboard for this paywall. + * @readonly + */ + readonly remoteConfig?: AdaptyRemoteConfig; + /** + * An identifier of a variation, + * used to attribute purchases to this paywall. + * @readonly + */ + readonly variationId: string; + + id: string; + version?: number; + payloadData?: string; + requestLocale: string; + onboardingBuilder?: AdaptyOnboardingBuilder; +} + +export interface AdaptyPlacement { + /** + * Parent A/B test name. + * @readonly + */ + readonly abTestName: string; + + /** + * A name of an audience to which the paywall belongs. + * @readonly + */ + readonly audienceName: string; + /** + * ID of a placement configured in Adapty Dashboard. + * @readonly + */ + readonly id: string; + /** + * Current revision (version) of a paywall. + * Every change within a paywall creates a new revision. + * @readonly + */ + readonly revision: number; + + isTrackingPurchases?: boolean; + audienceVersionId: string; +} + +/** + * Describes an object that represents a remote config of a paywall. + * @public + */ +export interface AdaptyRemoteConfig { + /** + * Identifier of a paywall locale. + * @readonly + */ + readonly lang: string; + /** + * A custom dictionary configured in Adapty Dashboard for this paywall. + * @readonly + */ + readonly data: Record; + /** + * A custom JSON string configured in Adapty Dashboard for this paywall. + * @readonly + */ + readonly dataString: string; +} + +export interface AdaptyPaywallBuilder { + readonly id: string; + readonly lang: string; +} + +export interface AdaptyOnboardingBuilder { + readonly url: string; +} + +export type AdaptyPurchaseResult = + | { + type: 'pending' | 'user_cancelled'; + } + | { + type: 'success'; + profile: AdaptyProfile; + ios?: { + jwsTransaction?: string; + }; + android?: { + purchaseToken?: string; + }; + }; + +/** + * Interface representing a user profile in Adapty, + * including details about the user's subscriptions and consumable products. + * @public + */ +export interface AdaptyProfile { + /** + * Object that maps access level identifiers (configured by you in Adapty Dashboard) + * to the corresponding access level details. The value can be `null` + * if the user does not have any access levels. + * @readonly + */ + readonly accessLevels?: Record; + + /** + * Object representing custom attributes set for the user using + * the {@link Adapty.updateProfile} method. + * @readonly + */ + readonly customAttributes?: Record; + + /** + * The identifier for a user in your system. + * @readonly + */ + readonly customerUserId?: string; + + /** + * Object that maps product ids from the store to an array of + * information about the user's non-subscription purchases. + * The value can be `null` if the user does not have any purchases. + * @readonly + */ + readonly nonSubscriptions?: Record; + + /** + * The identifier for a user in Adapty. + */ + readonly profileId: string; + + /** + * Object that maps product ids from a store to + * information about the user's subscriptions. + * The value can be `null` if the user does not have any subscriptions. + * @readonly + */ + readonly subscriptions?: Record; +} + +/** + * Interface representing access level details of a user. + * @public + */ +export interface AdaptyAccessLevel { + /** + * The date and time when the access level was activated. + * @readonly + */ + readonly activatedAt: Date; + + /** + * Type of active introductory offer, if any. + * @readonly + */ + readonly activeIntroductoryOfferType?: OfferType; + + /** + * Identifier of the active promotional offer, if any. + * @readonly + */ + readonly activePromotionalOfferId?: string; + + /** + * Type of the active promotional offer, if any. + * @readonly + */ + readonly activePromotionalOfferType?: OfferType; + + /** + * The date and time when a billing issue was detected. + * @readonly + */ + readonly billingIssueDetectedAt?: Date; + + /** + * The reason for the cancellation of the subscription. + * @readonly + */ + readonly cancellationReason?: CancellationReason; + + /** + * The expiration date of the access level, if applicable. + * @readonly + */ + readonly expiresAt?: Date; + + /** + * Unique identifier of the access level + * configured by you in Adapty Dashboard. + * @readonly + */ + readonly id: string; + + /** + * Flag indicating whether the access level is currently active. + * @readonly + */ + readonly isActive: boolean; + + /** + * Flag indicating whether this auto-renewable subscription is in the grace period. + * @readonly + */ + readonly isInGracePeriod: boolean; + + /** + * Flag indicating whether this access level is active for a lifetime. + * @readonly + */ + readonly isLifetime: boolean; + + /** + * Flag indicating whether this purchase was refunded. + * @readonly + */ + readonly isRefund: boolean; + + /** + * The date and time when the access level was renewed. + * @readonly + */ + readonly renewedAt?: Date; + + /** + * The start date of this access level. + * @readonly + */ + readonly startsAt?: Date; + + /** + * The store where the purchase that unlocked this access level was made. + * @readonly + */ + readonly store: VendorStore; + + /** + * The date and time when the auto-renewable subscription was cancelled. + * @readonly + */ + readonly unsubscribedAt?: Date; + + /** + * The identifier of the product in the store that unlocked this access level. + * @readonly + */ + readonly vendorProductId: string; + + /** + * Flag indicating whether this auto-renewable subscription is set to renew. + * @readonly + */ + readonly willRenew: boolean; + + android?: { + offerId?: string; + }; +} + +/** + * Interface representing a consumable or non-subscription purchase made by the user. + * @public + */ +export interface AdaptyNonSubscription { + /** + * Flag indicating whether the product is consumable. + * @readonly + */ + readonly isConsumable: boolean; + + /** + * Flag indicating whether the purchase was refunded. + * @readonly + */ + readonly isRefund: boolean; + + /** + * Flag indicating whether the product was purchased in a sandbox environment. + * @readonly + */ + readonly isSandbox: boolean; + + /** + * The date and time when the purchase was made. + * @readonly + */ + readonly purchasedAt: Date; + + /** + * The identifier of the product in the store that was purchased. + * @readonly + */ + readonly vendorProductId: string; + /** + * The identifier of the product in the store that was purchased. + * @readonly + */ + readonly vendorTransactionId?: string; + + /** + * The store where the purchase was made. + * @readonly + */ + readonly store: VendorStore; + + /** + * An identifier of the purchase in Adapty. + * You can use it to ensure that you've already processed this purchase + * (for example tracking one time products). + * @readonly + */ + readonly purchaseId: string; +} + +/** + * Interface representing details about a user's subscription. + * @public + */ +export interface AdaptySubscription { + /** + * The date and time when the subscription was activated. + * @readonly + */ + readonly activatedAt: Date; + + /** + * Type of active introductory offer, if any. + * @readonly + */ + readonly activeIntroductoryOfferType?: OfferType; + + /** + * Identifier of the active promotional offer, if any. + * @readonly + */ + readonly activePromotionalOfferId?: string; + + /** + * Type of the active promotional offer, if any. + * @readonly + */ + readonly activePromotionalOfferType?: OfferType; + + /** + * The date and time when a billing issue was detected. + * @readonly + */ + readonly billingIssueDetectedAt?: Date; + + /** + * The reason for the cancellation of the subscription. + * @readonly + */ + readonly cancellationReason?: CancellationReason; + + /** + * The expiration date of the subscription, if applicable. + * @readonly + */ + readonly expiresAt?: Date; + + /** + * Flag indicating whether the subscription is currently active. + * @readonly + */ + readonly isActive: boolean; + + /** + * Flag indicating whether the subscription is in the grace period. + * @readonly + */ + readonly isInGracePeriod: boolean; + + /** + * Flag indicating whether the subscription is set for a lifetime. + * @readonly + */ + readonly isLifetime: boolean; + + /** + * Flag indicating whether the subscription was refunded. + * @readonly + */ + readonly isRefund: boolean; + + /** + * Flag indicating whether the subscription was purchased in a sandbox environment. + * @readonly + */ + readonly isSandbox: boolean; + + /** + * The date and time when the subscription was renewed. + * @readonly + */ + readonly renewedAt?: Date; + + /** + * The date and time when the subscription starts. + * @readonly + */ + readonly startsAt?: Date; + + /** + * The store where the subscription was made. + * @readonly + */ + readonly store: VendorStore; + + /** + * The date and time when the subscription was cancelled. + * @readonly + */ + readonly unsubscribedAt?: Date; + + /** + * The identifier of the product in the store that was subscribed to. + * @readonly + */ + readonly vendorProductId: string; + + /** + * The identifier of the product in the store that was subscribed to. + * @readonly + */ + readonly vendorTransactionId: string; + /** + * An original transaction id of the purchase in a store that unlocked this subscription. + * For auto-renewable subscription, this will be an id of the first transaction in this subscription. + * @readonly + */ + readonly vendorOriginalTransactionId: string; + /** + * Flag indicating whether the subscription is set to auto-renew. + * @readonly + */ + readonly willRenew: boolean; +} + +/** + * Describes an object that represents a product. + * Used in {@link Adapty.getPaywallProducts} method and in {@link Adapty.makePurchase} method. + * @public + */ +export interface AdaptyPaywallProduct { + /** + * A description of the product. + */ + readonly localizedDescription: string; + /** + * The region code of the locale used to format the price of the product. + * ISO 3166 ALPHA-2 (US, DE) + */ + readonly regionCode?: string; + /** + * The name of the product. + */ + readonly localizedTitle: string; + /** + * Same as `abTestName` property of the parent {@link AdaptyPaywall}. + */ + readonly paywallABTestName: string; + /** + * Same as `name` property of the parent {@link AdaptyPaywall}. + */ + readonly paywallName: string; + readonly accessLevelId: string; + readonly productType: string; + /** + * The cost of the product in the local currency + */ + readonly price?: AdaptyPrice; + readonly adaptyId: string; + /** + * Same as `variationId` property of the parent {@link AdaptyPaywall}. + */ + readonly variationId: string; + /** + * Unique identifier of a product + * from App Store Connect or Google Play Console + */ + readonly vendorProductId: string; + paywallProductIndex: number; + webPurchaseUrl?: string; + payloadData?: string; + subscription?: AdaptySubscriptionDetails; + ios?: { + /** + * Boolean value that indicates + * whether the product is available for family sharing + * in App Store Connect. + * Will be `false` for iOS version below 14.0 and macOS version below 11.0. + * @see {@link https://developer.apple.com/documentation/storekit/skproduct/3564805-isfamilyshareable} + */ + readonly isFamilyShareable: boolean; + }; +} + +export interface AdaptySubscriptionDetails { + /** + * The period details for products that are subscriptions. + * Will be `null` for iOS version below 11.2 and macOS version below 10.14.4. + */ + subscriptionPeriod: AdaptySubscriptionPeriod; + /** + * The period’s language is determined + * by the preferred language set on the device. + */ + localizedSubscriptionPeriod?: string; + /** + * A subscription offer if available for the auto-renewable subscription. + */ + offer?: AdaptySubscriptionOffer; + + ios?: { + /** + * An identifier of the subscription group + * to which the subscription belongs. + * Will be `null` for iOS version below 12.0 and macOS version below 10.14. + */ + subscriptionGroupIdentifier?: string; + }; + + android?: { + basePlanId: string; + renewalType?: 'prepaid' | 'autorenewable'; + }; +} + +/** + * Subscription offer model to products + * @see {@link https://doc.adapty.io/docs/rn-api-reference#adaptysubscriptionoffer} + */ +export interface AdaptySubscriptionOffer { + readonly identifier: AdaptySubscriptionOfferId; + + phases: AdaptyDiscountPhase[]; + + android?: { + offerTags?: string[]; + }; +} + +export type AdaptySubscriptionOfferId = + | { id?: string; type: 'introductory' } + | { id: string; type: 'promotional' | 'win_back' }; + +/** + * Discount model to products + * @see {@link https://doc.adapty.io/docs/rn-api-reference#adaptyproductdiscount} + */ +export interface AdaptyDiscountPhase { + /** + * A formatted number of periods of a discount for a user’s locale. + * @readonly + */ + readonly localizedNumberOfPeriods?: string; + /** + * A formatted subscription period of a discount for a user’s locale. + * @readonly + */ + readonly localizedSubscriptionPeriod?: string; + /** + * A number of periods this product discount is available. + * @readonly + */ + readonly numberOfPeriods: number; + /** + * Discount price of a product in a local currency. + * @readonly + */ + readonly price: AdaptyPrice; + /** + * An information about period for a product discount. + * @readonly + */ + readonly subscriptionPeriod: AdaptySubscriptionPeriod; + + /** + * A payment mode for this product discount. + * @readonly + */ + readonly paymentMode: OfferType; +} + +/** + * An object containing information about a subscription period. + * @public + */ +export interface AdaptySubscriptionPeriod { + /** + * A number of period units. + * @readonly + */ + readonly numberOfUnits: number; + /** + * A unit of time that a subscription period is specified in. + * @readonly + */ + readonly unit: ProductPeriod; +} + +export interface AdaptyProfileParameters { + analyticsDisabled?: boolean; + codableCustomAttributes?: { [key: string]: any }; + appTrackingTransparencyStatus?: AppTrackingTransparencyStatus; + firstName?: string; + lastName?: string; + gender?: Gender; + birthday?: string; + email?: string; + phoneNumber?: string; +} + +export interface ProductReference { + vendorId: string; + adaptyId: string; + accessLevelId: string; + productType: string; + + ios?: { + promotionalOfferId?: string; + winBackOfferId?: string; + }; + + android?: { + basePlanId?: string; + offerId?: string; + }; +} + +/** + * Interface representing a product identifier with vendor and platform-specific information. + * @public + */ +export interface AdaptyProductIdentifier { + /** + * The vendor-specific product identifier (e.g., App Store or Google Play product ID). + * @readonly + */ + readonly vendorProductId: string; + + /** + * The base plan identifier for Google Play subscriptions (Android only). + * @readonly + */ + readonly basePlanId?: string; + + /** + * Internal Adapty product identifier. + * @readonly + */ + readonly adaptyProductId: string; +} + +export const WebPresentation = Object.freeze({ + BrowserOutApp: 'browser_out_app', + BrowserInApp: 'browser_in_app', +}); +export type WebPresentation = + (typeof WebPresentation)[keyof typeof WebPresentation]; + +export const RefundPreference = Object.freeze({ + NoPreference: 'no_preference', + Grant: 'grant', + Decline: 'decline', +}); +export type RefundPreference = + (typeof RefundPreference)[keyof typeof RefundPreference]; + +export type AdaptyInstallationStatus = + | { + status: 'not_available' | 'not_determined'; + } + | { + status: 'determined'; + details: AdaptyInstallationDetails; + }; + +export interface AdaptyInstallationDetails { + /** + * A unique identifier for this installation. + * @readonly + */ + readonly installId?: string; + /** + * The date and time when the app was installed. + * @readonly + */ + readonly installTime: Date; + /** + * The total number of times the app has been launched. + * @readonly + */ + readonly appLaunchCount: number; + /** + * Custom payload data associated with the installation. + * @readonly + */ + readonly payload?: string; +} diff --git a/src/types/inputs.ts b/src/types/inputs.ts new file mode 100644 index 0000000..eb1aad2 --- /dev/null +++ b/src/types/inputs.ts @@ -0,0 +1,310 @@ +import { AdaptyUiMediaCache } from '@/ui/types'; +import type { AdaptyMockConfig } from '@/mock/types'; +/** + * Log levels for the SDK + * + * @remarks + * Logging is performed on a native side. + * So you are expected to watch logs in Xcode or Android Studio. + */ +export const LogLevel = Object.freeze({ + /** + * Logs any additional information that may be useful during debugging, + * such as function calls, API queries, etc. + */ + VERBOSE: 'verbose', + /** + * Logs only errors + */ + ERROR: 'error', + /** + * Logs messages from the SDK + * that do not cause critical errors, + * but are worth paying attention to + */ + WARN: 'warn', + /** + * Logs various information messages, + * such as those that log the lifecycle of various modules + */ + INFO: 'info', +}); + +export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; + +export const FetchPolicy = Object.freeze({ + ReloadRevalidatingCacheData: 'reload_revalidating_cache_data', + ReturnCacheDataElseLoad: 'return_cache_data_else_load', + ReturnCacheDataIfNotExpiredElseLoad: + 'return_cache_data_if_not_expired_else_load', +}); +export type FetchPolicy = (typeof FetchPolicy)[keyof typeof FetchPolicy]; + +export type GetPlacementParamsInput = + GetPlacementForDefaultAudienceParamsInput & { + /** + * This value limits the timeout (in milliseconds) for this method. + * + * @remarks + * If the timeout is reached, cached data or local fallback will be returned. + */ + loadTimeoutMs?: number; + }; + +export type GetPlacementForDefaultAudienceParamsInput = + | { + /** + * Fetch policy + * + * @remarks + * By default SDK will try to load data from server and will return cached data in case of failure. + * Otherwise use `'return_cache_data_else_load'` to return cached data if it exists. + */ + fetchPolicy?: Exclude< + FetchPolicy, + 'return_cache_data_if_not_expired_else_load' + >; + } + | { + /** + * Fetch policy + * + * @remarks + * By default SDK will try to load data from server and will return cached data in case of failure. + * Otherwise use `'return_cache_data_else_load'` to return cached data if it exists. + */ + fetchPolicy: Extract< + FetchPolicy, + 'return_cache_data_if_not_expired_else_load' + >; + /** + * Max age for cached data. + * + * @remarks + * Max time (in seconds) the cache is valid in case of `'return_cache_data_if_not_expired_else_load'` fetch policy. + */ + maxAgeSeconds: number; + }; + +/** + * Describes optional parameters for the {@link activate} method. + */ +export interface ActivateParamsInput { + /** + * Turn it on if you handle purchases and subscription status yourself + * and use Adapty for sending subscription events and analytics + * + * @defaultValue `false` + */ + observerMode?: boolean; + /** + * User identifier in your system + * + * @remarks + * If none of the parameters are passed, the SDK will generate an ID + * and use it for a current device. + * Use your own ID: + * 1. If you want to support a cross-device experience + * 2. If you have your own authentication system, + * and you want to associate adapty profile with your user + */ + customerUserId?: string; + /** + * Log level for the SDK + * + * @remarks + * Logging is performed on a native side. + * So you are expected to watch logs in Xcode or Android Studio. + */ + logLevel?: LogLevel; + + serverCluster?: 'default' | 'eu' | 'cn'; + backendProxyHost?: string; + backendProxyPort?: number; + activateUi?: boolean; + mediaCache?: AdaptyUiMediaCache; + + /** + * Locks methods threads until the SDK is ready. + * @defaultValue `false` + * @deprecated Turned on by default + */ + lockMethodsUntilReady?: boolean; + /** + * Does not activate SDK until any other method is called + * Fixes annoying iOS simulator auhtentication + */ + __debugDeferActivation?: boolean; + /** + * Ignores multiple activation attempts on fast refresh. + * If true, skips activation if SDK is already activated. + */ + __ignoreActivationOnFastRefresh?: boolean; + /** + * Enables mock mode for web platform + * @remarks + * When enabled, SDK will use mock implementations instead of native modules. + * Useful for Expo Web and testing. + * @defaultValue `false` + */ + enableMock?: boolean; + /** + * Configuration for mock mode + * @remarks + * Allows customization of mock data returned by the SDK. + */ + mockConfig?: AdaptyMockConfig; + /** + * Disables IP address collection + * @defaultValue `false` + */ + ipAddressCollectionDisabled?: boolean; + ios?: { + /** + * Disables IDFA collection + * @default false + */ + idfaCollectionDisabled?: boolean; + appAccountToken?: string; + /** + * Controls whether the SDK will create a new profile when the app is restored from an iCloud backup + * @defaultValue `false` + */ + clearDataOnBackup?: boolean; + }; + android?: { + /** + * Disables Google AdvertisingID collection + * @default false + */ + adIdCollectionDisabled?: boolean; + pendingPrepaidPlansEnabled?: boolean; + localAccessLevelAllowed?: boolean; + obfuscatedAccountId?: string; + }; +} + +export interface IdentifyParamsInput { + ios?: { + appAccountToken?: string; + }; + android?: { + obfuscatedAccountId?: string; + }; +} + +export interface GetPaywallProductsParamsInput {} + +export const AdaptyAndroidSubscriptionUpdateReplacementMode = Object.freeze({ + ChargeFullPrice: 'charge_full_price', + Deferred: 'deferred', + WithoutProration: 'without_proration', + ChargeProratedPrice: 'charge_prorated_price', + WithTimeProration: 'with_time_proration', +}); +// satisfies Record< +// string, +// Schema['Input.AdaptyAndroidSubscriptionUpdateParameters']['replacement_mode'] +// >; + +export type AdaptyAndroidSubscriptionUpdateReplacementMode = + (typeof AdaptyAndroidSubscriptionUpdateReplacementMode)[keyof typeof AdaptyAndroidSubscriptionUpdateReplacementMode]; + +/** + * Android purchase parameters structure + * @public + */ +export interface AdaptyAndroidPurchaseParams { + /** + * Android subscription update parameters + * @platform android + */ + subscriptionUpdateParams?: { + oldSubVendorProductId: string; + prorationMode: AdaptyAndroidSubscriptionUpdateReplacementMode; + }; + /** + * Whether the offer is personalized + * @platform android + * @see {@link https://developer.android.com/google/play/billing/integrate#personalized-price} + */ + isOfferPersonalized?: boolean; +} + +export interface AdaptyAndroidSubscriptionUpdateParameters { + oldSubVendorProductId: string; + prorationMode: AdaptyAndroidSubscriptionUpdateReplacementMode; + /** + * @deprecated Use {@link AdaptyAndroidPurchaseParams.isOfferPersonalized} instead. + * This field has been moved to the upper level in the new structure. + * + * @example + * // OLD (deprecated): + * android: { + * oldSubVendorProductId: 'old_product_id', + * prorationMode: 'charge_prorated_price', + * isOfferPersonalized: true // This field is deprecated + * } + * + * // NEW: + * android: { + * subscriptionUpdateParams: { + * oldSubVendorProductId: 'old_product_id', + * prorationMode: 'charge_prorated_price' + * }, + * isOfferPersonalized: true // Moved to upper level + * } + */ + isOfferPersonalized?: boolean; +} + +export type MakePurchaseParamsInput = + | { + /** + * Android purchase parameters + * @platform android + */ + android?: AdaptyAndroidPurchaseParams; + } + | { + /** + * @deprecated Use the new parameter structure instead + * + * @example + * // OLD (deprecated): + * makePurchase(product, { + * android: { + * oldSubVendorProductId: 'old_product_id', + * prorationMode: 'charge_prorated_price', + * isOfferPersonalized: true + * } + * }); + * + * // NEW: + * makePurchase(product, { + * android: { + * subscriptionUpdateParams: { + * oldSubVendorProductId: 'old_product_id', + * prorationMode: 'charge_prorated_price' + * }, + * isOfferPersonalized: true, // Note: moved to upper level + * } + * }); + * + * @platform android + */ + android?: AdaptyAndroidSubscriptionUpdateParameters; + }; + +export type FileLocation = { + ios: { + fileName: string; + }; + android: + | { + relativeAssetPath: string; + } + | { + rawResName: string; + }; +}; diff --git a/src/types/onboarding-events.ts b/src/types/onboarding-events.ts new file mode 100644 index 0000000..623c2d6 --- /dev/null +++ b/src/types/onboarding-events.ts @@ -0,0 +1,82 @@ +import { AdaptyError } from '@/adapty-error'; +import type { + AdaptyUiOnboardingMeta, + OnboardingStateUpdatedAction, +} from '@/ui/types'; + +// Onboarding Event IDs +export const OnboardingEventId = { + Close: 'onboarding_on_close_action', + Custom: 'onboarding_on_custom_action', + Paywall: 'onboarding_on_paywall_action', + StateUpdated: 'onboarding_on_state_updated_action', + FinishedLoading: 'onboarding_did_finish_loading', + Analytics: 'onboarding_on_analytics_action', + Error: 'onboarding_did_fail_with_error', +} as const; + +export type OnboardingEventIdType = + (typeof OnboardingEventId)[keyof typeof OnboardingEventId]; + +// Event View +export interface OnboardingEventView { + id: string; + placementId?: string; + variationId?: string; +} + +// Base Event +interface BaseOnboardingEvent { + id: OnboardingEventIdType; + view: OnboardingEventView; +} + +// Event Types +export interface OnboardingCloseEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.Close; + actionId: string; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingCustomEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.Custom; + actionId: string; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingPaywallEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.Paywall; + actionId: string; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingStateUpdatedEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.StateUpdated; + action: OnboardingStateUpdatedAction; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingFinishedLoadingEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.FinishedLoading; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingAnalyticsEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.Analytics; + event: { name: string; elementId?: string; reply?: string }; + meta: AdaptyUiOnboardingMeta; +} + +export interface OnboardingErrorEvent extends BaseOnboardingEvent { + id: typeof OnboardingEventId.Error; + error: AdaptyError; +} + +export type ParsedOnboardingEvent = + | OnboardingCloseEvent + | OnboardingCustomEvent + | OnboardingPaywallEvent + | OnboardingStateUpdatedEvent + | OnboardingFinishedLoadingEvent + | OnboardingAnalyticsEvent + | OnboardingErrorEvent; diff --git a/src/types/paywall-events.ts b/src/types/paywall-events.ts new file mode 100644 index 0000000..5790c2d --- /dev/null +++ b/src/types/paywall-events.ts @@ -0,0 +1,125 @@ +import { AdaptyError } from '@/adapty-error'; +import type { + AdaptyPaywallProduct, + AdaptyProfile, + AdaptyPurchaseResult, +} from '@/types'; + +// Paywall Event IDs +export const PaywallEventId = { + DidAppear: 'paywall_view_did_appear', + DidDisappear: 'paywall_view_did_disappear', + DidPerformAction: 'paywall_view_did_perform_action', + DidSelectProduct: 'paywall_view_did_select_product', + DidStartPurchase: 'paywall_view_did_start_purchase', + DidFinishPurchase: 'paywall_view_did_finish_purchase', + DidFailPurchase: 'paywall_view_did_fail_purchase', + DidStartRestore: 'paywall_view_did_start_restore', + DidFinishRestore: 'paywall_view_did_finish_restore', + DidFailRestore: 'paywall_view_did_fail_restore', + DidFailRendering: 'paywall_view_did_fail_rendering', + DidFailLoadingProducts: 'paywall_view_did_fail_loading_products', + DidFinishWebPaymentNavigation: + 'paywall_view_did_finish_web_payment_navigation', +} as const; + +export type PaywallEventIdType = + (typeof PaywallEventId)[keyof typeof PaywallEventId]; + +// Event View +export interface PaywallEventView { + id: string; + placementId?: string; + variationId?: string; +} + +// Base Event +interface BasePaywallEvent { + id: PaywallEventIdType; + view: PaywallEventView; +} + +// Event Types +export interface PaywallDidAppearEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidAppear; +} + +export interface PaywallDidDisappearEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidDisappear; +} + +export interface PaywallDidPerformActionEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidPerformAction; + action: { + type: 'close' | 'system_back' | 'open_url' | 'custom'; + value?: string; // for open_url and custom + }; +} + +export interface PaywallDidSelectProductEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidSelectProduct; + productId: string; +} + +export interface PaywallDidStartPurchaseEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidStartPurchase; + product: AdaptyPaywallProduct; +} + +export interface PaywallDidFinishPurchaseEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFinishPurchase; + purchaseResult: AdaptyPurchaseResult; + product: AdaptyPaywallProduct; +} + +export interface PaywallDidFailPurchaseEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFailPurchase; + error: AdaptyError; + product: AdaptyPaywallProduct; +} + +export interface PaywallDidStartRestoreEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidStartRestore; +} + +export interface PaywallDidFinishRestoreEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFinishRestore; + profile: AdaptyProfile; +} + +export interface PaywallDidFailRestoreEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFailRestore; + error: AdaptyError; +} + +export interface PaywallDidFailRenderingEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFailRendering; + error: AdaptyError; +} + +export interface PaywallDidFailLoadingProductsEvent extends BasePaywallEvent { + id: typeof PaywallEventId.DidFailLoadingProducts; + error: AdaptyError; +} + +export interface PaywallDidFinishWebPaymentNavigationEvent + extends BasePaywallEvent { + id: typeof PaywallEventId.DidFinishWebPaymentNavigation; + product?: AdaptyPaywallProduct; + error?: AdaptyError; +} + +export type ParsedPaywallEvent = + | PaywallDidAppearEvent + | PaywallDidDisappearEvent + | PaywallDidPerformActionEvent + | PaywallDidSelectProductEvent + | PaywallDidStartPurchaseEvent + | PaywallDidFinishPurchaseEvent + | PaywallDidFailPurchaseEvent + | PaywallDidStartRestoreEvent + | PaywallDidFinishRestoreEvent + | PaywallDidFailRestoreEvent + | PaywallDidFailRenderingEvent + | PaywallDidFailLoadingProductsEvent + | PaywallDidFinishWebPaymentNavigationEvent; diff --git a/src/types/schema.d.ts b/src/types/schema.d.ts new file mode 100644 index 0000000..fc06de3 --- /dev/null +++ b/src/types/schema.d.ts @@ -0,0 +1,4 @@ +import type { components } from './api'; +export type Req = components['requests']; +export type Def = components['defs']; +export type Event = components['events']; From 996a05261fee4716c6812f635edcc6fab9b79145 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 29 Jan 2026 15:30:07 +0300 Subject: [PATCH 09/50] fix: add import resolver --- jest.config.cjs | 6 ++++++ tsconfig.json | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/jest.config.cjs b/jest.config.cjs index 3ba007c..bc0d05f 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -1,7 +1,13 @@ +const { pathsToModuleNameMapper } = require('ts-jest'); + module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['/src'], + moduleNameMapper: pathsToModuleNameMapper( + { '@/*': ['*'] }, + { prefix: '/src/' }, + ), testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'], collectCoverageFrom: [ 'src/**/*.ts', diff --git a/tsconfig.json b/tsconfig.json index bb2c2ec..e02699d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,10 @@ /* Modules */ "module": "ESNext", "moduleResolution": "bundler", + "baseUrl": "./src", + "paths": { + "@/*": ["./*"] + }, "resolveJsonModule": true, /* Emit */ From d6faaf0a7c894b8b3297378b6bf55402ce8d0d9e Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 29 Jan 2026 15:43:54 +0300 Subject: [PATCH 10/50] feat: add mock/replace import to fix ts errors --- src/adapty-error.ts | 73 ++++ src/adapty-handler.ts | 9 + src/coders/adapty-configuration.test.ts | 8 +- src/coders/adapty-configuration.ts | 2 +- src/coders/adapty-identify-params.test.ts | 42 +-- src/coders/adapty-identify-params.ts | 2 +- src/coders/adapty-purchase-params.test.ts | 8 +- src/coders/adapty-purchase-params.ts | 2 +- src/coders/adapty-purchase-result.test.ts | 20 +- src/coders/adapty-purchase-result.ts | 2 +- ...apty-ui-create-paywall-view-params.test.ts | 2 +- .../adapty-ui-create-paywall-view-params.ts | 2 +- src/coders/coder.ts | 2 +- src/logger/index.ts | 60 +++ src/mock/types.ts | 10 + src/platform.ts | 63 ++++ src/types/bridge.ts | 2 +- src/ui/types.ts | 354 ++++++++++++++++++ src/version.ts | 7 + 19 files changed, 623 insertions(+), 47 deletions(-) create mode 100644 src/adapty-error.ts create mode 100644 src/adapty-handler.ts create mode 100644 src/logger/index.ts create mode 100644 src/mock/types.ts create mode 100644 src/platform.ts create mode 100644 src/ui/types.ts create mode 100644 src/version.ts diff --git a/src/adapty-error.ts b/src/adapty-error.ts new file mode 100644 index 0000000..2588cbd --- /dev/null +++ b/src/adapty-error.ts @@ -0,0 +1,73 @@ +import { ErrorCode } from '@/types/error'; + +export interface AdaptyErrorInput { + adaptyCode: ErrorCode; + message: string; + detail?: string | undefined; +} + +export class AdaptyError extends Error { + // Custom prefix to be shown before log message + public static prefix = ''; + static middleware?: (error: AdaptyError) => void; + + // For example `2` for cancelled payment + public adaptyCode: ErrorCode; + // Message that is safe to show to a user + public localizedDescription: string; + public detail: string | undefined; + + constructor(input: AdaptyErrorInput) { + super(AdaptyError.getMessage(input)); + + this.adaptyCode = input.adaptyCode; + this.localizedDescription = input.message; + this.detail = input.detail; + + if (AdaptyError.middleware) { + AdaptyError.middleware(this); + } + } + + public static set onError(callback: (error: AdaptyError) => void) { + AdaptyError.middleware = callback; + } + + public static failedToDecodeNativeError( + message: string, + error: unknown, + ): AdaptyError { + return new AdaptyError({ + adaptyCode: 0, + message: message, + detail: JSON.stringify(error), + }); + } + + public static failedToEncode(message: string): AdaptyError { + return new AdaptyError({ + adaptyCode: 2009, + message: message, + }); + } + + public static failedToDecode(message: string): AdaptyError { + return new AdaptyError({ + adaptyCode: 2006, + message: message, + }); + } + + private static getMessage(input: AdaptyErrorInput): string { + const code = input.adaptyCode; + const codeText = ErrorCode[code]; + + let message = `#${code} (${codeText}): ${input.message}`; + + if (AdaptyError.prefix) { + message = `${AdaptyError.prefix} ${message}`; + } + + return message; + } +} diff --git a/src/adapty-handler.ts b/src/adapty-handler.ts new file mode 100644 index 0000000..3364baf --- /dev/null +++ b/src/adapty-handler.ts @@ -0,0 +1,9 @@ +/** + * Adapty handler type placeholder + * This is used for documentation links only + */ + +export interface Adapty { + // Placeholder interface for documentation + // Actual implementation provided by platform SDK +} diff --git a/src/coders/adapty-configuration.test.ts b/src/coders/adapty-configuration.test.ts index 63f2327..01f6e3b 100644 --- a/src/coders/adapty-configuration.test.ts +++ b/src/coders/adapty-configuration.test.ts @@ -2,7 +2,7 @@ import { AdaptyConfigurationCoder } from '@/coders/adapty-configuration'; import { LogLevel } from '@/types/inputs'; import version from '@/version'; -jest.mock('react-native', () => ({ +jest.mock('@/platform', () => ({ Platform: { OS: 'ios', }, @@ -87,8 +87,8 @@ describe('AdaptyConfigurationCoder', () => { }); it('should encode full configuration with all parameters on android', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'android' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'android' }; const params = { customerUserId: 'user123', observerMode: true, @@ -141,7 +141,7 @@ describe('AdaptyConfigurationCoder', () => { }, }); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should handle partial parameters', () => { diff --git a/src/coders/adapty-configuration.ts b/src/coders/adapty-configuration.ts index 9a39987..ea836dd 100644 --- a/src/coders/adapty-configuration.ts +++ b/src/coders/adapty-configuration.ts @@ -1,6 +1,6 @@ import * as Input from '@/types/inputs'; import type { Def } from '@/types/schema'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; import { AdaptyUiMediaCacheCoder } from '@/coders/adapty-ui-media-cache'; import version from '@/version'; diff --git a/src/coders/adapty-identify-params.test.ts b/src/coders/adapty-identify-params.test.ts index 8da9716..80bd996 100644 --- a/src/coders/adapty-identify-params.test.ts +++ b/src/coders/adapty-identify-params.test.ts @@ -14,8 +14,8 @@ describe('AdaptyIdentifyParamsCoder', () => { }); it('should encode iOS app account token on iOS platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'ios' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'ios' }; const params = { ios: { @@ -28,12 +28,12 @@ describe('AdaptyIdentifyParamsCoder', () => { app_account_token: 'ios-token-123', }); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should encode Android obfuscated account ID on Android platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'android' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'android' }; const params = { android: { @@ -46,12 +46,12 @@ describe('AdaptyIdentifyParamsCoder', () => { obfuscated_account_id: 'android-id-456', }); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should only encode iOS parameters when on iOS platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'ios' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'ios' }; const params = { ios: { @@ -67,12 +67,12 @@ describe('AdaptyIdentifyParamsCoder', () => { app_account_token: 'ios-token-123', }); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should only encode Android parameters when on Android platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'android' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'android' }; const params = { ios: { @@ -88,12 +88,12 @@ describe('AdaptyIdentifyParamsCoder', () => { obfuscated_account_id: 'android-id-456', }); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should ignore iOS parameters on Android platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'android' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'android' }; const params = { ios: { @@ -104,12 +104,12 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should ignore Android parameters on iOS platform', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'ios' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'ios' }; const params = { android: { @@ -120,12 +120,12 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it('should handle empty platform objects', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'ios' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'ios' }; const params = { ios: {}, @@ -135,6 +135,6 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); }); diff --git a/src/coders/adapty-identify-params.ts b/src/coders/adapty-identify-params.ts index 8cfdf7b..3213a7f 100644 --- a/src/coders/adapty-identify-params.ts +++ b/src/coders/adapty-identify-params.ts @@ -1,5 +1,5 @@ import * as Input from '@/types/inputs'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; type Model = Input.IdentifyParamsInput; type Serializable = { diff --git a/src/coders/adapty-purchase-params.test.ts b/src/coders/adapty-purchase-params.test.ts index bb18fc9..1a669f0 100644 --- a/src/coders/adapty-purchase-params.test.ts +++ b/src/coders/adapty-purchase-params.test.ts @@ -1,7 +1,7 @@ import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; import * as Input from '@/types/inputs'; -jest.mock('react-native', () => ({ +jest.mock('@/platform', () => ({ Platform: { OS: 'android', }, @@ -67,8 +67,8 @@ describe('AdaptyPurchaseParamsCoder', () => { }); it('should return empty object for non-Android platforms', () => { - const originalPlatform = require('react-native').Platform; - require('react-native').Platform = { OS: 'ios' }; + const originalPlatform = require('@/platform').Platform; + require('@/platform').Platform = { OS: 'ios' }; const params: Input.MakePurchaseParamsInput = { android: { @@ -85,7 +85,7 @@ describe('AdaptyPurchaseParamsCoder', () => { const result = coder.encode(params); expect(result).toEqual({}); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); }); }); diff --git a/src/coders/adapty-purchase-params.ts b/src/coders/adapty-purchase-params.ts index c36b318..8b82e6c 100644 --- a/src/coders/adapty-purchase-params.ts +++ b/src/coders/adapty-purchase-params.ts @@ -1,5 +1,5 @@ import * as Input from '@/types/inputs'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; type Model = Input.MakePurchaseParamsInput; type Serializable = Record; diff --git a/src/coders/adapty-purchase-result.test.ts b/src/coders/adapty-purchase-result.test.ts index f5a9610..2649283 100644 --- a/src/coders/adapty-purchase-result.test.ts +++ b/src/coders/adapty-purchase-result.test.ts @@ -2,7 +2,7 @@ import type { AdaptyPurchaseResult } from '@/types'; import type { Def } from '@/types/schema'; import { AdaptyPurchaseResultCoder } from '@/coders/adapty-purchase-result'; import { AdaptyProfileCoder } from '@/coders/adapty-profile'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; type Model = AdaptyPurchaseResult; type TestAdaptyPurchaseResultDef = @@ -223,7 +223,7 @@ function toModel(mock: (typeof mocks)[number]): Model { } } -jest.mock('react-native', () => ({ +jest.mock('@/platform', () => ({ Platform: { OS: 'ios', }, @@ -237,28 +237,28 @@ describe('AdaptyPurchaseResultCoder', () => { }); it.each(mocks)('should decode to expected result', mock => { - const originalPlatform = require('react-native').Platform; + const originalPlatform = require('@/platform').Platform; if (mock.apple_jws_transaction) { - require('react-native').Platform = { OS: 'ios' }; + require('@/platform').Platform = { OS: 'ios' }; } if (mock.google_purchase_token) { - require('react-native').Platform = { OS: 'android' }; + require('@/platform').Platform = { OS: 'android' }; } const decoded = coder.decode(mock as any); expect(decoded).toStrictEqual(toModel(mock)); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); it.each(mocks)('should decode/encode', mock => { - const originalPlatform = require('react-native').Platform; + const originalPlatform = require('@/platform').Platform; if (mock.apple_jws_transaction) { - require('react-native').Platform = { OS: 'ios' }; + require('@/platform').Platform = { OS: 'ios' }; } if (mock.google_purchase_token) { - require('react-native').Platform = { OS: 'android' }; + require('@/platform').Platform = { OS: 'android' }; } const decoded = coder.decode(mock as any); @@ -266,6 +266,6 @@ describe('AdaptyPurchaseResultCoder', () => { expect(encoded).toStrictEqual(mock); - require('react-native').Platform = originalPlatform; + require('@/platform').Platform = originalPlatform; }); }); diff --git a/src/coders/adapty-purchase-result.ts b/src/coders/adapty-purchase-result.ts index ce54376..c8f5c9c 100644 --- a/src/coders/adapty-purchase-result.ts +++ b/src/coders/adapty-purchase-result.ts @@ -3,7 +3,7 @@ import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; import { AdaptyProfileCoder } from '@/coders/adapty-profile'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; type Model = AdaptyPurchaseResult; type Serializable = Def['AdaptyPurchaseResult']; diff --git a/src/coders/adapty-ui-create-paywall-view-params.test.ts b/src/coders/adapty-ui-create-paywall-view-params.test.ts index fb034ed..0f525fa 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.test.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.test.ts @@ -1,5 +1,5 @@ import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; import type { CreatePaywallViewParamsInput } from '@/ui/types'; import type { AdaptyProductIdentifier } from '@/types'; diff --git a/src/coders/adapty-ui-create-paywall-view-params.ts b/src/coders/adapty-ui-create-paywall-view-params.ts index 09caecb..32439d1 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.ts @@ -1,4 +1,4 @@ -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; import type { CreatePaywallViewParamsInput, AdaptyCustomAsset, diff --git a/src/coders/coder.ts b/src/coders/coder.ts index e870550..5f569c6 100644 --- a/src/coders/coder.ts +++ b/src/coders/coder.ts @@ -1,6 +1,6 @@ import { AdaptyError } from '@/adapty-error'; import { Converter, Properties, StrType } from './types'; -import { Platform } from 'react-native'; +import { Platform } from '@/platform'; export abstract class Coder< Model extends Record, diff --git a/src/logger/index.ts b/src/logger/index.ts new file mode 100644 index 0000000..e799b58 --- /dev/null +++ b/src/logger/index.ts @@ -0,0 +1,60 @@ +/** + * Logger placeholder for platform-agnostic code + * Actual logging implementation should be provided by the platform SDK + */ + +export interface ScopeArgs { + methodName: string; +} + +interface Trace { + action: string; + fn: string; + payload: Record; + error?: boolean; + done?: boolean; +} + +/** + * LogScope provides methods for logging at different stages of execution + */ +export class LogScope { + start(_payload?: Record): void { + // Noop - actual implementation provided by platform SDK + } + + failed(_message?: string): void { + // Noop - actual implementation provided by platform SDK + } + + success(_payload?: Record): void { + // Noop - actual implementation provided by platform SDK + } +} + +/** + * LogContext accumulates logs for each step of a call + */ +export class LogContext { + public stack: Trace[] = []; + + decode(_args: ScopeArgs): LogScope { + return new LogScope(); + } + + encode(_args: ScopeArgs): LogScope { + return new LogScope(); + } + + call(_args: ScopeArgs): LogScope { + return new LogScope(); + } + + bridge(_args: ScopeArgs): LogScope { + return new LogScope(); + } + + event(_args: ScopeArgs): LogScope { + return new LogScope(); + } +} diff --git a/src/mock/types.ts b/src/mock/types.ts new file mode 100644 index 0000000..ca4ec9a --- /dev/null +++ b/src/mock/types.ts @@ -0,0 +1,10 @@ +/** + * Mock types placeholder for platform-agnostic code + * Full implementation should be provided by the platform SDK + */ + +export interface AdaptyMockConfig { + // Mock configuration interface + // This is a placeholder for type compatibility + [key: string]: unknown; +} diff --git a/src/platform.ts b/src/platform.ts new file mode 100644 index 0000000..208eca3 --- /dev/null +++ b/src/platform.ts @@ -0,0 +1,63 @@ +/** + * Platform detection and utilities for cross-platform code + * This is a mock replacement for react-native Platform + */ + +export type PlatformOS = 'ios' | 'android' | 'web' | 'unknown'; + +export interface PlatformSelectSpec { + ios?: T; + android?: T; + native?: T; + default?: T; + web?: T; +} + +export class Platform { + private static _os: PlatformOS = 'unknown'; + + static get OS(): PlatformOS { + return Platform._os; + } + + static set OS(value: PlatformOS) { + Platform._os = value; + } + + static select(spec: PlatformSelectSpec): T | undefined { + const platform = Platform.OS; + + // First, try to find exact platform match + if (platform in spec) { + return spec[platform as keyof typeof spec]; + } + + // For native platforms (ios/android), try native fallback + if (platform === 'ios' || platform === 'android') { + if ('native' in spec) { + return spec.native; + } + } + + // Try default if available + if ('default' in spec) { + return spec.default; + } + + // If OS is unknown, prefer ios over android as fallback + if (platform === 'unknown') { + if ('ios' in spec) return spec.ios; + if ('android' in spec) return spec.android; + if ('native' in spec) return spec.native; + } + + return undefined; + } +} + +/** + * EmitterSubscription mock for type compatibility + */ +export interface EmitterSubscription { + remove(): void; +} diff --git a/src/types/bridge.ts b/src/types/bridge.ts index 21f0e0c..f546f12 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -1,4 +1,4 @@ -import type { EmitterSubscription } from 'react-native'; +import type { EmitterSubscription } from '@/platform'; import type { AdaptyProfile, AdaptyInstallationDetails } from '@/types'; import type { AdaptyError } from '@/adapty-error'; diff --git a/src/ui/types.ts b/src/ui/types.ts new file mode 100644 index 0000000..90c2e52 --- /dev/null +++ b/src/ui/types.ts @@ -0,0 +1,354 @@ +import { AdaptyError } from '@/adapty-error'; +import { + AdaptyPaywallProduct, + AdaptyProductIdentifier, + AdaptyProfile, + AdaptyPurchaseResult, + WebPresentation, +} from '@/types'; +import { FileLocation, MakePurchaseParamsInput } from '@/types/inputs'; + +/** + * @internal + */ +export type ArgType = T extends () => any + ? void + : T extends (arg: infer U) => any + ? U + : void; + +/** + * EventHandler callback should not return a promise, + * because using `await` may postpone closing a paywall view. + * + * We don't want to block the UI thread. + */ +export type EventHandlerResult = boolean | void; + +/** + * Purchase parameters keyed by AdaptyProductIdentifier objects + */ +export type ProductPurchaseParams = Array<{ + productId: AdaptyProductIdentifier; + params: MakePurchaseParamsInput; +}>; + +/** + * Hashmap of possible events to their callbacks + * + * @see {@link https://adapty.io/docs/react-native-handling-events-1 | [DOC] Handling View Events} + */ +export interface EventHandlers { + /** + * Called when a user taps the close button on the paywall view + * + * If you return `true`, the paywall view will be closed. + * We strongly recommend to return `true` in this case. + * @default true + */ + onCloseButtonPress: () => EventHandlerResult; + /** + * Called when a user navigates back on Android + * + * If you return `true`, the paywall view will be closed. + * We strongly recommend to return `true` in this case. + * @default true + */ + onAndroidSystemBack: () => EventHandlerResult; + /** + * Called when a user taps the product in the paywall view + * + * If you return `true` from this callback, the paywall view will be closed. + */ + onProductSelected: (productId: string) => EventHandlerResult; + /** + * Called when a user taps the purchase button in the paywall view + * + * If you return `true` from this callback, the paywall view will be closed. + */ + onPurchaseStarted: (product: AdaptyPaywallProduct) => EventHandlerResult; + /** + * Called when the purchase succeeds, the user cancels their purchase, or the purchase appears to be pending + * + * If you return `true` from this callback, the paywall view will be closed. + * We strongly recommend returning `purchaseResult.type !== 'user_cancelled'` in this case. + * @default `purchaseResult.type !== 'user_cancelled'` + * + * @param {AdaptyPurchaseResult} purchaseResult - object, which provides details about the purchase. + * If the result is `'success'`, it also includes the updated user's profile. + */ + onPurchaseCompleted: ( + purchaseResult: AdaptyPurchaseResult, + product: AdaptyPaywallProduct, + ) => EventHandlerResult; + /** + * Called if a purchase fails after a user taps the purchase button + * + * If you return `true` from this callback, the paywall view will be closed. + * + * @param {AdaptyError} error - AdaptyError object with error code and message + */ + onPurchaseFailed: ( + error: AdaptyError, + product: AdaptyPaywallProduct, + ) => EventHandlerResult; + /** + * Called when a user taps the restore button in the paywall view + * + * If you return `true` from this callback, the paywall view will be closed. + */ + onRestoreStarted: () => EventHandlerResult; + + onPaywallClosed: () => EventHandlerResult; + + onPaywallShown: () => EventHandlerResult; + + onWebPaymentNavigationFinished: ( + product?: AdaptyPaywallProduct, + error?: AdaptyError, + ) => EventHandlerResult; + /** + * Called when a purchase is completed + * + * If you return `true` from this callback, the paywall view will be closed. + * We strongly recommend to return `true` in this case. + * @default true + * + * @param {AdaptyProfile} profile - updated user profile + */ + onRestoreCompleted: (profile: AdaptyProfile) => EventHandlerResult; + /** + * Called if a restore fails after a user taps the restore button + * + * If you return `true` from this callback, the paywall view will be closed. + * + * @param {AdaptyError} error - AdaptyError object with error code and message + */ + onRestoreFailed: (error: AdaptyError) => EventHandlerResult; + /** + * Called if a paywall view fails to render. + * This should not ever happen, but if it does, feel free to report it to us. + * + * If you return `true` from this callback, the paywall view will be closed. + * + * @param {AdaptyError} error - AdaptyError object with error code and message + */ + onRenderingFailed: (error: AdaptyError) => EventHandlerResult; + /** + * Called if a product list fails to load on a presented view, + * for example, if there is no internet connection + * + * If you return `true` from this callback, the paywall view will be closed. + * + * @param {AdaptyError} error - AdaptyError object with error code and message + */ + onLoadingProductsFailed: (error: AdaptyError) => EventHandlerResult; + onCustomAction: (actionId: string) => EventHandlerResult; + onUrlPress: (url: string) => EventHandlerResult; +} + +export interface OnboardingEventHandlers { + onClose: ( + actionId: string, + meta: AdaptyUiOnboardingMeta, + ) => EventHandlerResult; + onCustom: ( + actionId: string, + meta: AdaptyUiOnboardingMeta, + ) => EventHandlerResult; + onPaywall: ( + actionId: string, + meta: AdaptyUiOnboardingMeta, + ) => EventHandlerResult; + onStateUpdated: ( + action: OnboardingStateUpdatedAction, + meta: AdaptyUiOnboardingMeta, + ) => EventHandlerResult; + onFinishedLoading: (meta: AdaptyUiOnboardingMeta) => EventHandlerResult; + onAnalytics: ( + event: { + name: OnboardingAnalyticsEventName; + /** + * @deprecated Use `elementId` instead + */ + element_id?: string; + elementId?: string; + reply?: string; + }, + meta: AdaptyUiOnboardingMeta, + ) => EventHandlerResult; + onError: (error: AdaptyError) => EventHandlerResult; +} + +export type OnboardingAnalyticsEventName = + | 'onboarding_started' + | 'screen_presented' + | 'screen_completed' + | 'second_screen_presented' + | 'registration_screen_presented' + | 'products_screen_presented' + | 'user_email_collected' + | 'onboarding_completed' + | (string & {}); + +export type AdaptyUiOnboardingMeta = { + onboardingId: string; + screenClientId: string; + screenIndex: number; + totalScreens: number; +}; + +export type AdaptyUiOnboardingStateParams = { + id: string; + value: string; + label: string; +}; + +export type OnboardingStateUpdatedAction = + | { + elementId: string; + elementType: 'select'; + value: AdaptyUiOnboardingStateParams; + } + | { + elementId: string; + elementType: 'multi_select'; + value: AdaptyUiOnboardingStateParams[]; + } + | { + elementId: string; + elementType: 'input'; + value: + | { type: 'text' | 'email'; value: string } + | { type: 'number'; value: number }; + } + | { + elementId: string; + elementType: 'date_picker'; + value: { + day?: number; + month?: number; + year?: number; + }; + }; + +/** + * Additional options for creating a paywall view + * + * @see {@link https://docs.adapty.io/docs/paywall-builder-fetching | [DOC] Creating Paywall View} + */ +export interface CreatePaywallViewParamsInput { + /** + * `true` if you want to prefetch products before presenting a paywall view. + */ + prefetchProducts?: boolean; + /** + * This value limits the timeout (in milliseconds) for this method. + */ + loadTimeoutMs?: number; + /** + * If you are going to use custom tags functionality, pass an object with tags and corresponding replacement values + * + * ``` + * { + * 'USERNAME': 'Bruce', + * 'CITY': 'Philadelphia' + * } + * ``` + */ + customTags?: Record; + /** + * If you are going to use custom timer functionality, pass an object with timer ids and corresponding dates the timers should end at + */ + customTimers?: Record; + + customAssets?: Record; + + productPurchaseParams?: ProductPurchaseParams; +} + +/** + * Additional options for creating an onboarding view + * + * @see {@link https://adapty.io/docs/react-native-get-onboardings | [DOC] Creating Onboarding View} + */ +export interface CreateOnboardingViewParamsInput { + /** + * If you want to change the presentation behavior of external URLs, pass a preferred value. + * + * @default {@link WebPresentation.BrowserInApp} + */ + externalUrlsPresentation?: WebPresentation; +} + +export interface AdaptyUiView { + id: string; +} + +export interface AdaptyUiMediaCache { + memoryStorageTotalCostLimit?: number; + memoryStorageCountLimit?: number; + diskStorageSizeLimit?: number; +} + +export interface AdaptyUiDialogConfig { + /** + * The action title to display as part of the dialog. If you provide two actions, + * be sure `primaryAction` cancels the operation and leaves things unchanged. + */ + primaryActionTitle: string; + /** + * The secondary action title to display as part of the dialog. + */ + secondaryActionTitle?: string; + /** + * The title of the dialog. + */ + title?: string; + /** + * Descriptive text that provides additional details about the reason for the dialog. + */ + content?: string; +} + +export const AdaptyUiDialogActionType = Object.freeze({ + primary: 'primary', + secondary: 'secondary', +}); +export type AdaptyUiDialogActionType = + (typeof AdaptyUiDialogActionType)[keyof typeof AdaptyUiDialogActionType]; + +export type AdaptyCustomAsset = + | AdaptyCustomImageAsset + | AdaptyCustomVideoAsset + | AdaptyCustomColorAsset + | AdaptyCustomGradientAsset; + +export type AdaptyCustomImageAsset = + | { type: 'image'; base64: string } + | { type: 'image'; relativeAssetPath: string } // shorthand: uses same path for both iOS fileName and Android relativeAssetPath + | { type: 'image'; fileLocation: FileLocation }; // full control for platform-specific paths + +export type AdaptyCustomVideoAsset = + | { type: 'video'; relativeAssetPath: string } // shorthand: uses same path for both iOS fileName and Android relativeAssetPath + | { type: 'video'; fileLocation: FileLocation }; // full control for platform-specific paths + +export type AdaptyCustomColorAsset = + | { type: 'color'; argb: number /* e.g. 0xFFFF0000 (opaque red) */ } + | { type: 'color'; rgb: number /* e.g. 0xFF0000 (red) */ } + | { type: 'color'; rgba: number /* e.g. 0xFF0000FF (opaque red) */ }; + +export type AdaptyCustomGradientAsset = { + type: 'linear-gradient'; + values: ( + | { p: number; argb: number } + | { p: number; rgb: number } + | { p: number; rgba: number } + )[]; + points?: { x0?: number; y0?: number; x1?: number; y1?: number }; +}; + +/** + * iOS presentation style for paywall and onboarding views + */ +export type AdaptyIOSPresentationStyle = 'full_screen' | 'page_sheet'; diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..838484c --- /dev/null +++ b/src/version.ts @@ -0,0 +1,7 @@ +/** + * SDK version placeholder + * This will be replaced by the actual version during build + */ +const version = '0.0.0-dev.0000000000000000000000000000000000000000'; + +export default version; From 8e9bc070aa0d681399bdcdf9c99060c2dc6d2612 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 29 Jan 2026 18:32:20 +0300 Subject: [PATCH 11/50] feat: add DI --- AGENTS.md | 30 ++++ src/adapters/defaults.ts | 32 ++++ src/adapters/interfaces.ts | 69 ++++++++ src/coders/adapty-configuration.test.ts | 27 +-- src/coders/adapty-configuration.ts | 19 ++- src/coders/adapty-identify-params.test.ts | 41 ++--- src/coders/adapty-identify-params.ts | 8 +- src/coders/adapty-purchase-params.test.ts | 16 +- src/coders/adapty-purchase-params.ts | 6 +- src/coders/adapty-purchase-result.test.ts | 67 ++++---- src/coders/adapty-purchase-result.ts | 17 +- ...apty-ui-create-paywall-view-params.test.ts | 158 +++++++----------- .../adapty-ui-create-paywall-view-params.ts | 8 +- src/coders/coder.ts | 10 +- src/coders/factory.ts | 49 ++++++ src/coders/utils.ts | 30 ++-- src/index.ts | 20 +++ src/logger/index.ts | 47 +++--- src/platform.ts | 40 +---- 19 files changed, 405 insertions(+), 289 deletions(-) create mode 100644 src/adapters/defaults.ts create mode 100644 src/adapters/interfaces.ts create mode 100644 src/coders/factory.ts diff --git a/AGENTS.md b/AGENTS.md index b8adbbc..c185dd5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,6 +61,36 @@ Key compiler options: **Module:** ESNext **Module Resolution:** bundler +## Architecture: Platform DI + +This repo uses platform dependency injection to keep `@adapty/core` platform-agnostic. + +### Platform Adapters + +- **Interface:** `src/adapters/interfaces.ts` +- **Core rule:** Coders must depend on adapters, not on platform globals. +- **OS property:** use `Platform.OS` (capitalized) on `IPlatformAdapter`. +- **No `select`:** platform-specific selection should be done using `Platform.OS`, not `Platform.select`. + +### Default Adapters + +- **Defaults:** `src/adapters/defaults.ts` +- **DefaultPlatformAdapter** proxies to `src/platform.ts` (test helper). +- **DefaultLoggerAdapter** is a noop logger for core usage. + +### Coder Factory + +- **Factory:** `src/coders/factory.ts` +- Use `CoderFactory` to create coders with injected dependencies: + - `IPlatformAdapter` for platform OS + - `ISdkMetadataAdapter` for sdkName/sdkVersion + - `ILoggerAdapter` for logging + +### Testing Guidance + +- Tests should create coders with mock adapters, not mutate `Platform` globals. +- Prefer inline adapters: `{ OS: 'ios' }` / `{ OS: 'android' }`. + ## Build Output Built with `tsdown` (powered by Rolldown). Outputs multiple formats: diff --git a/src/adapters/defaults.ts b/src/adapters/defaults.ts new file mode 100644 index 0000000..b36ad21 --- /dev/null +++ b/src/adapters/defaults.ts @@ -0,0 +1,32 @@ +import type { + IPlatformAdapter, + ISdkMetadataAdapter, + PlatformOS, +} from './interfaces'; +import { Platform } from '@/platform'; +import { NoopLoggerAdapter } from '@/logger'; + +/** + * Default platform adapter with unknown OS + */ +export class DefaultPlatformAdapter implements IPlatformAdapter { + get OS(): PlatformOS { + return Platform.OS; + } + + set OS(value: PlatformOS) { + Platform.OS = value; + } +} + +/** + * Default SDK metadata adapter + */ +export class DefaultSdkMetadataAdapter implements ISdkMetadataAdapter { + constructor( + public readonly sdkName: string = 'unknown', + public readonly sdkVersion: string = '0.0.0', + ) {} +} + +export { NoopLoggerAdapter as DefaultLoggerAdapter }; diff --git a/src/adapters/interfaces.ts b/src/adapters/interfaces.ts new file mode 100644 index 0000000..4edc0dd --- /dev/null +++ b/src/adapters/interfaces.ts @@ -0,0 +1,69 @@ +export type PlatformOS = 'ios' | 'android' | 'web' | 'unknown'; + +/** + * Platform adapter interface for OS-specific behavior + */ +export interface IPlatformAdapter { + /** + * Current platform OS + */ + readonly OS: PlatformOS; +} + +/** + * SDK metadata adapter for version and SDK name + */ +export interface ISdkMetadataAdapter { + /** + * SDK name (e.g., 'react-native', 'capacitor') + */ + readonly sdkName: string; + /** + * SDK version string + */ + readonly sdkVersion: string; +} + +/** + * Logger adapter interface for platform-specific logging + */ +export interface ILoggerAdapter { + /** + * Create logging context for method execution + */ + createContext(): ILogContext; +} + +export interface ScopeArgs { + methodName: string; +} + +export interface LogTrace { + action: string; + fn: string; + payload: Record; + error?: boolean; + done?: boolean; +} + +/** + * Log context interface for tracking method execution + */ +export interface ILogContext { + readonly stack: LogTrace[]; + + decode(args: ScopeArgs): ILogScope; + encode(args: ScopeArgs): ILogScope; + call(args: ScopeArgs): ILogScope; + bridge(args: ScopeArgs): ILogScope; + event(args: ScopeArgs): ILogScope; +} + +/** + * Log scope interface for logging at execution stages + */ +export interface ILogScope { + start(payload?: Record): void; + failed(message?: string): void; + success(payload?: Record): void; +} diff --git a/src/coders/adapty-configuration.test.ts b/src/coders/adapty-configuration.test.ts index 01f6e3b..3a015d1 100644 --- a/src/coders/adapty-configuration.test.ts +++ b/src/coders/adapty-configuration.test.ts @@ -1,16 +1,25 @@ +import type { + IPlatformAdapter, + ISdkMetadataAdapter, + PlatformOS, +} from '@/adapters/interfaces'; import { AdaptyConfigurationCoder } from '@/coders/adapty-configuration'; import { LogLevel } from '@/types/inputs'; import version from '@/version'; -jest.mock('@/platform', () => ({ - Platform: { - OS: 'ios', - }, -})); - describe('AdaptyConfigurationCoder', () => { - const coder = new AdaptyConfigurationCoder(); + const sdkMetadata: ISdkMetadataAdapter = { + sdkName: 'react-native', + sdkVersion: version, + }; + const createCoder = (OS: PlatformOS = 'ios') => + new AdaptyConfigurationCoder({ OS } as IPlatformAdapter, sdkMetadata); const apiKey = 'test-api-key'; + let coder: AdaptyConfigurationCoder; + + beforeEach(() => { + coder = createCoder(); + }); it('should encode minimal configuration', () => { const params = {}; @@ -87,8 +96,7 @@ describe('AdaptyConfigurationCoder', () => { }); it('should encode full configuration with all parameters on android', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'android' }; + coder = createCoder('android'); const params = { customerUserId: 'user123', observerMode: true, @@ -141,7 +149,6 @@ describe('AdaptyConfigurationCoder', () => { }, }); - require('@/platform').Platform = originalPlatform; }); it('should handle partial parameters', () => { diff --git a/src/coders/adapty-configuration.ts b/src/coders/adapty-configuration.ts index ea836dd..778a783 100644 --- a/src/coders/adapty-configuration.ts +++ b/src/coders/adapty-configuration.ts @@ -1,18 +1,25 @@ +import type { + IPlatformAdapter, + ISdkMetadataAdapter, +} from '@/adapters/interfaces'; import * as Input from '@/types/inputs'; import type { Def } from '@/types/schema'; -import { Platform } from '@/platform'; import { AdaptyUiMediaCacheCoder } from '@/coders/adapty-ui-media-cache'; -import version from '@/version'; type Model = Input.ActivateParamsInput; type Serializable = Def['AdaptyConfiguration']; export class AdaptyConfigurationCoder { + constructor( + private readonly platform: IPlatformAdapter, + private readonly sdkMetadata: ISdkMetadataAdapter, + ) {} + encode(apiKey: string, params: Model): Serializable { const config: Serializable = { api_key: apiKey, - cross_platform_sdk_name: 'react-native', - cross_platform_sdk_version: version, + cross_platform_sdk_name: this.sdkMetadata.sdkName, + cross_platform_sdk_version: this.sdkMetadata.sdkVersion, }; if (params.customerUserId) { @@ -48,7 +55,7 @@ export class AdaptyConfigurationCoder { }, ); - if (Platform.OS === 'ios') { + if (this.platform.OS === 'ios') { config['apple_idfa_collection_disabled'] = params.ios?.idfaCollectionDisabled ?? false; if (params.ios?.appAccountToken) { @@ -61,7 +68,7 @@ export class AdaptyConfigurationCoder { } } - if (Platform.OS === 'android') { + if (this.platform.OS === 'android') { config['google_adid_collection_disabled'] = params.android?.adIdCollectionDisabled ?? false; config['google_enable_pending_prepaid_plans'] = diff --git a/src/coders/adapty-identify-params.test.ts b/src/coders/adapty-identify-params.test.ts index 80bd996..7880706 100644 --- a/src/coders/adapty-identify-params.test.ts +++ b/src/coders/adapty-identify-params.test.ts @@ -1,21 +1,24 @@ +import type { IPlatformAdapter, PlatformOS } from '@/adapters/interfaces'; import { AdaptyIdentifyParamsCoder } from '@/coders/adapty-identify-params'; describe('AdaptyIdentifyParamsCoder', () => { - const coder = new AdaptyIdentifyParamsCoder(); + const createCoder = (OS: PlatformOS = 'ios') => + new AdaptyIdentifyParamsCoder({ OS } as IPlatformAdapter); it('should return undefined for empty params', () => { + const coder = createCoder(); const result = coder.encode({}); expect(result).toBeUndefined(); }); it('should return undefined for undefined params', () => { + const coder = createCoder(); const result = coder.encode(undefined); expect(result).toBeUndefined(); }); it('should encode iOS app account token on iOS platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'ios' }; + const coder = createCoder('ios'); const params = { ios: { @@ -27,13 +30,10 @@ describe('AdaptyIdentifyParamsCoder', () => { expect(result).toEqual({ app_account_token: 'ios-token-123', }); - - require('@/platform').Platform = originalPlatform; }); it('should encode Android obfuscated account ID on Android platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'android' }; + const coder = createCoder('android'); const params = { android: { @@ -45,13 +45,10 @@ describe('AdaptyIdentifyParamsCoder', () => { expect(result).toEqual({ obfuscated_account_id: 'android-id-456', }); - - require('@/platform').Platform = originalPlatform; }); it('should only encode iOS parameters when on iOS platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'ios' }; + const coder = createCoder('ios'); const params = { ios: { @@ -66,13 +63,10 @@ describe('AdaptyIdentifyParamsCoder', () => { expect(result).toEqual({ app_account_token: 'ios-token-123', }); - - require('@/platform').Platform = originalPlatform; }); it('should only encode Android parameters when on Android platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'android' }; + const coder = createCoder('android'); const params = { ios: { @@ -87,13 +81,10 @@ describe('AdaptyIdentifyParamsCoder', () => { expect(result).toEqual({ obfuscated_account_id: 'android-id-456', }); - - require('@/platform').Platform = originalPlatform; }); it('should ignore iOS parameters on Android platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'android' }; + const coder = createCoder('android'); const params = { ios: { @@ -103,13 +94,10 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - - require('@/platform').Platform = originalPlatform; }); it('should ignore Android parameters on iOS platform', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'ios' }; + const coder = createCoder('ios'); const params = { android: { @@ -119,13 +107,10 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - - require('@/platform').Platform = originalPlatform; }); it('should handle empty platform objects', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'ios' }; + const coder = createCoder('ios'); const params = { ios: {}, @@ -134,7 +119,5 @@ describe('AdaptyIdentifyParamsCoder', () => { const result = coder.encode(params); expect(result).toBeUndefined(); - - require('@/platform').Platform = originalPlatform; }); }); diff --git a/src/coders/adapty-identify-params.ts b/src/coders/adapty-identify-params.ts index 3213a7f..21d1028 100644 --- a/src/coders/adapty-identify-params.ts +++ b/src/coders/adapty-identify-params.ts @@ -1,5 +1,5 @@ +import type { IPlatformAdapter } from '@/adapters/interfaces'; import * as Input from '@/types/inputs'; -import { Platform } from '@/platform'; type Model = Input.IdentifyParamsInput; type Serializable = { @@ -9,6 +9,8 @@ type Serializable = { }; export class AdaptyIdentifyParamsCoder { + constructor(private readonly platform: IPlatformAdapter) {} + encode(params?: Model): Serializable | undefined { if (!params) { return undefined; @@ -16,11 +18,11 @@ export class AdaptyIdentifyParamsCoder { const result: Serializable = {}; - if (Platform.OS === 'ios' && params.ios?.appAccountToken) { + if (this.platform.OS === 'ios' && params.ios?.appAccountToken) { result.app_account_token = params.ios.appAccountToken; } - if (Platform.OS === 'android' && params.android?.obfuscatedAccountId) { + if (this.platform.OS === 'android' && params.android?.obfuscatedAccountId) { result.obfuscated_account_id = params.android.obfuscatedAccountId; } diff --git a/src/coders/adapty-purchase-params.test.ts b/src/coders/adapty-purchase-params.test.ts index 1a669f0..fda0311 100644 --- a/src/coders/adapty-purchase-params.test.ts +++ b/src/coders/adapty-purchase-params.test.ts @@ -1,17 +1,14 @@ +import type { IPlatformAdapter, PlatformOS } from '@/adapters/interfaces'; import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; import * as Input from '@/types/inputs'; -jest.mock('@/platform', () => ({ - Platform: { - OS: 'android', - }, -})); - describe('AdaptyPurchaseParamsCoder', () => { + const createCoder = (OS: PlatformOS = 'android') => + new AdaptyPurchaseParamsCoder({ OS } as IPlatformAdapter); let coder: AdaptyPurchaseParamsCoder; beforeEach(() => { - coder = new AdaptyPurchaseParamsCoder(); + coder = createCoder(); }); describe('encode', () => { @@ -67,8 +64,7 @@ describe('AdaptyPurchaseParamsCoder', () => { }); it('should return empty object for non-Android platforms', () => { - const originalPlatform = require('@/platform').Platform; - require('@/platform').Platform = { OS: 'ios' }; + coder = createCoder('ios'); const params: Input.MakePurchaseParamsInput = { android: { @@ -84,8 +80,6 @@ describe('AdaptyPurchaseParamsCoder', () => { const result = coder.encode(params); expect(result).toEqual({}); - - require('@/platform').Platform = originalPlatform; }); }); }); diff --git a/src/coders/adapty-purchase-params.ts b/src/coders/adapty-purchase-params.ts index 8b82e6c..3ad6b9b 100644 --- a/src/coders/adapty-purchase-params.ts +++ b/src/coders/adapty-purchase-params.ts @@ -1,5 +1,5 @@ +import type { IPlatformAdapter } from '@/adapters/interfaces'; import * as Input from '@/types/inputs'; -import { Platform } from '@/platform'; type Model = Input.MakePurchaseParamsInput; type Serializable = Record; @@ -16,8 +16,10 @@ function isDeprecatedType( } export class AdaptyPurchaseParamsCoder { + constructor(private readonly platform: IPlatformAdapter) {} + encode(data: Model): Serializable { - if (Platform.OS !== 'android') { + if (this.platform.OS !== 'android') { return {}; } diff --git a/src/coders/adapty-purchase-result.test.ts b/src/coders/adapty-purchase-result.test.ts index 2649283..bb3f62f 100644 --- a/src/coders/adapty-purchase-result.test.ts +++ b/src/coders/adapty-purchase-result.test.ts @@ -1,8 +1,8 @@ -import type { AdaptyPurchaseResult } from '@/types'; -import type { Def } from '@/types/schema'; +import type { IPlatformAdapter, PlatformOS } from '@/adapters/interfaces'; import { AdaptyPurchaseResultCoder } from '@/coders/adapty-purchase-result'; import { AdaptyProfileCoder } from '@/coders/adapty-profile'; -import { Platform } from '@/platform'; +import type { AdaptyPurchaseResult } from '@/types'; +import type { Def } from '@/types/schema'; type Model = AdaptyPurchaseResult; type TestAdaptyPurchaseResultDef = @@ -203,16 +203,21 @@ const mocks: TestAdaptyPurchaseResultDef[] = [ { type: 'user_cancelled' }, ]; -function toModel(mock: (typeof mocks)[number]): Model { - const _profile = new AdaptyProfileCoder(); +function toModel( + mock: (typeof mocks)[number], + platformOS: PlatformOS, +): Model { + const _profile = new AdaptyProfileCoder({ + OS: platformOS, + } as IPlatformAdapter); if (mock.type === 'success') { return { type: mock.type, profile: _profile.decode(mock.profile as any), - ...(Platform.OS === 'ios' && mock.apple_jws_transaction + ...(platformOS === 'ios' && mock.apple_jws_transaction ? { ios: { jwsTransaction: mock.apple_jws_transaction } } : {}), - ...(Platform.OS === 'android' && mock.google_purchase_token + ...(platformOS === 'android' && mock.google_purchase_token ? { android: { purchaseToken: mock.google_purchase_token } } : {}), }; @@ -223,49 +228,35 @@ function toModel(mock: (typeof mocks)[number]): Model { } } -jest.mock('@/platform', () => ({ - Platform: { - OS: 'ios', - }, -})); - describe('AdaptyPurchaseResultCoder', () => { - let coder: AdaptyPurchaseResultCoder; - - beforeEach(() => { - coder = new AdaptyPurchaseResultCoder(); - }); - - it.each(mocks)('should decode to expected result', mock => { - const originalPlatform = require('@/platform').Platform; - if (mock.apple_jws_transaction) { - require('@/platform').Platform = { OS: 'ios' }; + const resolvePlatformOS = ( + mock: (typeof mocks)[number], + ): PlatformOS => { + if (mock.type === 'success' && mock.apple_jws_transaction) { + return 'ios'; } - if (mock.google_purchase_token) { - require('@/platform').Platform = { OS: 'android' }; + if (mock.type === 'success' && mock.google_purchase_token) { + return 'android'; } + return 'ios'; + }; + it.each(mocks)('should decode to expected result', mock => { + const platformOS = resolvePlatformOS(mock); + const platform: IPlatformAdapter = { OS: platformOS }; + const coder = new AdaptyPurchaseResultCoder(platform); const decoded = coder.decode(mock as any); - expect(decoded).toStrictEqual(toModel(mock)); - - require('@/platform').Platform = originalPlatform; + expect(decoded).toStrictEqual(toModel(mock, platformOS)); }); it.each(mocks)('should decode/encode', mock => { - const originalPlatform = require('@/platform').Platform; - if (mock.apple_jws_transaction) { - require('@/platform').Platform = { OS: 'ios' }; - } - if (mock.google_purchase_token) { - require('@/platform').Platform = { OS: 'android' }; - } - + const platformOS = resolvePlatformOS(mock); + const platform: IPlatformAdapter = { OS: platformOS }; + const coder = new AdaptyPurchaseResultCoder(platform); const decoded = coder.decode(mock as any); const encoded = coder.encode(decoded); expect(encoded).toStrictEqual(mock); - - require('@/platform').Platform = originalPlatform; }); }); diff --git a/src/coders/adapty-purchase-result.ts b/src/coders/adapty-purchase-result.ts index c8f5c9c..4af8eb2 100644 --- a/src/coders/adapty-purchase-result.ts +++ b/src/coders/adapty-purchase-result.ts @@ -1,9 +1,8 @@ import type { AdaptyPurchaseResult } from '@/types'; import type { Def } from '@/types/schema'; -import type { Properties } from './types'; -import { SimpleCoder } from './coder'; import { AdaptyProfileCoder } from '@/coders/adapty-profile'; -import { Platform } from '@/platform'; +import { SimpleCoder } from './coder'; +import type { Properties } from './types'; type Model = AdaptyPurchaseResult; type Serializable = Def['AdaptyPurchaseResult']; @@ -30,11 +29,11 @@ export class AdaptyPurchaseResultCoder extends SimpleCoder< } return { ...baseResult, - profile: new AdaptyProfileCoder().decode(data.profile), - ...(Platform.OS === 'ios' && data.apple_jws_transaction + profile: new AdaptyProfileCoder(this.platform).decode(data.profile), + ...(this.platform.OS === 'ios' && data.apple_jws_transaction ? { ios: { jwsTransaction: data.apple_jws_transaction } } : {}), - ...(Platform.OS === 'android' && data.google_purchase_token + ...(this.platform.OS === 'android' && data.google_purchase_token ? { android: { purchaseToken: data.google_purchase_token } } : {}), }; @@ -54,11 +53,11 @@ export class AdaptyPurchaseResultCoder extends SimpleCoder< return { type: 'success', - profile: new AdaptyProfileCoder().encode(data.profile), - ...(Platform.OS === 'ios' && data.ios?.jwsTransaction + profile: new AdaptyProfileCoder(this.platform).encode(data.profile), + ...(this.platform.OS === 'ios' && data.ios?.jwsTransaction ? { apple_jws_transaction: data.ios.jwsTransaction } : {}), - ...(Platform.OS === 'android' && data.android?.purchaseToken + ...(this.platform.OS === 'android' && data.android?.purchaseToken ? { google_purchase_token: data.android.purchaseToken } : {}), }; diff --git a/src/coders/adapty-ui-create-paywall-view-params.test.ts b/src/coders/adapty-ui-create-paywall-view-params.test.ts index 0f525fa..6ff70cc 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.test.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.test.ts @@ -1,13 +1,15 @@ +import type { IPlatformAdapter, PlatformOS } from '@/adapters/interfaces'; import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; -import { Platform } from '@/platform'; import type { CreatePaywallViewParamsInput } from '@/ui/types'; import type { AdaptyProductIdentifier } from '@/types'; describe('AdaptyUICreatePaywallViewParamsCoder', () => { + const createCoder = (OS: PlatformOS = 'ios') => + new AdaptyUICreatePaywallViewParamsCoder({ OS } as IPlatformAdapter); let coder: AdaptyUICreatePaywallViewParamsCoder; beforeEach(() => { - coder = new AdaptyUICreatePaywallViewParamsCoder(); + coder = createCoder(); }); it('should encode basic params', () => { @@ -188,112 +190,72 @@ describe('AdaptyUICreatePaywallViewParamsCoder', () => { }); it('should encode product purchase parameters on Android', () => { - const original = Platform.OS; - // mock Platform.OS to android - Object.defineProperty(Platform, 'OS', { - configurable: true, - get() { - return 'android'; - }, - } as any); - - try { - const productId: AdaptyProductIdentifier = { - vendorProductId: 'com.example.product', - adaptyProductId: 'adapty_product_id', - }; - - const input: CreatePaywallViewParamsInput = { - productPurchaseParams: [ - { - productId, - params: { - android: { - isOfferPersonalized: true, - }, + coder = createCoder('android'); + const productId: AdaptyProductIdentifier = { + vendorProductId: 'com.example.product', + adaptyProductId: 'adapty_product_id', + }; + + const input: CreatePaywallViewParamsInput = { + productPurchaseParams: [ + { + productId, + params: { + android: { + isOfferPersonalized: true, }, }, - ], - }; + }, + ], + }; - const result = coder.encode(input); + const result = coder.encode(input); - expect(result.product_purchase_parameters).toEqual({ - adapty_product_id: { - is_offer_personalized: true, - }, - }); - } finally { - Object.defineProperty(Platform, 'OS', { - configurable: true, - get() { - return original as any; - }, - } as any); - } + expect(result.product_purchase_parameters).toEqual({ + adapty_product_id: { + is_offer_personalized: true, + }, + }); }); it('should encode asset_id with android suffixes on Android', () => { - const originalOS = Platform.OS; - const originalSelect = Platform.select; - Object.defineProperty(Platform, 'OS', { - configurable: true, - get() { - return 'android'; - }, - } as any); - const selectSpy = jest - .spyOn(Platform, 'select') - .mockImplementation((spec: any) => spec.android); - - try { - const input: CreatePaywallViewParamsInput = { - customAssets: { - imgRel: { type: 'image', relativeAssetPath: 'images/test.png' }, - videoRel: { type: 'video', relativeAssetPath: 'videos/intro.mp4' }, - imgFL: { - type: 'image', - fileLocation: { - ios: { fileName: 'ios_name.png' }, - android: { relativeAssetPath: 'images/rel.png' }, - }, - }, - videoFL: { - type: 'video', - fileLocation: { - ios: { fileName: 'ios_video.mp4' }, - android: { rawResName: 'intro' }, - }, + coder = createCoder('android'); + const input: CreatePaywallViewParamsInput = { + customAssets: { + imgRel: { type: 'image', relativeAssetPath: 'images/test.png' }, + videoRel: { type: 'video', relativeAssetPath: 'videos/intro.mp4' }, + imgFL: { + type: 'image', + fileLocation: { + ios: { fileName: 'ios_name.png' }, + android: { relativeAssetPath: 'images/rel.png' }, }, }, - }; - - const result = coder.encode(input); - expect(result.custom_assets).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: 'imgRel', - asset_id: 'images/test.pnga', - }), - expect.objectContaining({ - id: 'videoRel', - asset_id: 'videos/intro.mp4a', - }), - expect.objectContaining({ id: 'imgFL', asset_id: 'images/rel.pnga' }), - expect.objectContaining({ id: 'videoFL', asset_id: 'intror' }), - ]), - ); - } finally { - Object.defineProperty(Platform, 'OS', { - configurable: true, - get() { - return originalOS as any; + videoFL: { + type: 'video', + fileLocation: { + ios: { fileName: 'ios_video.mp4' }, + android: { rawResName: 'intro' }, + }, }, - } as any); - selectSpy.mockRestore(); - // restore just in case - (Platform as any).select = originalSelect; - } + }, + }; + + const result = coder.encode(input); + expect(result.custom_assets).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'imgRel', + asset_id: 'images/test.pnga', + }), + expect.objectContaining({ + id: 'videoRel', + asset_id: 'videos/intro.mp4a', + }), + expect.objectContaining({ id: 'imgFL', asset_id: 'images/rel.pnga' }), + expect.objectContaining({ id: 'videoFL', asset_id: 'intror' }), + ]), + ); }); it('should handle empty input', () => { diff --git a/src/coders/adapty-ui-create-paywall-view-params.ts b/src/coders/adapty-ui-create-paywall-view-params.ts index 32439d1..ca1ddf7 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.ts @@ -1,4 +1,4 @@ -import { Platform } from '@/platform'; +import type { IPlatformAdapter } from '@/adapters/interfaces'; import type { CreatePaywallViewParamsInput, AdaptyCustomAsset, @@ -23,6 +23,8 @@ type Serializable = { }; export class AdaptyUICreatePaywallViewParamsCoder { + constructor(private readonly platform: IPlatformAdapter) {} + encode(data: Model): Serializable { const result: Serializable = {}; @@ -74,7 +76,7 @@ export class AdaptyUICreatePaywallViewParamsCoder { assets: Record, ): Def['AdaptyUI.CustomAssets'] { const getAssetId = (asset: any): string => { - return resolveAssetId(asset, spec => Platform.select(spec)) || ''; + return resolveAssetId(asset, this.platform.OS) || ''; }; return Object.entries(assets) @@ -167,7 +169,7 @@ export class AdaptyUICreatePaywallViewParamsCoder { ): Def['AdaptyUI.ProductPurchaseParameters'] { if (!params) return {}; - const purchaseParamsCoder = new AdaptyPurchaseParamsCoder(); + const purchaseParamsCoder = new AdaptyPurchaseParamsCoder(this.platform); return Object.fromEntries( params.map(({ productId, params }) => [ productId.adaptyProductId, diff --git a/src/coders/coder.ts b/src/coders/coder.ts index 5f569c6..c108f89 100644 --- a/src/coders/coder.ts +++ b/src/coders/coder.ts @@ -1,6 +1,7 @@ import { AdaptyError } from '@/adapty-error'; +import { DefaultPlatformAdapter } from '@/adapters/defaults'; +import type { IPlatformAdapter } from '@/adapters/interfaces'; import { Converter, Properties, StrType } from './types'; -import { Platform } from '@/platform'; export abstract class Coder< Model extends Record, @@ -8,8 +9,13 @@ export abstract class Coder< Serializable extends Record = Record, > implements Converter { + protected readonly platform: IPlatformAdapter; protected abstract properties: Properties; + constructor(platform: IPlatformAdapter = new DefaultPlatformAdapter()) { + this.platform = platform; + } + encode(data: CodableModel): Serializable { return this.encodeWithProperties(data, this.properties); } @@ -150,7 +156,7 @@ export abstract class Coder< if ( property.required && value === undefined && - (!platform || platform == Platform.OS) + (!platform || platform == this.platform.OS) ) { throw AdaptyError.failedToDecode( `Failed to decode native response, because it is missing required property "${key}"`, diff --git a/src/coders/factory.ts b/src/coders/factory.ts new file mode 100644 index 0000000..b740618 --- /dev/null +++ b/src/coders/factory.ts @@ -0,0 +1,49 @@ +import type { + IPlatformAdapter, + ISdkMetadataAdapter, + ILoggerAdapter, +} from '@/adapters/interfaces'; +import { AdaptyConfigurationCoder } from './adapty-configuration'; +import { AdaptyIdentifyParamsCoder } from './adapty-identify-params'; +import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; +import { AdaptyPurchaseResultCoder } from './adapty-purchase-result'; +import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; + +/** + * Dependencies container for coders + */ +export interface CoderDependencies { + platform: IPlatformAdapter; + sdkMetadata: ISdkMetadataAdapter; + logger?: ILoggerAdapter; +} + +/** + * Factory for creating coders with injected dependencies + */ +export class CoderFactory { + constructor(private readonly deps: CoderDependencies) {} + + createConfigurationCoder(): AdaptyConfigurationCoder { + return new AdaptyConfigurationCoder( + this.deps.platform, + this.deps.sdkMetadata, + ); + } + + createPurchaseParamsCoder(): AdaptyPurchaseParamsCoder { + return new AdaptyPurchaseParamsCoder(this.deps.platform); + } + + createPurchaseResultCoder(): AdaptyPurchaseResultCoder { + return new AdaptyPurchaseResultCoder(this.deps.platform); + } + + createIdentifyParamsCoder(): AdaptyIdentifyParamsCoder { + return new AdaptyIdentifyParamsCoder(this.deps.platform); + } + + createUiCreatePaywallViewParamsCoder(): AdaptyUICreatePaywallViewParamsCoder { + return new AdaptyUICreatePaywallViewParamsCoder(this.deps.platform); + } +} diff --git a/src/coders/utils.ts b/src/coders/utils.ts index 0cdf213..ca7aed0 100644 --- a/src/coders/utils.ts +++ b/src/coders/utils.ts @@ -38,30 +38,24 @@ export const extractBase64Data = (input: string): string => { }; import type { FileLocation } from '@/types/inputs'; - -type PlatformSelector = { ios: T; android: T }; +import type { PlatformOS } from '@/adapters/interfaces'; export const resolveAssetId = ( asset: { relativeAssetPath: string } | { fileLocation: FileLocation }, - select: (spec: PlatformSelector) => T | undefined, + platformOS: PlatformOS, ): string => { if ('relativeAssetPath' in asset) { - return ( - select({ - ios: asset.relativeAssetPath, - android: `${asset.relativeAssetPath}a`, - }) || '' - ); + return platformOS === 'android' + ? `${asset.relativeAssetPath}a` + : asset.relativeAssetPath; } const fileLocation = asset.fileLocation; - return ( - select({ - ios: fileLocation.ios.fileName, - android: - 'relativeAssetPath' in fileLocation.android - ? `${fileLocation.android.relativeAssetPath}a` - : `${(fileLocation.android as any).rawResName}r`, - }) || '' - ); + if (platformOS === 'android') { + return 'relativeAssetPath' in fileLocation.android + ? `${fileLocation.android.relativeAssetPath}a` + : `${(fileLocation.android as { rawResName: string }).rawResName}r`; + } + + return fileLocation.ios.fileName; }; diff --git a/src/index.ts b/src/index.ts index f27e635..d04b717 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,23 @@ */ export const version = '0.0.0-dev.0000000000000000000000000000000000000000'; + +export type { + IPlatformAdapter, + ISdkMetadataAdapter, + ILoggerAdapter, + ILogContext, + ILogScope, + LogTrace, + PlatformOS, + ScopeArgs, +} from './adapters/interfaces'; + +export { + DefaultPlatformAdapter, + DefaultSdkMetadataAdapter, + DefaultLoggerAdapter, +} from './adapters/defaults'; + +export { CoderFactory } from './coders/factory'; +export type { CoderDependencies } from './coders/factory'; diff --git a/src/logger/index.ts b/src/logger/index.ts index e799b58..ef5267c 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -3,23 +3,19 @@ * Actual logging implementation should be provided by the platform SDK */ -export interface ScopeArgs { - methodName: string; -} - -interface Trace { - action: string; - fn: string; - payload: Record; - error?: boolean; - done?: boolean; -} +import type { + ILoggerAdapter, + ILogContext, + ILogScope, + LogTrace, + ScopeArgs, +} from '@/adapters/interfaces'; /** * LogScope provides methods for logging at different stages of execution */ -export class LogScope { - start(_payload?: Record): void { +export class LogScope implements ILogScope { + start(_payload?: Record): void { // Noop - actual implementation provided by platform SDK } @@ -27,7 +23,7 @@ export class LogScope { // Noop - actual implementation provided by platform SDK } - success(_payload?: Record): void { + success(_payload?: Record): void { // Noop - actual implementation provided by platform SDK } } @@ -35,26 +31,35 @@ export class LogScope { /** * LogContext accumulates logs for each step of a call */ -export class LogContext { - public stack: Trace[] = []; +export class LogContext implements ILogContext { + public stack: LogTrace[] = []; - decode(_args: ScopeArgs): LogScope { + decode(_args: ScopeArgs): ILogScope { return new LogScope(); } - encode(_args: ScopeArgs): LogScope { + encode(_args: ScopeArgs): ILogScope { return new LogScope(); } - call(_args: ScopeArgs): LogScope { + call(_args: ScopeArgs): ILogScope { return new LogScope(); } - bridge(_args: ScopeArgs): LogScope { + bridge(_args: ScopeArgs): ILogScope { return new LogScope(); } - event(_args: ScopeArgs): LogScope { + event(_args: ScopeArgs): ILogScope { return new LogScope(); } } + +/** + * Noop logger adapter - doesn't log anything + */ +export class NoopLoggerAdapter implements ILoggerAdapter { + createContext(): ILogContext { + return new LogContext(); + } +} diff --git a/src/platform.ts b/src/platform.ts index 208eca3..abbc79e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -3,15 +3,7 @@ * This is a mock replacement for react-native Platform */ -export type PlatformOS = 'ios' | 'android' | 'web' | 'unknown'; - -export interface PlatformSelectSpec { - ios?: T; - android?: T; - native?: T; - default?: T; - web?: T; -} +import type { PlatformOS } from '@/adapters/interfaces'; export class Platform { private static _os: PlatformOS = 'unknown'; @@ -23,36 +15,6 @@ export class Platform { static set OS(value: PlatformOS) { Platform._os = value; } - - static select(spec: PlatformSelectSpec): T | undefined { - const platform = Platform.OS; - - // First, try to find exact platform match - if (platform in spec) { - return spec[platform as keyof typeof spec]; - } - - // For native platforms (ios/android), try native fallback - if (platform === 'ios' || platform === 'android') { - if ('native' in spec) { - return spec.native; - } - } - - // Try default if available - if ('default' in spec) { - return spec.default; - } - - // If OS is unknown, prefer ios over android as fallback - if (platform === 'unknown') { - if ('ios' in spec) return spec.ios; - if ('android' in spec) return spec.android; - if ('native' in spec) return spec.native; - } - - return undefined; - } } /** From f195404af7c8393651e756e21d65e48acccd85a3 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 29 Jan 2026 19:07:21 +0300 Subject: [PATCH 12/50] feat: add all coders to factory --- AGENTS.md | 2 + src/coders/factory.ts | 170 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index c185dd5..13fb2fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,6 +85,8 @@ This repo uses platform dependency injection to keep `@adapty/core` platform-agn - `IPlatformAdapter` for platform OS - `ISdkMetadataAdapter` for sdkName/sdkVersion - `ILoggerAdapter` for logging +- **Instantiation rule:** do not instantiate `*Coder` via `new` outside `src/coders/**`. In SDK / bridge / feature code, use `CoderFactory` as the single entry point. +- **When adding a new coder:** add a corresponding `create*Coder()` method to `CoderFactory` and use it instead of direct construction. ### Testing Guidance diff --git a/src/coders/factory.ts b/src/coders/factory.ts index b740618..19dd097 100644 --- a/src/coders/factory.ts +++ b/src/coders/factory.ts @@ -3,11 +3,46 @@ import type { ISdkMetadataAdapter, ILoggerAdapter, } from '@/adapters/interfaces'; +import { AdaptyAccessLevelCoder } from './adapty-access-level'; import { AdaptyConfigurationCoder } from './adapty-configuration'; +import { AdaptyDiscountPhaseCoder } from './adapty-discount-phase'; import { AdaptyIdentifyParamsCoder } from './adapty-identify-params'; +import { AdaptyInstallationDetailsCoder } from './adapty-installation-details'; +import { AdaptyInstallationStatusCoder } from './adapty-installation-status'; +import { AdaptyNativeErrorCoder } from './adapty-native-error'; +import { AdaptyNonSubscriptionCoder } from './adapty-non-subscription'; +import { AdaptyOnboardingBuilderCoder } from './adapty-onboarding-builder'; +import { AdaptyOnboardingCoder } from './adapty-onboarding'; +import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; +import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; +import { AdaptyPaywallCoder } from './adapty-paywall'; +import { AdaptyPlacementCoder } from './adapty-placement'; +import { AdaptyPriceCoder } from './adapty-price'; +import { AdaptyProfileParametersCoder } from './adapty-profile-parameters'; +import { AdaptyProfileCoder } from './adapty-profile'; import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; import { AdaptyPurchaseResultCoder } from './adapty-purchase-result'; +import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; +import { AdaptySubscriptionDetailsCoder } from './adapty-subscription-details'; +import { AdaptySubscriptionOfferIdCoder } from './adapty-subscription-offer-identifier'; +import { AdaptySubscriptionOfferCoder } from './adapty-subscription-offer'; +import { AdaptySubscriptionPeriodCoder } from './adapty-subscription-period'; +import { AdaptySubscriptionCoder } from './adapty-subscription'; +import { AdaptyUiDialogConfigCoder } from './adapty-ui-dialog-config'; +import { AdaptyUICreateOnboardingViewParamsCoder } from './adapty-ui-create-onboarding-view-params'; import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; +import { AdaptyUiMediaCacheCoder } from './adapty-ui-media-cache'; +import { AdaptyUiOnboardingMetaCoder } from './adapty-ui-onboarding-meta'; +import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state-params'; +import { AdaptyUiOnboardingStateUpdatedActionCoder } from './adapty-ui-onboarding-state-updated-action'; +import { ArrayCoder } from './array'; +import { BridgeErrorCoder } from './bridge-error'; +import type { SimpleCoder } from './coder'; +import { DateCoder } from './date'; +import { HashmapCoder } from './hashmap'; +import { JSONCoder } from './json'; +import { ProductReferenceCoder } from './product-reference'; +import type { Converter } from './types'; /** * Dependencies container for coders @@ -24,6 +59,10 @@ export interface CoderDependencies { export class CoderFactory { constructor(private readonly deps: CoderDependencies) {} + createAccessLevelCoder(): AdaptyAccessLevelCoder { + return new AdaptyAccessLevelCoder(this.deps.platform); + } + createConfigurationCoder(): AdaptyConfigurationCoder { return new AdaptyConfigurationCoder( this.deps.platform, @@ -31,6 +70,14 @@ export class CoderFactory { ); } + createDateCoder(): DateCoder { + return new DateCoder(); + } + + createDiscountPhaseCoder(): AdaptyDiscountPhaseCoder { + return new AdaptyDiscountPhaseCoder(this.deps.platform); + } + createPurchaseParamsCoder(): AdaptyPurchaseParamsCoder { return new AdaptyPurchaseParamsCoder(this.deps.platform); } @@ -43,7 +90,130 @@ export class CoderFactory { return new AdaptyIdentifyParamsCoder(this.deps.platform); } + createInstallationDetailsCoder(): AdaptyInstallationDetailsCoder { + return new AdaptyInstallationDetailsCoder(); + } + + createInstallationStatusCoder(): AdaptyInstallationStatusCoder { + return new AdaptyInstallationStatusCoder(); + } + + createJsonCoder(): JSONCoder { + return new JSONCoder(); + } + + createNativeErrorCoder(): AdaptyNativeErrorCoder { + return new AdaptyNativeErrorCoder(this.deps.platform); + } + + createNonSubscriptionCoder(): AdaptyNonSubscriptionCoder { + return new AdaptyNonSubscriptionCoder(this.deps.platform); + } + + createOnboardingBuilderCoder(): AdaptyOnboardingBuilderCoder { + return new AdaptyOnboardingBuilderCoder(this.deps.platform); + } + + createOnboardingCoder(): AdaptyOnboardingCoder { + return new AdaptyOnboardingCoder(this.deps.platform); + } + + createPaywallBuilderCoder(): AdaptyPaywallBuilderCoder { + return new AdaptyPaywallBuilderCoder(this.deps.platform); + } + + createPaywallCoder(): AdaptyPaywallCoder { + return new AdaptyPaywallCoder(this.deps.platform); + } + + createPaywallProductCoder(): AdaptyPaywallProductCoder { + return new AdaptyPaywallProductCoder(this.deps.platform); + } + + createPlacementCoder(): AdaptyPlacementCoder { + return new AdaptyPlacementCoder(this.deps.platform); + } + + createPriceCoder(): AdaptyPriceCoder { + return new AdaptyPriceCoder(this.deps.platform); + } + + createProductReferenceCoder(): ProductReferenceCoder { + return new ProductReferenceCoder(this.deps.platform); + } + + createProfileCoder(): AdaptyProfileCoder { + return new AdaptyProfileCoder(this.deps.platform); + } + + createProfileParametersCoder(): AdaptyProfileParametersCoder { + return new AdaptyProfileParametersCoder(this.deps.platform); + } + + createRemoteConfigCoder(): AdaptyRemoteConfigCoder { + return new AdaptyRemoteConfigCoder(this.deps.platform); + } + + createSubscriptionCoder(): AdaptySubscriptionCoder { + return new AdaptySubscriptionCoder(this.deps.platform); + } + + createSubscriptionDetailsCoder(): AdaptySubscriptionDetailsCoder { + return new AdaptySubscriptionDetailsCoder(this.deps.platform); + } + + createSubscriptionOfferCoder(): AdaptySubscriptionOfferCoder { + return new AdaptySubscriptionOfferCoder(this.deps.platform); + } + + createSubscriptionOfferIdCoder(): AdaptySubscriptionOfferIdCoder { + return new AdaptySubscriptionOfferIdCoder(this.deps.platform); + } + + createSubscriptionPeriodCoder(): AdaptySubscriptionPeriodCoder { + return new AdaptySubscriptionPeriodCoder(this.deps.platform); + } + + createUiCreateOnboardingViewParamsCoder(): AdaptyUICreateOnboardingViewParamsCoder { + return new AdaptyUICreateOnboardingViewParamsCoder(); + } + createUiCreatePaywallViewParamsCoder(): AdaptyUICreatePaywallViewParamsCoder { return new AdaptyUICreatePaywallViewParamsCoder(this.deps.platform); } + + createUiDialogConfigCoder(): AdaptyUiDialogConfigCoder { + return new AdaptyUiDialogConfigCoder(this.deps.platform); + } + + createUiMediaCacheCoder(): AdaptyUiMediaCacheCoder { + return new AdaptyUiMediaCacheCoder(this.deps.platform); + } + + createUiOnboardingMetaCoder(): AdaptyUiOnboardingMetaCoder { + return new AdaptyUiOnboardingMetaCoder(this.deps.platform); + } + + createUiOnboardingStateParamsCoder(): AdaptyUiOnboardingStateParamsCoder { + return new AdaptyUiOnboardingStateParamsCoder(this.deps.platform); + } + + createUiOnboardingStateUpdatedActionCoder(): AdaptyUiOnboardingStateUpdatedActionCoder { + return new AdaptyUiOnboardingStateUpdatedActionCoder(this.deps.platform); + } + + createBridgeErrorCoder(): BridgeErrorCoder { + return new BridgeErrorCoder(this.deps.platform); + } + + createArrayCoder< + Model extends Record, + ModelCoder extends SimpleCoder, + >(coder: new () => ModelCoder): ArrayCoder { + return new ArrayCoder(coder); + } + + createHashmapCoder>(coder: T | null): HashmapCoder { + return new HashmapCoder(coder); + } } From 00ef84834321d240865006da1e17af931cd9cb9a Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 30 Jan 2026 14:44:07 +0300 Subject: [PATCH 13/50] feat: add build:watch --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2a034ee..0468acf 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "sideEffects": false, "scripts": { "build": "tsdown", + "build:watch": "tsdown --watch", "clean": "rm -rf dist .tsbuildinfo", "prebuild": "yarn clean", "lint": "eslint ./src --ext .ts", From 83b2d2a4cde459114df80023ec3ef8d629eea9b5 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 30 Jan 2026 14:44:46 +0300 Subject: [PATCH 14/50] feat: add export ActivateParamsInput --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index d04b717..bd7584b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,3 +26,5 @@ export { export { CoderFactory } from './coders/factory'; export type { CoderDependencies } from './coders/factory'; + +export type { ActivateParamsInput } from './types/inputs'; From 3e2065a07887077bf762437504557a34060b535b Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 30 Jan 2026 14:56:35 +0300 Subject: [PATCH 15/50] feat: add Usage in Downstream SDKs to AGENTS.md --- AGENTS.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 13fb2fd..7a6888c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,6 +93,41 @@ This repo uses platform dependency injection to keep `@adapty/core` platform-agn - Tests should create coders with mock adapters, not mutate `Platform` globals. - Prefer inline adapters: `{ OS: 'ios' }` / `{ OS: 'android' }`. +### Usage in Downstream SDKs + +Downstream SDKs (React Native, Capacitor) should: + +1. **Implement platform adapters:** + - Create classes implementing `IPlatformAdapter` and `ISdkMetadataAdapter` + - Platform adapter wraps platform-specific API (e.g., `Platform.OS` from react-native) + - SDK metadata adapter provides `sdkName` and `sdkVersion` + +2. **Create singleton CoderFactory:** + - Initialize with platform-specific adapters + - Use factory methods instead of direct coder instantiation + +3. **Import shared types:** + - Use exported types from `@adapty/core` (e.g., `ActivateParamsInput`) + - Avoid duplicating types between core and SDKs + +**Example:** +```typescript +// In React Native SDK +import { CoderFactory, type ActivateParamsInput } from '@adapty/core'; +import { ReactNativePlatformAdapter, ReactNativeSdkMetadataAdapter } from './adapters'; + +export const coderFactory = new CoderFactory({ + platform: new ReactNativePlatformAdapter(), + sdkMetadata: new ReactNativeSdkMetadataAdapter(), +}); + +// Use core types in method signatures +public async activate(apiKey: string, params: ActivateParamsInput = {}): Promise { + const coder = coderFactory.createConfigurationCoder(); + // ... +} +``` + ## Build Output Built with `tsdown` (powered by Rolldown). Outputs multiple formats: From a2dcedc274baeb57cef51e969e802b5c71f27740 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 30 Jan 2026 15:06:48 +0300 Subject: [PATCH 16/50] feat: mass re-export all types from types folder --- src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index bd7584b..78f9592 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,4 +27,10 @@ export { export { CoderFactory } from './coders/factory'; export type { CoderDependencies } from './coders/factory'; -export type { ActivateParamsInput } from './types/inputs'; +// Mass re-export all types from types folder +export * from './types/index'; +export * from './types/inputs'; +export * from './types/error'; +export * from './types/bridge'; +export * from './types/paywall-events'; +export * from './types/onboarding-events'; From df216e8995ce64b2173012ee6bfc3b3533641841 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 30 Jan 2026 23:59:40 +0300 Subject: [PATCH 17/50] refactor ArrayCoder --- src/coders/adapty-paywall.test.ts | 4 +-- src/coders/adapty-paywall.ts | 2 +- src/coders/adapty-profile.test.ts | 4 +-- src/coders/adapty-profile.ts | 4 +-- src/coders/adapty-subscription-offer.test.ts | 5 +-- src/coders/adapty-subscription-offer.ts | 2 +- src/coders/array.ts | 35 +++++--------------- src/coders/factory.ts | 13 +++++--- src/coders/parse.ts | 2 +- 9 files changed, 24 insertions(+), 47 deletions(-) diff --git a/src/coders/adapty-paywall.test.ts b/src/coders/adapty-paywall.test.ts index 149abfc..c5f92c2 100644 --- a/src/coders/adapty-paywall.test.ts +++ b/src/coders/adapty-paywall.test.ts @@ -77,9 +77,7 @@ const mocks: Def['AdaptyPaywall'][] = [ ]; function toModel(mock: (typeof mocks)[number]): Model { - const _products = new ArrayCoder( - ProductReferenceCoder, - ); + const _products = new ArrayCoder(() => new ProductReferenceCoder()); const _remoteConfig = new AdaptyRemoteConfigCoder(); const _paywallBuilder = new AdaptyPaywallBuilderCoder(); diff --git a/src/coders/adapty-paywall.ts b/src/coders/adapty-paywall.ts index 93c6a19..c48a796 100644 --- a/src/coders/adapty-paywall.ts +++ b/src/coders/adapty-paywall.ts @@ -30,7 +30,7 @@ export class AdaptyPaywallCoder extends Coder< key: 'products', required: true, type: 'array', - converter: new ArrayCoder(ProductReferenceCoder), + converter: new ArrayCoder(() => new ProductReferenceCoder()), }, remoteConfig: { key: 'remote_config', diff --git a/src/coders/adapty-profile.test.ts b/src/coders/adapty-profile.test.ts index 79312e4..bcdf2f1 100644 --- a/src/coders/adapty-profile.test.ts +++ b/src/coders/adapty-profile.test.ts @@ -187,9 +187,7 @@ function toModel(mock: (typeof mocks)[number]): Model { const _levels = new HashmapCoder(new AdaptyAccessLevelCoder()); const _subs = new HashmapCoder(new AdaptySubscriptionCoder()); const _nonsubs = new HashmapCoder( - new ArrayCoder( - AdaptyNonSubscriptionCoder, - ), + new ArrayCoder(() => new AdaptyNonSubscriptionCoder()), ); return { diff --git a/src/coders/adapty-profile.ts b/src/coders/adapty-profile.ts index dded29b..44004ac 100644 --- a/src/coders/adapty-profile.ts +++ b/src/coders/adapty-profile.ts @@ -34,9 +34,7 @@ export class AdaptyProfileCoder extends SimpleCoder { required: false, type: 'object', converter: new HashmapCoder( - new ArrayCoder( - AdaptyNonSubscriptionCoder, - ), + new ArrayCoder(() => new AdaptyNonSubscriptionCoder()), ), }, profileId: { diff --git a/src/coders/adapty-subscription-offer.test.ts b/src/coders/adapty-subscription-offer.test.ts index adddf23..c9f48b0 100644 --- a/src/coders/adapty-subscription-offer.test.ts +++ b/src/coders/adapty-subscription-offer.test.ts @@ -60,10 +60,7 @@ const mocks: Def['AdaptySubscriptionOffer'][] = [ function toModel(mock: (typeof mocks)[number]): Model { const _offerId = new AdaptySubscriptionOfferIdCoder(); - const _discounts = new ArrayCoder< - AdaptyDiscountPhase, - AdaptyDiscountPhaseCoder - >(AdaptyDiscountPhaseCoder); + const _discounts = new ArrayCoder(() => new AdaptyDiscountPhaseCoder()); return { identifier: _offerId.decode(mock.offer_identifier), diff --git a/src/coders/adapty-subscription-offer.ts b/src/coders/adapty-subscription-offer.ts index 2d409f5..0b9e3a3 100644 --- a/src/coders/adapty-subscription-offer.ts +++ b/src/coders/adapty-subscription-offer.ts @@ -24,7 +24,7 @@ export class AdaptySubscriptionOfferCoder extends SimpleCoder< key: 'phases', required: true, type: 'array', - converter: new ArrayCoder(AdaptyDiscountPhaseCoder), + converter: new ArrayCoder(() => new AdaptyDiscountPhaseCoder()), }, android: { offerTags: { diff --git a/src/coders/array.ts b/src/coders/array.ts index 2178aad..372ea78 100644 --- a/src/coders/array.ts +++ b/src/coders/array.ts @@ -1,34 +1,17 @@ -import { SimpleCoder } from './coder'; import { Converter } from './types'; // Coder for Array -export class ArrayCoder< - Model extends Record, - ModelCoder extends SimpleCoder, -> implements Converter -{ - private coder: ModelCoder; +// Uses factory function to create coder instances (supports dependency injection) +export class ArrayCoder implements Converter { + constructor(private readonly coderFactory: () => Converter) {} - constructor(coder: new () => ModelCoder) { - this.coder = new coder(); + decode(input: Serializable[]): Model[] { + const coder = this.coderFactory(); + return input.map(item => coder.decode(item)); } - decode(input: any[]): Model[] { - const result: Model[] = []; - - input.forEach(value => { - result.push(this.coder.decode(value)); - }); - - return result; - } - - encode(value: Model[]): any[] { - const result: any[] = []; - value.forEach(model => { - result.push(this.coder.encode(model)); - }); - - return result; + encode(value: Model[]): Serializable[] { + const coder = this.coderFactory(); + return value.map(item => coder.encode(item)); } } diff --git a/src/coders/factory.ts b/src/coders/factory.ts index 19dd097..58fc5de 100644 --- a/src/coders/factory.ts +++ b/src/coders/factory.ts @@ -206,11 +206,14 @@ export class CoderFactory { return new BridgeErrorCoder(this.deps.platform); } - createArrayCoder< - Model extends Record, - ModelCoder extends SimpleCoder, - >(coder: new () => ModelCoder): ArrayCoder { - return new ArrayCoder(coder); + createArrayCoder( + coderFactory: () => Converter, + ): ArrayCoder { + return new ArrayCoder(coderFactory); + } + + createPaywallProductArrayCoder(): ArrayCoder { + return new ArrayCoder(() => this.createPaywallProductCoder()); } createHashmapCoder>(coder: T | null): HashmapCoder { diff --git a/src/coders/parse.ts b/src/coders/parse.ts index bdfe555..ec15817 100644 --- a/src/coders/parse.ts +++ b/src/coders/parse.ts @@ -167,7 +167,7 @@ function getCoder( case 'BridgeError': return new BridgeErrorCoder(); case 'Array': - return new ArrayCoder(AdaptyPaywallProductCoder as any); + return new ArrayCoder(() => new AdaptyPaywallProductCoder()); case 'String': return null; } From 8dd92498041587e7e0b99cf6cb90bcebd4055a81 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 18:05:52 +0300 Subject: [PATCH 18/50] feat: add tsdown output from env var --- .env.example | 8 ++++++++ .gitignore | 2 ++ AGENTS.md | 24 ++++++++++++++++++++++++ package.json | 3 ++- tsdown.config.ts | 6 +++++- yarn.lock | 5 +++++ 6 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..df3799a --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Custom build output directory (optional) +# If not set, defaults to './dist' +# Example for building to sibling repo: +# BUILD_OUT_DIR=../AdaptySDK-React-Native-Devtools/pure-rn/node_modules/@adapty/core/dist +# Example for React Native SDK: +# BUILD_OUT_DIR=../AdaptySDK-React-Native/node_modules/@adapty/core/dist + +BUILD_OUT_DIR=../AdaptySDK-React-Native/node_modules/@adapty/core/dist diff --git a/.gitignore b/.gitignore index 9935272..9091b12 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ yarn-error.log* .env .env.local .env.*.local + +.claude/skills/* \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 7a6888c..407deb1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,6 +147,30 @@ Published files (via `files` in package.json): Source TypeScript files (`src/`) are NOT published - only compiled output. +## Local Development Configuration + +### Custom Build Output Directory + +For local development with downstream SDKs, you can configure a custom build output directory using the `BUILD_OUT_DIR` environment variable: + +1. **Create `.env` file** in the repository root (already gitignored) +2. **Set output path** to your target repository: + ```bash + # For React Native Devtools + BUILD_OUT_DIR=../AdaptySDK-React-Native-Devtools/pure-rn/node_modules/@adapty/core/dist + + # For React Native SDK + BUILD_OUT_DIR=../AdaptySDK-React-Native/node_modules/@adapty/core/dist + ``` +3. **Run watch mode:** `yarn build:watch` + +**How it works:** +- `tsdown.config.ts` reads `BUILD_OUT_DIR` from `.env` via `dotenv` package +- If not set, defaults to `./dist` +- See `.env.example` for configuration examples + +This allows real-time development: changes in core automatically rebuild into the downstream SDK without manual copying or publishing. + ## Versioning - **Dev builds:** `0.0.0-dev.` (40-char commit SHA) diff --git a/package.json b/package.json index 0468acf..405179f 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,10 @@ "tsc": "tsc --noEmit" }, "devDependencies": { + "@types/jest": "^29.5.3", "@typescript-eslint/eslint-plugin": "^8.21.0", "@typescript-eslint/parser": "^8.21.0", - "@types/jest": "^29.5.3", + "dotenv": "^17.2.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "jest": "^29.3.1", diff --git a/tsdown.config.ts b/tsdown.config.ts index ed7c235..444337c 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,10 +1,14 @@ import { defineConfig } from 'tsdown'; +import { config } from 'dotenv'; + +// Load environment variables from .env file +config(); export default defineConfig({ entry: ['./src/index.ts'], format: ['esm', 'cjs'], dts: true, - outDir: 'dist', + outDir: process.env['BUILD_OUT_DIR'] || 'dist', fixedExtension: true, platform: 'neutral', target: 'es2020', diff --git a/yarn.lock b/yarn.lock index d775719..7713a8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1410,6 +1410,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dotenv@^17.2.3: + version "17.2.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" + integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== + dts-resolver@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/dts-resolver/-/dts-resolver-1.2.0.tgz#1495d8319f28e41a5369c6d6d05c2cb8ba790ad1" From 48447989b87d07ee56e26db2389a8eb6b6907254 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 18:24:14 +0300 Subject: [PATCH 19/50] fix ts --- src/coders/adapty-paywall.test.ts | 2 +- src/coders/adapty-profile.test.ts | 2 +- src/coders/adapty-profile.ts | 2 +- src/coders/adapty-subscription-offer.test.ts | 2 +- src/coders/factory.ts | 1 - 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coders/adapty-paywall.test.ts b/src/coders/adapty-paywall.test.ts index c5f92c2..7cd5e8a 100644 --- a/src/coders/adapty-paywall.test.ts +++ b/src/coders/adapty-paywall.test.ts @@ -1,4 +1,4 @@ -import type { AdaptyPaywall, ProductReference } from '@/types'; +import type { AdaptyPaywall } from '@/types'; import type { Def } from '@/types/schema'; import { AdaptyPaywallCoder } from './adapty-paywall'; import { ProductReferenceCoder } from './product-reference'; diff --git a/src/coders/adapty-profile.test.ts b/src/coders/adapty-profile.test.ts index bcdf2f1..3ca729a 100644 --- a/src/coders/adapty-profile.test.ts +++ b/src/coders/adapty-profile.test.ts @@ -1,5 +1,5 @@ import type { Def } from '@/types/schema'; -import type { AdaptyNonSubscription, AdaptyProfile } from '@/types'; +import type { AdaptyProfile } from '@/types'; import { AdaptyProfileCoder } from './adapty-profile'; import { AdaptyAccessLevelCoder } from './adapty-access-level'; import { HashmapCoder } from './hashmap'; diff --git a/src/coders/adapty-profile.ts b/src/coders/adapty-profile.ts index 44004ac..8b2f81d 100644 --- a/src/coders/adapty-profile.ts +++ b/src/coders/adapty-profile.ts @@ -1,4 +1,4 @@ -import type { AdaptyNonSubscription, AdaptyProfile } from '../types'; +import type { AdaptyProfile } from '../types'; import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; diff --git a/src/coders/adapty-subscription-offer.test.ts b/src/coders/adapty-subscription-offer.test.ts index c9f48b0..f955ded 100644 --- a/src/coders/adapty-subscription-offer.test.ts +++ b/src/coders/adapty-subscription-offer.test.ts @@ -1,4 +1,4 @@ -import type { AdaptyDiscountPhase, AdaptySubscriptionOffer } from '@/types'; +import type { AdaptySubscriptionOffer } from '@/types'; import type { Def } from '@/types/schema'; import { AdaptyDiscountPhaseCoder } from './adapty-discount-phase'; import { ArrayCoder } from './array'; diff --git a/src/coders/factory.ts b/src/coders/factory.ts index 58fc5de..bedd90d 100644 --- a/src/coders/factory.ts +++ b/src/coders/factory.ts @@ -37,7 +37,6 @@ import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state import { AdaptyUiOnboardingStateUpdatedActionCoder } from './adapty-ui-onboarding-state-updated-action'; import { ArrayCoder } from './array'; import { BridgeErrorCoder } from './bridge-error'; -import type { SimpleCoder } from './coder'; import { DateCoder } from './date'; import { HashmapCoder } from './hashmap'; import { JSONCoder } from './json'; From 115486f260c833c79caece17aceb52112629043d Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 18:37:45 +0300 Subject: [PATCH 20/50] feat: add pre-commit hook with lint-staged Configure husky for git hooks and lint-staged to automatically run ESLint and Prettier on staged TypeScript files before commit. --- .husky/pre-commit | 1 + package.json | 11 +- yarn.lock | 275 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 285 insertions(+), 2 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/package.json b/package.json index 405179f..6d133c8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "format": "prettier ./src -w", "format-check": "prettier ./src --check", "test": "jest", - "tsc": "tsc --noEmit" + "tsc": "tsc --noEmit", + "prepare": "husky" }, "devDependencies": { "@types/jest": "^29.5.3", @@ -43,7 +44,9 @@ "dotenv": "^17.2.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", + "husky": "^9.1.7", "jest": "^29.3.1", + "lint-staged": "^15.2.11", "prettier": "^3.0.0", "ts-jest": "^29.1.1", "tsdown": "^0.9.0", @@ -55,6 +58,12 @@ "publishConfig": { "access": "public" }, + "lint-staged": { + "*.ts": [ + "eslint --fix", + "prettier --write" + ] + }, "repository": { "type": "git", "url": "git+https://github.com/adaptyteam/AdaptySDK-JS-Core.git" diff --git a/yarn.lock b/yarn.lock index 7713a8a..e117971 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1058,11 +1058,23 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-escapes@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.2.0.tgz#31b25afa3edd3efc09d98c2fee831d460ff06b49" + integrity sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw== + dependencies: + environment "^1.0.0" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -1075,6 +1087,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.0.0, ansi-styles@^6.2.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + ansis@^3.17.0: version "3.17.0" resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" @@ -1266,6 +1283,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.4.1: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -1288,6 +1310,21 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + dependencies: + restore-cursor "^5.0.0" + +cli-truncate@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" + integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== + dependencies: + slice-ansi "^5.0.0" + string-width "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1319,6 +1356,16 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1433,6 +1480,11 @@ emittery@^0.13.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== +emoji-regex@^10.3.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.6.0.tgz#bf3d6e8f7f8fd22a65d9703475bc0147357a6b0d" + integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1443,6 +1495,11 @@ empathic@^1.0.0: resolved "https://registry.yarnpkg.com/empathic/-/empathic-1.1.0.tgz#a0de7dcaab07695bcab54117116d44c92b89e79f" integrity sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA== +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + error-ex@^1.3.1: version "1.3.4" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" @@ -1570,6 +1627,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter3@^5.0.1: + version "5.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" + integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -1585,6 +1647,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -1704,6 +1781,11 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0, get-east-asian-width@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz#9bc4caa131702b4b61729cb7e42735bc550c9ee6" + integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -1714,6 +1796,11 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-tsconfig@^4.10.0: version "4.13.0" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" @@ -1796,6 +1883,16 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +husky@^9.1.7: + version "9.1.7" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" + integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -1862,6 +1959,18 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-fullwidth-code-point@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz#046b2a6d4f6b156b2233d3207d4b5a9783999b98" + integrity sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== + dependencies: + get-east-asian-width "^1.3.1" + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" @@ -1889,6 +1998,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2459,11 +2573,44 @@ lightningcss@^1.29.3: lightningcss-win32-arm64-msvc "1.31.1" lightningcss-win32-x64-msvc "1.31.1" +lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +lint-staged@^15.2.11: + version "15.5.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.5.2.tgz#beff028fd0681f7db26ffbb67050a21ed4d059a3" + integrity sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w== + dependencies: + chalk "^5.4.1" + commander "^13.1.0" + debug "^4.4.0" + execa "^8.0.1" + lilconfig "^3.1.3" + listr2 "^8.2.5" + micromatch "^4.0.8" + pidtree "^0.6.0" + string-argv "^0.3.2" + yaml "^2.7.0" + +listr2@^8.2.5: + version "8.3.3" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.3.3.tgz#815fc8f738260ff220981bf9e866b3e11e8121bf" + integrity sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ== + dependencies: + cli-truncate "^4.0.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^6.1.0" + rfdc "^1.4.1" + wrap-ansi "^9.0.0" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2488,6 +2635,17 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +log-update@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + dependencies: + ansi-escapes "^7.0.0" + cli-cursor "^5.0.0" + slice-ansi "^7.1.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2526,7 +2684,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -micromatch@^4.0.4: +micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -2539,6 +2697,16 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2595,6 +2763,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2609,6 +2784,20 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -2721,6 +2910,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -2746,6 +2940,11 @@ picomatch@^4.0.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + pirates@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" @@ -2856,11 +3055,24 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + dependencies: + onetime "^7.0.0" + signal-exit "^4.1.0" + reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== +rfdc@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -2939,6 +3151,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -2949,6 +3166,22 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +slice-ansi@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.2.tgz#adf7be70aa6d72162d907cd0e6d5c11f507b5403" + integrity sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== + dependencies: + ansi-styles "^6.2.1" + is-fullwidth-code-point "^5.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -2974,6 +3207,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +string-argv@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -2991,6 +3229,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2998,6 +3245,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -3008,6 +3262,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -3264,6 +3523,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" + integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -3287,6 +3555,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" + integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From e5a1ebda554c4d2e118de754c6d751b03e58a668 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 19:14:29 +0300 Subject: [PATCH 21/50] refactor: apply prettier --- .prettierrc.json | 9 +++++---- src/coders/adapty-configuration.test.ts | 1 - src/coders/adapty-installation-details.ts | 8 ++++---- src/coders/adapty-installation-status.ts | 8 ++++---- src/coders/adapty-purchase-result.test.ts | 9 ++------- src/coders/array.ts | 9 +++++++-- src/coders/coder.ts | 3 +-- src/coders/error-coder.ts | 5 +++-- src/coders/factory.ts | 4 +++- src/coders/hashmap.ts | 7 ++++--- src/coders/types.ts | 16 ++++++++-------- src/types/api.d.ts | 4 ++-- src/types/paywall-events.ts | 3 +-- src/ui/types.ts | 4 ++-- 14 files changed, 46 insertions(+), 44 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 6bf50f2..0fb8603 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,8 +1,9 @@ { - "semi": true, - "singleQuote": true, - "trailingComma": "all", + "parser": "typescript", "printWidth": 80, + "singleQuote": true, "tabWidth": 2, - "arrowParens": "always" + "semi": true, + "trailingComma": "all", + "arrowParens": "avoid" } diff --git a/src/coders/adapty-configuration.test.ts b/src/coders/adapty-configuration.test.ts index 3a015d1..e7811a1 100644 --- a/src/coders/adapty-configuration.test.ts +++ b/src/coders/adapty-configuration.test.ts @@ -148,7 +148,6 @@ describe('AdaptyConfigurationCoder', () => { disk_storage_size_limit: 200 * 1024 * 1024, }, }); - }); it('should handle partial parameters', () => { diff --git a/src/coders/adapty-installation-details.ts b/src/coders/adapty-installation-details.ts index 527fcca..04044a5 100644 --- a/src/coders/adapty-installation-details.ts +++ b/src/coders/adapty-installation-details.ts @@ -3,10 +3,10 @@ import type { Def } from '@/types/schema'; import { Converter } from './types'; import { DateCoder } from './date'; -export class AdaptyInstallationDetailsCoder - implements - Converter -{ +export class AdaptyInstallationDetailsCoder implements Converter< + AdaptyInstallationDetails, + Def['AdaptyInstallationDetails'] +> { encode(model: AdaptyInstallationDetails): Def['AdaptyInstallationDetails'] { const result: Def['AdaptyInstallationDetails'] = { install_time: new DateCoder().encode(model.installTime), diff --git a/src/coders/adapty-installation-status.ts b/src/coders/adapty-installation-status.ts index d462193..9d3193d 100644 --- a/src/coders/adapty-installation-status.ts +++ b/src/coders/adapty-installation-status.ts @@ -3,10 +3,10 @@ import type { Def } from '@/types/schema'; import { Converter } from './types'; import { DateCoder } from './date'; -export class AdaptyInstallationStatusCoder - implements - Converter -{ +export class AdaptyInstallationStatusCoder implements Converter< + AdaptyInstallationStatus, + Def['AdaptyInstallationStatus'] +> { encode(model: AdaptyInstallationStatus): Def['AdaptyInstallationStatus'] { if (model.status === 'determined') { const details: Def['AdaptyInstallationDetails'] = { diff --git a/src/coders/adapty-purchase-result.test.ts b/src/coders/adapty-purchase-result.test.ts index bb3f62f..4bd54a1 100644 --- a/src/coders/adapty-purchase-result.test.ts +++ b/src/coders/adapty-purchase-result.test.ts @@ -203,10 +203,7 @@ const mocks: TestAdaptyPurchaseResultDef[] = [ { type: 'user_cancelled' }, ]; -function toModel( - mock: (typeof mocks)[number], - platformOS: PlatformOS, -): Model { +function toModel(mock: (typeof mocks)[number], platformOS: PlatformOS): Model { const _profile = new AdaptyProfileCoder({ OS: platformOS, } as IPlatformAdapter); @@ -229,9 +226,7 @@ function toModel( } describe('AdaptyPurchaseResultCoder', () => { - const resolvePlatformOS = ( - mock: (typeof mocks)[number], - ): PlatformOS => { + const resolvePlatformOS = (mock: (typeof mocks)[number]): PlatformOS => { if (mock.type === 'success' && mock.apple_jws_transaction) { return 'ios'; } diff --git a/src/coders/array.ts b/src/coders/array.ts index 372ea78..862fd3a 100644 --- a/src/coders/array.ts +++ b/src/coders/array.ts @@ -2,8 +2,13 @@ import { Converter } from './types'; // Coder for Array // Uses factory function to create coder instances (supports dependency injection) -export class ArrayCoder implements Converter { - constructor(private readonly coderFactory: () => Converter) {} +export class ArrayCoder implements Converter< + Model[], + Serializable[] +> { + constructor( + private readonly coderFactory: () => Converter, + ) {} decode(input: Serializable[]): Model[] { const coder = this.coderFactory(); diff --git a/src/coders/coder.ts b/src/coders/coder.ts index c108f89..0001fd3 100644 --- a/src/coders/coder.ts +++ b/src/coders/coder.ts @@ -7,8 +7,7 @@ export abstract class Coder< Model extends Record, CodableModel extends Partial, Serializable extends Record = Record, -> implements Converter -{ +> implements Converter { protected readonly platform: IPlatformAdapter; protected abstract properties: Properties; diff --git a/src/coders/error-coder.ts b/src/coders/error-coder.ts index 1cb59e9..5b4c389 100644 --- a/src/coders/error-coder.ts +++ b/src/coders/error-coder.ts @@ -1,8 +1,9 @@ import { AdaptyError } from '../adapty-error'; import { Converter } from './types'; -export interface ErrorConverter> - extends Converter { +export interface ErrorConverter< + Model extends Record, +> extends Converter { type: 'error'; getError: (data: Model) => AdaptyError; } diff --git a/src/coders/factory.ts b/src/coders/factory.ts index bedd90d..e3d0724 100644 --- a/src/coders/factory.ts +++ b/src/coders/factory.ts @@ -215,7 +215,9 @@ export class CoderFactory { return new ArrayCoder(() => this.createPaywallProductCoder()); } - createHashmapCoder>(coder: T | null): HashmapCoder { + createHashmapCoder>( + coder: T | null, + ): HashmapCoder { return new HashmapCoder(coder); } } diff --git a/src/coders/hashmap.ts b/src/coders/hashmap.ts index 7d31512..79e8a4f 100644 --- a/src/coders/hashmap.ts +++ b/src/coders/hashmap.ts @@ -1,9 +1,10 @@ import type { Converter } from './types'; // Coder for Record -export class HashmapCoder> - implements Converter, Record> -{ +export class HashmapCoder> implements Converter< + Record, + Record +> { private coder: T | null; constructor(coder: T | null) { diff --git a/src/coders/types.ts b/src/coders/types.ts index 6fd6637..6b43607 100644 --- a/src/coders/types.ts +++ b/src/coders/types.ts @@ -1,14 +1,14 @@ export type StrType = T extends string ? 'string' : T extends Date - ? 'string' // endoded JSON - : T extends boolean - ? 'boolean' - : T extends number - ? 'number' - : T extends any[] - ? 'array' - : 'object'; + ? 'string' // endoded JSON + : T extends boolean + ? 'boolean' + : T extends number + ? 'number' + : T extends any[] + ? 'array' + : 'object'; export interface Converter { type?: 'data' | 'error'; // identify error converters via type diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 8607d1d..07c5438 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -6,8 +6,8 @@ type XOR = T | U extends object type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] - ? OneOf<[XOR, ...Rest]> - : never; + ? OneOf<[XOR, ...Rest]> + : never; export type paths = Record; diff --git a/src/types/paywall-events.ts b/src/types/paywall-events.ts index 5790c2d..2691b7a 100644 --- a/src/types/paywall-events.ts +++ b/src/types/paywall-events.ts @@ -102,8 +102,7 @@ export interface PaywallDidFailLoadingProductsEvent extends BasePaywallEvent { error: AdaptyError; } -export interface PaywallDidFinishWebPaymentNavigationEvent - extends BasePaywallEvent { +export interface PaywallDidFinishWebPaymentNavigationEvent extends BasePaywallEvent { id: typeof PaywallEventId.DidFinishWebPaymentNavigation; product?: AdaptyPaywallProduct; error?: AdaptyError; diff --git a/src/ui/types.ts b/src/ui/types.ts index 90c2e52..36d6500 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -14,8 +14,8 @@ import { FileLocation, MakePurchaseParamsInput } from '@/types/inputs'; export type ArgType = T extends () => any ? void : T extends (arg: infer U) => any - ? U - : void; + ? U + : void; /** * EventHandler callback should not return a promise, From 2e674513a0d69f52eae178122db79518b2c609f8 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 19:15:53 +0300 Subject: [PATCH 22/50] feat: add GitHub Actions PR validation workflow Add automated PR validation workflow with: - Setup job for dependency caching (yarn 1.22.19 via corepack) - Validate job running tsc, prettier, eslint, tests, and build - Auto-assign reviewers from CODEOWNERS on PR open - Optimized for ubuntu-slim with pre-installed Node.js 24 - Concurrency control to cancel stale workflow runs Add CODEOWNERS file for automatic reviewer assignment --- .github/CODEOWNERS | 5 + .github/workflows/pr-validation.yml | 158 ++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/pr-validation.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0f94158 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# CODEOWNERS file for AdaptySDK-JS-Core +# These owners will be automatically requested for review when someone opens a pull request + +# Default owners for everything in the repo +* @StanislavMayorov diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..c3268b5 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,158 @@ +name: PR Validation + +on: + workflow_call: + pull_request: + branches: + - master + - dev + +concurrency: + group: pr-validation-${{ github.ref }} + cancel-in-progress: true + +jobs: + setup: + name: Setup Dependencies + runs-on: ubuntu-slim + outputs: + cache-hit: ${{ steps.cache.outputs.cache-hit }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache node_modules + id: cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + node-modules-${{ runner.os }}- + + - name: Enable Corepack + if: steps.cache.outputs.cache-hit != 'true' + run: | + corepack enable || { + echo "Corepack not available, falling back to npm install" + npm install -g yarn@1.22.19 + } + + - name: Prepare Yarn + if: steps.cache.outputs.cache-hit != 'true' + run: | + if command -v corepack > /dev/null 2>&1; then + corepack prepare yarn@1.22.19 --activate + fi + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile --prefer-offline + + validate: + name: Validate Code Quality + runs-on: ubuntu-slim + needs: setup + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Enable Corepack + run: | + corepack enable || { + echo "Corepack not available, falling back to npm install" + npm install -g yarn@1.22.19 + } + + - name: Prepare Yarn + run: | + if command -v corepack > /dev/null 2>&1; then + corepack prepare yarn@1.22.19 --activate + fi + + - name: Restore node_modules cache + uses: actions/cache/restore@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + node-modules-${{ runner.os }}- + fail-on-cache-miss: true + + - name: TypeScript type check + run: yarn tsc + + - name: Prettier format check + run: yarn format-check + + - name: ESLint check + run: yarn lint + + - name: Run tests + run: yarn test + + - name: Build verification + run: yarn build + + auto-assign-reviewers: + name: Auto-assign Reviewers + runs-on: ubuntu-slim + if: github.event.action == 'opened' + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Assign reviewers from CODEOWNERS + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = '.github/CODEOWNERS'; + + if (!fs.existsSync(path)) { + console.log('CODEOWNERS file not found, skipping auto-assignment'); + return; + } + + const content = fs.readFileSync(path, 'utf8'); + const lines = content.split('\n').filter(line => + line.trim() && !line.trim().startsWith('#') + ); + + const reviewers = new Set(); + + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length > 1) { + for (let i = 1; i < parts.length; i++) { + const owner = parts[i].replace('@', ''); + if (!owner.includes('/')) { + reviewers.add(owner); + } + } + } + } + + const prAuthor = context.payload.pull_request.user.login; + const finalReviewers = Array.from(reviewers).filter(r => r !== prAuthor); + + if (finalReviewers.length === 0) { + console.log('No reviewers found in CODEOWNERS'); + return; + } + + console.log(`Assigning reviewers: ${finalReviewers.join(', ')}`); + + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + reviewers: finalReviewers + }); + } catch (error) { + console.log(`Failed to assign reviewers: ${error.message}`); + } From 2be943ea091b98de13000533139731dfebd4f7e2 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 19:23:12 +0300 Subject: [PATCH 23/50] fix: add fallback dependency installation in validate job Remove fail-on-cache-miss to handle cache absence gracefully. Add fallback yarn install step that runs when cache is not found. This fixes the issue where workflow fails on first run or after cache expiration. --- .github/workflows/pr-validation.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index c3268b5..028a204 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -71,13 +71,17 @@ jobs: fi - name: Restore node_modules cache + id: cache-restore uses: actions/cache/restore@v4 with: path: node_modules key: node-modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} restore-keys: | node-modules-${{ runner.os }}- - fail-on-cache-miss: true + + - name: Install dependencies (fallback) + if: steps.cache-restore.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile --prefer-offline - name: TypeScript type check run: yarn tsc From c334fcece9deb997c3c14036da2a83edd7bd38eb Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 2 Feb 2026 20:05:31 +0300 Subject: [PATCH 24/50] ci: add github publish action. unified npm publish workflow with auto-detection and OIDC - Auto-detect release type based on git event (dev branch vs tag) - Extract auto-assign reviewers into separate workflow for reusability --- .github/workflows/pr-auto-assign.yml | 72 ++++++++++ .github/workflows/pr-validation.yml | 63 --------- .github/workflows/publish.yml | 168 ++++++++++++++++++++++++ AGENTS.md | 33 ++++- README.md | 64 --------- package.json | 2 +- src/coders/adapty-configuration.test.ts | 3 +- src/version.ts | 7 - 8 files changed, 269 insertions(+), 143 deletions(-) create mode 100644 .github/workflows/pr-auto-assign.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 src/version.ts diff --git a/.github/workflows/pr-auto-assign.yml b/.github/workflows/pr-auto-assign.yml new file mode 100644 index 0000000..a51180f --- /dev/null +++ b/.github/workflows/pr-auto-assign.yml @@ -0,0 +1,72 @@ +name: Auto-assign PR Reviewers + +on: + pull_request: + types: [opened] + branches: + - master + - dev + +permissions: + pull-requests: write + contents: read + +jobs: + auto-assign-reviewers: + name: Auto-assign Reviewers + runs-on: ubuntu-slim + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Assign reviewers from CODEOWNERS + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = '.github/CODEOWNERS'; + + if (!fs.existsSync(path)) { + console.log('CODEOWNERS file not found, skipping auto-assignment'); + return; + } + + const content = fs.readFileSync(path, 'utf8'); + const lines = content.split('\n').filter(line => + line.trim() && !line.trim().startsWith('#') + ); + + const reviewers = new Set(); + + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length > 1) { + for (let i = 1; i < parts.length; i++) { + const owner = parts[i].replace('@', ''); + if (!owner.includes('/')) { + reviewers.add(owner); + } + } + } + } + + const prAuthor = context.payload.pull_request.user.login; + const finalReviewers = Array.from(reviewers).filter(r => r !== prAuthor); + + if (finalReviewers.length === 0) { + console.log('No reviewers found in CODEOWNERS'); + return; + } + + console.log(`Assigning reviewers: ${finalReviewers.join(', ')}`); + + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + reviewers: finalReviewers + }); + } catch (error) { + console.log(`Failed to assign reviewers: ${error.message}`); + } diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 028a204..436ec95 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -97,66 +97,3 @@ jobs: - name: Build verification run: yarn build - - auto-assign-reviewers: - name: Auto-assign Reviewers - runs-on: ubuntu-slim - if: github.event.action == 'opened' - permissions: - pull-requests: write - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Assign reviewers from CODEOWNERS - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const path = '.github/CODEOWNERS'; - - if (!fs.existsSync(path)) { - console.log('CODEOWNERS file not found, skipping auto-assignment'); - return; - } - - const content = fs.readFileSync(path, 'utf8'); - const lines = content.split('\n').filter(line => - line.trim() && !line.trim().startsWith('#') - ); - - const reviewers = new Set(); - - for (const line of lines) { - const parts = line.trim().split(/\s+/); - if (parts.length > 1) { - for (let i = 1; i < parts.length; i++) { - const owner = parts[i].replace('@', ''); - if (!owner.includes('/')) { - reviewers.add(owner); - } - } - } - } - - const prAuthor = context.payload.pull_request.user.login; - const finalReviewers = Array.from(reviewers).filter(r => r !== prAuthor); - - if (finalReviewers.length === 0) { - console.log('No reviewers found in CODEOWNERS'); - return; - } - - console.log(`Assigning reviewers: ${finalReviewers.join(', ')}`); - - try { - await github.rest.pulls.requestReviewers({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - reviewers: finalReviewers - }); - } catch (error) { - console.log(`Failed to assign reviewers: ${error.message}`); - } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..02e9946 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,168 @@ +name: Publish to npm + +on: + # Trigger on push to dev branch (dev builds) + push: + branches: + - dev + # Trigger on git tags (production releases) + tags: + - 'v*' + + # Manual workflow dispatch with options + workflow_dispatch: + inputs: + release-type: + description: 'Release type' + type: choice + required: true + default: 'dev' + options: + - 'dev' + - 'production' + dry-run: + description: 'Perform a dry run (no actual publish)' + type: choice + required: true + default: 'true' + options: + - 'true' + - 'false' + +permissions: + id-token: write # Required for npm provenance (OIDC) + contents: read + +concurrency: + group: publish-${{ github.ref }}-${{ github.event.inputs.release-type || 'auto' }} + cancel-in-progress: false + +jobs: + # Reuse validation workflow + validation: + name: Run Validation + uses: ./.github/workflows/pr-validation.yml + + # Publish job - runs after validation + publish: + name: Publish Package + runs-on: ubuntu-slim + needs: validation + environment: publish + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for commit SHA + + - name: Enable Corepack + run: | + corepack enable || { + echo "Corepack not available, falling back to npm install" + npm install -g yarn@1.22.19 + } + + - name: Prepare Yarn + run: | + if command -v corepack > /dev/null 2>&1; then + corepack prepare yarn@1.22.19 --activate + fi + + - name: Cache node_modules + id: cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + restore-keys: | + node-modules-${{ runner.os }}- + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile --prefer-offline + + - name: Determine release type + id: release-type + run: | + if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == refs/heads/dev ]]; then + echo "type=dev" >> $GITHUB_OUTPUT + echo "📦 Detected: Dev build (push to dev branch)" + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == refs/tags/v* ]]; then + echo "type=production" >> $GITHUB_OUTPUT + echo "🚀 Detected: Production release (git tag)" + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "type=${{ github.event.inputs.release-type }}" >> $GITHUB_OUTPUT + echo "🔧 Detected: Manual workflow (${{ github.event.inputs.release-type }})" + else + echo "❌ Unknown release type" + exit 1 + fi + + - name: Update version for dev build + if: steps.release-type.outputs.type == 'dev' + run: | + # Get base version from package.json + BASE_VERSION=$(node -p "require('./package.json').version") + echo "Base version: $BASE_VERSION" + + # Get full commit SHA (40 characters) + COMMIT_SHA=$(git rev-parse HEAD) + echo "Commit SHA: $COMMIT_SHA" + + # Create dev version + DEV_VERSION="${BASE_VERSION}-dev.${COMMIT_SHA}" + echo "Dev version: $DEV_VERSION" + + # Update package.json (without git tag) + npm version $DEV_VERSION --no-git-tag-version + + # Verify version was updated + UPDATED_VERSION=$(node -p "require('./package.json').version") + echo "Updated version: $UPDATED_VERSION" + + - name: Build package + run: yarn build + + - name: Verify build outputs + run: | + echo "Checking dist directory..." + ls -la dist/ + echo "" + echo "Checking package files that will be published..." + npm pack --dry-run + + - name: Publish to npm (dry-run mode) + if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true' + run: | + echo "Running in dry-run mode - no actual publish" + if [[ "${{ steps.release-type.outputs.type }}" == "dev" ]]; then + npm publish --provenance --tag dev --dry-run --ignore-scripts + else + npm publish --provenance --dry-run --ignore-scripts + fi + + - name: Publish to npm (dev build) + if: | + steps.release-type.outputs.type == 'dev' && + github.event.inputs.dry-run != 'true' + run: npm publish --provenance --tag dev --ignore-scripts + + - name: Publish to npm (production release) + if: | + steps.release-type.outputs.type == 'production' && + github.event.inputs.dry-run != 'true' + run: npm publish --provenance --ignore-scripts + + - name: Output published version + if: github.event.inputs.dry-run != 'true' + run: | + PACKAGE_VERSION=$(node -p "require('./package.json').version") + echo "✅ Successfully published @adapty/core@${PACKAGE_VERSION}" + echo "Package URL: https://www.npmjs.com/package/@adapty/core/v/${PACKAGE_VERSION}" + + if [[ "${{ steps.release-type.outputs.type }}" == "dev" ]]; then + echo "Install with: npm install @adapty/core@dev" + else + echo "Install with: npm install @adapty/core" + fi diff --git a/AGENTS.md b/AGENTS.md index 407deb1..f23db63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,20 +173,39 @@ This allows real-time development: changes in core automatically rebuild into th ## Versioning -- **Dev builds:** `0.0.0-dev.` (40-char commit SHA) +- **Dev builds:** `-dev.` (40-char commit SHA) - Example: `0.0.0-dev.abc123...xyz789` - - Placeholder in repo: `0.0.0-dev.0000000000000000000000000000000000000000` - - CI must replace with actual git SHA before publishing + - Base version is read from `package.json` (e.g., `0.0.0`) + - CI adds `-dev.` suffix before publishing - **Production releases:** Semantic versioning (e.g., `1.0.0`, `1.0.1`) - Downstream SDKs should pin exact dev version when testing CI ## CI Publishing Policy - **Registry:** npmjs.com (public via `publishConfig.access: "public"`) -- **`dev` branch:** Publish after merge using dist-tag `dev` - - Command: `npm publish --tag dev` -- **`master` branch:** Publish ONLY on git tag (release) - - Command: `npm publish` (default `latest` tag) +- **Workflow:** `.github/workflows/publish.yml` - Unified workflow for both dev and production releases + - Auto-detects release type based on git event (push to `dev` branch vs push to tag) + - Supports manual workflow dispatch with release-type selection + - Includes dry-run mode for testing + +### Dev Builds (dev branch) + +- **Trigger:** Push to `dev` branch (or manual workflow dispatch with release-type: dev) +- **Version:** Automatic `-dev.` suffix (40-char full SHA) +- **Dist-tag:** `dev` +- **Command:** `npm publish --provenance --tag dev --ignore-scripts` +- **Install:** `npm install @adapty/core@dev` + +### Production Releases (git tags) + +- **Trigger:** Git tags matching `v*` pattern (e.g., `v1.0.0`) or manual workflow dispatch with release-type: production +- **Version:** Uses version from `package.json` directly +- **Dist-tag:** `latest` (default) +- **Command:** `npm publish --provenance --ignore-scripts` +- **Install:** `npm install @adapty/core` (installs latest) +- **Workflow:** Update version in `package.json`, then create and push git tag + +**Important:** The `--tag dev` flag ensures dev builds are NOT installed by default. Only production releases (published with `latest` tag) are installed when users run `npm install @adapty/core` without specifying a version. ## Package Manager & Tooling diff --git a/README.md b/README.md index 957cf5d..e5edb02 100644 --- a/README.md +++ b/README.md @@ -4,68 +4,6 @@ Platform-agnostic core for Adapty SDKs. This package contains shared TypeScript/JavaScript code used by Adapty React Native and Capacitor SDKs. -## Installation - -```bash -yarn install -``` - -## Development - -### Build - -```bash -yarn build -``` - -Builds the library to `dist/` folder with: -- ESM format (`index.mjs`) -- CommonJS format (`index.cjs`) -- TypeScript declarations (`.d.mts`, `.d.cts`) -- Source maps - -### Type Checking - -```bash -yarn tsc -``` - -### Linting - -```bash -yarn lint -``` - -### Formatting - -```bash -# Check formatting -yarn format-check - -# Fix formatting -yarn format -``` - -### Testing - -```bash -yarn test -``` - -## Project Structure - -``` -. -├── src/ # Source TypeScript files -├── dist/ # Build output (gitignored) -├── package.json # Package configuration -├── tsconfig.json # TypeScript configuration -├── tsdown.config.ts # Build tool configuration -├── .eslintrc.cjs # ESLint configuration -├── .prettierrc.json # Prettier configuration -└── jest.config.cjs # Jest configuration -``` - ## Publishing This package is published to npm as `@adapty/core`: @@ -73,8 +11,6 @@ This package is published to npm as `@adapty/core`: - **Dev builds**: `0.0.0-dev.` with `dev` dist-tag - **Production releases**: Semantic versioning with `latest` dist-tag -See `AGENTS.md` for detailed publishing policy. - ## License MIT \ No newline at end of file diff --git a/package.json b/package.json index 6d133c8..aba9179 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@adapty/core", - "version": "0.0.0-dev.0000000000000000000000000000000000000000", + "version": "3.15.0", "description": "Platform-agnostic core for Adapty JS SDKs", "license": "MIT", "author": "Adapty team ", diff --git a/src/coders/adapty-configuration.test.ts b/src/coders/adapty-configuration.test.ts index e7811a1..619b3dd 100644 --- a/src/coders/adapty-configuration.test.ts +++ b/src/coders/adapty-configuration.test.ts @@ -5,7 +5,8 @@ import type { } from '@/adapters/interfaces'; import { AdaptyConfigurationCoder } from '@/coders/adapty-configuration'; import { LogLevel } from '@/types/inputs'; -import version from '@/version'; + +const version = '1.0.0'; describe('AdaptyConfigurationCoder', () => { const sdkMetadata: ISdkMetadataAdapter = { diff --git a/src/version.ts b/src/version.ts deleted file mode 100644 index 838484c..0000000 --- a/src/version.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * SDK version placeholder - * This will be replaced by the actual version during build - */ -const version = '0.0.0-dev.0000000000000000000000000000000000000000'; - -export default version; From 996851c03505334ec9eb29f1b993a5e3c514856a Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 6 Feb 2026 19:44:38 +0300 Subject: [PATCH 25/50] feat: export Converter and ErrorConverter types for RN SDK migration --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 78f9592..f7a505c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,8 @@ export { export { CoderFactory } from './coders/factory'; export type { CoderDependencies } from './coders/factory'; +export type { Converter } from './coders/types'; +export type { ErrorConverter } from './coders/error-coder'; // Mass re-export all types from types folder export * from './types/index'; From c608c52ca1d9ea82edb52be451314dfad3559e5d Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 6 Feb 2026 20:13:01 +0300 Subject: [PATCH 26/50] feat: export AdaptyError and AdaptyErrorInput for RN SDK consumption Allows RN SDK to re-export AdaptyError from @adapty/core instead of maintaining its own copy, ensuring single class identity for instanceof checks. --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index f7a505c..eb46c84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,8 @@ export { DefaultLoggerAdapter, } from './adapters/defaults'; +export { AdaptyError } from './adapty-error'; +export type { AdaptyErrorInput } from './adapty-error'; export { CoderFactory } from './coders/factory'; export type { CoderDependencies } from './coders/factory'; export type { Converter } from './coders/types'; From af5dcbd604a1ad222f6e47f44894b1ad1a5ea55f Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 9 Feb 2026 08:45:13 -0800 Subject: [PATCH 27/50] refactor: remove platform-specific AddListenerGeneric/AddListenerFn from core These types depend on EmitterSubscription which is platform-specific. Each downstream SDK (RN, Capacitor) defines its own listener types with different event sets and subscription mechanisms. --- src/platform.ts | 7 ------- src/types/bridge.ts | 17 ----------------- 2 files changed, 24 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index abbc79e..70f373e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -16,10 +16,3 @@ export class Platform { Platform._os = value; } } - -/** - * EmitterSubscription mock for type compatibility - */ -export interface EmitterSubscription { - remove(): void; -} diff --git a/src/types/bridge.ts b/src/types/bridge.ts index f546f12..f01029f 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -1,7 +1,3 @@ -import type { EmitterSubscription } from '@/platform'; -import type { AdaptyProfile, AdaptyInstallationDetails } from '@/types'; -import type { AdaptyError } from '@/adapty-error'; - /** * Valid list of callable bridge handlers * Must be the same as @@ -84,16 +80,3 @@ interface EventMap { } export type UserEventName = keyof EventMap; - -export type AddListenerGeneric = ( - event: E, - callback: (data: Data) => void | Promise, -) => EmitterSubscription; - -export type AddListenerFn = - | AddListenerGeneric<'onLatestProfileLoad', AdaptyProfile> - | AddListenerGeneric< - 'onInstallationDetailsSuccess', - AdaptyInstallationDetails - > - | AddListenerGeneric<'onInstallationDetailsFail', AdaptyError>; From 1fe84a6620c434154c903e27e35074aa305ccada Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 9 Feb 2026 09:40:24 -0800 Subject: [PATCH 28/50] feat: export components type from api schema --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index eb46c84..b19e5db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,3 +38,5 @@ export * from './types/error'; export * from './types/bridge'; export * from './types/paywall-events'; export * from './types/onboarding-events'; + +export type { components } from './types/api'; From 6bdb7b709614af4d38157ad0637e6e8cf22f54fe Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 9 Feb 2026 20:59:17 +0300 Subject: [PATCH 29/50] feat: move cap logger as is --- src/logger/console-sink.ts | 28 +++++++ src/logger/index.ts | 6 ++ src/logger/log-context.ts | 114 ++++++++++++++++++++++++++++ src/logger/log-scope.ts | 46 +++++++++++ src/logger/log.ts | 151 +++++++++++++++++++++++++++++++++++++ src/logger/logger.mock.ts | 35 +++++++++ src/logger/types.ts | 23 ++++++ 7 files changed, 403 insertions(+) create mode 100644 src/logger/console-sink.ts create mode 100644 src/logger/log-context.ts create mode 100644 src/logger/log-scope.ts create mode 100644 src/logger/log.ts create mode 100644 src/logger/logger.mock.ts create mode 100644 src/logger/types.ts diff --git a/src/logger/console-sink.ts b/src/logger/console-sink.ts new file mode 100644 index 0000000..96f2585 --- /dev/null +++ b/src/logger/console-sink.ts @@ -0,0 +1,28 @@ +import type { LogSink } from './types'; +import type { LogLevel } from '../types/inputs'; + +function getConsoleForLevel( + level: LogLevel, +): (message?: any, ...optionalParams: any[]) => void { + /* eslint-disable no-console */ + switch (level) { + case 'error': + return console.error; + case 'warn': + return console.warn; + case 'verbose': + return console.debug; + case 'info': + default: + return console.info; + } + /* eslint-enable no-console */ +} + +export const consoleLogSink: LogSink = { + id: 'console', + handle: event => { + const logger = getConsoleForLevel(event.level); + logger(event.formatted, event.params); + }, +}; diff --git a/src/logger/index.ts b/src/logger/index.ts index ef5267c..eae671e 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -63,3 +63,9 @@ export class NoopLoggerAdapter implements ILoggerAdapter { return new LogContext(); } } + +export { LogContext } from './log-context'; +export { Log } from './log'; +export { LogScope } from './log-scope'; +export type { LogSink, LoggerConfig, LogEvent } from './types'; +export { consoleLogSink } from './console-sink'; diff --git a/src/logger/log-context.ts b/src/logger/log-context.ts new file mode 100644 index 0000000..2d213e8 --- /dev/null +++ b/src/logger/log-context.ts @@ -0,0 +1,114 @@ +import { Log } from './log'; +import { LogScope } from './log-scope'; +import type { LogArgs } from './log-scope'; +export interface ScopeArgs { + methodName: string; +} + +// Scope of a single stack trace record +type Scope = 'EVENT' | 'CALL' | 'ENCODE' | 'BRIDGE' | 'DECODE'; + +// Displays an interface for a single stack trace record +interface Trace { + action: Scope; + fn: string; + payload: LogArgs; + error?: boolean; + done?: boolean; +} + +// LogContext instance is a logger context for a single call to a native SDK method +// It accumulates logs for each step of the call +export class LogContext { + public stack: Trace[] = []; + + private createScope(step: Scope, args: ScopeArgs, message: string): LogScope { + return new LogScope({ + ...args, + onStart: payload => { + this.stack.push({ action: step, fn: args.methodName, payload }); + + Log.verbose( + args.methodName, + () => `${message}...`, + () => payload(), + ); + }, + onSuccess: payload => { + this.stack.push({ + action: step, + fn: args.methodName, + payload, + done: true, + }); + + Log.verbose( + args.methodName, + () => `${message}: OK`, + () => payload(), + ); + }, + onFailed: payload => { + this.stack.push({ + action: step, + fn: args.methodName, + payload, + error: true, + }); + + const resolvedStack = this.stack.map(s => ({ + ...s, + payload: s.payload(), + })); + const p = payload() as Record; + p['__stack__'] = resolvedStack; + Log.error( + args.methodName, + () => `${message}: FAILED`, + () => p, + ); + }, + onWait: payload => { + this.stack.push({ action: step, fn: args.methodName, payload }); + Log.verbose( + args.methodName, + () => ` ${message}`, + () => payload(), + ); + }, + onWaitComplete: payload => { + this.stack.push({ action: step, fn: args.methodName, payload }); + Log.verbose( + args.methodName, + () => ` ${message}`, + () => payload(), + ); + }, + }); + } + + // Creates a scope for a event listener + public event(method: ScopeArgs): LogScope { + return this.createScope( + 'EVENT', + method, + `Receiving event "${method.methodName}"`, + ); + } + // Create a scope for a public call + public call(args: ScopeArgs): LogScope { + return this.createScope('CALL', args, 'Calling method'); + } + // Creates a scope for encoding an object + public encode(args: ScopeArgs): LogScope { + return this.createScope('ENCODE', args, 'Encoding object'); + } + // Creates a scope for calling a bridge function + public bridge(args: ScopeArgs): LogScope { + return this.createScope('BRIDGE', args, 'Calling bridge function'); + } + // Creates a scope for decoding a string + public decode(args: ScopeArgs): LogScope { + return this.createScope('DECODE', args, 'Decoding string'); + } +} diff --git a/src/logger/log-scope.ts b/src/logger/log-scope.ts new file mode 100644 index 0000000..27ad133 --- /dev/null +++ b/src/logger/log-scope.ts @@ -0,0 +1,46 @@ +import { ScopeArgs } from './log-context'; + +export type LogArgs = () => Record; +type LogCallback = (LazyParams: LogArgs) => void; + +interface LogScopeConstructor extends ScopeArgs { + onStart: LogCallback; + onSuccess: LogCallback; + onFailed: LogCallback; + onWait: LogCallback; + onWaitComplete: LogCallback; +} + +export class LogScope { + public methodName: string; + private onStart: LogCallback; + private onSuccess: LogCallback; + private onFailed: LogCallback; + private onWait: LogCallback; + private onWaitComplete: LogCallback; + + constructor(args: LogScopeConstructor) { + this.methodName = args.methodName; + this.onStart = args.onStart; + this.onSuccess = args.onSuccess; + this.onFailed = args.onFailed; + this.onWait = args.onWait; + this.onWaitComplete = args.onWaitComplete; + } + + public start(args: LogArgs) { + this.onStart(args); + } + public wait(args: LogArgs) { + this.onWait(args); + } + public waitComplete(args: LogArgs) { + this.onWaitComplete(args); + } + public success(args: LogArgs) { + this.onSuccess(args); + } + public failed(args: LogArgs) { + this.onFailed(args); + } +} diff --git a/src/logger/log.ts b/src/logger/log.ts new file mode 100644 index 0000000..eac3671 --- /dev/null +++ b/src/logger/log.ts @@ -0,0 +1,151 @@ +import { LogLevel } from '../types/inputs'; +import type { LogEvent, LogSink, LoggerConfig } from './types'; +import { consoleLogSink } from './console-sink'; + +import VERSION from '../../version'; + +// Type for lazy evaluation functions +type LazyMessage = () => string; +type LazyParams = () => Record; + +export class Log { + public static logLevel: LogLevel | null = null; + private static sinks: LogSink[] = [consoleLogSink]; + private static defaultMeta?: LoggerConfig['defaultMeta']; + + // Formats a message for logging + private static formatMessage(message: string, funcName: string): string { + const now = new Date().toISOString(); + const version = VERSION; + + return `[${now}] [adapty@${version}] "${funcName}": ${message}`; + } + + /** Configure JS logger: replace sinks and/or set default metadata */ + public static configure(config: LoggerConfig): void { + if (config.sinks) { + for (const sink of this.sinks) sink.destroy?.(); + this.sinks = config.sinks.slice(); + } + this.defaultMeta = config.defaultMeta; + } + + /** Register additional sink */ + public static addSink(sink: LogSink): void { + this.sinks.push(sink); + } + + /** Remove sink by id and call its destroy if present */ + public static removeSink(id: string): void { + const sink = this.sinks.find(sink => sink.id === id); + this.sinks = this.sinks.filter(sink => sink.id !== id); + sink?.destroy?.(); + } + + /** Clear all sinks and destroy them */ + public static clearSinks(): void { + const prev = this.sinks; + this.sinks = []; + for (const sink of prev) sink.destroy?.(); + } + + /** + * Gets the appropriate log level integer for a log level. + * @internal + */ + private static getLogLevelInt(logLevel: LogLevel): number { + switch (logLevel) { + case LogLevel.ERROR: + return 0; + case LogLevel.WARN: + return 1; + case LogLevel.INFO: + return 2; + case LogLevel.VERBOSE: + return 3; + } + } + + /** + * Logs a message to the console if the log level is appropriate. + * Uses lazy evaluation to avoid unnecessary computations. + * @internal + */ + public static log( + logLevel: LogLevel, + funcName: string, + message: LazyMessage, + params?: LazyParams, + ): void { + if (!Log.logLevel) { + return; + } + + const currentLevel = Log.getLogLevelInt(Log.logLevel); + const messageLevel = Log.getLogLevelInt(logLevel); + + if (messageLevel <= currentLevel) { + // Lazy evaluation: only compute once per entry + const resolvedMessage = message(); + const resolvedParams = params ? params() : undefined; + const mergedParams = this.defaultMeta + ? { + ...(this.defaultMeta as Record), + ...(resolvedParams ?? {}), + } + : resolvedParams; + + const formatted = Log.formatMessage(resolvedMessage, funcName); + const event: LogEvent = { + timestamp: new Date().toISOString(), + version: String(VERSION), + level: logLevel, + funcName, + message: resolvedMessage, + params: mergedParams, + formatted, + }; + + for (const sink of this.sinks) { + if (sink.levels && !sink.levels.includes(logLevel)) continue; + try { + sink.handle(event); + } catch { + // ignore sink errors + } + } + } + } + + public static info( + funcName: string, + message: LazyMessage, + params?: LazyParams, + ): void { + this.log(LogLevel.INFO, funcName, message, params); + } + + public static warn( + funcName: string, + message: LazyMessage, + params?: LazyParams, + ): void { + this.log(LogLevel.WARN, funcName, message, params); + } + + public static error( + funcName: string, + message: LazyMessage, + params?: LazyParams, + ): void { + this.log(LogLevel.ERROR, funcName, message, params); + } + + public static verbose( + funcName: string, + message: LazyMessage, + params?: LazyParams, + ): void { + this.log(LogLevel.VERBOSE, funcName, message, params); + } +} diff --git a/src/logger/logger.mock.ts b/src/logger/logger.mock.ts new file mode 100644 index 0000000..a46a873 --- /dev/null +++ b/src/logger/logger.mock.ts @@ -0,0 +1,35 @@ +export const mockLogger = { + LogContext: jest.fn().mockImplementation(() => ({ + call: jest.fn().mockReturnValue({ + start: jest.fn(), + success: jest.fn(), + failed: jest.fn(), + }), + event: jest.fn().mockReturnValue({ + start: jest.fn(), + success: jest.fn(), + failed: jest.fn(), + }), + })), + Log: jest.fn(), + LogScope: jest.fn(), + consoleLogSink: jest.fn(), +}; + +export function createMockLogContext() { + const mockLog = { + start: jest.fn(), + success: jest.fn(), + failed: jest.fn(), + }; + + const mockLogContextInstance = { + call: jest.fn().mockReturnValue(mockLog), + event: jest.fn().mockReturnValue(mockLog), + }; + + return { + mockLog, + mockLogContext: mockLogContextInstance, + }; +} diff --git a/src/logger/types.ts b/src/logger/types.ts new file mode 100644 index 0000000..6edb17c --- /dev/null +++ b/src/logger/types.ts @@ -0,0 +1,23 @@ +import type { LogLevel } from '../types/inputs'; + +export interface LogEvent { + timestamp: string; + version: string; + level: LogLevel; + funcName: string; + message: string; + params?: Record; + formatted: string; +} + +export interface LogSink { + id: string; + levels?: LogLevel[]; + handle: (event: LogEvent) => void; + destroy?: () => void; +} + +export interface LoggerConfig { + sinks?: LogSink[]; + defaultMeta?: Record; +} From 0d16d2bdf5b37f281a8df153b6f15aa4c9de9a9a Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Mon, 9 Feb 2026 10:21:44 -0800 Subject: [PATCH 30/50] =?UTF-8?q?refactor:=20unify=20logger=20=E2=80=94=20?= =?UTF-8?q?export=20Capacitor-based=20implementation=20from=20@adapty/core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clean up logger/index.ts: remove duplicate noop classes, keep real implementation re-exports - Create src/version.ts, fix VERSION import path in log.ts - Update ILogScope/ILogContext interfaces to use lazy LogArgs type - Move NoopLoggerAdapter to adapters/defaults.ts (now DefaultLoggerAdapter) - Export Log, LogContext, LogScope, LogSink, LoggerConfig, LogEvent, consoleLogSink from main index - Update coders/parse.ts call sites to use lazy args --- src/adapters/defaults.ts | 13 ++++++-- src/adapters/interfaces.ts | 15 ++++++--- src/coders/parse.ts | 6 ++-- src/index.ts | 4 +++ src/logger/index.ts | 66 -------------------------------------- src/logger/log-scope.ts | 3 +- src/logger/log.ts | 2 +- src/version.ts | 1 + 8 files changed, 33 insertions(+), 77 deletions(-) create mode 100644 src/version.ts diff --git a/src/adapters/defaults.ts b/src/adapters/defaults.ts index b36ad21..0fde3c4 100644 --- a/src/adapters/defaults.ts +++ b/src/adapters/defaults.ts @@ -1,10 +1,12 @@ import type { IPlatformAdapter, ISdkMetadataAdapter, + ILoggerAdapter, + ILogContext, PlatformOS, } from './interfaces'; import { Platform } from '@/platform'; -import { NoopLoggerAdapter } from '@/logger'; +import { LogContext } from '@/logger'; /** * Default platform adapter with unknown OS @@ -29,4 +31,11 @@ export class DefaultSdkMetadataAdapter implements ISdkMetadataAdapter { ) {} } -export { NoopLoggerAdapter as DefaultLoggerAdapter }; +/** + * Default logger adapter using the real LogContext implementation + */ +export class DefaultLoggerAdapter implements ILoggerAdapter { + createContext(): ILogContext { + return new LogContext(); + } +} diff --git a/src/adapters/interfaces.ts b/src/adapters/interfaces.ts index 4edc0dd..db92338 100644 --- a/src/adapters/interfaces.ts +++ b/src/adapters/interfaces.ts @@ -24,6 +24,11 @@ export interface ISdkMetadataAdapter { readonly sdkVersion: string; } +/** + * Lazy-evaluated log arguments + */ +export type LogArgs = () => Record; + /** * Logger adapter interface for platform-specific logging */ @@ -41,7 +46,7 @@ export interface ScopeArgs { export interface LogTrace { action: string; fn: string; - payload: Record; + payload: LogArgs; error?: boolean; done?: boolean; } @@ -63,7 +68,9 @@ export interface ILogContext { * Log scope interface for logging at execution stages */ export interface ILogScope { - start(payload?: Record): void; - failed(message?: string): void; - success(payload?: Record): void; + start(args: LogArgs): void; + failed(args: LogArgs): void; + success(args: LogArgs): void; + wait?(args: LogArgs): void; + waitComplete?(args: LogArgs): void; } diff --git a/src/coders/parse.ts b/src/coders/parse.ts index ec15817..387f3fd 100644 --- a/src/coders/parse.ts +++ b/src/coders/parse.ts @@ -60,7 +60,7 @@ export function parseMethodResult( ctx?: LogContext, ): T { const log = ctx?.decode({ methodName: 'parseMethodResult' }); - log?.start({ input }); + log?.start(() => ({ input })); let obj: AdaptyResult; @@ -74,7 +74,7 @@ export function parseMethodResult( }`, ); - log?.failed(adaptyError.message); + log?.failed(() => ({ error: adaptyError.message })); throw adaptyError; } @@ -101,7 +101,7 @@ export function parseMethodResult( const adaptyError = AdaptyError.failedToDecode( `Failed to decode native response. Response does not have expected "success" or "error" property`, ); - log?.failed(adaptyError.message); + log?.failed(() => ({ error: adaptyError.message })); throw adaptyError; } } diff --git a/src/index.ts b/src/index.ts index b19e5db..a3645d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export type { ILoggerAdapter, ILogContext, ILogScope, + LogArgs, LogTrace, PlatformOS, ScopeArgs, @@ -24,6 +25,9 @@ export { DefaultLoggerAdapter, } from './adapters/defaults'; +export { Log, LogContext, LogScope, consoleLogSink } from './logger'; +export type { LogSink, LoggerConfig, LogEvent } from './logger'; + export { AdaptyError } from './adapty-error'; export type { AdaptyErrorInput } from './adapty-error'; export { CoderFactory } from './coders/factory'; diff --git a/src/logger/index.ts b/src/logger/index.ts index eae671e..6123505 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,69 +1,3 @@ -/** - * Logger placeholder for platform-agnostic code - * Actual logging implementation should be provided by the platform SDK - */ - -import type { - ILoggerAdapter, - ILogContext, - ILogScope, - LogTrace, - ScopeArgs, -} from '@/adapters/interfaces'; - -/** - * LogScope provides methods for logging at different stages of execution - */ -export class LogScope implements ILogScope { - start(_payload?: Record): void { - // Noop - actual implementation provided by platform SDK - } - - failed(_message?: string): void { - // Noop - actual implementation provided by platform SDK - } - - success(_payload?: Record): void { - // Noop - actual implementation provided by platform SDK - } -} - -/** - * LogContext accumulates logs for each step of a call - */ -export class LogContext implements ILogContext { - public stack: LogTrace[] = []; - - decode(_args: ScopeArgs): ILogScope { - return new LogScope(); - } - - encode(_args: ScopeArgs): ILogScope { - return new LogScope(); - } - - call(_args: ScopeArgs): ILogScope { - return new LogScope(); - } - - bridge(_args: ScopeArgs): ILogScope { - return new LogScope(); - } - - event(_args: ScopeArgs): ILogScope { - return new LogScope(); - } -} - -/** - * Noop logger adapter - doesn't log anything - */ -export class NoopLoggerAdapter implements ILoggerAdapter { - createContext(): ILogContext { - return new LogContext(); - } -} - export { LogContext } from './log-context'; export { Log } from './log'; export { LogScope } from './log-scope'; diff --git a/src/logger/log-scope.ts b/src/logger/log-scope.ts index 27ad133..2373d01 100644 --- a/src/logger/log-scope.ts +++ b/src/logger/log-scope.ts @@ -1,6 +1,7 @@ +import type { LogArgs } from '@/adapters/interfaces'; import { ScopeArgs } from './log-context'; -export type LogArgs = () => Record; +export type { LogArgs }; type LogCallback = (LazyParams: LogArgs) => void; interface LogScopeConstructor extends ScopeArgs { diff --git a/src/logger/log.ts b/src/logger/log.ts index eac3671..4a945c0 100644 --- a/src/logger/log.ts +++ b/src/logger/log.ts @@ -2,7 +2,7 @@ import { LogLevel } from '../types/inputs'; import type { LogEvent, LogSink, LoggerConfig } from './types'; import { consoleLogSink } from './console-sink'; -import VERSION from '../../version'; +import VERSION from '../version'; // Type for lazy evaluation functions type LazyMessage = () => string; diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..ca96c9c --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export default '0.0.0-dev.0000000000000000000000000000000000000000'; From e9e7b6762238a557118e82ade27ff1266a51312f Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 03:44:20 -0800 Subject: [PATCH 31/50] feat(core): add Log.setVersion() and export LogArgs type - Add static _version field with setVersion() for SDK version injection - Update formatMessage() and LogEvent to use _version instead of hardcoded VERSION - Export LogArgs type from logger/index.ts --- src/logger/index.ts | 1 + src/logger/log.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/logger/index.ts b/src/logger/index.ts index 6123505..95e7c83 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,5 +1,6 @@ export { LogContext } from './log-context'; export { Log } from './log'; export { LogScope } from './log-scope'; +export type { LogArgs } from './log-scope'; export type { LogSink, LoggerConfig, LogEvent } from './types'; export { consoleLogSink } from './console-sink'; diff --git a/src/logger/log.ts b/src/logger/log.ts index 4a945c0..8131169 100644 --- a/src/logger/log.ts +++ b/src/logger/log.ts @@ -12,13 +12,18 @@ export class Log { public static logLevel: LogLevel | null = null; private static sinks: LogSink[] = [consoleLogSink]; private static defaultMeta?: LoggerConfig['defaultMeta']; + private static _version: string = VERSION; + + /** Override the version shown in log messages (e.g. SDK version instead of core version) */ + public static setVersion(version: string): void { + this._version = version; + } // Formats a message for logging private static formatMessage(message: string, funcName: string): string { const now = new Date().toISOString(); - const version = VERSION; - return `[${now}] [adapty@${version}] "${funcName}": ${message}`; + return `[${now}] [adapty@${this._version}] "${funcName}": ${message}`; } /** Configure JS logger: replace sinks and/or set default metadata */ @@ -98,7 +103,7 @@ export class Log { const formatted = Log.formatMessage(resolvedMessage, funcName); const event: LogEvent = { timestamp: new Date().toISOString(), - version: String(VERSION), + version: this._version, level: logLevel, funcName, message: resolvedMessage, From 2ac8578c6ffb6d42b8985e402afa03faa0f2a97b Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 05:54:50 -0800 Subject: [PATCH 32/50] chore: remove unused version.ts, clarify adapty-handler.ts purpose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit version.ts was dead code — not imported anywhere in core. adapty-handler.ts is a JSDoc-only placeholder, comment updated to reflect that. --- src/adapty-handler.ts | 10 +++------- src/version.ts | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 src/version.ts diff --git a/src/adapty-handler.ts b/src/adapty-handler.ts index 3364baf..9100847 100644 --- a/src/adapty-handler.ts +++ b/src/adapty-handler.ts @@ -1,9 +1,5 @@ /** - * Adapty handler type placeholder - * This is used for documentation links only + * Placeholder interface kept solely for {@link Adapty} references in JSDoc comments. + * Actual implementation is provided by platform SDKs (React Native, Capacitor). */ - -export interface Adapty { - // Placeholder interface for documentation - // Actual implementation provided by platform SDK -} +export interface Adapty {} diff --git a/src/version.ts b/src/version.ts deleted file mode 100644 index ca96c9c..0000000 --- a/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export default '0.0.0-dev.0000000000000000000000000000000000000000'; From fb236f0e3f93f846992fcf8e29a64ea78d0c67ee Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 16:55:46 +0300 Subject: [PATCH 33/50] feat: move utils from cap --- src/utils/compact-object.test.ts | 132 ++++++++++ src/utils/compact-object.ts | 10 + src/utils/map-values.test.ts | 166 +++++++++++++ src/utils/map-values.ts | 15 ++ src/utils/merge-options.test.ts | 357 +++++++++++++++++++++++++++ src/utils/merge-options.ts | 106 ++++++++ src/utils/with-error-context.test.ts | 211 ++++++++++++++++ src/utils/with-error-context.ts | 44 ++++ 8 files changed, 1041 insertions(+) create mode 100644 src/utils/compact-object.test.ts create mode 100644 src/utils/compact-object.ts create mode 100644 src/utils/map-values.test.ts create mode 100644 src/utils/map-values.ts create mode 100644 src/utils/merge-options.test.ts create mode 100644 src/utils/merge-options.ts create mode 100644 src/utils/with-error-context.test.ts create mode 100644 src/utils/with-error-context.ts diff --git a/src/utils/compact-object.test.ts b/src/utils/compact-object.test.ts new file mode 100644 index 0000000..9a5d52a --- /dev/null +++ b/src/utils/compact-object.test.ts @@ -0,0 +1,132 @@ +import { filterUndefined } from './compact-object'; + +describe('filterUndefined', () => { + it('should remove undefined properties', () => { + const input = { + a: 1, + b: undefined, + c: 'hello', + d: undefined, + }; + + const result = filterUndefined(input); + + expect(result).toEqual({ + a: 1, + c: 'hello', + }); + }); + + it('should preserve all falsy values except undefined', () => { + const input = { + null_value: null, + false_value: false, + zero_value: 0, + empty_string: '', + undefined_value: undefined, + truthy_value: 'test', + }; + + const result = filterUndefined(input); + + expect(result).toEqual({ + null_value: null, + false_value: false, + zero_value: 0, + empty_string: '', + truthy_value: 'test', + }); + }); + + it('should handle empty object', () => { + const input = {}; + const result = filterUndefined(input); + expect(result).toEqual({}); + }); + + it('should handle object with all undefined values', () => { + const input = { + a: undefined, + b: undefined, + c: undefined, + }; + + const result = filterUndefined(input); + expect(result).toEqual({}); + }); + + it('should handle object with no undefined values', () => { + const input = { + a: 1, + b: 'test', + c: true, + d: { nested: 'object' }, + }; + + const result = filterUndefined(input); + expect(result).toEqual(input); + }); + + it('should preserve nested objects (without deep filtering)', () => { + const input = { + a: 1, + b: undefined, + nested: { + x: 1, + y: undefined, // This should remain as the function doesn't do deep filtering + }, + }; + + const result = filterUndefined(input); + + expect(result).toEqual({ + a: 1, + nested: { + x: 1, + y: undefined, + }, + }); + }); + + it('should preserve array values', () => { + const input = { + array: [1, undefined, 'test', null], + b: undefined, + c: 'value', + }; + + const result = filterUndefined(input); + + expect(result).toEqual({ + array: [1, undefined, 'test', null], + c: 'value', + }); + }); + + it('should maintain type structure', () => { + interface TestType { + required: string; + optional?: number; + nullable: string | null; + } + + const input: TestType = { + required: 'test', + optional: undefined, + nullable: null, + }; + + const result = filterUndefined(input); + + // Type check - result should still be of TestType + expect(result).toEqual({ + required: 'test', + nullable: null, + }); + + // Verify properties exist or don't exist + expect('required' in result).toBe(true); + expect('optional' in result).toBe(false); + expect('nullable' in result).toBe(true); + }); +}); diff --git a/src/utils/compact-object.ts b/src/utils/compact-object.ts new file mode 100644 index 0000000..2799e28 --- /dev/null +++ b/src/utils/compact-object.ts @@ -0,0 +1,10 @@ +/** + * Removes undefined properties from an object, preserving the original type structure + * @param obj - Source object with potentially undefined values + * @returns Object with undefined values filtered out + */ +export function filterUndefined>(obj: T): T { + return Object.fromEntries( + Object.entries(obj).filter(([_, value]) => value !== undefined), + ) as T; +} diff --git a/src/utils/map-values.test.ts b/src/utils/map-values.test.ts new file mode 100644 index 0000000..3c1925d --- /dev/null +++ b/src/utils/map-values.test.ts @@ -0,0 +1,166 @@ +import { mapValues } from './map-values'; + +describe('mapValues', () => { + it('should transform all values using the provided function', () => { + const input = { + a: 1, + b: 2, + c: 3, + }; + + const result = mapValues(input, value => value * 2); + + expect(result).toEqual({ + a: 2, + b: 4, + c: 6, + }); + }); + + it('should pass both value and key to the transformer function', () => { + const input = { + foo: 10, + bar: 20, + }; + + const result = mapValues(input, (value, key) => `${key}:${value}`); + + expect(result).toEqual({ + foo: 'foo:10', + bar: 'bar:20', + }); + }); + + it('should handle empty object', () => { + const input = {}; + const result = mapValues(input, value => value); + expect(result).toEqual({}); + }); + + it('should preserve object structure with mixed types', () => { + const input = { + str: 'hello', + num: 42, + bool: true, + obj: { nested: 'value' }, + arr: [1, 2, 3], + }; + + const result = mapValues(input, value => value); + + expect(result).toEqual(input); + }); + + it('should handle transformation to different types', () => { + const input = { + a: 1, + b: 2, + c: 3, + }; + + const result = mapValues(input, (value, key) => ({ + original: value, + key: key, + })); + + expect(result).toEqual({ + a: { original: 1, key: 'a' }, + b: { original: 2, key: 'b' }, + c: { original: 3, key: 'c' }, + }); + }); + + it('should handle nullable values', () => { + const input = { + a: null, + b: undefined, + c: 'value', + }; + + const result = mapValues(input, value => + value === null ? 'null' : value === undefined ? 'undefined' : value, + ); + + expect(result).toEqual({ + a: 'null', + b: 'undefined', + c: 'value', + }); + }); + + it('should work with function values', () => { + const fn1 = () => 'one'; + const fn2 = () => 'two'; + + const input = { + func1: fn1, + func2: fn2, + }; + + const result = mapValues(input, (fn, key) => { + if (typeof fn === 'function') { + return () => `${key}:${fn()}`; + } + return fn; + }); + + expect(typeof result.func1).toBe('function'); + expect(typeof result.func2).toBe('function'); + expect(result.func1()).toBe('func1:one'); + expect(result.func2()).toBe('func2:two'); + }); + + it('should maintain type structure', () => { + interface TestType { + id: number; + name: string; + active: boolean; + } + + const input: TestType = { + id: 1, + name: 'Test', + active: true, + }; + + const result = mapValues(input, (value, key) => { + if (key === 'id') return (value as number) * 10; + if (key === 'name') return (value as string).toUpperCase(); + return value; + }); + + expect(result).toEqual({ + id: 10, + name: 'TEST', + active: true, + }); + }); + + it('should handle objects with symbol keys gracefully', () => { + const sym = Symbol('test'); + const input = { + regular: 'value', + [sym]: 'symbol-value', + } as any; + + // Object.entries does not include symbol keys + const result = mapValues(input, value => `transformed:${value}`); + + expect(result.regular).toBe('transformed:value'); + // Symbol keys are not transformed by Object.entries + expect(result[sym]).toBeUndefined(); + }); + + it('should not mutate the original object', () => { + const input = { + a: 1, + b: 2, + }; + + const inputCopy = { ...input }; + + mapValues(input, value => value * 2); + + expect(input).toEqual(inputCopy); + }); +}); diff --git a/src/utils/map-values.ts b/src/utils/map-values.ts new file mode 100644 index 0000000..2157c67 --- /dev/null +++ b/src/utils/map-values.ts @@ -0,0 +1,15 @@ +/** + * Maps over values of an object, transforming each value using the provided function. + * + * @param obj - The object to map over + * @param fn - Function to transform each value + * @returns New object with transformed values + */ +export function mapValues>( + obj: T, + fn: (value: T[keyof T], key: keyof T) => any, +): T { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key, fn(value, key)]), + ) as T; +} diff --git a/src/utils/merge-options.test.ts b/src/utils/merge-options.test.ts new file mode 100644 index 0000000..3746805 --- /dev/null +++ b/src/utils/merge-options.test.ts @@ -0,0 +1,357 @@ +import { mergeOptions } from './merge-options'; + +describe('mergeOptions', () => { + describe('basic merging', () => { + it('should merge simple objects', () => { + const defaults = { a: 1, b: 2 }; + const options = { b: 3, c: 4 }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ a: 1, b: 3, c: 4 }); + }); + + it('should use defaults when options is empty', () => { + const defaults = { a: 1, b: 2 }; + const options = {}; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual(defaults); + }); + + it('should use options when defaults is empty', () => { + const defaults = {}; + const options = { a: 1, b: 2 }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual(options); + }); + + it('should return empty object when both are empty', () => { + const result = mergeOptions({}, {}); + + expect(result).toEqual({}); + }); + }); + + describe('deep merging', () => { + it('should merge nested objects', () => { + const defaults = { + config: { + timeout: 5000, + retries: 3, + }, + debug: true, + }; + + const options = { + config: { + timeout: 10000, + }, + endpoint: 'https://api.example.com', + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + config: { + timeout: 10000, + retries: 3, + }, + debug: true, + endpoint: 'https://api.example.com', + }); + }); + + it('should merge deeply nested objects', () => { + const defaults = { + level1: { + level2: { + level3: { + value: 'default', + other: 42, + }, + }, + }, + }; + + const options = { + level1: { + level2: { + level3: { + value: 'custom', + }, + }, + }, + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + level1: { + level2: { + level3: { + value: 'custom', + other: 42, + }, + }, + }, + }); + }); + }); + + describe('array handling', () => { + it('should replace arrays rather than merge them', () => { + const defaults = { items: [1, 2, 3] }; + const options = { items: [4, 5] }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ items: [4, 5] }); + }); + + it('should handle arrays in nested objects', () => { + const defaults = { + config: { + tags: ['default'], + settings: { values: [1, 2] }, + }, + }; + + const options = { + config: { + tags: ['custom', 'new'], + }, + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + config: { + tags: ['custom', 'new'], + settings: { values: [1, 2] }, + }, + }); + }); + }); + + describe('null and undefined handling', () => { + it('should handle null values', () => { + const defaults = { a: 1, b: null }; + const options = { a: null, c: 3 }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ a: null, b: null, c: 3 }); + }); + + it('should handle undefined values', () => { + const defaults = { a: 1, b: undefined }; + const options = { a: undefined, c: 3 }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ a: undefined, b: undefined, c: 3 }); + }); + + it('should handle null in nested objects', () => { + const defaults = { + config: { + value: 'default', + nullable: null, + }, + }; + + const options = { + config: { + value: null, + }, + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + config: { + value: null, + nullable: null, + }, + }); + }); + }); + + describe('primitive value types', () => { + it('should handle different primitive types', () => { + const defaults = { + string: 'default', + number: 42, + boolean: false, + bigint: BigInt(123), + }; + + const options = { + string: 'custom', + number: 100, + boolean: true, + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + string: 'custom', + number: 100, + boolean: true, + bigint: BigInt(123), + }); + }); + + it('should handle symbol values', () => { + const sym1 = Symbol('default'); + const sym2 = Symbol('custom'); + + const defaults = { symbol: sym1 }; + const options = { symbol: sym2 }; + + const result = mergeOptions(options, defaults); + + expect(result.symbol).toBe(sym2); + }); + }); + + describe('complex scenarios', () => { + it('should merge complex configuration objects', () => { + const defaults = { + api: { + baseUrl: 'https://default.com', + timeout: 5000, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'DefaultAgent/1.0', + }, + }, + features: { + analytics: true, + debug: false, + }, + endpoints: ['/users', '/posts'], + }; + + const options = { + api: { + baseUrl: 'https://custom.com', + headers: { + Authorization: 'Bearer token', + 'User-Agent': 'CustomAgent/2.0', + }, + }, + features: { + debug: true, + }, + endpoints: ['/custom'], + }; + + const result = mergeOptions(options, defaults); + + expect(result).toEqual({ + api: { + baseUrl: 'https://custom.com', + timeout: 5000, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'CustomAgent/2.0', + }, + }, + features: { + analytics: true, + debug: true, + }, + endpoints: ['/custom'], + }); + }); + + it('should handle function values', () => { + const defaultFn = () => 'default'; + const customFn = () => 'custom'; + + const defaults = { callback: defaultFn }; + const options = { callback: customFn }; + + const result = mergeOptions(options, defaults); + + expect(result.callback).toBe(customFn); + expect(result.callback()).toBe('custom'); + }); + + it('should handle Date objects', () => { + const defaultDate = new Date('2023-01-01'); + const customDate = new Date('2024-01-01'); + + const defaults = { timestamp: defaultDate }; + const options = { timestamp: customDate }; + + const result = mergeOptions(options, defaults); + + expect(result.timestamp).toBe(customDate); + }); + }); + + describe('type safety', () => { + interface TestConfig { + name: string; + count: number; + enabled: boolean; + } + + it('should maintain type safety with generics', () => { + const defaults = { name: 'default', count: 0, enabled: false }; + const options = { name: 'test', count: 5 }; + + const result = mergeOptions(options, defaults); + + expect(result.name).toBe('test'); + expect(result.count).toBe(5); + expect(result.enabled).toBe(false); + }); + }); + + describe('edge cases', () => { + it('should handle objects with prototype pollution attempts', () => { + const maliciousOptions = JSON.parse('{"__proto__": {"polluted": true}}'); + const defaults = { safe: true }; + + const result = mergeOptions(maliciousOptions, defaults); + + expect(result.safe).toBe(true); + expect((result as any).__proto__.polluted).toBeUndefined(); + }); + + it('should handle circular references in objects', () => { + const defaults = { a: 1 }; + const options: any = { b: 2 }; + options.circular = options; + + // Should not throw error due to circular reference + expect(() => mergeOptions(options, defaults)).toThrow(); + }); + + it('should handle very large objects', () => { + const largeDefaults: any = {}; + const largeOptions: any = {}; + + for (let i = 0; i < 1000; i++) { + largeDefaults[`key${i}`] = `default${i}`; + if (i % 2 === 0) { + largeOptions[`key${i}`] = `custom${i}`; + } + } + + const result = mergeOptions(largeOptions, largeDefaults); + + expect(Object.keys(result)).toHaveLength(1000); + expect(result.key0).toBe('custom0'); + expect(result.key1).toBe('default1'); + }); + }); +}); diff --git a/src/utils/merge-options.ts b/src/utils/merge-options.ts new file mode 100644 index 0000000..814fd80 --- /dev/null +++ b/src/utils/merge-options.ts @@ -0,0 +1,106 @@ +/** + * Checks if a value is a plain object (not an array, null, Date, etc.) + */ +function isPlainObject(value: any): boolean { + return ( + value !== null && + typeof value === 'object' && + !Array.isArray(value) && + !(value instanceof Date) && + !(value instanceof RegExp) && + Object.prototype.toString.call(value) === '[object Object]' + ); +} + +/** + * Deep merge implementation that replaces arrays and merges objects + */ +function deepMerge(target: any, source: any, seen = new Set()): any { + // Handle null/undefined cases + if (source === null || source === undefined) { + return source; + } + + if (target === null || target === undefined) { + return source; + } + + // Handle arrays - replace them (source overwrites target) + if (Array.isArray(source)) { + return source; + } + + // If target is array but source is not, return source + if (Array.isArray(target)) { + return source; + } + + // Handle plain objects - merge recursively + if (isPlainObject(target) && isPlainObject(source)) { + // Check for circular references + if (seen.has(source) || seen.has(target)) { + throw new Error('Converting circular structure to JSON'); + } + + seen.add(source); + seen.add(target); + + const result: any = {}; + + // Get all keys from both objects + const allKeys = new Set([...Object.keys(target), ...Object.keys(source)]); + + for (const key of allKeys) { + // Skip prototype pollution attempts + if (key === '__proto__' || key === 'constructor' || key === 'prototype') { + continue; + } + + if (key in source) { + if (key in target) { + // Check for circular reference in values + if (seen.has(source[key]) || seen.has(target[key])) { + throw new Error('Converting circular structure to JSON'); + } + // Recursively merge if key exists in both + result[key] = deepMerge(target[key], source[key], seen); + } else { + // Check for circular reference in source value + if (seen.has(source[key])) { + throw new Error('Converting circular structure to JSON'); + } + // Use source value if key only exists in source + result[key] = source[key]; + } + } else { + // Check for circular reference in target value + if (seen.has(target[key])) { + throw new Error('Converting circular structure to JSON'); + } + // Use target value if key only exists in target + result[key] = target[key]; + } + } + + seen.delete(source); + seen.delete(target); + + return result; + } + + // For all other cases (primitives, functions, dates, etc.), return source + return source; +} + +/** + * Universal method for merging options with defaults + * @param options - input options + * @param defaults - default values + * @returns merged object with TResult type + */ +export function mergeOptions( + options: any, + defaults: any, +): TResult { + return deepMerge(defaults, options) as TResult; +} diff --git a/src/utils/with-error-context.test.ts b/src/utils/with-error-context.test.ts new file mode 100644 index 0000000..d990100 --- /dev/null +++ b/src/utils/with-error-context.test.ts @@ -0,0 +1,211 @@ +import { withErrorContext } from './with-error-context'; + +describe('withErrorContext', () => { + it('should return the callback result when no error is thrown', () => { + const callback = (a: number, b: number) => a + b; + const wrapped = withErrorContext(callback, 'testHandler', 'TestSource'); + + const result = wrapped(2, 3); + + expect(result).toBe(5); + }); + + it('should pass through all arguments to the original callback', () => { + const mockCallback = jest.fn( + (a: string, b: number, c: boolean) => `${a}-${b}-${c}`, + ); + const wrapped = withErrorContext(mockCallback, 'testHandler', 'TestSource'); + + wrapped('test', 42, true); + + expect(mockCallback).toHaveBeenCalledWith('test', 42, true); + expect(mockCallback).toHaveBeenCalledTimes(1); + }); + + it('should preserve this context', () => { + const obj = { + value: 42, + getValue: function () { + return this.value; + }, + }; + + const wrapped = withErrorContext(obj.getValue, 'getValue', 'TestObject'); + + const result = wrapped.call(obj); + + expect(result).toBe(42); + }); + + it('should wrap Error exceptions with context message', () => { + const originalError = new Error('Original error message'); + const callback = () => { + throw originalError; + }; + + const wrapped = withErrorContext(callback, 'failingHandler', 'ErrorSource'); + + expect(() => wrapped()).toThrow(); + + try { + wrapped(); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe( + "Unhandled exception in user's handler in ErrorSource.failingHandler", + ); + expect((error as any).originalError).toBe(originalError); + } + }); + + it('should handle non-Error exceptions', () => { + const callback = () => { + throw 'string error'; + }; + + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + try { + wrapped(); + fail('Should have thrown an error'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe( + "Unhandled exception in user's handler in Source.handler", + ); + expect((error as any).originalError).toBe('string error'); + } + }); + + it('should handle object exceptions', () => { + const objectError = { code: 123, message: 'Custom error object' }; + const callback = () => { + throw objectError; + }; + + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + try { + wrapped(); + fail('Should have thrown an error'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as any).originalError).toBe(objectError); + } + }); + + it('should format error message with source and handlerName', () => { + const callback = () => { + throw new Error('test'); + }; + + const wrapped = withErrorContext( + callback, + 'onPurchaseCompleted', + 'PaywallViewController', + ); + + try { + wrapped(); + fail('Should have thrown an error'); + } catch (error) { + expect((error as Error).message).toBe( + "Unhandled exception in user's handler in PaywallViewController.onPurchaseCompleted", + ); + } + }); + + it('should handle callbacks with no arguments', () => { + const callback = () => 'no args'; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + const result = wrapped(); + + expect(result).toBe('no args'); + }); + + it('should handle callbacks that return undefined', () => { + const callback = () => undefined; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + const result = wrapped(); + + expect(result).toBeUndefined(); + }); + + it('should handle callbacks that return null', () => { + const callback = () => null; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + const result = wrapped(); + + expect(result).toBeNull(); + }); + + it('should handle callbacks that return objects', () => { + const returnValue = { success: true, data: [1, 2, 3] }; + const callback = () => returnValue; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + const result = wrapped(); + + expect(result).toBe(returnValue); + }); + + it('should handle async callbacks', async () => { + const callback = async (value: number) => { + return Promise.resolve(value * 2); + }; + + const wrapped = withErrorContext(callback, 'asyncHandler', 'AsyncSource'); + + const result = await wrapped(21); + + expect(result).toBe(42); + }); + + it('should handle async callbacks that reject', async () => { + const callback = async () => { + throw new Error('Async error'); + }; + + const wrapped = withErrorContext(callback, 'asyncHandler', 'AsyncSource'); + + // Note: withErrorContext wraps synchronous try/catch, but doesn't wrap Promise rejections + // The callback executes successfully and returns a rejected Promise + // To properly handle async errors, the caller needs to catch the rejection + await expect(wrapped()).rejects.toThrow('Async error'); + }); + + it('should be callable multiple times', () => { + let counter = 0; + const callback = () => ++counter; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + expect(wrapped()).toBe(1); + expect(wrapped()).toBe(2); + expect(wrapped()).toBe(3); + }); + + it('should handle callbacks with complex argument types', () => { + interface ComplexArg { + id: number; + data: { nested: string }; + fn: () => void; + } + + const callback = (arg: ComplexArg) => arg.data.nested; + const wrapped = withErrorContext(callback, 'handler', 'Source'); + + const complexArg: ComplexArg = { + id: 1, + data: { nested: 'value' }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + fn: () => {}, + }; + + const result = wrapped(complexArg); + + expect(result).toBe('value'); + }); +}); diff --git a/src/utils/with-error-context.ts b/src/utils/with-error-context.ts new file mode 100644 index 0000000..d6fb41c --- /dev/null +++ b/src/utils/with-error-context.ts @@ -0,0 +1,44 @@ +type AnyCallback = (...args: any[]) => any; + +function toJSON(this: Error & { originalError: any }) { + return { + message: this.message, + name: this.name, + stack: this.stack, + originalError: + this.originalError instanceof Error + ? { + message: this.originalError.message, + name: this.originalError.name, + } + : this.originalError, + }; +} +/** + * Wraps a user-provided callback with error context enrichment. + * Catches exceptions and wraps them with source and handler information. + */ +export function withErrorContext( + callback: T, + handlerName: string, + source: string, +): T & AnyCallback { + const wrapped = function ( + this: unknown, + ...args: Parameters + ): ReturnType { + try { + return callback.apply(this, args); + } catch (error) { + const message = `Unhandled exception in user's handler in ${source}.${handlerName}`; + + const wrappedError = new Error(message); + (wrappedError as any).originalError = error; + (wrappedError as any).toJSON = toJSON; + + throw wrappedError; + } + }; + + return wrapped as T & AnyCallback; +} From c139e0cc4ff4a96ad2c96b5525dde999a6df797e Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 16:56:12 +0300 Subject: [PATCH 34/50] feat: move utils from RN --- src/utils/generate-id.test.ts | 35 +++++++++++++++++++++++++++++++++++ src/utils/generate-id.ts | 11 +++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/utils/generate-id.test.ts create mode 100644 src/utils/generate-id.ts diff --git a/src/utils/generate-id.test.ts b/src/utils/generate-id.test.ts new file mode 100644 index 0000000..34c359c --- /dev/null +++ b/src/utils/generate-id.test.ts @@ -0,0 +1,35 @@ +import { generateId } from '@/utils/generate-id'; + +describe('generateId', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns predictable id with mocked Math.random', () => { + const values = [0.1, 0.2, 0.3]; + let idx = 0; + jest + .spyOn(Math, 'random') + .mockImplementation(() => values[idx++ % values.length]!); + const id = generateId(); + const expected = values + .map(v => v.toString(36).slice(2)) + .join('') + .slice(0, 12); + expect(id).toBe(expected); + }); + + it('generates unique values across multiple calls', () => { + const count = 200; + const ids = new Set(); + for (let i = 0; i < count; i++) { + ids.add(generateId()); + } + expect(ids.size).toBe(count); + }); + + it('matches expected format', () => { + const id = generateId(); + expect(/^[a-z0-9]{12}$/.test(id)).toBe(true); + }); +}); diff --git a/src/utils/generate-id.ts b/src/utils/generate-id.ts new file mode 100644 index 0000000..fbeb932 --- /dev/null +++ b/src/utils/generate-id.ts @@ -0,0 +1,11 @@ +/** + * Generates a short unique identifier. + * Format: 12 lowercase alphanumeric characters + */ +export function generateId(): string { + let id = ''; + while (id.length < 12) { + id += Math.random().toString(36).slice(2); + } + return id.slice(0, 12); +} From 3e893bb5bde3fc77df0bab94b622178300035cf3 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 06:51:50 -0800 Subject: [PATCH 35/50] fix(core): remove broken VERSION import in logger Replace VERSION import (from deleted version.ts) with 'unknown' default. SDK overrides this via Log.setVersion() anyway. --- src/logger/log.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/logger/log.ts b/src/logger/log.ts index 8131169..dbf8469 100644 --- a/src/logger/log.ts +++ b/src/logger/log.ts @@ -2,8 +2,6 @@ import { LogLevel } from '../types/inputs'; import type { LogEvent, LogSink, LoggerConfig } from './types'; import { consoleLogSink } from './console-sink'; -import VERSION from '../version'; - // Type for lazy evaluation functions type LazyMessage = () => string; type LazyParams = () => Record; @@ -12,7 +10,7 @@ export class Log { public static logLevel: LogLevel | null = null; private static sinks: LogSink[] = [consoleLogSink]; private static defaultMeta?: LoggerConfig['defaultMeta']; - private static _version: string = VERSION; + private static _version: string = 'unknown'; /** Override the version shown in log messages (e.g. SDK version instead of core version) */ public static setVersion(version: string): void { From 4881b47a11e4233b49baf27ef3f3c8f397dec834 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 18:01:50 +0300 Subject: [PATCH 36/50] feat(core): add Log.setVersion() and export LogArgs type - Add static _version field with setVersion() (@internal) for SDK version injection - Update formatMessage() and LogEvent to use _version instead of hardcoded VERSION - Export LogArgs type from logger/index.ts --- cross_platform.yaml | 1630 +++++++++++++++++++++++++++++++++++++++++++ src/logger/log.ts | 5 +- 2 files changed, 1634 insertions(+), 1 deletion(-) create mode 100644 cross_platform.yaml diff --git a/cross_platform.yaml b/cross_platform.yaml new file mode 100644 index 0000000..1ad5b9a --- /dev/null +++ b/cross_platform.yaml @@ -0,0 +1,1630 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://adapty.io/crossPlatform/3.15.2/schema" +title: "Cross Platform Format" + +$requests: + ### activate ### + Activate.Request: #request + type: object + required: [method, configuration] + properties: + method: { const: "activate" } + configuration: { $ref: "#/$defs/AdaptyConfiguration" } + + Activate.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### createPaywallView ### + AdaptyUICreatePaywallView.Request: #request + type: object + required: + - method + - paywall + properties: + method: { const: "adapty_ui_create_paywall_view" } + paywall: { $ref: "#/$defs/AdaptyPaywall" } + load_timeout: { type: number, description: "seconds" } + preload_products: { type: boolean, default: false } + custom_tags: { $ref: "#/$defs/AdaptyUI.CustomTagsValues" } + custom_timers: { $ref: "#/$defs/AdaptyUI.CustomTimersValues" } + custom_assets: { $ref: "#/$defs/AdaptyUI.CustomAssets" } + product_purchase_parameters: + { $ref: "#/$defs/AdaptyUI.ProductPurchaseParameters" } + + AdaptyUICreatePaywallView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyUI.PaywallView" } + + ### createOnboardingView ### + AdaptyUICreateOnboardingView.Request: #request + type: object + required: + - method + - onboarding + properties: + method: { const: "adapty_ui_create_onboarding_view" } + onboarding: { $ref: "#/$defs/AdaptyOnboarding" } + external_urls_presentation: { $ref: "#/$defs/AdaptyWebPresentation" } + + AdaptyUICreateOnboardingView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + + ### dismissOnboardingView ### + AdaptyUIDismissOnboardingView.Request: #request + type: object + required: [method, id] + properties: + method: { const: "adapty_ui_dismiss_onboarding_view" } + id: { type: string, description: "View Id" } + destroy: { type: boolean, default: false } + + AdaptyUIDismissOnboardingView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### dismissPaywallView ### + AdaptyUIDismissPaywallView.Request: #request + type: object + required: [method, id] + properties: + method: { const: "adapty_ui_dismiss_paywall_view" } + id: { type: string, description: "View Id" } + destroy: { type: boolean, default: false } + + AdaptyUIDismissPaywallView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### presentOnboardingView ### + AdaptyUIPresentOnboardingView.Request: #request + type: object + required: [method, id] + properties: + method: { const: "adapty_ui_present_onboarding_view" } + id: { type: string, description: "View Id" } + ios_presentation_style: { $ref: "#/$defs/AdaptyUI.IOSPresentationStyle" } + + AdaptyUIPresentOnboardingView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### presentPaywallView ### + AdaptyUIPresentPaywallView.Request: #request + type: object + required: [method, id] + properties: + method: { const: "adapty_ui_present_paywall_view" } + id: { type: string, description: "View Id" } + ios_presentation_style: { $ref: "#/$defs/AdaptyUI.IOSPresentationStyle" } + + AdaptyUIPresentPaywallView.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### adaptyUIShowDialog ### + AdaptyUIShowDialog.Request: #request + type: object + required: [method, id, configuration] + properties: + method: { const: "adapty_ui_show_dialog" } + id: { type: string, description: "View Id" } + configuration: { $ref: "#/$defs/AdaptyUI.DialogConfiguration" } + + AdaptyUIShowDialog.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyUI.DialogActionType" } + + ### getOnboarding ### + GetOnboarding.Request: #request + type: object + required: [method, placement_id] + properties: + method: { const: "get_onboarding" } + placement_id: { type: string } + locale: { $ref: "#/$defs/AdaptyLocale" } + fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } + load_timeout: { type: number, description: "seconds" } + + GetOnboarding.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyOnboarding" } + + ### getOnboardingForDefaultAudience ### + GetOnboardingForDefaultAudience.Request: #request + type: object + required: [method, placement_id] + properties: + method: { const: "get_onboarding_for_default_audience" } + placement_id: { type: string } + locale: { $ref: "#/$defs/AdaptyLocale" } + fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } + + GetOnboardingForDefaultAudience.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyOnboarding" } + + ### getPaywall ### + GetPaywall.Request: #request + type: object + required: [method, placement_id] + properties: + method: { const: "get_paywall" } + placement_id: { type: string } + locale: { $ref: "#/$defs/AdaptyLocale" } + fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } + load_timeout: { type: number, description: "seconds" } + + GetPaywall.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyPaywall" } + + ### getPaywallForDefaultAudience ### + GetPaywallForDefaultAudience.Request: #request + type: object + required: [method, placement_id] + properties: + method: { const: "get_paywall_for_default_audience" } + placement_id: { type: string } + locale: { $ref: "#/$defs/AdaptyLocale" } + fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } + + GetPaywallForDefaultAudience.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyPaywall" } + + ### getPaywallProducts ### + GetPaywallProducts.Request: #request + type: object + required: [method, paywall] + properties: + method: { const: "get_paywall_products" } + paywall: { $ref: "#/$defs/AdaptyPaywall" } + + GetPaywallProducts.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: + { + type: array, + items: { $ref: "#/$defs/AdaptyPaywallProduct.Response" }, + } + + ### getProfile ### + GetProfile.Request: #request + type: object + required: [method] + properties: + method: { const: "get_profile" } + + GetProfile.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyProfile" } + + ### identify ### + Identify.Request: #request + type: object + required: [method, customer_user_id] + properties: + method: { const: "identify" } + customer_user_id: { type: string } + parameters: { $ref: "#/$defs/CustomerIdentityParameters" } + + Identify.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### isActivated ### + IsActivated.Request: #request + type: object + required: [method] + properties: + method: { const: "is_activated" } + + IsActivated.Response: #response + type: object + required: [success] + properties: + success: { type: boolean } + + ### getLogLevel ### + GetLogLevel.Request: #request + type: object + required: [method] + properties: + method: { const: "get_log_level" } + + GetLogLevel.Response: #response + type: object + required: [success] + properties: + success: { $ref: "#/$defs/AdaptyLog.Level" } + + ### setLogLevel ### + SetLogLevel.Request: #request + type: object + required: [method, value] + properties: + method: { const: "set_log_level" } + value: { $ref: "#/$defs/AdaptyLog.Level" } + + SetLogLevel.Response: #response + type: object + required: [success] + properties: + success: { const: true } + + ### logout ### + Logout.Request: #request + type: object + required: [method] + properties: + method: { const: "logout" } + + Logout.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### logShowPaywall ### + LogShowPaywall.Request: #request + type: object + required: [method, paywall] + properties: + method: { const: "log_show_paywall" } + paywall: { $ref: "#/$defs/AdaptyPaywall" } + + LogShowPaywall.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### makePurchase ### + MakePurchase.Request: #request + type: object + required: [method, product] + properties: + method: { const: "make_purchase" } + product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } + parameters: { $ref: "#/$defs/AdaptyPurchaseParameters" } + + MakePurchase.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyPurchaseResult" } + + OpenWebPaywall.Request: #request + type: object + required: [method] + properties: + method: { const: "open_web_paywall" } + open_in: { $ref: "#/$defs/AdaptyWebPresentation", default: "browser_out_app" } + oneOf: + - required: [product] + properties: + product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } + - required: [paywall] + properties: + paywall: { $ref: "#/$defs/AdaptyPaywall" } + + + OpenWebPaywall.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + CreateWebPaywallUrl.Request: #request + type: object + required: [method] + properties: + method: { const: "create_web_paywall_url" } + oneOf: + - required: [product] + properties: + product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } + - required: [paywall] + properties: + paywall: { $ref: "#/$defs/AdaptyPaywall" } + + CreateWebPurchaseUrl.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { type: string } + + ### getCurrentInstallationStatus ### + GetCurrentInstallationStatus.Request: #request + type: object + required: [method] + properties: + method: { const: "get_current_installation_status" } + + GetCurrentInstallationStatus.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyInstallationStatus" } + + ### presentCodeRedemptionSheet ### + PresentCodeRedemptionSheet.Request: #request + type: object + required: [method] + properties: + method: { const: "present_code_redemption_sheet" } + + PresentCodeRedemptionSheet.Response: #response + type: object + required: [success] + properties: + success: { const: true } + + ### reportTransaction ### + ReportTransaction.Request: #request + type: object + required: [method, transaction_id] + properties: + method: { const: "report_transaction" } + variation_id: { type: string } + transaction_id: { type: string } + + ReportTransaction.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### restorePurchases ### + RestorePurchases.Request: #request + type: object + required: [method] + properties: + method: { const: "restore_purchases" } + + RestorePurchases.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { $ref: "#/$defs/AdaptyProfile" } + + ### getSDKVersion ### + GetSDKVersion.Request: #request + type: object + required: [method] + properties: + method: { const: "get_sdk_version" } + + GetSDKVersion.Response: #response + type: object + required: [success] + properties: + success: { type: string } + + ### setFallback ### + SetFallback.Request: #request + type: object + required: [method] + properties: + method: { const: "set_fallback" } + oneOf: + - required: [asset_id] + properties: + asset_id: { type: string } + - required: [path] + properties: + path: { type: string } + + SetFallback.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### setIntegrationIdentifier ### + SetIntegrationIdentifier.Request: #request + type: object + required: [method, key_values] + properties: + method: { const: "set_integration_identifiers" } + key_values: + type: object + additionalProperties: + type: string + nullable: false + + SetIntegrationIdentifier.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### updateAttributionData ### + UpdateAttributionData.Request: #request + type: object + required: [method, attribution, source] + properties: + method: { const: "update_attribution_data" } + attribution: { type: string } + source: { type: string } + + UpdateAttributionData.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### updateCollectingRefundDataConsent ### + UpdateCollectingRefundDataConsent.Request: #request + type: object + required: [method, consent] + properties: + method: { const: "update_collecting_refund_data_consent" } + consent: { type: boolean } + + UpdateCollectingRefundDataConsent.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### updateProfile ### + UpdateProfile.Request: #request + type: object + required: [method, params] + properties: + method: { const: "update_profile" } + params: { $ref: "#/$defs/AdaptyProfileParameters" } + + UpdateProfile.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + + ### updateRefundPreference ### + UpdateRefundPreference.Request: #request + type: object + required: [method, refund_preference] + properties: + method: { const: "update_refund_preference" } + refund_preference: { $ref: "#/$defs/AdaptyRefundPreference" } + + UpdateRefundPreference.Response: #response + type: object + oneOf: + - required: [error] + properties: + error: { $ref: "#/$defs/AdaptyError" } + - required: [success] + properties: + success: { const: true } + +$events: + Event.DidLoadLatestProfile: + type: object + required: [id, profile] + properties: + id: { const: "did_load_latest_profile" } + profile: { $ref: "#/$defs/AdaptyProfile" } + + Event.OnInstallationDetailsSuccess: + type: object + required: [id, details] + properties: + id: { const: "on_installation_details_success" } + details: { $ref: "#/$defs/AdaptyInstallationDetails" } + + Event.OnInstallationDetailsFail: + type: object + required: [id, error] + properties: + id: { const: "on_installation_details_fail" } + error: { $ref: "#/$defs/AdaptyError" } + + PaywallViewEvent.DidAppear: + type: object + required: [id, view] + properties: + id: { const: "paywall_view_did_appear" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + + PaywallViewEvent.DidDisappear: + type: object + required: [id, view] + properties: + id: { const: "paywall_view_did_disappear" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + + PaywallViewEvent.DidUserAction: + type: object + required: [id, view, action] + properties: + id: { const: "paywall_view_did_perform_action" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + action: { $ref: "#/$defs/AdaptyUI.UserAction" } + + PaywallViewEvent.DidSelectProduct: + type: object + required: [id, view, product_id] + properties: + id: { const: "paywall_view_did_select_product" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + product_id: { type: string } + + PaywallViewEvent.WillPurchase: + type: object + required: [id, view, product] + properties: + id: { const: "paywall_view_did_start_purchase" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } + + PaywallViewEvent.DidPurchase: + type: object + required: [id, view, product, purchased_result] + properties: + id: { const: "paywall_view_did_finish_purchase" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } + purchased_result: { $ref: "#/$defs/AdaptyPurchaseResult" } + + PaywallViewEvent.DidFailPurchase: + type: object + required: [id, view, product, error] + properties: + id: { const: "paywall_view_did_fail_purchase" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } + error: { $ref: "#/$defs/AdaptyError" } + + PaywallViewEvent.WillRestorePurchase: + type: object + required: [id, view] + properties: + id: { const: "paywall_view_did_start_restore" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + + PaywallViewEvent.DidRestorePurchase: + type: object + required: [id, view, profile] + properties: + id: { const: "paywall_view_did_finish_restore" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + profile: { $ref: "#/$defs/AdaptyProfile" } + + PaywallViewEvent.DidFailRestorePurchase: + type: object + required: [id, view, error] + properties: + id: { const: "paywall_view_did_fail_restore" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + error: { $ref: "#/$defs/AdaptyError" } + + PaywallViewEvent.DidFailRendering: + type: object + required: [id, view, error] + properties: + id: { const: "paywall_view_did_fail_rendering" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + error: { $ref: "#/$defs/AdaptyError" } + + PaywallViewEvent.DidFailLoadingProducts: + type: object + required: [id, view, error] + properties: + id: { const: "paywall_view_did_fail_loading_products" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + error: { $ref: "#/$defs/AdaptyError" } + + PaywallViewEvent.DidFinishWebPaymentNavigation: + type: object + required: [id, view] + properties: + id: { const: "paywall_view_did_finish_web_payment_navigation" } + view: { $ref: "#/$defs/AdaptyUI.PaywallView" } + product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } + error: { $ref: "#/$defs/AdaptyError" } + + OnboardingViewEvent.DidFailWithError: + type: object + required: [id, view, error] + properties: + id: { const: "onboarding_did_fail_with_error" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + error: { $ref: "#/$defs/AdaptyError" } + + OnboardingViewEvent.OnAnalyticsEvent: + type: object + required: [id, view, meta, event] + properties: + id: { const: "onboarding_on_analytics_action" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + event: + type: object + required: [name] + properties: + name: + oneOf: + - type: string + description: any value for unknown event + - enum: + - onboarding_started + - screen_presented + - screen_completed + - second_screen_presented + - registration_screen_presented + - products_screen_presented + - user_email_collected + - onboarding_completed + element_id: { type: string } + reply: { type: string } + + OnboardingViewEvent.DidFinishLoading: + type: object + required: [id, view, meta] + properties: + id: { const: "onboarding_did_finish_loading" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + + OnboardingViewEvent.OnCloseAction: + type: object + required: [id, view, meta, action_id] + properties: + id: { const: "onboarding_on_close_action" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + action_id: { type: string } + + OnboardingViewEvent.OnCustomAction: + type: object + required: [id, view, meta, action_id] + properties: + id: { const: "onboarding_on_custom_action" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + action_id: { type: string } + + OnboardingViewEvent.OnPaywallAction: + type: object + required: [id, view, meta, action_id] + properties: + id: { const: "onboarding_on_paywall_action" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + action_id: { type: string } + + OnboardingViewEvent.OnStateUpdatedAction: + type: object + required: [id, view, meta, action] + properties: + id: { const: "onboarding_on_state_updated_action" } + view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } + meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } + action: + type: object + required: [element_id, element_type, value] + oneOf: + - properties: + element_id: { type: string } + element_type: { const: select } + value: { $ref: "#/$defs/AdaptyUI.OnboardingsStateParams" } + - properties: + element_id: { type: string } + element_type: { const: multi_select } + value: + type: array + items: { $ref: "#/$defs/AdaptyUI.OnboardingsStateParams" } + - properties: + element_id: { type: string } + element_type: { const: input } + value: + type: object + required: [type, value] + oneOf: + - properties: + type: { enum: [text, email] } + value: { type: string } + - properties: + type: { const: number } + value: { type: number } + - properties: + element_id: { type: string } + element_type: { const: date_picker } + value: + type: object + properties: + day: { type: integer } + month: { type: integer } + year: { type: integer } + +$defs: + ### Entities ### + + AdaptyError: #response + type: object + required: [adapty_code, message] + properties: + adapty_code: { type: integer } + message: { type: string } + detail: { type: string } + + AdaptyLog.Level: #request #response + type: string + enum: + - error + - warn + - info + - verbose + - debug + + AdaptyLocale: + type: string + example: "en" + + UUID: + type: string + format: uuid + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + description: "UUID in RFC 4122 format" + example: "123e4567-e89b-12d3-a456-426614174000" + + Date: #response #request + type: string + format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + + AdaptyConfiguration: #request + type: object + required: [api_key] + properties: + api_key: { type: string } + customer_user_id: { type: string } + customer_identity_parameters: { $ref: "#/$defs/CustomerIdentityParameters" } + observer_mode: { type: boolean, default: false } + apple_idfa_collection_disabled: { type: boolean, default: false } + google_adid_collection_disabled: { type: boolean, default: false } + google_enable_pending_prepaid_plans: { type: boolean, default: false } + google_local_access_level_allowed: { type: boolean, default: false } + ip_address_collection_disabled: { type: boolean, default: false } + clear_data_on_backup: { type: boolean, default: false } + server_cluster: { type: string, enum: ["default", "eu", "cn"] } + backend_proxy_host: { type: string } + backend_proxy_port: { type: integer } + log_level: { $ref: "#/$defs/AdaptyLog.Level" } + cross_platform_sdk_name: { type: string } + cross_platform_sdk_version: { type: string } + activate_ui: { type: boolean, dafault: false } + media_cache: + type: object + properties: + memory_storage_total_cost_limit: + { type: integer, description: "bytes" } + memory_storage_count_limit: { type: integer } + disk_storage_size_limit: { type: integer, description: "bytes" } + + CustomerIdentityParameters: #request + type: object + properties: + app_account_token: { $ref: "#/$defs/UUID", description: "iOS Only" } + obfuscated_account_id: { type: string, description: "Android Only" } + obfuscated_profile_id: { type: string, description: "Android Only" } + + AdaptyPaywallProduct.Response: #response + type: object + required: + - vendor_product_id + - adapty_product_id + - access_level_id + - product_type + - paywall_product_index + - paywall_variation_id + - paywall_ab_test_name + - paywall_name + - localized_description + - localized_title + - is_family_shareable # iOS Only + - price + properties: + vendor_product_id: { type: string } + adapty_product_id: { type: string } + access_level_id: { type: string } + product_type: { type: string } + paywall_product_index: { type: integer } + paywall_variation_id: { type: string } + paywall_ab_test_name: { type: string } + paywall_name: { type: string } + web_purchase_url: { type: string } + localized_description: { type: string } + localized_title: { type: string } + is_family_shareable: + { type: boolean, default: false, description: "iOS Only" } + region_code: { type: string } + price: { $ref: "#/$defs/AdaptyPrice" } + subscription: { $ref: "#/$defs/AdaptyPaywallProduct.Subscription" } + payload_data: { type: string } + + AdaptyPrice: #response + type: object + required: [amount] + properties: + amount: { type: number } + currency_code: { type: string } + currency_symbol: { type: string } + localized_string: { type: string } + + AdaptyPaywallProduct.Subscription: #response + type: object + required: + - group_identifier # iOS Only + - period + - renewal_type # Android Only + - base_plan_id # Android Only + properties: + group_identifier: { type: string, description: "iOS Only" } + period: { $ref: "#/$defs/AdaptySubscriptionPeriod" } + localized_period: { type: string } + offer: { $ref: "#/$defs/AdaptySubscriptionOffer" } + renewal_type: # Android Only + type: string + enum: + - prepaid + - autorenewable + default: autorenewable + description: "Android Only" + base_plan_id: { type: string, description: "Android Only" } # Android Only + + AdaptyPaywallProduct.Request: #request + type: object + required: + - vendor_product_id + - adapty_product_id + - access_level_id + - product_type + - paywall_product_index + - paywall_variation_id + - paywall_ab_test_name + - paywall_name + properties: + vendor_product_id: { type: string } + adapty_product_id: { type: string } + access_level_id: { type: string } + product_type: { type: string } + paywall_product_index: { type: integer } + subscription_offer_identifier: + { $ref: "#/$defs/AdaptySubscriptionOffer.Identifier" } + paywall_variation_id: { type: string } + paywall_ab_test_name: { type: string } + paywall_name: { type: string } + web_purchase_url: { type: string } + payload_data: { type: string } + + AdaptySubscriptionOffer: #response + type: object + required: + - offer_identifier + - phases + properties: + offer_identifier: { $ref: "#/$defs/AdaptySubscriptionOffer.Identifier" } + phases: + type: array + items: { $ref: "#/$defs/AdaptySubscriptionOffer.Phase" } + offer_tags: # Android Only + type: array + items: { type: string } + description: Android Only + + AdaptySubscriptionOffer.Phase: + type: object + required: + - price + - subscription_period + - number_of_periods + - payment_mode + properties: + price: { $ref: "#/$defs/AdaptyPrice" } + number_of_periods: { type: integer } + payment_mode: { $ref: "#/$defs/AdaptySubscriptionOffer.PaymentMode" } + subscription_period: { $ref: "#/$defs/AdaptySubscriptionPeriod" } + localized_subscription_period: { type: string } + localized_number_of_periods: { type: string } + + AdaptyOnboarding: #request #response + type: object + required: + - placement + - onboarding_id + - onboarding_name + - variation_id + - onboarding_builder + - response_created_at + - request_locale + properties: + placement: { $ref: "#/$defs/AdaptyPlacement" } + onboarding_id: { type: string } + onboarding_name: { type: string } + variation_id: { type: string } + remote_config: { $ref: "#/$defs/AdaptyRemoteConfig" } + onboarding_builder: + type: object + required: [config_url] + properties: + config_url: { type: string } + payload_data: { type: string } + response_created_at: { type: integer } + request_locale: { $ref: "#/$defs/AdaptyLocale" } + + + AdaptyPaywall: #request #response + type: object + required: + - placement + - paywall_id + - paywall_name + - variation_id + - products + - response_created_at + - request_locale + properties: + placement: { $ref: "#/$defs/AdaptyPlacement" } + paywall_id: { type: string } + paywall_name: { type: string } + variation_id: { type: string } + remote_config: { $ref: "#/$defs/AdaptyRemoteConfig" } + paywall_builder: { $ref: "#/$defs/AdaptyPaywall.ViewConfiguration" } + products: + type: array + items: { $ref: "#/$defs/AdaptyPaywall.ProductReference" } + web_purchase_url: { type: string } + payload_data: { type: string } + response_created_at: { type: integer } + request_locale: { $ref: "#/$defs/AdaptyLocale" } + + + AdaptyPlacement: #request #response + type: object + required: + - developer_id + - audience_name + - revision + - ab_test_name + - placement_audience_version_id + properties: + developer_id: { type: string } + audience_name: { type: string } + revision: { type: integer } + ab_test_name: { type: string } + is_tracking_purchases: { type: bool, default: false } + placement_audience_version_id: { type: string } + + AdaptyPlacementFetchPolicy: #request + type: object + oneOf: + - required: [type] + properties: + type: + type: string + enum: + - reload_revalidating_cache_data + - return_cache_data_else_load + - required: [type, max_age] + properties: + type: + type: string + const: return_cache_data_if_not_expired_else_load + max_age: + type: number + description: "seconds" + + AdaptyRemoteConfig: #request #response + type: object + required: [lang, data] + properties: + lang: { $ref: "#/$defs/AdaptyLocale" } + data: { type: string, description: "custom JSON string" } + + AdaptyPaywall.ProductReference: #request #response + type: object + required: + - vendor_product_id + - adapty_product_id + - access_level_id + - product_type + properties: + vendor_product_id: { type: string } + adapty_product_id: { type: string } + access_level_id: { type: string } + product_type: { type: string } + promotional_offer_id: { type: string, description: "iOS Only" } + win_back_offer_id: { type: string, description: "iOS Only" } + base_plan_id: { type: string, description: "Android Only" } + offer_id: { type: string, description: "Android Only" } + + AdaptyPaywall.ViewConfiguration: + type: object + required: + - paywall_builder_id + - lang + # - json + properties: + paywall_builder_id: { type: string } + lang: { $ref: "#/$defs/AdaptyLocale" } + json: { type: string } + + AdaptySubscriptionPeriod: #response + type: object + required: [unit, number_of_units] + properties: + unit: { $ref: "#/$defs/AdaptySubscriptionPeriod.Unit" } + number_of_units: { type: integer } + + AdaptySubscriptionPeriod.Unit: #response + type: string + enum: + - day + - week + - month + - year + - unknown + + AdaptyProfile: #response #event + type: object + required: + - profile_id + - segment_hash + - is_test_user + - timestamp + properties: + profile_id: { type: string } + customer_user_id: { type: string } + segment_hash: { type: string } + custom_attributes: { $ref: "#/$defs/AdaptyProfile.CustomAttributes" } + paid_access_levels: + type: object + additionalProperties: { $ref: "#/$defs/AdaptyProfile.AccessLevel" } + subscriptions: + type: object + additionalProperties: { $ref: "#/$defs/AdaptyProfile.Subscription" } + non_subscriptions: + type: object + additionalProperties: + type: array + items: { $ref: "#/$defs/AdaptyProfile.NonSubscription" } + timestamp: { type: integer } + is_test_user: { type: boolean } + + AdaptyProfile.AccessLevel: #response + type: object + required: + - id + - is_active + - vendor_product_id + - store + - activated_at + - is_lifetime + - will_renew + - is_in_grace_period + - is_refund + properties: + id: { type: string } + is_active: { type: boolean } + vendor_product_id: { type: string } + store: { type: string } + activated_at: { $ref: "#/$defs/Date" } + renewed_at: { $ref: "#/$defs/Date" } + expires_at: { $ref: "#/$defs/Date" } + is_lifetime: { type: boolean } + active_introductory_offer_type: { type: string } + active_promotional_offer_type: { type: string } + active_promotional_offer_id: { type: string } + offer_id: { type: string } + will_renew: { type: boolean } + is_in_grace_period: { type: boolean } + unsubscribed_at: { $ref: "#/$defs/Date" } + billing_issue_detected_at: { $ref: "#/$defs/Date" } + starts_at: { $ref: "#/$defs/Date" } + cancellation_reason: { type: string } + is_refund: { type: boolean } + + AdaptyProfile.NonSubscription: #response + type: object + required: + - purchase_id + - store + - vendor_product_id + - purchased_at + - is_sandbox + - is_refund + - is_consumable + properties: + purchase_id: { type: string } + store: { type: string } + vendor_product_id: { type: string } + vendor_transaction_id: { type: string } + purchased_at: { $ref: "#/$defs/Date" } + is_sandbox: { type: boolean } + is_refund: { type: boolean } + is_consumable: { type: boolean } + + AdaptyProfile.Subscription: #response + type: object + required: + - store + - vendor_product_id + - vendor_transaction_id + - vendor_original_transaction_id + - is_active + - is_lifetime + - activated_at + - is_in_grace_period + - is_refund + - is_sandbox + - will_renew + properties: + store: { type: string } + vendor_product_id: { type: string } + vendor_transaction_id: { type: string } + vendor_original_transaction_id: { type: string } + is_active: { type: boolean } + is_lifetime: { type: boolean } + activated_at: { $ref: "#/$defs/Date" } + renewed_at: { $ref: "#/$defs/Date" } + expires_at: { $ref: "#/$defs/Date" } + starts_at: { $ref: "#/$defs/Date" } + unsubscribed_at: { $ref: "#/$defs/Date" } + billing_issue_detected_at: { $ref: "#/$defs/Date" } + is_in_grace_period: { type: boolean } + is_refund: { type: boolean } + is_sandbox: { type: boolean } + will_renew: { type: boolean } + active_introductory_offer_type: { type: string } + active_promotional_offer_type: { type: string } + active_promotional_offer_id: { type: string } + offer_id: { type: string } + cancellation_reason: { type: string } + + AdaptyProfile.CustomAttributes: + type: object + additionalProperties: + oneOf: + - type: string + nullable: false + - type: number + format: double + nullable: false + + AdaptyProfile.Gender: #request + type: string + enum: + - f + - m + - o + description: | + * `f` - female + * `m` - male + * `o` - other + + AdaptyProfileParameters: #request + type: object + properties: + first_name: { type: string } + last_name: { type: string } + gender: { $ref: "#/$defs/AdaptyProfile.Gender" } + birthday: { type: string, format: "YYYY-MM-dd" } + email: { type: string } + phone_number: { type: string } + att_status: { type: integer, description: "iOS Only" } + custom_attributes: { $ref: "#/$defs/AdaptyProfile.CustomAttributes" } + analytics_disabled: { type: boolean } + + AdaptySubscriptionOffer.Identifier: #request #response + type: string + oneOf: + - required: + - type + - id # Android Only + properties: + id: { type: string } + type: { type: string, const: introductory } + - required: [id, type] + properties: + id: { type: string } + type: { type: string, enum: [promotional, win_back] } + + AdaptySubscriptionOffer.PaymentMode: #response + type: string + enum: + - pay_as_you_go + - pay_up_front + - free_trial + - unknown + + AdaptyPurchaseResult: #response + type: object + oneOf: + - required: [type] + properties: + type: { type: string, enum: ["pending", "user_cancelled"] } + - required: [type, profile] + properties: + type: { type: string, const: "success" } + profile: { $ref: "#/$defs/AdaptyProfile" } + apple_jws_transaction: { type: string } + google_purchase_token: { type: string } + + + AdaptyInstallationStatus: #response #event + type: object + oneOf: + - required: [status] + properties: + status: { type: string, enum: ["not_available", "not_determined"] } + - required: [status, details] + properties: + status: { type: string, const: "determined" } + details: { $ref: "#/$defs/AdaptyInstallationDetails" } + + AdaptyInstallationDetails: #response #event + type: object + required: [ install_time, app_launch_count] + properties: + install_id: { type: string } + install_time: { $ref: "#/$defs/Date", nullable: false } + app_launch_count: { type: integer, nullable: false } + payload: { type: string } + + AdaptyUI.CustomAssets: + type: array + items: + oneOf: + - $ref: "#/$assets/Color" + - $ref: "#/$assets/ColorGradient" + - $ref: "#/$assets/Image" + - $ref: "#/$assets/Video" + + AdaptyUI.PaywallView: #response #event + type: object + required: + - id + - placement_id + - variation_id + properties: + id: { type: string } + placement_id: { type: string } + variation_id: { type: string } + + AdaptyUI.OnboardingView: #response #event + type: object + required: + - id + - placement_id + - variation_id + properties: + id: { type: string } + placement_id: { type: string } + variation_id: { type: string } + + AdaptyUI.UserAction: #event + type: object + oneOf: + - required: [type] + properties: + type: { type: string, const: ["close", "system_back"] } + - required: [type, value] + properties: + type: { type: string, enum: ["open_url", "custom"] } + value: { type: string } + + AdaptyUI.CustomTagsValues: #request + type: object + additionalProperties: { type: string, nullable: false } + + AdaptyUI.CustomTimersValues: #request + type: object + additionalProperties: { $ref: "#/$defs/Date", nullable: false } + + AdaptyUI.AndroidPersonalizedOffers: #request + type: object + additionalProperties: { type: boolean, nullable: false } + + AdaptyUI.ProductPurchaseParameters: #request + type: object + propertyNames: { description: "key is adapty_product_id" } + additionalProperties: { + $ref: "#/$defs/AdaptyPurchaseParameters", + nullable: false + } + + AdaptyUI.IOSPresentationStyle: + type: string + enum: + - full_screen + - page_sheet + + AdaptyWebPresentation: #request + type: string + enum: + - browser_out_app + - browser_in_app + + AdaptyUI.DialogConfiguration: #request + type: object + required: [default_action] + properties: + title: { type: string } + content: { type: string } + default_action_title: { type: string } + secondary_action_title: { type: string } + + AdaptyUI.DialogActionType: + type: string + enum: + - primary + - secondary + + AdaptyPurchaseParameters: #request + type: object + properties: + subscription_update_params: + { + $ref: "#/$defs/AdaptySubscriptionUpdateParameters", + description: "Android Only", + } + is_offer_personalized: { type: boolean, description: "Android Only" } + + + AdaptySubscriptionUpdateParameters: #request # Android Only + type: object + required: + - old_sub_vendor_product_id + - replacement_mode + properties: + old_sub_vendor_product_id: { type: string } + replacement_mode: + type: string + enum: + - charge_full_price + - deferred + - without_proration + - charge_prorated_price + - with_time_proration + + AdaptyRefundPreference: + type: string + enum: + - no_preference + - grant + - decline + + AdaptyUI.OnboardingMeta: #event + type: object + required: [onboarding_id, screen_cid, screen_index, total_screens] + properties: + onboarding_id: { type: string } + screen_cid: { type: string } + screen_index: { type: integer } + total_screens: { type: integer } + + AdaptyUI.OnboardingsStateParams: #event + type: object + required: [id, value, label] + properties: + id: { type: string } + value: { type: string } + label: { type: string } + +$assets: + Color: + type: object + required: [id, type, value] + properties: + id: { type: string } + type: { const: color } + value: { $ref: "#/$assets/Color.Hex" } + + Color.Hex: + type: string + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$" + description: RRGGBB or RRGGBBAA + + ColorGradient: + type: object + required: [id, type, values, points] + properties: + id: { type: string } + type: + type: string + enum: + - linear-gradient + values: + type: array + items: + type: object + required: [color, p] + properties: + color: { $ref: "#/$assets/Color.Hex" } + p: { type: number } + points: + type: object + required: [x0, y0, x1, y1] + properties: + x0: { type: number } + y0: { type: number } + x1: { type: number } + y1: { type: number } + + Image: + type: object + required: [id, type] + properties: + id: { type: string } + type: { const: image } + oneOf: + - required: [value] + properties: + value: { type: string, contentEncoding: base64 } + - required: [asset_id] + properties: + asset_id: { type: string } + - required: [path] + properties: + path: { type: string } + + Video: + type: object + required: [id, type] + properties: + id: { type: string } + type: { const: video } + oneOf: + - required: [asset_id] + properties: + asset_id: { type: string } + - required: [path] + properties: + path: { type: string } diff --git a/src/logger/log.ts b/src/logger/log.ts index dbf8469..e82522d 100644 --- a/src/logger/log.ts +++ b/src/logger/log.ts @@ -12,7 +12,10 @@ export class Log { private static defaultMeta?: LoggerConfig['defaultMeta']; private static _version: string = 'unknown'; - /** Override the version shown in log messages (e.g. SDK version instead of core version) */ + /** + * Override the version shown in log messages + * @internal + */ public static setVersion(version: string): void { this._version = version; } From 6b807869776ace160c4e1dd11b16bfe3cbbe6737 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 18:09:14 +0300 Subject: [PATCH 37/50] feat(core): add Log.setVersion() and export LogArgs type - Add static _version field with setVersion() (@internal) for SDK version injection - Update formatMessage() and LogEvent to use _version instead of hardcoded VERSION - Export LogArgs type from logger/index.ts --- cross_platform.yaml | 1630 ------------------------------------------- 1 file changed, 1630 deletions(-) delete mode 100644 cross_platform.yaml diff --git a/cross_platform.yaml b/cross_platform.yaml deleted file mode 100644 index 1ad5b9a..0000000 --- a/cross_platform.yaml +++ /dev/null @@ -1,1630 +0,0 @@ -$schema: "https://json-schema.org/draft/2020-12/schema" -$id: "https://adapty.io/crossPlatform/3.15.2/schema" -title: "Cross Platform Format" - -$requests: - ### activate ### - Activate.Request: #request - type: object - required: [method, configuration] - properties: - method: { const: "activate" } - configuration: { $ref: "#/$defs/AdaptyConfiguration" } - - Activate.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### createPaywallView ### - AdaptyUICreatePaywallView.Request: #request - type: object - required: - - method - - paywall - properties: - method: { const: "adapty_ui_create_paywall_view" } - paywall: { $ref: "#/$defs/AdaptyPaywall" } - load_timeout: { type: number, description: "seconds" } - preload_products: { type: boolean, default: false } - custom_tags: { $ref: "#/$defs/AdaptyUI.CustomTagsValues" } - custom_timers: { $ref: "#/$defs/AdaptyUI.CustomTimersValues" } - custom_assets: { $ref: "#/$defs/AdaptyUI.CustomAssets" } - product_purchase_parameters: - { $ref: "#/$defs/AdaptyUI.ProductPurchaseParameters" } - - AdaptyUICreatePaywallView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyUI.PaywallView" } - - ### createOnboardingView ### - AdaptyUICreateOnboardingView.Request: #request - type: object - required: - - method - - onboarding - properties: - method: { const: "adapty_ui_create_onboarding_view" } - onboarding: { $ref: "#/$defs/AdaptyOnboarding" } - external_urls_presentation: { $ref: "#/$defs/AdaptyWebPresentation" } - - AdaptyUICreateOnboardingView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - - ### dismissOnboardingView ### - AdaptyUIDismissOnboardingView.Request: #request - type: object - required: [method, id] - properties: - method: { const: "adapty_ui_dismiss_onboarding_view" } - id: { type: string, description: "View Id" } - destroy: { type: boolean, default: false } - - AdaptyUIDismissOnboardingView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### dismissPaywallView ### - AdaptyUIDismissPaywallView.Request: #request - type: object - required: [method, id] - properties: - method: { const: "adapty_ui_dismiss_paywall_view" } - id: { type: string, description: "View Id" } - destroy: { type: boolean, default: false } - - AdaptyUIDismissPaywallView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### presentOnboardingView ### - AdaptyUIPresentOnboardingView.Request: #request - type: object - required: [method, id] - properties: - method: { const: "adapty_ui_present_onboarding_view" } - id: { type: string, description: "View Id" } - ios_presentation_style: { $ref: "#/$defs/AdaptyUI.IOSPresentationStyle" } - - AdaptyUIPresentOnboardingView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### presentPaywallView ### - AdaptyUIPresentPaywallView.Request: #request - type: object - required: [method, id] - properties: - method: { const: "adapty_ui_present_paywall_view" } - id: { type: string, description: "View Id" } - ios_presentation_style: { $ref: "#/$defs/AdaptyUI.IOSPresentationStyle" } - - AdaptyUIPresentPaywallView.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### adaptyUIShowDialog ### - AdaptyUIShowDialog.Request: #request - type: object - required: [method, id, configuration] - properties: - method: { const: "adapty_ui_show_dialog" } - id: { type: string, description: "View Id" } - configuration: { $ref: "#/$defs/AdaptyUI.DialogConfiguration" } - - AdaptyUIShowDialog.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyUI.DialogActionType" } - - ### getOnboarding ### - GetOnboarding.Request: #request - type: object - required: [method, placement_id] - properties: - method: { const: "get_onboarding" } - placement_id: { type: string } - locale: { $ref: "#/$defs/AdaptyLocale" } - fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } - load_timeout: { type: number, description: "seconds" } - - GetOnboarding.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyOnboarding" } - - ### getOnboardingForDefaultAudience ### - GetOnboardingForDefaultAudience.Request: #request - type: object - required: [method, placement_id] - properties: - method: { const: "get_onboarding_for_default_audience" } - placement_id: { type: string } - locale: { $ref: "#/$defs/AdaptyLocale" } - fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } - - GetOnboardingForDefaultAudience.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyOnboarding" } - - ### getPaywall ### - GetPaywall.Request: #request - type: object - required: [method, placement_id] - properties: - method: { const: "get_paywall" } - placement_id: { type: string } - locale: { $ref: "#/$defs/AdaptyLocale" } - fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } - load_timeout: { type: number, description: "seconds" } - - GetPaywall.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyPaywall" } - - ### getPaywallForDefaultAudience ### - GetPaywallForDefaultAudience.Request: #request - type: object - required: [method, placement_id] - properties: - method: { const: "get_paywall_for_default_audience" } - placement_id: { type: string } - locale: { $ref: "#/$defs/AdaptyLocale" } - fetch_policy: { $ref: "#/$defs/AdaptyPlacementFetchPolicy" } - - GetPaywallForDefaultAudience.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyPaywall" } - - ### getPaywallProducts ### - GetPaywallProducts.Request: #request - type: object - required: [method, paywall] - properties: - method: { const: "get_paywall_products" } - paywall: { $ref: "#/$defs/AdaptyPaywall" } - - GetPaywallProducts.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: - { - type: array, - items: { $ref: "#/$defs/AdaptyPaywallProduct.Response" }, - } - - ### getProfile ### - GetProfile.Request: #request - type: object - required: [method] - properties: - method: { const: "get_profile" } - - GetProfile.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyProfile" } - - ### identify ### - Identify.Request: #request - type: object - required: [method, customer_user_id] - properties: - method: { const: "identify" } - customer_user_id: { type: string } - parameters: { $ref: "#/$defs/CustomerIdentityParameters" } - - Identify.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### isActivated ### - IsActivated.Request: #request - type: object - required: [method] - properties: - method: { const: "is_activated" } - - IsActivated.Response: #response - type: object - required: [success] - properties: - success: { type: boolean } - - ### getLogLevel ### - GetLogLevel.Request: #request - type: object - required: [method] - properties: - method: { const: "get_log_level" } - - GetLogLevel.Response: #response - type: object - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyLog.Level" } - - ### setLogLevel ### - SetLogLevel.Request: #request - type: object - required: [method, value] - properties: - method: { const: "set_log_level" } - value: { $ref: "#/$defs/AdaptyLog.Level" } - - SetLogLevel.Response: #response - type: object - required: [success] - properties: - success: { const: true } - - ### logout ### - Logout.Request: #request - type: object - required: [method] - properties: - method: { const: "logout" } - - Logout.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### logShowPaywall ### - LogShowPaywall.Request: #request - type: object - required: [method, paywall] - properties: - method: { const: "log_show_paywall" } - paywall: { $ref: "#/$defs/AdaptyPaywall" } - - LogShowPaywall.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### makePurchase ### - MakePurchase.Request: #request - type: object - required: [method, product] - properties: - method: { const: "make_purchase" } - product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } - parameters: { $ref: "#/$defs/AdaptyPurchaseParameters" } - - MakePurchase.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyPurchaseResult" } - - OpenWebPaywall.Request: #request - type: object - required: [method] - properties: - method: { const: "open_web_paywall" } - open_in: { $ref: "#/$defs/AdaptyWebPresentation", default: "browser_out_app" } - oneOf: - - required: [product] - properties: - product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } - - required: [paywall] - properties: - paywall: { $ref: "#/$defs/AdaptyPaywall" } - - - OpenWebPaywall.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - CreateWebPaywallUrl.Request: #request - type: object - required: [method] - properties: - method: { const: "create_web_paywall_url" } - oneOf: - - required: [product] - properties: - product: { $ref: "#/$defs/AdaptyPaywallProduct.Request" } - - required: [paywall] - properties: - paywall: { $ref: "#/$defs/AdaptyPaywall" } - - CreateWebPurchaseUrl.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { type: string } - - ### getCurrentInstallationStatus ### - GetCurrentInstallationStatus.Request: #request - type: object - required: [method] - properties: - method: { const: "get_current_installation_status" } - - GetCurrentInstallationStatus.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyInstallationStatus" } - - ### presentCodeRedemptionSheet ### - PresentCodeRedemptionSheet.Request: #request - type: object - required: [method] - properties: - method: { const: "present_code_redemption_sheet" } - - PresentCodeRedemptionSheet.Response: #response - type: object - required: [success] - properties: - success: { const: true } - - ### reportTransaction ### - ReportTransaction.Request: #request - type: object - required: [method, transaction_id] - properties: - method: { const: "report_transaction" } - variation_id: { type: string } - transaction_id: { type: string } - - ReportTransaction.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### restorePurchases ### - RestorePurchases.Request: #request - type: object - required: [method] - properties: - method: { const: "restore_purchases" } - - RestorePurchases.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { $ref: "#/$defs/AdaptyProfile" } - - ### getSDKVersion ### - GetSDKVersion.Request: #request - type: object - required: [method] - properties: - method: { const: "get_sdk_version" } - - GetSDKVersion.Response: #response - type: object - required: [success] - properties: - success: { type: string } - - ### setFallback ### - SetFallback.Request: #request - type: object - required: [method] - properties: - method: { const: "set_fallback" } - oneOf: - - required: [asset_id] - properties: - asset_id: { type: string } - - required: [path] - properties: - path: { type: string } - - SetFallback.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### setIntegrationIdentifier ### - SetIntegrationIdentifier.Request: #request - type: object - required: [method, key_values] - properties: - method: { const: "set_integration_identifiers" } - key_values: - type: object - additionalProperties: - type: string - nullable: false - - SetIntegrationIdentifier.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### updateAttributionData ### - UpdateAttributionData.Request: #request - type: object - required: [method, attribution, source] - properties: - method: { const: "update_attribution_data" } - attribution: { type: string } - source: { type: string } - - UpdateAttributionData.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### updateCollectingRefundDataConsent ### - UpdateCollectingRefundDataConsent.Request: #request - type: object - required: [method, consent] - properties: - method: { const: "update_collecting_refund_data_consent" } - consent: { type: boolean } - - UpdateCollectingRefundDataConsent.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### updateProfile ### - UpdateProfile.Request: #request - type: object - required: [method, params] - properties: - method: { const: "update_profile" } - params: { $ref: "#/$defs/AdaptyProfileParameters" } - - UpdateProfile.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - - ### updateRefundPreference ### - UpdateRefundPreference.Request: #request - type: object - required: [method, refund_preference] - properties: - method: { const: "update_refund_preference" } - refund_preference: { $ref: "#/$defs/AdaptyRefundPreference" } - - UpdateRefundPreference.Response: #response - type: object - oneOf: - - required: [error] - properties: - error: { $ref: "#/$defs/AdaptyError" } - - required: [success] - properties: - success: { const: true } - -$events: - Event.DidLoadLatestProfile: - type: object - required: [id, profile] - properties: - id: { const: "did_load_latest_profile" } - profile: { $ref: "#/$defs/AdaptyProfile" } - - Event.OnInstallationDetailsSuccess: - type: object - required: [id, details] - properties: - id: { const: "on_installation_details_success" } - details: { $ref: "#/$defs/AdaptyInstallationDetails" } - - Event.OnInstallationDetailsFail: - type: object - required: [id, error] - properties: - id: { const: "on_installation_details_fail" } - error: { $ref: "#/$defs/AdaptyError" } - - PaywallViewEvent.DidAppear: - type: object - required: [id, view] - properties: - id: { const: "paywall_view_did_appear" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - - PaywallViewEvent.DidDisappear: - type: object - required: [id, view] - properties: - id: { const: "paywall_view_did_disappear" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - - PaywallViewEvent.DidUserAction: - type: object - required: [id, view, action] - properties: - id: { const: "paywall_view_did_perform_action" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - action: { $ref: "#/$defs/AdaptyUI.UserAction" } - - PaywallViewEvent.DidSelectProduct: - type: object - required: [id, view, product_id] - properties: - id: { const: "paywall_view_did_select_product" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - product_id: { type: string } - - PaywallViewEvent.WillPurchase: - type: object - required: [id, view, product] - properties: - id: { const: "paywall_view_did_start_purchase" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } - - PaywallViewEvent.DidPurchase: - type: object - required: [id, view, product, purchased_result] - properties: - id: { const: "paywall_view_did_finish_purchase" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } - purchased_result: { $ref: "#/$defs/AdaptyPurchaseResult" } - - PaywallViewEvent.DidFailPurchase: - type: object - required: [id, view, product, error] - properties: - id: { const: "paywall_view_did_fail_purchase" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } - error: { $ref: "#/$defs/AdaptyError" } - - PaywallViewEvent.WillRestorePurchase: - type: object - required: [id, view] - properties: - id: { const: "paywall_view_did_start_restore" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - - PaywallViewEvent.DidRestorePurchase: - type: object - required: [id, view, profile] - properties: - id: { const: "paywall_view_did_finish_restore" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - profile: { $ref: "#/$defs/AdaptyProfile" } - - PaywallViewEvent.DidFailRestorePurchase: - type: object - required: [id, view, error] - properties: - id: { const: "paywall_view_did_fail_restore" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - error: { $ref: "#/$defs/AdaptyError" } - - PaywallViewEvent.DidFailRendering: - type: object - required: [id, view, error] - properties: - id: { const: "paywall_view_did_fail_rendering" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - error: { $ref: "#/$defs/AdaptyError" } - - PaywallViewEvent.DidFailLoadingProducts: - type: object - required: [id, view, error] - properties: - id: { const: "paywall_view_did_fail_loading_products" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - error: { $ref: "#/$defs/AdaptyError" } - - PaywallViewEvent.DidFinishWebPaymentNavigation: - type: object - required: [id, view] - properties: - id: { const: "paywall_view_did_finish_web_payment_navigation" } - view: { $ref: "#/$defs/AdaptyUI.PaywallView" } - product: { $ref: "#/$defs/AdaptyPaywallProduct.Response" } - error: { $ref: "#/$defs/AdaptyError" } - - OnboardingViewEvent.DidFailWithError: - type: object - required: [id, view, error] - properties: - id: { const: "onboarding_did_fail_with_error" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - error: { $ref: "#/$defs/AdaptyError" } - - OnboardingViewEvent.OnAnalyticsEvent: - type: object - required: [id, view, meta, event] - properties: - id: { const: "onboarding_on_analytics_action" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - event: - type: object - required: [name] - properties: - name: - oneOf: - - type: string - description: any value for unknown event - - enum: - - onboarding_started - - screen_presented - - screen_completed - - second_screen_presented - - registration_screen_presented - - products_screen_presented - - user_email_collected - - onboarding_completed - element_id: { type: string } - reply: { type: string } - - OnboardingViewEvent.DidFinishLoading: - type: object - required: [id, view, meta] - properties: - id: { const: "onboarding_did_finish_loading" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - - OnboardingViewEvent.OnCloseAction: - type: object - required: [id, view, meta, action_id] - properties: - id: { const: "onboarding_on_close_action" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - action_id: { type: string } - - OnboardingViewEvent.OnCustomAction: - type: object - required: [id, view, meta, action_id] - properties: - id: { const: "onboarding_on_custom_action" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - action_id: { type: string } - - OnboardingViewEvent.OnPaywallAction: - type: object - required: [id, view, meta, action_id] - properties: - id: { const: "onboarding_on_paywall_action" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - action_id: { type: string } - - OnboardingViewEvent.OnStateUpdatedAction: - type: object - required: [id, view, meta, action] - properties: - id: { const: "onboarding_on_state_updated_action" } - view: { $ref: "#/$defs/AdaptyUI.OnboardingView" } - meta: { $ref: "#/$defs/AdaptyUI.OnboardingMeta" } - action: - type: object - required: [element_id, element_type, value] - oneOf: - - properties: - element_id: { type: string } - element_type: { const: select } - value: { $ref: "#/$defs/AdaptyUI.OnboardingsStateParams" } - - properties: - element_id: { type: string } - element_type: { const: multi_select } - value: - type: array - items: { $ref: "#/$defs/AdaptyUI.OnboardingsStateParams" } - - properties: - element_id: { type: string } - element_type: { const: input } - value: - type: object - required: [type, value] - oneOf: - - properties: - type: { enum: [text, email] } - value: { type: string } - - properties: - type: { const: number } - value: { type: number } - - properties: - element_id: { type: string } - element_type: { const: date_picker } - value: - type: object - properties: - day: { type: integer } - month: { type: integer } - year: { type: integer } - -$defs: - ### Entities ### - - AdaptyError: #response - type: object - required: [adapty_code, message] - properties: - adapty_code: { type: integer } - message: { type: string } - detail: { type: string } - - AdaptyLog.Level: #request #response - type: string - enum: - - error - - warn - - info - - verbose - - debug - - AdaptyLocale: - type: string - example: "en" - - UUID: - type: string - format: uuid - pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" - description: "UUID in RFC 4122 format" - example: "123e4567-e89b-12d3-a456-426614174000" - - Date: #response #request - type: string - format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - - AdaptyConfiguration: #request - type: object - required: [api_key] - properties: - api_key: { type: string } - customer_user_id: { type: string } - customer_identity_parameters: { $ref: "#/$defs/CustomerIdentityParameters" } - observer_mode: { type: boolean, default: false } - apple_idfa_collection_disabled: { type: boolean, default: false } - google_adid_collection_disabled: { type: boolean, default: false } - google_enable_pending_prepaid_plans: { type: boolean, default: false } - google_local_access_level_allowed: { type: boolean, default: false } - ip_address_collection_disabled: { type: boolean, default: false } - clear_data_on_backup: { type: boolean, default: false } - server_cluster: { type: string, enum: ["default", "eu", "cn"] } - backend_proxy_host: { type: string } - backend_proxy_port: { type: integer } - log_level: { $ref: "#/$defs/AdaptyLog.Level" } - cross_platform_sdk_name: { type: string } - cross_platform_sdk_version: { type: string } - activate_ui: { type: boolean, dafault: false } - media_cache: - type: object - properties: - memory_storage_total_cost_limit: - { type: integer, description: "bytes" } - memory_storage_count_limit: { type: integer } - disk_storage_size_limit: { type: integer, description: "bytes" } - - CustomerIdentityParameters: #request - type: object - properties: - app_account_token: { $ref: "#/$defs/UUID", description: "iOS Only" } - obfuscated_account_id: { type: string, description: "Android Only" } - obfuscated_profile_id: { type: string, description: "Android Only" } - - AdaptyPaywallProduct.Response: #response - type: object - required: - - vendor_product_id - - adapty_product_id - - access_level_id - - product_type - - paywall_product_index - - paywall_variation_id - - paywall_ab_test_name - - paywall_name - - localized_description - - localized_title - - is_family_shareable # iOS Only - - price - properties: - vendor_product_id: { type: string } - adapty_product_id: { type: string } - access_level_id: { type: string } - product_type: { type: string } - paywall_product_index: { type: integer } - paywall_variation_id: { type: string } - paywall_ab_test_name: { type: string } - paywall_name: { type: string } - web_purchase_url: { type: string } - localized_description: { type: string } - localized_title: { type: string } - is_family_shareable: - { type: boolean, default: false, description: "iOS Only" } - region_code: { type: string } - price: { $ref: "#/$defs/AdaptyPrice" } - subscription: { $ref: "#/$defs/AdaptyPaywallProduct.Subscription" } - payload_data: { type: string } - - AdaptyPrice: #response - type: object - required: [amount] - properties: - amount: { type: number } - currency_code: { type: string } - currency_symbol: { type: string } - localized_string: { type: string } - - AdaptyPaywallProduct.Subscription: #response - type: object - required: - - group_identifier # iOS Only - - period - - renewal_type # Android Only - - base_plan_id # Android Only - properties: - group_identifier: { type: string, description: "iOS Only" } - period: { $ref: "#/$defs/AdaptySubscriptionPeriod" } - localized_period: { type: string } - offer: { $ref: "#/$defs/AdaptySubscriptionOffer" } - renewal_type: # Android Only - type: string - enum: - - prepaid - - autorenewable - default: autorenewable - description: "Android Only" - base_plan_id: { type: string, description: "Android Only" } # Android Only - - AdaptyPaywallProduct.Request: #request - type: object - required: - - vendor_product_id - - adapty_product_id - - access_level_id - - product_type - - paywall_product_index - - paywall_variation_id - - paywall_ab_test_name - - paywall_name - properties: - vendor_product_id: { type: string } - adapty_product_id: { type: string } - access_level_id: { type: string } - product_type: { type: string } - paywall_product_index: { type: integer } - subscription_offer_identifier: - { $ref: "#/$defs/AdaptySubscriptionOffer.Identifier" } - paywall_variation_id: { type: string } - paywall_ab_test_name: { type: string } - paywall_name: { type: string } - web_purchase_url: { type: string } - payload_data: { type: string } - - AdaptySubscriptionOffer: #response - type: object - required: - - offer_identifier - - phases - properties: - offer_identifier: { $ref: "#/$defs/AdaptySubscriptionOffer.Identifier" } - phases: - type: array - items: { $ref: "#/$defs/AdaptySubscriptionOffer.Phase" } - offer_tags: # Android Only - type: array - items: { type: string } - description: Android Only - - AdaptySubscriptionOffer.Phase: - type: object - required: - - price - - subscription_period - - number_of_periods - - payment_mode - properties: - price: { $ref: "#/$defs/AdaptyPrice" } - number_of_periods: { type: integer } - payment_mode: { $ref: "#/$defs/AdaptySubscriptionOffer.PaymentMode" } - subscription_period: { $ref: "#/$defs/AdaptySubscriptionPeriod" } - localized_subscription_period: { type: string } - localized_number_of_periods: { type: string } - - AdaptyOnboarding: #request #response - type: object - required: - - placement - - onboarding_id - - onboarding_name - - variation_id - - onboarding_builder - - response_created_at - - request_locale - properties: - placement: { $ref: "#/$defs/AdaptyPlacement" } - onboarding_id: { type: string } - onboarding_name: { type: string } - variation_id: { type: string } - remote_config: { $ref: "#/$defs/AdaptyRemoteConfig" } - onboarding_builder: - type: object - required: [config_url] - properties: - config_url: { type: string } - payload_data: { type: string } - response_created_at: { type: integer } - request_locale: { $ref: "#/$defs/AdaptyLocale" } - - - AdaptyPaywall: #request #response - type: object - required: - - placement - - paywall_id - - paywall_name - - variation_id - - products - - response_created_at - - request_locale - properties: - placement: { $ref: "#/$defs/AdaptyPlacement" } - paywall_id: { type: string } - paywall_name: { type: string } - variation_id: { type: string } - remote_config: { $ref: "#/$defs/AdaptyRemoteConfig" } - paywall_builder: { $ref: "#/$defs/AdaptyPaywall.ViewConfiguration" } - products: - type: array - items: { $ref: "#/$defs/AdaptyPaywall.ProductReference" } - web_purchase_url: { type: string } - payload_data: { type: string } - response_created_at: { type: integer } - request_locale: { $ref: "#/$defs/AdaptyLocale" } - - - AdaptyPlacement: #request #response - type: object - required: - - developer_id - - audience_name - - revision - - ab_test_name - - placement_audience_version_id - properties: - developer_id: { type: string } - audience_name: { type: string } - revision: { type: integer } - ab_test_name: { type: string } - is_tracking_purchases: { type: bool, default: false } - placement_audience_version_id: { type: string } - - AdaptyPlacementFetchPolicy: #request - type: object - oneOf: - - required: [type] - properties: - type: - type: string - enum: - - reload_revalidating_cache_data - - return_cache_data_else_load - - required: [type, max_age] - properties: - type: - type: string - const: return_cache_data_if_not_expired_else_load - max_age: - type: number - description: "seconds" - - AdaptyRemoteConfig: #request #response - type: object - required: [lang, data] - properties: - lang: { $ref: "#/$defs/AdaptyLocale" } - data: { type: string, description: "custom JSON string" } - - AdaptyPaywall.ProductReference: #request #response - type: object - required: - - vendor_product_id - - adapty_product_id - - access_level_id - - product_type - properties: - vendor_product_id: { type: string } - adapty_product_id: { type: string } - access_level_id: { type: string } - product_type: { type: string } - promotional_offer_id: { type: string, description: "iOS Only" } - win_back_offer_id: { type: string, description: "iOS Only" } - base_plan_id: { type: string, description: "Android Only" } - offer_id: { type: string, description: "Android Only" } - - AdaptyPaywall.ViewConfiguration: - type: object - required: - - paywall_builder_id - - lang - # - json - properties: - paywall_builder_id: { type: string } - lang: { $ref: "#/$defs/AdaptyLocale" } - json: { type: string } - - AdaptySubscriptionPeriod: #response - type: object - required: [unit, number_of_units] - properties: - unit: { $ref: "#/$defs/AdaptySubscriptionPeriod.Unit" } - number_of_units: { type: integer } - - AdaptySubscriptionPeriod.Unit: #response - type: string - enum: - - day - - week - - month - - year - - unknown - - AdaptyProfile: #response #event - type: object - required: - - profile_id - - segment_hash - - is_test_user - - timestamp - properties: - profile_id: { type: string } - customer_user_id: { type: string } - segment_hash: { type: string } - custom_attributes: { $ref: "#/$defs/AdaptyProfile.CustomAttributes" } - paid_access_levels: - type: object - additionalProperties: { $ref: "#/$defs/AdaptyProfile.AccessLevel" } - subscriptions: - type: object - additionalProperties: { $ref: "#/$defs/AdaptyProfile.Subscription" } - non_subscriptions: - type: object - additionalProperties: - type: array - items: { $ref: "#/$defs/AdaptyProfile.NonSubscription" } - timestamp: { type: integer } - is_test_user: { type: boolean } - - AdaptyProfile.AccessLevel: #response - type: object - required: - - id - - is_active - - vendor_product_id - - store - - activated_at - - is_lifetime - - will_renew - - is_in_grace_period - - is_refund - properties: - id: { type: string } - is_active: { type: boolean } - vendor_product_id: { type: string } - store: { type: string } - activated_at: { $ref: "#/$defs/Date" } - renewed_at: { $ref: "#/$defs/Date" } - expires_at: { $ref: "#/$defs/Date" } - is_lifetime: { type: boolean } - active_introductory_offer_type: { type: string } - active_promotional_offer_type: { type: string } - active_promotional_offer_id: { type: string } - offer_id: { type: string } - will_renew: { type: boolean } - is_in_grace_period: { type: boolean } - unsubscribed_at: { $ref: "#/$defs/Date" } - billing_issue_detected_at: { $ref: "#/$defs/Date" } - starts_at: { $ref: "#/$defs/Date" } - cancellation_reason: { type: string } - is_refund: { type: boolean } - - AdaptyProfile.NonSubscription: #response - type: object - required: - - purchase_id - - store - - vendor_product_id - - purchased_at - - is_sandbox - - is_refund - - is_consumable - properties: - purchase_id: { type: string } - store: { type: string } - vendor_product_id: { type: string } - vendor_transaction_id: { type: string } - purchased_at: { $ref: "#/$defs/Date" } - is_sandbox: { type: boolean } - is_refund: { type: boolean } - is_consumable: { type: boolean } - - AdaptyProfile.Subscription: #response - type: object - required: - - store - - vendor_product_id - - vendor_transaction_id - - vendor_original_transaction_id - - is_active - - is_lifetime - - activated_at - - is_in_grace_period - - is_refund - - is_sandbox - - will_renew - properties: - store: { type: string } - vendor_product_id: { type: string } - vendor_transaction_id: { type: string } - vendor_original_transaction_id: { type: string } - is_active: { type: boolean } - is_lifetime: { type: boolean } - activated_at: { $ref: "#/$defs/Date" } - renewed_at: { $ref: "#/$defs/Date" } - expires_at: { $ref: "#/$defs/Date" } - starts_at: { $ref: "#/$defs/Date" } - unsubscribed_at: { $ref: "#/$defs/Date" } - billing_issue_detected_at: { $ref: "#/$defs/Date" } - is_in_grace_period: { type: boolean } - is_refund: { type: boolean } - is_sandbox: { type: boolean } - will_renew: { type: boolean } - active_introductory_offer_type: { type: string } - active_promotional_offer_type: { type: string } - active_promotional_offer_id: { type: string } - offer_id: { type: string } - cancellation_reason: { type: string } - - AdaptyProfile.CustomAttributes: - type: object - additionalProperties: - oneOf: - - type: string - nullable: false - - type: number - format: double - nullable: false - - AdaptyProfile.Gender: #request - type: string - enum: - - f - - m - - o - description: | - * `f` - female - * `m` - male - * `o` - other - - AdaptyProfileParameters: #request - type: object - properties: - first_name: { type: string } - last_name: { type: string } - gender: { $ref: "#/$defs/AdaptyProfile.Gender" } - birthday: { type: string, format: "YYYY-MM-dd" } - email: { type: string } - phone_number: { type: string } - att_status: { type: integer, description: "iOS Only" } - custom_attributes: { $ref: "#/$defs/AdaptyProfile.CustomAttributes" } - analytics_disabled: { type: boolean } - - AdaptySubscriptionOffer.Identifier: #request #response - type: string - oneOf: - - required: - - type - - id # Android Only - properties: - id: { type: string } - type: { type: string, const: introductory } - - required: [id, type] - properties: - id: { type: string } - type: { type: string, enum: [promotional, win_back] } - - AdaptySubscriptionOffer.PaymentMode: #response - type: string - enum: - - pay_as_you_go - - pay_up_front - - free_trial - - unknown - - AdaptyPurchaseResult: #response - type: object - oneOf: - - required: [type] - properties: - type: { type: string, enum: ["pending", "user_cancelled"] } - - required: [type, profile] - properties: - type: { type: string, const: "success" } - profile: { $ref: "#/$defs/AdaptyProfile" } - apple_jws_transaction: { type: string } - google_purchase_token: { type: string } - - - AdaptyInstallationStatus: #response #event - type: object - oneOf: - - required: [status] - properties: - status: { type: string, enum: ["not_available", "not_determined"] } - - required: [status, details] - properties: - status: { type: string, const: "determined" } - details: { $ref: "#/$defs/AdaptyInstallationDetails" } - - AdaptyInstallationDetails: #response #event - type: object - required: [ install_time, app_launch_count] - properties: - install_id: { type: string } - install_time: { $ref: "#/$defs/Date", nullable: false } - app_launch_count: { type: integer, nullable: false } - payload: { type: string } - - AdaptyUI.CustomAssets: - type: array - items: - oneOf: - - $ref: "#/$assets/Color" - - $ref: "#/$assets/ColorGradient" - - $ref: "#/$assets/Image" - - $ref: "#/$assets/Video" - - AdaptyUI.PaywallView: #response #event - type: object - required: - - id - - placement_id - - variation_id - properties: - id: { type: string } - placement_id: { type: string } - variation_id: { type: string } - - AdaptyUI.OnboardingView: #response #event - type: object - required: - - id - - placement_id - - variation_id - properties: - id: { type: string } - placement_id: { type: string } - variation_id: { type: string } - - AdaptyUI.UserAction: #event - type: object - oneOf: - - required: [type] - properties: - type: { type: string, const: ["close", "system_back"] } - - required: [type, value] - properties: - type: { type: string, enum: ["open_url", "custom"] } - value: { type: string } - - AdaptyUI.CustomTagsValues: #request - type: object - additionalProperties: { type: string, nullable: false } - - AdaptyUI.CustomTimersValues: #request - type: object - additionalProperties: { $ref: "#/$defs/Date", nullable: false } - - AdaptyUI.AndroidPersonalizedOffers: #request - type: object - additionalProperties: { type: boolean, nullable: false } - - AdaptyUI.ProductPurchaseParameters: #request - type: object - propertyNames: { description: "key is adapty_product_id" } - additionalProperties: { - $ref: "#/$defs/AdaptyPurchaseParameters", - nullable: false - } - - AdaptyUI.IOSPresentationStyle: - type: string - enum: - - full_screen - - page_sheet - - AdaptyWebPresentation: #request - type: string - enum: - - browser_out_app - - browser_in_app - - AdaptyUI.DialogConfiguration: #request - type: object - required: [default_action] - properties: - title: { type: string } - content: { type: string } - default_action_title: { type: string } - secondary_action_title: { type: string } - - AdaptyUI.DialogActionType: - type: string - enum: - - primary - - secondary - - AdaptyPurchaseParameters: #request - type: object - properties: - subscription_update_params: - { - $ref: "#/$defs/AdaptySubscriptionUpdateParameters", - description: "Android Only", - } - is_offer_personalized: { type: boolean, description: "Android Only" } - - - AdaptySubscriptionUpdateParameters: #request # Android Only - type: object - required: - - old_sub_vendor_product_id - - replacement_mode - properties: - old_sub_vendor_product_id: { type: string } - replacement_mode: - type: string - enum: - - charge_full_price - - deferred - - without_proration - - charge_prorated_price - - with_time_proration - - AdaptyRefundPreference: - type: string - enum: - - no_preference - - grant - - decline - - AdaptyUI.OnboardingMeta: #event - type: object - required: [onboarding_id, screen_cid, screen_index, total_screens] - properties: - onboarding_id: { type: string } - screen_cid: { type: string } - screen_index: { type: integer } - total_screens: { type: integer } - - AdaptyUI.OnboardingsStateParams: #event - type: object - required: [id, value, label] - properties: - id: { type: string } - value: { type: string } - label: { type: string } - -$assets: - Color: - type: object - required: [id, type, value] - properties: - id: { type: string } - type: { const: color } - value: { $ref: "#/$assets/Color.Hex" } - - Color.Hex: - type: string - pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$" - description: RRGGBB or RRGGBBAA - - ColorGradient: - type: object - required: [id, type, values, points] - properties: - id: { type: string } - type: - type: string - enum: - - linear-gradient - values: - type: array - items: - type: object - required: [color, p] - properties: - color: { $ref: "#/$assets/Color.Hex" } - p: { type: number } - points: - type: object - required: [x0, y0, x1, y1] - properties: - x0: { type: number } - y0: { type: number } - x1: { type: number } - y1: { type: number } - - Image: - type: object - required: [id, type] - properties: - id: { type: string } - type: { const: image } - oneOf: - - required: [value] - properties: - value: { type: string, contentEncoding: base64 } - - required: [asset_id] - properties: - asset_id: { type: string } - - required: [path] - properties: - path: { type: string } - - Video: - type: object - required: [id, type] - properties: - id: { type: string } - type: { const: video } - oneOf: - - required: [asset_id] - properties: - asset_id: { type: string } - - required: [path] - properties: - path: { type: string } From 59e53f513e3e9840ac40bd57e4ecef8a548667ce Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Tue, 10 Feb 2026 07:11:31 -0800 Subject: [PATCH 38/50] feat(core): export all utils (generateId, filterUndefined, mapValues, mergeOptions, withErrorContext) --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index a3645d7..ecd89dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,3 +44,9 @@ export * from './types/paywall-events'; export * from './types/onboarding-events'; export type { components } from './types/api'; + +export { generateId } from './utils/generate-id'; +export { filterUndefined } from './utils/compact-object'; +export { mapValues } from './utils/map-values'; +export { mergeOptions } from './utils/merge-options'; +export { withErrorContext } from './utils/with-error-context'; From 80675b34a0b7d1ffdcbef2c7d4574ca03f8d340f Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 11 Feb 2026 04:20:50 -0800 Subject: [PATCH 39/50] feat: extract UI event mappings from RN SDK (verbatim copy) Copy paywall and onboarding event mapping constants and extractCallbackArgs functions from react-native-adapty into @adapty/core as platform-agnostic shared code. Files are verbatim copies with only import paths adapted. --- src/index.ts | 4 + src/ui/onboarding-event-mapping.ts | 127 +++++++++++++++++++++++++++++ src/ui/paywall-event-mapping.ts | 113 +++++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 src/ui/onboarding-event-mapping.ts create mode 100644 src/ui/paywall-event-mapping.ts diff --git a/src/index.ts b/src/index.ts index ecd89dc..598288a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,10 @@ export * from './types/onboarding-events'; export type { components } from './types/api'; +// UI event mappings +export * from './ui/paywall-event-mapping'; +export * from './ui/onboarding-event-mapping'; + export { generateId } from './utils/generate-id'; export { filterUndefined } from './utils/compact-object'; export { mapValues } from './utils/map-values'; diff --git a/src/ui/onboarding-event-mapping.ts b/src/ui/onboarding-event-mapping.ts new file mode 100644 index 0000000..5abe2ac --- /dev/null +++ b/src/ui/onboarding-event-mapping.ts @@ -0,0 +1,127 @@ +import type { OnboardingEventHandlers } from './types'; +import { OnboardingEventId } from '@/types/onboarding-events'; +import type { ParsedOnboardingEvent } from '@/types/onboarding-events'; + +type EventName = keyof OnboardingEventHandlers; + +type UiEventMapping = { + [nativeEventId: string]: { + handlerName: keyof OnboardingEventHandlers; + propertyMap?: { + [key: string]: string; + }; + }[]; +}; + +export const ONBOARDING_EVENT_MAPPINGS: UiEventMapping = { + onboarding_on_close_action: [ + { + handlerName: 'onClose', + }, + ], + onboarding_on_custom_action: [ + { + handlerName: 'onCustom', + }, + ], + onboarding_on_paywall_action: [ + { + handlerName: 'onPaywall', + }, + ], + onboarding_on_state_updated_action: [ + { + handlerName: 'onStateUpdated', + }, + ], + onboarding_did_finish_loading: [ + { + handlerName: 'onFinishedLoading', + }, + ], + onboarding_on_analytics_action: [ + { + handlerName: 'onAnalytics', + }, + ], + onboarding_did_fail_with_error: [ + { + handlerName: 'onError', + }, + ], +}; + +export const HANDLER_TO_EVENT_CONFIG: Record< + keyof OnboardingEventHandlers, + { + nativeEvent: string; + handlerName: keyof OnboardingEventHandlers; + } +> = Object.entries(ONBOARDING_EVENT_MAPPINGS).reduce( + (acc, [nativeEvent, mappings]) => { + mappings.forEach(({ handlerName }) => { + acc[handlerName] = { + nativeEvent, + handlerName, + }; + }); + return acc; + }, + {} as Record< + keyof OnboardingEventHandlers, + { + nativeEvent: string; + handlerName: keyof OnboardingEventHandlers; + } + >, +); + +// Reverse mapping: nativeEvent -> EventName[] +export const NATIVE_EVENT_TO_HANDLERS: Record = + Object.entries(HANDLER_TO_EVENT_CONFIG).reduce( + (acc, [handlerName, config]) => { + if (!acc[config.nativeEvent]) { + acc[config.nativeEvent] = []; + } + const handlers = acc[config.nativeEvent]; + if (handlers) { + handlers.push(handlerName as EventName); + } + return acc; + }, + {} as Record, + ); + +type ExtractedArgs = Parameters< + OnboardingEventHandlers[T] +>; + +export function extractOnboardingCallbackArgs< + T extends keyof OnboardingEventHandlers, +>(_handlerName: T, event: ParsedOnboardingEvent): ExtractedArgs { + switch (event.id) { + case OnboardingEventId.Close: + case OnboardingEventId.Custom: + case OnboardingEventId.Paywall: + return [event.actionId, event.meta] as ExtractedArgs; + + case OnboardingEventId.StateUpdated: + return [event.action, event.meta] as ExtractedArgs; + + case OnboardingEventId.FinishedLoading: + return [event.meta] as ExtractedArgs; + + case OnboardingEventId.Analytics: + return [ + { + ...event.event, + // Add backward compatibility: populate element_id from elementId + element_id: event.event.elementId, + }, + event.meta, + ] as ExtractedArgs; + + case OnboardingEventId.Error: + return [event.error] as ExtractedArgs; + } +} diff --git a/src/ui/paywall-event-mapping.ts b/src/ui/paywall-event-mapping.ts new file mode 100644 index 0000000..331365e --- /dev/null +++ b/src/ui/paywall-event-mapping.ts @@ -0,0 +1,113 @@ +import type { EventHandlers } from './types'; +import { PaywallEventId } from '@/types/paywall-events'; +import type { + ParsedPaywallEvent, + PaywallEventIdType, +} from '@/types/paywall-events'; + +type EventName = keyof EventHandlers; + +/** + * Resolves native event to handler name based on event data + */ +export const NATIVE_EVENT_RESOLVER: Record< + PaywallEventIdType, + (event: ParsedPaywallEvent) => EventName | null +> = { + paywall_view_did_perform_action: event => { + if (event.id !== PaywallEventId.DidPerformAction) return null; + + const actionMap: Record = { + close: 'onCloseButtonPress', + system_back: 'onAndroidSystemBack', + open_url: 'onUrlPress', + custom: 'onCustomAction', + }; + + return actionMap[event.action.type] || null; + }, + paywall_view_did_appear: () => 'onPaywallShown', + paywall_view_did_disappear: () => 'onPaywallClosed', + paywall_view_did_select_product: () => 'onProductSelected', + paywall_view_did_start_purchase: () => 'onPurchaseStarted', + paywall_view_did_finish_purchase: () => 'onPurchaseCompleted', + paywall_view_did_fail_purchase: () => 'onPurchaseFailed', + paywall_view_did_start_restore: () => 'onRestoreStarted', + paywall_view_did_finish_restore: () => 'onRestoreCompleted', + paywall_view_did_fail_restore: () => 'onRestoreFailed', + paywall_view_did_fail_rendering: () => 'onRenderingFailed', + paywall_view_did_fail_loading_products: () => 'onLoadingProductsFailed', + paywall_view_did_finish_web_payment_navigation: () => + 'onWebPaymentNavigationFinished', +}; + +/** + * Maps handler name to native event name + * Used in addListener/addInternalListener to subscribe to correct native event + */ +export const HANDLER_TO_NATIVE_EVENT: Record = { + onCloseButtonPress: 'paywall_view_did_perform_action', + onAndroidSystemBack: 'paywall_view_did_perform_action', + onUrlPress: 'paywall_view_did_perform_action', + onCustomAction: 'paywall_view_did_perform_action', + onPaywallShown: 'paywall_view_did_appear', + onPaywallClosed: 'paywall_view_did_disappear', + onProductSelected: 'paywall_view_did_select_product', + onPurchaseStarted: 'paywall_view_did_start_purchase', + onPurchaseCompleted: 'paywall_view_did_finish_purchase', + onPurchaseFailed: 'paywall_view_did_fail_purchase', + onRestoreStarted: 'paywall_view_did_start_restore', + onRestoreCompleted: 'paywall_view_did_finish_restore', + onRestoreFailed: 'paywall_view_did_fail_restore', + onRenderingFailed: 'paywall_view_did_fail_rendering', + onLoadingProductsFailed: 'paywall_view_did_fail_loading_products', + onWebPaymentNavigationFinished: + 'paywall_view_did_finish_web_payment_navigation', +}; + +type ExtractedArgs = Parameters< + EventHandlers[T] +>; + +export function extractPaywallCallbackArgs( + handlerName: T, + event: ParsedPaywallEvent, +): ExtractedArgs { + switch (event.id) { + case PaywallEventId.DidSelectProduct: + return [event.productId] as ExtractedArgs; + + case PaywallEventId.DidStartPurchase: + return [event.product] as ExtractedArgs; + + case PaywallEventId.DidFinishPurchase: + return [event.purchaseResult, event.product] as ExtractedArgs; + + case PaywallEventId.DidFailPurchase: + return [event.error, event.product] as ExtractedArgs; + + case PaywallEventId.DidFinishRestore: + return [event.profile] as ExtractedArgs; + + case PaywallEventId.DidFailRestore: + case PaywallEventId.DidFailRendering: + case PaywallEventId.DidFailLoadingProducts: + return [event.error] as ExtractedArgs; + + case PaywallEventId.DidPerformAction: + // For DidPerformAction, different handlers need different arguments + if (handlerName === 'onUrlPress' || handlerName === 'onCustomAction') { + return [event.action.value ?? ''] as ExtractedArgs; + } + // onCloseButtonPress, onAndroidSystemBack don't take arguments + return [] as ExtractedArgs; + + case PaywallEventId.DidFinishWebPaymentNavigation: + return [event.product, event.error] as unknown as ExtractedArgs; + + case PaywallEventId.DidAppear: + case PaywallEventId.DidDisappear: + case PaywallEventId.DidStartRestore: + return [] as ExtractedArgs; + } +} From b84006a7ad9859b5dbd43d8df149fc7eed466675 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 11 Feb 2026 04:36:00 -0800 Subject: [PATCH 40/50] refactor: use PaywallEventId constants as mapping keys, add v4 rename TODOs --- src/ui/paywall-event-mapping.ts | 63 +++++++++++++++++---------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/ui/paywall-event-mapping.ts b/src/ui/paywall-event-mapping.ts index 331365e..c3b4d57 100644 --- a/src/ui/paywall-event-mapping.ts +++ b/src/ui/paywall-event-mapping.ts @@ -14,7 +14,7 @@ export const NATIVE_EVENT_RESOLVER: Record< PaywallEventIdType, (event: ParsedPaywallEvent) => EventName | null > = { - paywall_view_did_perform_action: event => { + [PaywallEventId.DidPerformAction]: event => { if (event.id !== PaywallEventId.DidPerformAction) return null; const actionMap: Record = { @@ -26,18 +26,20 @@ export const NATIVE_EVENT_RESOLVER: Record< return actionMap[event.action.type] || null; }, - paywall_view_did_appear: () => 'onPaywallShown', - paywall_view_did_disappear: () => 'onPaywallClosed', - paywall_view_did_select_product: () => 'onProductSelected', - paywall_view_did_start_purchase: () => 'onPurchaseStarted', - paywall_view_did_finish_purchase: () => 'onPurchaseCompleted', - paywall_view_did_fail_purchase: () => 'onPurchaseFailed', - paywall_view_did_start_restore: () => 'onRestoreStarted', - paywall_view_did_finish_restore: () => 'onRestoreCompleted', - paywall_view_did_fail_restore: () => 'onRestoreFailed', - paywall_view_did_fail_rendering: () => 'onRenderingFailed', - paywall_view_did_fail_loading_products: () => 'onLoadingProductsFailed', - paywall_view_did_finish_web_payment_navigation: () => + // TODO: v4 — rename onPaywallShown to onAppeared for consistency with Capacitor SDK + [PaywallEventId.DidAppear]: () => 'onPaywallShown', + // TODO: v4 — rename onPaywallClosed to onDisappeared for consistency with Capacitor SDK + [PaywallEventId.DidDisappear]: () => 'onPaywallClosed', + [PaywallEventId.DidSelectProduct]: () => 'onProductSelected', + [PaywallEventId.DidStartPurchase]: () => 'onPurchaseStarted', + [PaywallEventId.DidFinishPurchase]: () => 'onPurchaseCompleted', + [PaywallEventId.DidFailPurchase]: () => 'onPurchaseFailed', + [PaywallEventId.DidStartRestore]: () => 'onRestoreStarted', + [PaywallEventId.DidFinishRestore]: () => 'onRestoreCompleted', + [PaywallEventId.DidFailRestore]: () => 'onRestoreFailed', + [PaywallEventId.DidFailRendering]: () => 'onRenderingFailed', + [PaywallEventId.DidFailLoadingProducts]: () => 'onLoadingProductsFailed', + [PaywallEventId.DidFinishWebPaymentNavigation]: () => 'onWebPaymentNavigationFinished', }; @@ -46,23 +48,24 @@ export const NATIVE_EVENT_RESOLVER: Record< * Used in addListener/addInternalListener to subscribe to correct native event */ export const HANDLER_TO_NATIVE_EVENT: Record = { - onCloseButtonPress: 'paywall_view_did_perform_action', - onAndroidSystemBack: 'paywall_view_did_perform_action', - onUrlPress: 'paywall_view_did_perform_action', - onCustomAction: 'paywall_view_did_perform_action', - onPaywallShown: 'paywall_view_did_appear', - onPaywallClosed: 'paywall_view_did_disappear', - onProductSelected: 'paywall_view_did_select_product', - onPurchaseStarted: 'paywall_view_did_start_purchase', - onPurchaseCompleted: 'paywall_view_did_finish_purchase', - onPurchaseFailed: 'paywall_view_did_fail_purchase', - onRestoreStarted: 'paywall_view_did_start_restore', - onRestoreCompleted: 'paywall_view_did_finish_restore', - onRestoreFailed: 'paywall_view_did_fail_restore', - onRenderingFailed: 'paywall_view_did_fail_rendering', - onLoadingProductsFailed: 'paywall_view_did_fail_loading_products', - onWebPaymentNavigationFinished: - 'paywall_view_did_finish_web_payment_navigation', + onCloseButtonPress: PaywallEventId.DidPerformAction, + onAndroidSystemBack: PaywallEventId.DidPerformAction, + onUrlPress: PaywallEventId.DidPerformAction, + onCustomAction: PaywallEventId.DidPerformAction, + // TODO: v4 — rename onPaywallShown to onAppeared for consistency with Capacitor SDK + onPaywallShown: PaywallEventId.DidAppear, + // TODO: v4 — rename onPaywallClosed to onDisappeared for consistency with Capacitor SDK + onPaywallClosed: PaywallEventId.DidDisappear, + onProductSelected: PaywallEventId.DidSelectProduct, + onPurchaseStarted: PaywallEventId.DidStartPurchase, + onPurchaseCompleted: PaywallEventId.DidFinishPurchase, + onPurchaseFailed: PaywallEventId.DidFailPurchase, + onRestoreStarted: PaywallEventId.DidStartRestore, + onRestoreCompleted: PaywallEventId.DidFinishRestore, + onRestoreFailed: PaywallEventId.DidFailRestore, + onRenderingFailed: PaywallEventId.DidFailRendering, + onLoadingProductsFailed: PaywallEventId.DidFailLoadingProducts, + onWebPaymentNavigationFinished: PaywallEventId.DidFinishWebPaymentNavigation, }; type ExtractedArgs = Parameters< From dafa1e06efbd554ad3c76cb254efb6784706de79 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 11 Feb 2026 04:50:51 -0800 Subject: [PATCH 41/50] feat: export ui/types from core public API --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 598288a..7d1b795 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,7 @@ export type { components } from './types/api'; // UI event mappings export * from './ui/paywall-event-mapping'; export * from './ui/onboarding-event-mapping'; +export * from './ui/types'; export { generateId } from './utils/generate-id'; export { filterUndefined } from './utils/compact-object'; From ad2fcbcc89de75c24d2cefa3cb4da898f9f793a2 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 11 Feb 2026 06:14:10 -0800 Subject: [PATCH 42/50] chore: remove unused version placeholder export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No downstream SDK imports version from @adapty/core — each SDK maintains its own version file. The dev placeholder was never consumed and only added noise. --- src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7d1b795..4ade785 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,6 @@ * Add your exports here as you develop the SDK. */ -export const version = '0.0.0-dev.0000000000000000000000000000000000000000'; - export type { IPlatformAdapter, ISdkMetadataAdapter, From 11e2c53bb5aaf58bf319abd22d240e6936c010b9 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Wed, 11 Feb 2026 08:20:02 -0800 Subject: [PATCH 43/50] refactor: parse functions accept CoderFactory parameter instead of direct instantiation Fixes bug where coders were created without IPlatformAdapter. --- src/coders/parse-onboarding.ts | 17 +++++----- src/coders/parse-paywall.ts | 52 +++++++++++++++++++---------- src/coders/parse.ts | 61 +++++++++++++++------------------- src/index.ts | 9 +++++ 4 files changed, 78 insertions(+), 61 deletions(-) diff --git a/src/coders/parse-onboarding.ts b/src/coders/parse-onboarding.ts index 5341b34..d5c17b5 100644 --- a/src/coders/parse-onboarding.ts +++ b/src/coders/parse-onboarding.ts @@ -1,10 +1,8 @@ import { AdaptyError } from '@/adapty-error'; import { LogContext } from '../logger'; import { ErrorConverter } from './error-coder'; +import type { CoderFactory } from './factory'; import type { Converter } from './types'; -import { AdaptyNativeErrorCoder } from './adapty-native-error'; -import { AdaptyUiOnboardingMetaCoder } from '@/coders/adapty-ui-onboarding-meta'; -import { AdaptyUiOnboardingStateUpdatedActionCoder } from '@/coders/adapty-ui-onboarding-state-updated-action'; import type { AdaptyUiOnboardingMeta } from '@/ui/types'; import type { OnboardingStateUpdatedAction } from '@/ui/types'; import { @@ -30,6 +28,7 @@ export { // Parser export function parseOnboardingEvent( + factory: CoderFactory, input: string, ctx?: LogContext, ): ParsedOnboardingEvent | null { @@ -54,7 +53,7 @@ export function parseOnboardingEvent( variationId: viewObj['variation_id'] as string | undefined, }; const decodeMeta = () => - getOnboardingCoder('meta', ctx)!.decode( + getOnboardingCoder(factory, 'meta', ctx)!.decode( obj['meta'], ) as AdaptyUiOnboardingMeta; @@ -73,7 +72,7 @@ export function parseOnboardingEvent( return { id: eventId, view, - action: getOnboardingCoder('action', ctx)!.decode( + action: getOnboardingCoder(factory, 'action', ctx)!.decode( obj['action'], ) as OnboardingStateUpdatedAction, meta: decodeMeta(), @@ -102,6 +101,7 @@ export function parseOnboardingEvent( case OnboardingEventId.Error: { const errorCoder = getOnboardingCoder( + factory, 'error', ctx, ) as ErrorConverter; @@ -121,15 +121,16 @@ export function parseOnboardingEvent( type OnboardingCoderType = 'meta' | 'action' | 'error'; function getOnboardingCoder( + factory: CoderFactory, type: OnboardingCoderType, _ctx?: LogContext, ): Converter | ErrorConverter { switch (type) { case 'meta': - return new AdaptyUiOnboardingMetaCoder(); + return factory.createUiOnboardingMetaCoder(); case 'action': - return new AdaptyUiOnboardingStateUpdatedActionCoder(); + return factory.createUiOnboardingStateUpdatedActionCoder(); case 'error': - return new AdaptyNativeErrorCoder(); + return factory.createNativeErrorCoder(); } } diff --git a/src/coders/parse-paywall.ts b/src/coders/parse-paywall.ts index c1a8b59..093aab8 100644 --- a/src/coders/parse-paywall.ts +++ b/src/coders/parse-paywall.ts @@ -1,11 +1,8 @@ import { AdaptyError } from '@/adapty-error'; import { LogContext } from '../logger'; import { ErrorConverter } from './error-coder'; +import type { CoderFactory } from './factory'; import type { Converter } from './types'; -import { AdaptyNativeErrorCoder } from './adapty-native-error'; -import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; -import { AdaptyProfileCoder } from './adapty-profile'; -import { AdaptyPurchaseResultCoder } from './adapty-purchase-result'; import type { AdaptyPaywallProduct, AdaptyProfile, @@ -40,6 +37,7 @@ export { // Parser export function parsePaywallEvent( + factory: CoderFactory, input: string, ctx?: LogContext, ): ParsedPaywallEvent | null { @@ -104,7 +102,7 @@ export function parsePaywallEvent( return { id: eventId, view, - product: getPaywallCoder('product', ctx)!.decode( + product: getPaywallCoder(factory, 'product', ctx)!.decode( obj['product'], ) as AdaptyPaywallProduct, }; @@ -113,22 +111,26 @@ export function parsePaywallEvent( return { id: eventId, view, - purchaseResult: getPaywallCoder('purchaseResult', ctx)!.decode( + purchaseResult: getPaywallCoder(factory, 'purchaseResult', ctx)!.decode( obj['purchased_result'], ) as AdaptyPurchaseResult, - product: getPaywallCoder('product', ctx)!.decode( + product: getPaywallCoder(factory, 'product', ctx)!.decode( obj['product'], ) as AdaptyPaywallProduct, }; case PaywallEventId.DidFailPurchase: { - const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const errorCoder = getPaywallCoder( + factory, + 'error', + ctx, + ) as ErrorConverter; const decodedError = errorCoder.decode(obj['error']); return { id: eventId, view, error: errorCoder.getError(decodedError), - product: getPaywallCoder('product', ctx)!.decode( + product: getPaywallCoder(factory, 'product', ctx)!.decode( obj['product'], ) as AdaptyPaywallProduct, }; @@ -144,13 +146,17 @@ export function parsePaywallEvent( return { id: eventId, view, - profile: getPaywallCoder('profile', ctx)!.decode( + profile: getPaywallCoder(factory, 'profile', ctx)!.decode( obj['profile'], ) as AdaptyProfile, }; case PaywallEventId.DidFailRestore: { - const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const errorCoder = getPaywallCoder( + factory, + 'error', + ctx, + ) as ErrorConverter; const decodedError = errorCoder.decode(obj['error']); return { id: eventId, @@ -160,7 +166,11 @@ export function parsePaywallEvent( } case PaywallEventId.DidFailRendering: { - const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const errorCoder = getPaywallCoder( + factory, + 'error', + ctx, + ) as ErrorConverter; const decodedError = errorCoder.decode(obj['error']); return { id: eventId, @@ -170,7 +180,11 @@ export function parsePaywallEvent( } case PaywallEventId.DidFailLoadingProducts: { - const errorCoder = getPaywallCoder('error', ctx) as ErrorConverter; + const errorCoder = getPaywallCoder( + factory, + 'error', + ctx, + ) as ErrorConverter; const decodedError = errorCoder.decode(obj['error']); return { id: eventId, @@ -184,13 +198,14 @@ export function parsePaywallEvent( id: eventId, view, product: obj['product'] - ? (getPaywallCoder('product', ctx)!.decode( + ? (getPaywallCoder(factory, 'product', ctx)!.decode( obj['product'], ) as AdaptyPaywallProduct) : undefined, error: obj['error'] ? (() => { const errorCoder = getPaywallCoder( + factory, 'error', ctx, ) as ErrorConverter; @@ -208,17 +223,18 @@ export function parsePaywallEvent( type PaywallCoderType = 'product' | 'profile' | 'purchaseResult' | 'error'; function getPaywallCoder( + factory: CoderFactory, type: PaywallCoderType, _ctx?: LogContext, ): Converter | ErrorConverter { switch (type) { case 'product': - return new AdaptyPaywallProductCoder(); + return factory.createPaywallProductCoder(); case 'profile': - return new AdaptyProfileCoder(); + return factory.createProfileCoder(); case 'purchaseResult': - return new AdaptyPurchaseResultCoder(); + return factory.createPurchaseResultCoder(); case 'error': - return new AdaptyNativeErrorCoder(); + return factory.createNativeErrorCoder(); } } diff --git a/src/coders/parse.ts b/src/coders/parse.ts index 387f3fd..70226ff 100644 --- a/src/coders/parse.ts +++ b/src/coders/parse.ts @@ -1,22 +1,8 @@ import { AdaptyError } from '@/adapty-error'; import { LogContext } from '../logger'; -import { AdaptyNativeErrorCoder } from './adapty-native-error'; -import { AdaptyPaywallCoder } from './adapty-paywall'; -import { AdaptyPaywallProductCoder } from './adapty-paywall-product'; -import { AdaptyProfileCoder } from './adapty-profile'; -import { ArrayCoder } from './array'; -import { BridgeErrorCoder } from './bridge-error'; import { ErrorConverter } from './error-coder'; +import type { CoderFactory } from './factory'; import type { Converter } from './types'; -import { AdaptyRemoteConfigCoder } from './adapty-remote-config'; -import { AdaptyPaywallBuilderCoder } from './adapty-paywall-builder'; -import { AdaptyPurchaseResultCoder } from '@/coders/adapty-purchase-result'; -import { AdaptyOnboardingCoder } from '@/coders/adapty-onboarding'; -import { AdaptyUiOnboardingMetaCoder } from '@/coders/adapty-ui-onboarding-meta'; -import { AdaptyUiOnboardingStateParamsCoder } from '@/coders/adapty-ui-onboarding-state-params'; -import { AdaptyUiOnboardingStateUpdatedActionCoder } from '@/coders/adapty-ui-onboarding-state-updated-action'; -import { AdaptyInstallationStatusCoder } from '@/coders/adapty-installation-status'; -import { AdaptyInstallationDetailsCoder } from '@/coders/adapty-installation-details'; const AdaptyTypes = [ 'AdaptyError', @@ -55,6 +41,7 @@ interface AdaptyResultError { } export function parseMethodResult( + factory: CoderFactory, input: string, resultType: AdaptyType, ctx?: LogContext, @@ -91,10 +78,10 @@ export function parseMethodResult( return obj.success as T; } - const coder = getCoder(resultType, ctx); + const coder = getCoder(factory, resultType, ctx); return coder?.decode(obj.success); } else if (obj.hasOwnProperty('error')) { - const coder = getCoder('AdaptyError', ctx); + const coder = getCoder(factory, 'AdaptyError', ctx); const errorData = coder?.decode(obj.error); throw (coder as ErrorConverter).getError(errorData); } else { @@ -107,6 +94,7 @@ export function parseMethodResult( } export function parseCommonEvent( + factory: CoderFactory, event: string, input: string, ctx?: LogContext, @@ -121,17 +109,20 @@ export function parseCommonEvent( } switch (event) { case 'did_load_latest_profile': - return getCoder('AdaptyProfile', ctx)?.decode(obj['profile']); + return getCoder(factory, 'AdaptyProfile', ctx)?.decode(obj['profile']); case 'on_installation_details_success': - return getCoder('AdaptyInstallationDetails', ctx)?.decode(obj['details']); + return getCoder(factory, 'AdaptyInstallationDetails', ctx)?.decode( + obj['details'], + ); case 'on_installation_details_fail': - return getCoder('AdaptyError', ctx)?.decode(obj['error']); + return getCoder(factory, 'AdaptyError', ctx)?.decode(obj['error']); default: return null; } } function getCoder( + factory: CoderFactory, type: AdaptyType, ctx?: LogContext, ): Converter | ErrorConverter | null { @@ -139,35 +130,35 @@ function getCoder( switch (type as AdaptyType) { case 'AdaptyError': - return new AdaptyNativeErrorCoder(); + return factory.createNativeErrorCoder(); case 'AdaptyProfile': - return new AdaptyProfileCoder(); + return factory.createProfileCoder(); case 'AdaptyPaywall': - return new AdaptyPaywallCoder(); + return factory.createPaywallCoder(); case 'AdaptyPaywallProduct': - return new AdaptyPaywallProductCoder(); + return factory.createPaywallProductCoder(); case 'AdaptyRemoteConfig': - return new AdaptyRemoteConfigCoder(); + return factory.createRemoteConfigCoder(); case 'AdaptyPaywallBuilder': - return new AdaptyPaywallBuilderCoder(); + return factory.createPaywallBuilderCoder(); case 'AdaptyOnboarding': - return new AdaptyOnboardingCoder(); + return factory.createOnboardingCoder(); case 'AdaptyPurchaseResult': - return new AdaptyPurchaseResultCoder(); + return factory.createPurchaseResultCoder(); case 'AdaptyInstallationStatus': - return new AdaptyInstallationStatusCoder(); + return factory.createInstallationStatusCoder(); case 'AdaptyInstallationDetails': - return new AdaptyInstallationDetailsCoder(); + return factory.createInstallationDetailsCoder(); case 'AdaptyUiOnboardingMeta': - return new AdaptyUiOnboardingMetaCoder(); + return factory.createUiOnboardingMetaCoder(); case 'AdaptyUiOnboardingStateParams': - return new AdaptyUiOnboardingStateParamsCoder(); + return factory.createUiOnboardingStateParamsCoder(); case 'AdaptyUiOnboardingStateUpdatedAction': - return new AdaptyUiOnboardingStateUpdatedActionCoder(); + return factory.createUiOnboardingStateUpdatedActionCoder(); case 'BridgeError': - return new BridgeErrorCoder(); + return factory.createBridgeErrorCoder(); case 'Array': - return new ArrayCoder(() => new AdaptyPaywallProductCoder()); + return factory.createPaywallProductArrayCoder(); case 'String': return null; } diff --git a/src/index.ts b/src/index.ts index 4ade785..2d4e79f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,15 @@ export * from './ui/paywall-event-mapping'; export * from './ui/onboarding-event-mapping'; export * from './ui/types'; +// Parse functions (accept CoderFactory as first parameter) +export { + parseMethodResult, + parseCommonEvent, + type AdaptyType, +} from './coders/parse'; +export { parsePaywallEvent } from './coders/parse-paywall'; +export { parseOnboardingEvent } from './coders/parse-onboarding'; + export { generateId } from './utils/generate-id'; export { filterUndefined } from './utils/compact-object'; export { mapValues } from './utils/map-values'; From d3bf58c11baa4ea5d69e6e3bce2bb7306ec014fe Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 12 Feb 2026 07:54:21 -0800 Subject: [PATCH 44/50] refactor: simplify DefaultPlatformAdapter, remove Platform dependency --- src/adapters/defaults.test.ts | 35 +++++++++++++++++++++++++++++++++++ src/adapters/defaults.ts | 9 +-------- 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 src/adapters/defaults.test.ts diff --git a/src/adapters/defaults.test.ts b/src/adapters/defaults.test.ts new file mode 100644 index 0000000..3757069 --- /dev/null +++ b/src/adapters/defaults.test.ts @@ -0,0 +1,35 @@ +import { + DefaultPlatformAdapter, + DefaultSdkMetadataAdapter, + DefaultLoggerAdapter, +} from './defaults'; + +describe('DefaultPlatformAdapter', () => { + it('should return "unknown" as OS', () => { + const adapter = new DefaultPlatformAdapter(); + expect(adapter.OS).toBe('unknown'); + }); +}); + +describe('DefaultSdkMetadataAdapter', () => { + it('should return default values', () => { + const adapter = new DefaultSdkMetadataAdapter(); + expect(adapter.sdkName).toBe('unknown'); + expect(adapter.sdkVersion).toBe('0.0.0'); + }); + + it('should accept custom values', () => { + const adapter = new DefaultSdkMetadataAdapter('test-sdk', '1.2.3'); + expect(adapter.sdkName).toBe('test-sdk'); + expect(adapter.sdkVersion).toBe('1.2.3'); + }); +}); + +describe('DefaultLoggerAdapter', () => { + it('should create a log context', () => { + const adapter = new DefaultLoggerAdapter(); + const ctx = adapter.createContext(); + expect(ctx).toBeDefined(); + expect(ctx.stack).toBeDefined(); + }); +}); diff --git a/src/adapters/defaults.ts b/src/adapters/defaults.ts index 0fde3c4..354a3b1 100644 --- a/src/adapters/defaults.ts +++ b/src/adapters/defaults.ts @@ -5,20 +5,13 @@ import type { ILogContext, PlatformOS, } from './interfaces'; -import { Platform } from '@/platform'; import { LogContext } from '@/logger'; /** * Default platform adapter with unknown OS */ export class DefaultPlatformAdapter implements IPlatformAdapter { - get OS(): PlatformOS { - return Platform.OS; - } - - set OS(value: PlatformOS) { - Platform.OS = value; - } + readonly OS: PlatformOS = 'unknown'; } /** From 4407cae81522d0e0cd5cceaaeb91cda315d07a61 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 12 Feb 2026 07:58:51 -0800 Subject: [PATCH 45/50] refactor: remove dead Platform singleton --- src/platform.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/platform.ts diff --git a/src/platform.ts b/src/platform.ts deleted file mode 100644 index 70f373e..0000000 --- a/src/platform.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Platform detection and utilities for cross-platform code - * This is a mock replacement for react-native Platform - */ - -import type { PlatformOS } from '@/adapters/interfaces'; - -export class Platform { - private static _os: PlatformOS = 'unknown'; - - static get OS(): PlatformOS { - return Platform._os; - } - - static set OS(value: PlatformOS) { - Platform._os = value; - } -} From 03c4f199c7f5bc4e4a91c11029e770ccb1ccd7cd Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 12 Feb 2026 08:18:57 -0800 Subject: [PATCH 46/50] docs: update AGENTS.md, remove stale platform.ts reference --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index f23db63..0bb9789 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,7 +75,7 @@ This repo uses platform dependency injection to keep `@adapty/core` platform-agn ### Default Adapters - **Defaults:** `src/adapters/defaults.ts` -- **DefaultPlatformAdapter** proxies to `src/platform.ts` (test helper). +- **DefaultPlatformAdapter** returns `'unknown'` as the default OS value. - **DefaultLoggerAdapter** is a noop logger for core usage. ### Coder Factory From 707fa61e123e75cd5eb35e0222fcf1fd01bc0116 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Thu, 12 Feb 2026 08:47:25 -0800 Subject: [PATCH 47/50] feat: improve typescript --- src/adapters/interfaces.ts | 2 +- src/coders/adapty-native-error.ts | 2 +- src/coders/adapty-purchase-params.ts | 11 +++++---- .../adapty-ui-create-paywall-view-params.ts | 5 +++- ...apty-ui-onboarding-state-updated-action.ts | 8 +++++-- src/coders/hashmap.ts | 8 ++++--- src/coders/json.ts | 4 ++-- src/coders/types.ts | 2 +- src/logger/log.ts | 2 +- src/logger/types.ts | 2 +- src/types/api.d.ts | 2 +- src/ui/types.ts | 4 ++-- src/utils/compact-object.ts | 2 +- src/utils/merge-options.test.ts | 23 +++++++++++++------ src/utils/merge-options.ts | 16 ++++++++----- src/utils/with-error-context.ts | 21 +++++++++-------- 16 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/adapters/interfaces.ts b/src/adapters/interfaces.ts index db92338..c6e971d 100644 --- a/src/adapters/interfaces.ts +++ b/src/adapters/interfaces.ts @@ -27,7 +27,7 @@ export interface ISdkMetadataAdapter { /** * Lazy-evaluated log arguments */ -export type LogArgs = () => Record; +export type LogArgs = () => Record; /** * Logger adapter interface for platform-specific logging diff --git a/src/coders/adapty-native-error.ts b/src/coders/adapty-native-error.ts index 9cda3a4..7848aa9 100644 --- a/src/coders/adapty-native-error.ts +++ b/src/coders/adapty-native-error.ts @@ -22,7 +22,7 @@ export class AdaptyNativeErrorCoder public getError(data: Model): AdaptyError { return new AdaptyError({ - adaptyCode: data.adaptyCode as any, + adaptyCode: data.adaptyCode as AdaptyError['adaptyCode'], message: data.message, detail: data.detail, }); diff --git a/src/coders/adapty-purchase-params.ts b/src/coders/adapty-purchase-params.ts index 3ad6b9b..8f928ca 100644 --- a/src/coders/adapty-purchase-params.ts +++ b/src/coders/adapty-purchase-params.ts @@ -2,14 +2,17 @@ import type { IPlatformAdapter } from '@/adapters/interfaces'; import * as Input from '@/types/inputs'; type Model = Input.MakePurchaseParamsInput; -type Serializable = Record; +type Serializable = Record; function isDeprecatedType( - data: any, + data: unknown, ): data is { android?: Input.AdaptyAndroidSubscriptionUpdateParameters } { return ( - data && - data.android && + typeof data === 'object' && + data !== null && + 'android' in data && + typeof data.android === 'object' && + data.android !== null && 'oldSubVendorProductId' in data.android && 'prorationMode' in data.android ); diff --git a/src/coders/adapty-ui-create-paywall-view-params.ts b/src/coders/adapty-ui-create-paywall-view-params.ts index ca1ddf7..9fb675b 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.ts @@ -3,6 +3,7 @@ import type { CreatePaywallViewParamsInput, AdaptyCustomAsset, } from '@/ui/types'; +import type { FileLocation } from '@/types/inputs'; import type { Def } from '@/types/schema'; import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; import { @@ -75,7 +76,9 @@ export class AdaptyUICreatePaywallViewParamsCoder { private encodeCustomAssets( assets: Record, ): Def['AdaptyUI.CustomAssets'] { - const getAssetId = (asset: any): string => { + const getAssetId = ( + asset: { relativeAssetPath: string } | { fileLocation: FileLocation }, + ): string => { return resolveAssetId(asset, this.platform.OS) || ''; }; diff --git a/src/coders/adapty-ui-onboarding-state-updated-action.ts b/src/coders/adapty-ui-onboarding-state-updated-action.ts index 76e8ad0..5ea1f6b 100644 --- a/src/coders/adapty-ui-onboarding-state-updated-action.ts +++ b/src/coders/adapty-ui-onboarding-state-updated-action.ts @@ -50,12 +50,16 @@ export class AdaptyUiOnboardingStateUpdatedActionCoder extends SimpleCoder< case 'input': return { ...base, - value: data.value as any, + elementType: 'input', + value: data.value as + | { type: 'text' | 'email'; value: string } + | { type: 'number'; value: number }, }; case 'date_picker': return { ...base, - value: data.value as any, + elementType: 'date_picker', + value: data.value as { day?: number; month?: number; year?: number }, }; default: throw new Error(`Unknown element_type: ${elementType}`); diff --git a/src/coders/hashmap.ts b/src/coders/hashmap.ts index 79e8a4f..f4431f6 100644 --- a/src/coders/hashmap.ts +++ b/src/coders/hashmap.ts @@ -2,7 +2,7 @@ import type { Converter } from './types'; // Coder for Record export class HashmapCoder> implements Converter< - Record, + Record>, Record > { private coder: T | null; @@ -11,8 +11,10 @@ export class HashmapCoder> implements Converter< this.coder = coder; } - decode(input: Record): Record { - const result: Record = {}; + decode( + input: Record, + ): Record> { + const result: Record = {}; Object.keys(input).forEach(key => { const property = input[key as string]; diff --git a/src/coders/json.ts b/src/coders/json.ts index 865b6d2..9439708 100644 --- a/src/coders/json.ts +++ b/src/coders/json.ts @@ -1,6 +1,6 @@ import { Converter } from './types'; -type Model = any; +type Model = Record; type Serialized = string; export class JSONCoder implements Converter { @@ -9,7 +9,7 @@ export class JSONCoder implements Converter { return {}; } - return JSON.parse(input); + return JSON.parse(input) as Model; } encode(value: Model): Serialized { if (Object.keys(value).length === 0) { diff --git a/src/coders/types.ts b/src/coders/types.ts index 6b43607..cdba793 100644 --- a/src/coders/types.ts +++ b/src/coders/types.ts @@ -6,7 +6,7 @@ export type StrType = T extends string ? 'boolean' : T extends number ? 'number' - : T extends any[] + : T extends unknown[] ? 'array' : 'object'; diff --git a/src/logger/log.ts b/src/logger/log.ts index e82522d..15a0887 100644 --- a/src/logger/log.ts +++ b/src/logger/log.ts @@ -4,7 +4,7 @@ import { consoleLogSink } from './console-sink'; // Type for lazy evaluation functions type LazyMessage = () => string; -type LazyParams = () => Record; +type LazyParams = () => Record; export class Log { public static logLevel: LogLevel | null = null; diff --git a/src/logger/types.ts b/src/logger/types.ts index 6edb17c..ecfcdf8 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -6,7 +6,7 @@ export interface LogEvent { level: LogLevel; funcName: string; message: string; - params?: Record; + params?: Record; formatted: string; } diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 07c5438..dffa136 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -3,7 +3,7 @@ type Without = { [P in Exclude]?: never }; type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] +type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> diff --git a/src/ui/types.ts b/src/ui/types.ts index 36d6500..d98d7d8 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -11,9 +11,9 @@ import { FileLocation, MakePurchaseParamsInput } from '@/types/inputs'; /** * @internal */ -export type ArgType = T extends () => any +export type ArgType = T extends () => unknown ? void - : T extends (arg: infer U) => any + : T extends (arg: infer U) => unknown ? U : void; diff --git a/src/utils/compact-object.ts b/src/utils/compact-object.ts index 2799e28..0a1f128 100644 --- a/src/utils/compact-object.ts +++ b/src/utils/compact-object.ts @@ -3,7 +3,7 @@ * @param obj - Source object with potentially undefined values * @returns Object with undefined values filtered out */ -export function filterUndefined>(obj: T): T { +export function filterUndefined(obj: T): T { return Object.fromEntries( Object.entries(obj).filter(([_, value]) => value !== undefined), ) as T; diff --git a/src/utils/merge-options.test.ts b/src/utils/merge-options.test.ts index 3746805..51b722a 100644 --- a/src/utils/merge-options.test.ts +++ b/src/utils/merge-options.test.ts @@ -213,7 +213,7 @@ describe('mergeOptions', () => { const defaults = { symbol: sym1 }; const options = { symbol: sym2 }; - const result = mergeOptions(options, defaults); + const result = mergeOptions<{ symbol: symbol }>(options, defaults); expect(result.symbol).toBe(sym2); }); @@ -278,7 +278,10 @@ describe('mergeOptions', () => { const defaults = { callback: defaultFn }; const options = { callback: customFn }; - const result = mergeOptions(options, defaults); + const result = mergeOptions<{ callback: () => string }>( + options, + defaults, + ); expect(result.callback).toBe(customFn); expect(result.callback()).toBe('custom'); @@ -291,7 +294,7 @@ describe('mergeOptions', () => { const defaults = { timestamp: defaultDate }; const options = { timestamp: customDate }; - const result = mergeOptions(options, defaults); + const result = mergeOptions<{ timestamp: Date }>(options, defaults); expect(result.timestamp).toBe(customDate); }); @@ -321,7 +324,10 @@ describe('mergeOptions', () => { const maliciousOptions = JSON.parse('{"__proto__": {"polluted": true}}'); const defaults = { safe: true }; - const result = mergeOptions(maliciousOptions, defaults); + const result = mergeOptions<{ safe: boolean }>( + maliciousOptions, + defaults, + ); expect(result.safe).toBe(true); expect((result as any).__proto__.polluted).toBeUndefined(); @@ -347,11 +353,14 @@ describe('mergeOptions', () => { } } - const result = mergeOptions(largeOptions, largeDefaults); + const result = mergeOptions>( + largeOptions, + largeDefaults, + ); expect(Object.keys(result)).toHaveLength(1000); - expect(result.key0).toBe('custom0'); - expect(result.key1).toBe('default1'); + expect(result['key0']).toBe('custom0'); + expect(result['key1']).toBe('default1'); }); }); }); diff --git a/src/utils/merge-options.ts b/src/utils/merge-options.ts index 814fd80..c12e222 100644 --- a/src/utils/merge-options.ts +++ b/src/utils/merge-options.ts @@ -1,7 +1,7 @@ /** * Checks if a value is a plain object (not an array, null, Date, etc.) */ -function isPlainObject(value: any): boolean { +function isPlainObject(value: unknown): value is Record { return ( value !== null && typeof value === 'object' && @@ -15,7 +15,11 @@ function isPlainObject(value: any): boolean { /** * Deep merge implementation that replaces arrays and merges objects */ -function deepMerge(target: any, source: any, seen = new Set()): any { +function deepMerge( + target: unknown, + source: unknown, + seen = new Set(), +): unknown { // Handle null/undefined cases if (source === null || source === undefined) { return source; @@ -45,7 +49,7 @@ function deepMerge(target: any, source: any, seen = new Set()): any { seen.add(source); seen.add(target); - const result: any = {}; + const result: Record = {}; // Get all keys from both objects const allKeys = new Set([...Object.keys(target), ...Object.keys(source)]); @@ -98,9 +102,9 @@ function deepMerge(target: any, source: any, seen = new Set()): any { * @param defaults - default values * @returns merged object with TResult type */ -export function mergeOptions( - options: any, - defaults: any, +export function mergeOptions( + options: unknown, + defaults: unknown, ): TResult { return deepMerge(defaults, options) as TResult; } diff --git a/src/utils/with-error-context.ts b/src/utils/with-error-context.ts index d6fb41c..0cdcb0f 100644 --- a/src/utils/with-error-context.ts +++ b/src/utils/with-error-context.ts @@ -1,6 +1,4 @@ -type AnyCallback = (...args: any[]) => any; - -function toJSON(this: Error & { originalError: any }) { +function toJSON(this: Error & { originalError: unknown }) { return { message: this.message, name: this.name, @@ -18,27 +16,30 @@ function toJSON(this: Error & { originalError: any }) { * Wraps a user-provided callback with error context enrichment. * Catches exceptions and wraps them with source and handler information. */ -export function withErrorContext( +export function withErrorContext unknown>( callback: T, handlerName: string, source: string, -): T & AnyCallback { +): T { const wrapped = function ( this: unknown, ...args: Parameters ): ReturnType { try { - return callback.apply(this, args); + return callback.apply(this, args) as ReturnType; } catch (error) { const message = `Unhandled exception in user's handler in ${source}.${handlerName}`; - const wrappedError = new Error(message); - (wrappedError as any).originalError = error; - (wrappedError as any).toJSON = toJSON; + const wrappedError = new Error(message) as Error & { + originalError: unknown; + toJSON: typeof toJSON; + }; + wrappedError.originalError = error; + wrappedError.toJSON = toJSON; throw wrappedError; } }; - return wrapped as T & AnyCallback; + return wrapped as T; } From 1d86b96a1871f1cb3768120d847e7bf4f1ed8201 Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 13 Feb 2026 13:34:32 +0300 Subject: [PATCH 48/50] refactor: rename UI folder --- src/coders/adapty-ui-create-onboarding-view-params.test.ts | 2 +- src/coders/adapty-ui-create-onboarding-view-params.ts | 2 +- src/coders/adapty-ui-create-paywall-view-params.test.ts | 2 +- src/coders/adapty-ui-create-paywall-view-params.ts | 2 +- src/coders/adapty-ui-dialog-config.ts | 2 +- src/coders/adapty-ui-media-cache.ts | 2 +- src/coders/adapty-ui-onboarding-meta.test.ts | 2 +- src/coders/adapty-ui-onboarding-meta.ts | 2 +- src/coders/adapty-ui-onboarding-state-params.test.ts | 2 +- src/coders/adapty-ui-onboarding-state-params.ts | 2 +- .../adapty-ui-onboarding-state-updated-action.test.ts | 2 +- src/coders/adapty-ui-onboarding-state-updated-action.ts | 2 +- src/coders/parse-onboarding.ts | 4 ++-- src/index.ts | 6 +++--- src/types/inputs.ts | 2 +- src/types/onboarding-events.ts | 2 +- src/{ui => ui-builder}/onboarding-event-mapping.ts | 0 src/{ui => ui-builder}/paywall-event-mapping.ts | 0 src/{ui => ui-builder}/types.ts | 0 19 files changed, 19 insertions(+), 19 deletions(-) rename src/{ui => ui-builder}/onboarding-event-mapping.ts (100%) rename src/{ui => ui-builder}/paywall-event-mapping.ts (100%) rename src/{ui => ui-builder}/types.ts (100%) diff --git a/src/coders/adapty-ui-create-onboarding-view-params.test.ts b/src/coders/adapty-ui-create-onboarding-view-params.test.ts index 2a621c3..574f496 100644 --- a/src/coders/adapty-ui-create-onboarding-view-params.test.ts +++ b/src/coders/adapty-ui-create-onboarding-view-params.test.ts @@ -1,5 +1,5 @@ import { AdaptyUICreateOnboardingViewParamsCoder } from './adapty-ui-create-onboarding-view-params'; -import { CreateOnboardingViewParamsInput } from '@/ui/types'; +import { CreateOnboardingViewParamsInput } from '@/ui-builder/types'; import { WebPresentation } from '@/types'; describe('AdaptyUICreateOnboardingViewParamsCoder', () => { diff --git a/src/coders/adapty-ui-create-onboarding-view-params.ts b/src/coders/adapty-ui-create-onboarding-view-params.ts index c681729..47464a3 100644 --- a/src/coders/adapty-ui-create-onboarding-view-params.ts +++ b/src/coders/adapty-ui-create-onboarding-view-params.ts @@ -1,4 +1,4 @@ -import { CreateOnboardingViewParamsInput } from '@/ui/types'; +import { CreateOnboardingViewParamsInput } from '@/ui-builder/types'; import { Req } from '@/types/schema'; type Serializable = Pick< diff --git a/src/coders/adapty-ui-create-paywall-view-params.test.ts b/src/coders/adapty-ui-create-paywall-view-params.test.ts index 6ff70cc..b0acff1 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.test.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.test.ts @@ -1,6 +1,6 @@ import type { IPlatformAdapter, PlatformOS } from '@/adapters/interfaces'; import { AdaptyUICreatePaywallViewParamsCoder } from './adapty-ui-create-paywall-view-params'; -import type { CreatePaywallViewParamsInput } from '@/ui/types'; +import type { CreatePaywallViewParamsInput } from '@/ui-builder/types'; import type { AdaptyProductIdentifier } from '@/types'; describe('AdaptyUICreatePaywallViewParamsCoder', () => { diff --git a/src/coders/adapty-ui-create-paywall-view-params.ts b/src/coders/adapty-ui-create-paywall-view-params.ts index 9fb675b..78c0dfb 100644 --- a/src/coders/adapty-ui-create-paywall-view-params.ts +++ b/src/coders/adapty-ui-create-paywall-view-params.ts @@ -2,7 +2,7 @@ import type { IPlatformAdapter } from '@/adapters/interfaces'; import type { CreatePaywallViewParamsInput, AdaptyCustomAsset, -} from '@/ui/types'; +} from '@/ui-builder/types'; import type { FileLocation } from '@/types/inputs'; import type { Def } from '@/types/schema'; import { AdaptyPurchaseParamsCoder } from './adapty-purchase-params'; diff --git a/src/coders/adapty-ui-dialog-config.ts b/src/coders/adapty-ui-dialog-config.ts index 3029b99..da3da9a 100644 --- a/src/coders/adapty-ui-dialog-config.ts +++ b/src/coders/adapty-ui-dialog-config.ts @@ -1,7 +1,7 @@ import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; -import { AdaptyUiDialogConfig } from '@/ui/types'; +import { AdaptyUiDialogConfig } from '@/ui-builder/types'; type Model = AdaptyUiDialogConfig; type Serializable = Def['AdaptyUI.DialogConfiguration']; diff --git a/src/coders/adapty-ui-media-cache.ts b/src/coders/adapty-ui-media-cache.ts index 03fdf8a..079c303 100644 --- a/src/coders/adapty-ui-media-cache.ts +++ b/src/coders/adapty-ui-media-cache.ts @@ -1,7 +1,7 @@ import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; -import { AdaptyUiMediaCache } from '@/ui/types'; +import { AdaptyUiMediaCache } from '@/ui-builder/types'; type Model = AdaptyUiMediaCache; type Serializable = Required['media_cache']; diff --git a/src/coders/adapty-ui-onboarding-meta.test.ts b/src/coders/adapty-ui-onboarding-meta.test.ts index 2fbad5c..1a844ba 100644 --- a/src/coders/adapty-ui-onboarding-meta.test.ts +++ b/src/coders/adapty-ui-onboarding-meta.test.ts @@ -1,5 +1,5 @@ import { AdaptyUiOnboardingMetaCoder } from './adapty-ui-onboarding-meta'; -import type { AdaptyUiOnboardingMeta } from '@/ui/types'; +import type { AdaptyUiOnboardingMeta } from '@/ui-builder/types'; import type { Def } from '@/types/schema'; type Model = AdaptyUiOnboardingMeta; diff --git a/src/coders/adapty-ui-onboarding-meta.ts b/src/coders/adapty-ui-onboarding-meta.ts index 0e91407..ab62742 100644 --- a/src/coders/adapty-ui-onboarding-meta.ts +++ b/src/coders/adapty-ui-onboarding-meta.ts @@ -1,7 +1,7 @@ import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; -import { AdaptyUiOnboardingMeta } from '@/ui/types'; +import { AdaptyUiOnboardingMeta } from '@/ui-builder/types'; type Model = AdaptyUiOnboardingMeta; type Serializable = Def['AdaptyUI.OnboardingMeta']; diff --git a/src/coders/adapty-ui-onboarding-state-params.test.ts b/src/coders/adapty-ui-onboarding-state-params.test.ts index 66b3345..6df65d5 100644 --- a/src/coders/adapty-ui-onboarding-state-params.test.ts +++ b/src/coders/adapty-ui-onboarding-state-params.test.ts @@ -1,5 +1,5 @@ import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state-params'; -import type { AdaptyUiOnboardingStateParams } from '@/ui/types'; +import type { AdaptyUiOnboardingStateParams } from '@/ui-builder/types'; import type { Def } from '@/types/schema'; type Model = AdaptyUiOnboardingStateParams; diff --git a/src/coders/adapty-ui-onboarding-state-params.ts b/src/coders/adapty-ui-onboarding-state-params.ts index 1f905c7..c7a8b09 100644 --- a/src/coders/adapty-ui-onboarding-state-params.ts +++ b/src/coders/adapty-ui-onboarding-state-params.ts @@ -1,7 +1,7 @@ import type { Def } from '@/types/schema'; import type { Properties } from './types'; import { SimpleCoder } from './coder'; -import { AdaptyUiOnboardingStateParams } from '@/ui/types'; +import { AdaptyUiOnboardingStateParams } from '@/ui-builder/types'; type Model = AdaptyUiOnboardingStateParams; type Serializable = Def['AdaptyUI.OnboardingsStateParams']; diff --git a/src/coders/adapty-ui-onboarding-state-updated-action.test.ts b/src/coders/adapty-ui-onboarding-state-updated-action.test.ts index 3d442ea..8f9c519 100644 --- a/src/coders/adapty-ui-onboarding-state-updated-action.test.ts +++ b/src/coders/adapty-ui-onboarding-state-updated-action.test.ts @@ -1,5 +1,5 @@ import { AdaptyUiOnboardingStateUpdatedActionCoder } from './adapty-ui-onboarding-state-updated-action'; -import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import type { OnboardingStateUpdatedAction } from '@/ui-builder/types'; import type { Event } from '@/types/schema'; type Serializable = Event['OnboardingViewEvent.OnStateUpdatedAction']['action']; diff --git a/src/coders/adapty-ui-onboarding-state-updated-action.ts b/src/coders/adapty-ui-onboarding-state-updated-action.ts index 5ea1f6b..1f74633 100644 --- a/src/coders/adapty-ui-onboarding-state-updated-action.ts +++ b/src/coders/adapty-ui-onboarding-state-updated-action.ts @@ -1,5 +1,5 @@ import type { Def, Event } from '@/types/schema'; -import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import type { OnboardingStateUpdatedAction } from '@/ui-builder/types'; import { SimpleCoder } from './coder'; import type { Properties } from './types'; import { AdaptyUiOnboardingStateParamsCoder } from './adapty-ui-onboarding-state-params'; diff --git a/src/coders/parse-onboarding.ts b/src/coders/parse-onboarding.ts index d5c17b5..f27ef96 100644 --- a/src/coders/parse-onboarding.ts +++ b/src/coders/parse-onboarding.ts @@ -3,8 +3,8 @@ import { LogContext } from '../logger'; import { ErrorConverter } from './error-coder'; import type { CoderFactory } from './factory'; import type { Converter } from './types'; -import type { AdaptyUiOnboardingMeta } from '@/ui/types'; -import type { OnboardingStateUpdatedAction } from '@/ui/types'; +import type { AdaptyUiOnboardingMeta } from '@/ui-builder/types'; +import type { OnboardingStateUpdatedAction } from '@/ui-builder/types'; import { OnboardingEventId, type OnboardingEventView, diff --git a/src/index.ts b/src/index.ts index 2d4e79f..25af630 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,9 +44,9 @@ export * from './types/onboarding-events'; export type { components } from './types/api'; // UI event mappings -export * from './ui/paywall-event-mapping'; -export * from './ui/onboarding-event-mapping'; -export * from './ui/types'; +export * from '@/ui-builder/paywall-event-mapping'; +export * from '@/ui-builder/onboarding-event-mapping'; +export * from '@/ui-builder/types'; // Parse functions (accept CoderFactory as first parameter) export { diff --git a/src/types/inputs.ts b/src/types/inputs.ts index eb1aad2..49f10ad 100644 --- a/src/types/inputs.ts +++ b/src/types/inputs.ts @@ -1,4 +1,4 @@ -import { AdaptyUiMediaCache } from '@/ui/types'; +import { AdaptyUiMediaCache } from '@/ui-builder/types'; import type { AdaptyMockConfig } from '@/mock/types'; /** * Log levels for the SDK diff --git a/src/types/onboarding-events.ts b/src/types/onboarding-events.ts index 623c2d6..bfc3038 100644 --- a/src/types/onboarding-events.ts +++ b/src/types/onboarding-events.ts @@ -2,7 +2,7 @@ import { AdaptyError } from '@/adapty-error'; import type { AdaptyUiOnboardingMeta, OnboardingStateUpdatedAction, -} from '@/ui/types'; +} from '@/ui-builder/types'; // Onboarding Event IDs export const OnboardingEventId = { diff --git a/src/ui/onboarding-event-mapping.ts b/src/ui-builder/onboarding-event-mapping.ts similarity index 100% rename from src/ui/onboarding-event-mapping.ts rename to src/ui-builder/onboarding-event-mapping.ts diff --git a/src/ui/paywall-event-mapping.ts b/src/ui-builder/paywall-event-mapping.ts similarity index 100% rename from src/ui/paywall-event-mapping.ts rename to src/ui-builder/paywall-event-mapping.ts diff --git a/src/ui/types.ts b/src/ui-builder/types.ts similarity index 100% rename from src/ui/types.ts rename to src/ui-builder/types.ts From 8c9f2a00107326efdad767e22b0ca028f7d8cb6c Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 13 Feb 2026 13:44:20 +0300 Subject: [PATCH 49/50] docs: improve README description, add branding header --- README.md | 26 +++++++++++++++++++++++--- package.json | 6 ++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e5edb02..0aab768 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,28 @@ +

+ + + + + +
Easy In-App Purchases Integration to +
Make Your App Profitable +

+ # @adapty/core -Platform-agnostic core for Adapty SDKs. +> **⚠️ Internal package — do not use directly.** +> +> This package is an internal dependency of Adapty JavaScript SDKs. It is not intended for direct installation. + +This package contains the shared TypeScript foundation used by Adapty JavaScript SDKs, like React Native and Capacitor. + +## Documentation + +For integration guides, see the [Adapty documentation](https://adapty.io/docs/). + +## Having an issue? -This package contains shared TypeScript/JavaScript code used by Adapty React Native and Capacitor SDKs. +If you have an issue, please report it in the SDK you are using or drop us an email at [support@adapty.io](mailto:support@adapty.io). ## Publishing @@ -13,4 +33,4 @@ This package is published to npm as `@adapty/core`: ## License -MIT \ No newline at end of file +MIT diff --git a/package.json b/package.json index aba9179..a320fd1 100644 --- a/package.json +++ b/package.json @@ -70,13 +70,11 @@ }, "homepage": "https://adapty.io/docs/", "bugs": { - "url": "https://github.com/adaptyteam/AdaptySDK-JS-Core/issues" + "url": "https://github.com/adaptyteam/AdaptySDK-React-Native/issues" }, "keywords": [ "adapty", "sdk", - "core", - "subscriptions", - "in-app-purchases" + "core" ] } From b8350024f9018d9d0ea30b5b7739330f5971896a Mon Sep 17 00:00:00 2001 From: Stanislav Mayorov Date: Fri, 13 Feb 2026 06:17:49 -0800 Subject: [PATCH 50/50] chore: add .claude/settings.local.json to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9091b12..5072ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ yarn-error.log* .env.local .env.*.local -.claude/skills/* \ No newline at end of file +.claude/skills/* +.claude/settings.local.json \ No newline at end of file