From 8c4dc0a2fcaace6744e279bd9ebcfbc2bdcd8974 Mon Sep 17 00:00:00 2001 From: Rafa Cardenas <253999660+rafa-stacks@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:23:15 -0600 Subject: [PATCH 1/4] fix: only warn when nft supply refresh job is found --- src/token-processor/queue/job/update-token-supply-job.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/token-processor/queue/job/update-token-supply-job.ts b/src/token-processor/queue/job/update-token-supply-job.ts index 4129d861..146a61d8 100644 --- a/src/token-processor/queue/job/update-token-supply-job.ts +++ b/src/token-processor/queue/job/update-token-supply-job.ts @@ -47,7 +47,10 @@ export class UpdateTokenSupplyJob extends Job { await this.handleFt(client, token); break; case DbTokenType.nft: - throw new Error(`UpdateTokenSupplyJob does not support NFTs`); + logger.warn( + `UpdateTokenSupplyJob does not support NFTs, ignoring job ${this.description()}` + ); + return; case DbTokenType.sft: await this.handleSft(client, token); break; @@ -62,7 +65,7 @@ export class UpdateTokenSupplyJob extends Job { case DbTokenType.ft: return `FT SUPPLY ${this.contract.principal} (id=${this.token.id})`; case DbTokenType.nft: - throw new Error(`UpdateTokenSupplyJob does not support NFTs`); + return `NFT SUPPLY ${this.contract.principal}#${this.token.token_number} (id=${this.token.id})`; case DbTokenType.sft: return `SFT SUPPLY ${this.contract.principal}#${this.token.token_number} (id=${this.token.id})`; } From b217e5402164f1b6591ece82aa78520e5da65012 Mon Sep 17 00:00:00 2001 From: Rafa Cardenas <253999660+rafa-stacks@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:44:51 -0600 Subject: [PATCH 2/4] fix: throw user error --- .../queue/job/update-token-supply-job.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/token-processor/queue/job/update-token-supply-job.ts b/src/token-processor/queue/job/update-token-supply-job.ts index 146a61d8..38df9848 100644 --- a/src/token-processor/queue/job/update-token-supply-job.ts +++ b/src/token-processor/queue/job/update-token-supply-job.ts @@ -2,7 +2,7 @@ import { cvToHex, uintCV } from '@stacks/transactions'; import codec from '@stacks/codec'; import { DbSmartContract, DbToken, DbTokenType } from '../../../pg/types.js'; import { StacksNodeRpcClient } from '../../stacks-node/stacks-node-rpc-client.js'; -import { SmartContractClarityError } from '../../util/errors.js'; +import { SmartContractClarityError, UserError } from '../../util/errors.js'; import { Job } from './job.js'; import { PgNumeric, logger } from '@stacks/api-toolkit'; @@ -47,10 +47,9 @@ export class UpdateTokenSupplyJob extends Job { await this.handleFt(client, token); break; case DbTokenType.nft: - logger.warn( - `UpdateTokenSupplyJob does not support NFTs, ignoring job ${this.description()}` + throw new UserError( + `NFTs do not have a total supply. Malformed token ${contract.principal}#${token.token_number} (id=${token.id})` ); - return; case DbTokenType.sft: await this.handleSft(client, token); break; @@ -65,7 +64,8 @@ export class UpdateTokenSupplyJob extends Job { case DbTokenType.ft: return `FT SUPPLY ${this.contract.principal} (id=${this.token.id})`; case DbTokenType.nft: - return `NFT SUPPLY ${this.contract.principal}#${this.token.token_number} (id=${this.token.id})`; + // Not supported. + return 'UpdateTokenSupplyJob'; case DbTokenType.sft: return `SFT SUPPLY ${this.contract.principal}#${this.token.token_number} (id=${this.token.id})`; } From 0dd4d2aa929e09f90df6895d586d10e48f107c5e Mon Sep 17 00:00:00 2001 From: Rafa Cardenas <253999660+rafa-stacks@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:49:55 -0600 Subject: [PATCH 3/4] upgrade to latest toolkit --- migrations/1670266371036_jobs.ts | 5 + package-lock.json | 647 ++++--------------------------- package.json | 6 +- tests/docker-container.ts | 259 ------------- tests/setup.ts | 12 +- 5 files changed, 97 insertions(+), 832 deletions(-) delete mode 100644 tests/docker-container.ts diff --git a/migrations/1670266371036_jobs.ts b/migrations/1670266371036_jobs.ts index 15a8d551..b07b5d52 100644 --- a/migrations/1670266371036_jobs.ts +++ b/migrations/1670266371036_jobs.ts @@ -72,3 +72,8 @@ export function up(pgm: MigrationBuilder): void { where: "status = 'queued'", }); } + +export function down(pgm: MigrationBuilder): void { + pgm.dropTable('jobs'); + pgm.dropType('job_status'); +} diff --git a/package-lock.json b/package-lock.json index 9d37b86a..0224b8f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fastify/type-provider-typebox": "^6.1.0", "@google-cloud/storage": "^7.19.0", "@sinclair/typebox": "^0.34.48", - "@stacks/api-toolkit": "^1.12.2", + "@stacks/api-toolkit": "^1.13.0", "@stacks/blockchain-api-client": "^8.14.2", "@stacks/codec": "^1.6.0", "@stacks/node-publisher-client": "^2.0.5", @@ -25,12 +25,14 @@ "fastify": "^5.8.2", "fastify-metrics": "^12.1.0", "json5": "^2.2.3", - "node-pg-migrate": "^8.0.4", + "node-pg-migrate": "^7.9.1", "p-queue": "^9.1.0", + "prom-client": "^15.1.3", "sharp": "^0.34.5", "undici": "^7.24.4" }, "devDependencies": { + "@stacks/api-test-toolkit": "^1.13.0", "@stacks/eslint-config": "^3.0.0-develop.2", "@types/dockerode": "^3.3.40", "@typescript-eslint/eslint-plugin": "^8.57.1", @@ -2034,436 +2036,45 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, - "node_modules/@stacks/api-toolkit": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@stacks/api-toolkit/-/api-toolkit-1.12.2.tgz", - "integrity": "sha512-CXVKs+WSIsEB7UhUHe9MCWc5A6IHQs6dt219UO5kAnRDOFarG8krZyzoI20GWZ98KJ7+M4sNeYjsgtNTxRHuKA==", + "node_modules/@stacks/api-test-toolkit": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@stacks/api-test-toolkit/-/api-test-toolkit-1.13.0.tgz", + "integrity": "sha512-PP1ilUWCAPAVnToltR4HU+f88QrwuWXrWjmH/PXwO8NLeq+ng7SMcnntj5CpWCEaHq5ANSKAWCKuO5nxNm1/fg==", + "dev": true, "license": "Apache 2.0", "dependencies": { - "@fastify/cors": "^8.0.0", - "@fastify/swagger": "^8.3.1", - "@fastify/type-provider-typebox": "^3.2.0", - "@sinclair/typebox": "^0.28.20", - "@types/node": "^22.14.1", - "fastify": "^4.3.0", - "fastify-metrics": "^10.2.0", - "node-pg-migrate": "^6.2.2", - "pino": "^8.11.0", - "postgres": "^3.3.4" - }, - "bin": { - "api-toolkit-git-info": "bin/api-toolkit-git-info.js" + "dockerode": "^4.0.10" }, "engines": { "node": ">=22" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/ajv-compiler": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", - "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "fast-uri": "^2.0.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/ajv-compiler/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/cors": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.5.0.tgz", - "integrity": "sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==", - "license": "MIT", - "dependencies": { - "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.6" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/error": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", - "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/fast-json-stringify-compiler": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", - "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", - "license": "MIT", - "dependencies": { - "fast-json-stringify": "^5.7.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/merge-json-schemas": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", - "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/swagger": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.15.0.tgz", - "integrity": "sha512-zy+HEEKFqPMS2sFUsQU5X0MHplhKJvWeohBwTCkBAJA/GDYGLGUWQaETEhptiqxK7Hs0fQB9B4MDb3pbwIiCwA==", - "license": "MIT", - "dependencies": { - "fastify-plugin": "^4.0.0", - "json-schema-resolver": "^2.0.0", - "openapi-types": "^12.0.0", - "rfdc": "^1.3.0", - "yaml": "^2.2.2" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@fastify/type-provider-typebox": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@fastify/type-provider-typebox/-/type-provider-typebox-3.6.0.tgz", - "integrity": "sha512-HTeOLvirfGg0u1KGao3iXn5rZpYNqlrOmyDnXSXAbWVPa+mDQTTBNs/x5uZzOB6vFAqr0Xcf7x1lxOamNSYKjw==", - "license": "MIT", - "peerDependencies": { - "@sinclair/typebox": ">=0.26 <=0.32" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/@sinclair/typebox": { - "version": "0.28.20", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.28.20.tgz", - "integrity": "sha512-QCF3BGfacwD+3CKhGsMeixnwOmX4AWgm61nKkNdRStyLVu0mpVFYlDSY8gVBOOED1oSwzbJauIWl/+REj8K5+w==", - "license": "MIT", - "peer": true - }, - "node_modules/@stacks/api-toolkit/node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/avvio": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", - "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", - "license": "MIT", - "dependencies": { - "@fastify/error": "^3.3.0", - "fastq": "^1.17.1" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/fast-json-stringify": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", - "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", - "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.1.0", - "ajv": "^8.10.0", - "ajv-formats": "^3.0.1", - "fast-deep-equal": "^3.1.3", - "fast-uri": "^2.1.0", - "json-schema-ref-resolver": "^1.0.1", - "rfdc": "^1.2.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/fast-uri": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", - "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/fastify": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", - "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^3.5.0", - "@fastify/error": "^3.4.0", - "@fastify/fast-json-stringify-compiler": "^4.3.0", - "abstract-logging": "^2.0.1", - "avvio": "^8.3.0", - "fast-content-type-parse": "^1.1.0", - "fast-json-stringify": "^5.8.0", - "find-my-way": "^8.0.0", - "light-my-request": "^5.11.0", - "pino": "^9.0.0", - "process-warning": "^3.0.0", - "proxy-addr": "^2.0.7", - "rfdc": "^1.3.0", - "secure-json-parse": "^2.7.0", - "semver": "^7.5.4", - "toad-cache": "^3.3.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/fastify-metrics": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-10.6.0.tgz", - "integrity": "sha512-QIPncCnwBOEObMn+VaRhsBC1ox8qEsaiYF2sV/A1UbXj7ic70W8/HNn/hlEC2W8JQbBeZMx++o1um2fPfhsFDQ==", - "license": "MIT", - "dependencies": { - "fastify-plugin": "^4.3.0", - "prom-client": "^14.2.0" }, "peerDependencies": { - "fastify": ">=4" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/fastify/node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" + "@stacks/api-toolkit": "^1.12.2" } }, - "node_modules/@stacks/api-toolkit/node_modules/fastify/node_modules/pino/node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/find-my-way": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", - "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^3.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/json-schema-ref-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", - "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/json-schema-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", - "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "rfdc": "^1.1.4", - "uri-js": "^4.2.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/light-my-request": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", - "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^0.7.0", - "process-warning": "^3.0.0", - "set-cookie-parser": "^2.4.1" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/node-pg-migrate": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/node-pg-migrate/-/node-pg-migrate-6.2.2.tgz", - "integrity": "sha512-0WYLTXpWu2doeZhiwJUW/1u21OqAFU2CMQ8YZ8VBcJ0xrdqYAjtd8GGFe5A5DM4NJdIZsqJcLPDFqY0FQsmivw==", - "deprecated": "Version no longer supported. Upgrade to @latest", - "license": "MIT", + "node_modules/@stacks/api-toolkit": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@stacks/api-toolkit/-/api-toolkit-1.13.0.tgz", + "integrity": "sha512-1w2B1M5U+Xv8d2YqCRmaeTYMLU/QBy9y/uYEejz0albDStF09scVNd9FBHRBvAVGRaPTee9orh1+D4p6Zcg0vg==", + "license": "Apache 2.0", + "peer": true, "dependencies": { - "@types/pg": "^8.0.0", - "decamelize": "^5.0.0", - "mkdirp": "~1.0.0", - "yargs": "~17.3.0" - }, - "bin": { - "node-pg-migrate": "bin/node-pg-migrate" - }, - "engines": { - "node": ">=12.20.0" + "@fastify/cors": "^11.2.0", + "@fastify/swagger": "^9.7.0", + "@fastify/type-provider-typebox": "^6.1.0", + "@sinclair/typebox": "^0.34.48", + "@types/node": "^24.12.2", + "fastify": "^5.8.4", + "fastify-metrics": "^12.1.0", + "node-pg-migrate": "^7.9.1", + "pino": "^8.11.0", + "postgres": "^3.4.8" }, - "peerDependencies": { - "pg": ">=4.3.0 <9.0.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/process-warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", - "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/ret": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", - "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/safe-regex2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", - "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", - "license": "MIT", - "dependencies": { - "ret": "~0.4.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/@stacks/api-toolkit/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/@stacks/api-toolkit/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/@stacks/api-toolkit/node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "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.0.0" + "api-toolkit-git-info": "bin/api-toolkit-git-info.js" }, "engines": { - "node": ">=12" + "node": ">=22" } }, "node_modules/@stacks/blockchain-api-client": { @@ -2645,24 +2256,14 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, - "node_modules/@types/pg": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.5.tgz", - "integrity": "sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, "node_modules/@types/request": { "version": "2.48.12", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", @@ -3818,18 +3419,6 @@ } } }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3900,9 +3489,9 @@ } }, "node_modules/docker-modem": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", - "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.7.tgz", + "integrity": "sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3931,16 +3520,16 @@ } }, "node_modules/dockerode": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.9.tgz", - "integrity": "sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.10.tgz", + "integrity": "sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", - "docker-modem": "^5.0.6", + "docker-modem": "^5.0.7", "protobufjs": "^7.3.2", "tar-fs": "^2.1.4", "uuid": "^10.0.0" @@ -4941,12 +4530,6 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "node_modules/fast-content-type-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", - "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", - "license": "MIT" - }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -5071,9 +4654,9 @@ } }, "node_modules/fastify": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.2.tgz", - "integrity": "sha512-lZmt3navvZG915IE+f7/TIVamxIwmBd+OMB+O9WBzcpIwOo6F0LTh0sluoMFk5VkrKTvvrwIaoJPkir4Z+jtAg==", + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.4.tgz", + "integrity": "sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==", "funding": [ { "type": "github", @@ -5132,24 +4715,6 @@ ], "license": "MIT" }, - "node_modules/fastify-metrics/node_modules/prom-client": { - "version": "15.1.3", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", - "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.4.0", - "tdigest": "^0.1.1" - }, - "engines": { - "node": "^16 || ^18 || >=20" - } - }, - "node_modules/fastify-plugin": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.3.0.tgz", - "integrity": "sha512-M3+i368lV0OYTJ5TfClIoPKEKSOF7112iiPdwgfSR0gN98BjA1Nk+c6oBHtfcVt9KiMxl+EQKHC1QNWo3ZOpYQ==" - }, "node_modules/fastify/node_modules/pino": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", @@ -5345,18 +4910,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", @@ -5370,15 +4923,6 @@ "node": ">= 0.12" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5570,15 +5114,15 @@ } }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -6677,18 +6221,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -6696,15 +6228,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mnemonist": { - "version": "0.39.6", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", - "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", - "license": "MIT", - "dependencies": { - "obliterator": "^2.0.1" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6760,19 +6283,21 @@ } }, "node_modules/node-pg-migrate": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/node-pg-migrate/-/node-pg-migrate-8.0.4.tgz", - "integrity": "sha512-HTlJ6fOT/2xHhAUtsqSN85PGMAqSbfGJNRwQF8+ZwQ1+sVGNUTl/ZGEshPsOI3yV22tPIyHXrKXr3S0JxeYLrg==", + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/node-pg-migrate/-/node-pg-migrate-7.9.1.tgz", + "integrity": "sha512-6z4OSN27ye8aYdX9ZU7NN2PTI5pOp34hTr+22Ej12djIYECq++gT7LPLZVOQXEeVCBOZQLqf87kC3Y36G434OQ==", "license": "MIT", "dependencies": { - "glob": "~11.1.0", + "glob": "~11.0.0", "yargs": "~17.7.0" }, "bin": { - "node-pg-migrate": "bin/node-pg-migrate.js" + "node-pg-migrate": "bin/node-pg-migrate.js", + "node-pg-migrate-cjs": "bin/node-pg-migrate.js", + "node-pg-migrate-esm": "bin/node-pg-migrate.mjs" }, "engines": { - "node": ">=20.11.0" + "node": ">=18.19.0" }, "peerDependencies": { "@types/pg": ">=6.0.0 <9.0.0", @@ -6881,12 +6406,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obliterator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", - "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", - "license": "MIT" - }, "node_modules/on-exit-leak-free": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", @@ -7336,9 +6855,13 @@ } }, "node_modules/postgres": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.5.tgz", - "integrity": "sha512-+JD93VELV9gHkqpV5gdL5/70HdGtEw4/XE1S4BC8f1mcPmdib3K5XsKVbnR1XcAyC41zOnifJ+9YRKxdIsXiUw==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", + "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", + "license": "Unlicense", + "engines": { + "node": ">=12" + }, "funding": { "type": "individual", "url": "https://github.com/sponsors/porsager" @@ -7444,14 +6967,16 @@ "license": "MIT" }, "node_modules/prom-client": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", - "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", "dependencies": { + "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" }, "engines": { - "node": ">=10" + "node": "^16 || ^18 || >=20" } }, "node_modules/propagate": { @@ -7489,28 +7014,6 @@ "node": ">=12.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -7526,6 +7029,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, "engines": { "node": ">=6" } @@ -8089,6 +7593,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/socket.io-client": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", @@ -8753,6 +8269,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index f548f27c..f97dd558 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "prettier": "@stacks/prettier-config", "devDependencies": { + "@stacks/api-test-toolkit": "^1.13.0", "@stacks/eslint-config": "^3.0.0-develop.2", "@types/dockerode": "^3.3.40", "@typescript-eslint/eslint-plugin": "^8.57.1", @@ -54,7 +55,7 @@ "@fastify/type-provider-typebox": "^6.1.0", "@google-cloud/storage": "^7.19.0", "@sinclair/typebox": "^0.34.48", - "@stacks/api-toolkit": "^1.12.2", + "@stacks/api-toolkit": "^1.13.0", "@stacks/blockchain-api-client": "^8.14.2", "@stacks/codec": "^1.6.0", "@stacks/node-publisher-client": "^2.0.5", @@ -65,8 +66,9 @@ "fastify": "^5.8.2", "fastify-metrics": "^12.1.0", "json5": "^2.2.3", - "node-pg-migrate": "^8.0.4", + "node-pg-migrate": "^7.9.1", "p-queue": "^9.1.0", + "prom-client": "^15.1.3", "sharp": "^0.34.5", "undici": "^7.24.4" } diff --git a/tests/docker-container.ts b/tests/docker-container.ts deleted file mode 100644 index d68d5485..00000000 --- a/tests/docker-container.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -import { strict as assert } from 'node:assert'; -import * as net from 'node:net'; -import Docker from 'dockerode'; - -export interface PortMapping { - host: number; - container: number; -} - -export interface ContainerConfig { - /** Docker image (e.g. "postgres:17") */ - image: string; - /** Container name */ - name: string; - /** Host to bind to (default: "127.0.0.1") */ - host?: string; - /** Port mappings (host → container) */ - ports: PortMapping[]; - /** Port to wait on before declaring the container ready (default: first port's host side) */ - waitPort?: number; - /** Set to false to skip the port-readiness check (e.g. for one-shot sidecars) */ - waitForReady?: boolean; - /** Environment variables */ - env?: string[]; - /** Override the image entrypoint */ - entrypoint?: string[]; - /** Override the image command */ - command?: string[]; - /** Bind-mount volumes ("host:container") */ - volumes?: string[]; - /** Extra /etc/hosts entries ("hostname:ip") */ - extraHosts?: string[]; - /** Docker healthcheck command (passed after CMD-SHELL) */ - healthcheck?: string; - /** Restart policy (default: no) */ - restartPolicy?: 'no' | 'always' | 'on-failure' | 'unless-stopped'; - /** Labels to attach to the container */ - labels?: Record; - /** Startup timeout in ms (default: 120_000) */ - timeoutMs?: number; -} - -const DEFAULTS = { - host: '127.0.0.1', - timeoutMs: 120_000, -} as const; - -function createDockerClient(): Docker { - if (process.env.DOCKER_HOST) { - const dockerHost = new URL(process.env.DOCKER_HOST); - return new Docker({ - host: dockerHost.hostname, - port: Number(dockerHost.port), - protocol: dockerHost.protocol.replace(':', '') as 'http' | 'https' | 'ssh', - }); - } - return new Docker({ socketPath: process.env.DOCKER_SOCKET_PATH ?? '/var/run/docker.sock' }); -} - -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -function streamToPromise(stream: NodeJS.ReadableStream): Promise { - return new Promise((resolve, reject) => { - stream.on('end', () => resolve()); - stream.on('error', reject); - }); -} - -async function pullImageIfMissing(docker: Docker, image: string): Promise { - const images = (await docker.listImages()) as { RepoTags?: string[] }[]; - const hasImage = images.some(img => img.RepoTags?.includes(image)); - if (hasImage) return; - - process.stdout.write(`[testenv] pulling image ${image}\n`); - const stream = await docker.pull(image); - await new Promise((resolve, reject) => { - docker.modem.followProgress(stream, err => { - if (err) { - reject(err instanceof Error ? err : new Error(String(err))); - return; - } - resolve(); - }); - }); -} - -async function getContainer(docker: Docker, name: string) { - const containers = await docker.listContainers({ - all: true, - filters: { name: [name] }, - }); - // Docker's name filter does substring matching, so we need an exact match. - // Container names are stored with a leading slash (e.g. "/my-container"). - const exact = containers.find(c => c.Names.some(n => n === `/${name}` || n === name)); - if (!exact) return undefined; - assert.ok(exact.Id); - return docker.getContainer(exact.Id); -} - -async function ensureContainerRunning(docker: Docker, config: ContainerConfig) { - const host = config.host ?? DEFAULTS.host; - const { - name, - image, - ports, - env, - entrypoint, - command, - volumes, - extraHosts, - healthcheck, - restartPolicy, - labels, - } = config; - - const existing = await getContainer(docker, name); - if (existing) { - const inspect = await existing.inspect(); - if (!inspect.State.Running) { - process.stdout.write(`[testenv] starting existing container ${name}\n`); - await existing.start(); - } else { - process.stdout.write(`[testenv] container ${name} already running\n`); - } - return existing; - } - - const exposedPorts: Record = {}; - const portBindings: Record = {}; - for (const { host: hostPort, container: containerPort } of ports) { - const key = `${containerPort}/tcp`; - exposedPorts[key] = {}; - portBindings[key] = [{ HostPort: String(hostPort), HostIp: host }]; - } - - const binds = volumes?.map(v => { - // Resolve relative paths from the project root - if (!v.startsWith('/')) { - const [hostPath, ...rest] = v.split(':'); - const resolved = `${process.cwd()}/${hostPath}`; - return [resolved, ...rest].join(':'); - } - return v; - }); - - process.stdout.write(`[testenv] creating container ${name}\n`); - const container = await docker.createContainer({ - name, - Image: image, - Env: env, - ...(entrypoint && { Entrypoint: entrypoint }), - ...(command && { Cmd: command }), - ExposedPorts: exposedPorts, - HostConfig: { - PortBindings: portBindings, - AutoRemove: false, - ...(binds && { Binds: binds }), - ...(extraHosts && { ExtraHosts: extraHosts }), - RestartPolicy: { Name: restartPolicy ?? 'no' }, - }, - Labels: labels, - ...(healthcheck && { - Healthcheck: { - Test: ['CMD-SHELL', healthcheck], - Interval: 2_000_000_000, - Timeout: 2_000_000_000, - Retries: 30, - StartPeriod: 2_000_000_000, - }, - }), - }); - await container.start(); - return container; -} - -async function waitForPort(host: string, port: number, timeoutMs: number): Promise { - const startedAt = Date.now(); - while (Date.now() - startedAt < timeoutMs) { - const ok = await new Promise(resolve => { - const socket = net.createConnection(port, host); - socket.setTimeout(1_000); - socket.on('connect', () => { - socket.end(); - resolve(true); - }); - socket.on('timeout', () => { - socket.destroy(); - resolve(false); - }); - socket.on('error', () => resolve(false)); - }); - if (ok) return; - await sleep(500); - } - throw new Error(`timed out waiting for ${host}:${port}`); -} - -export async function runUp(config: ContainerConfig): Promise { - const host = config.host ?? DEFAULTS.host; - const timeoutMs = config.timeoutMs ?? DEFAULTS.timeoutMs; - const docker = createDockerClient(); - await pullImageIfMissing(docker, config.image); - await ensureContainerRunning(docker, config); - if (config.waitForReady !== false && config.ports.length > 0) { - const port = config.waitPort ?? config.ports[0].host; - await waitForPort(host, port, timeoutMs); - process.stdout.write(`[testenv] ${config.name} ready on ${host}:${port}\n`); - } else { - process.stdout.write(`[testenv] ${config.name} started (no readiness check)\n`); - } -} - -export async function runDown(config: ContainerConfig): Promise { - const docker = createDockerClient(); - const container = await getContainer(docker, config.name); - if (!container) { - process.stdout.write(`[testenv] container ${config.name} is already absent\n`); - return; - } - const inspect = await container.inspect(); - if (inspect.State.Running) { - process.stdout.write(`[testenv] stopping ${config.name}\n`); - await container.stop({ t: 0 }); - } - process.stdout.write(`[testenv] removing ${config.name}\n`); - await container.remove({ force: true, v: true }); -} - -export async function runLogs(config: ContainerConfig, argv: string[]): Promise { - const follow = argv.includes('-f') || argv.includes('--follow') || !argv.includes('--once'); - const docker = createDockerClient(); - const container = await getContainer(docker, config.name); - if (!container) { - throw new Error(`container ${config.name} not found`); - } - if (follow) { - const logStream = await container.logs({ - stdout: true, - stderr: true, - follow: true, - timestamps: true, - tail: 200, - }); - container.modem.demuxStream(logStream, process.stdout, process.stderr); - await streamToPromise(logStream); - return; - } - const output = await container.logs({ - stdout: true, - stderr: true, - follow: false, - timestamps: true, - tail: 200, - }); - process.stdout.write(output.toString('utf8')); -} diff --git a/tests/setup.ts b/tests/setup.ts index d9d64ff6..79414de3 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,8 +1,8 @@ -import type { ContainerConfig } from './docker-container.ts'; -import { runDown, runUp } from './docker-container.ts'; +import type { DockerTestContainerConfig } from '@stacks/api-test-toolkit'; +import { dockerTestUp, dockerTestDown } from '@stacks/api-test-toolkit'; -function defaultContainers(): ContainerConfig[] { - const postgres: ContainerConfig = { +function defaultContainers(): DockerTestContainerConfig[] { + const postgres: DockerTestContainerConfig = { image: 'postgres:17', name: `metadata-api-test-postgres`, ports: [{ host: 5432, container: 5432 }], @@ -20,7 +20,7 @@ function defaultContainers(): ContainerConfig[] { export async function globalSetup() { const containers = defaultContainers(); for (const config of containers) { - await runUp(config); + await dockerTestUp({ config }); } process.stdout.write(`[testenv:metadata-api] all containers ready\n`); } @@ -28,7 +28,7 @@ export async function globalSetup() { export async function globalTeardown() { const containers = defaultContainers(); for (const config of [...containers].reverse()) { - await runDown(config); + await dockerTestDown({ config }); } process.stdout.write(`[testenv:metadata-api] all containers removed\n`); } From fb25ae45de726f1052e21dd4e0e6a231ab3eaf3a Mon Sep 17 00:00:00 2001 From: Rafa Cardenas <253999660+rafa-stacks@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:55:26 -0600 Subject: [PATCH 4/4] chore(release): 2.2.4 [skip ci] --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 3f5bb9c2..820a2f04 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@stacks/token-metadata-api-client", - "version": "2.2.3", + "version": "2.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@stacks/token-metadata-api-client", - "version": "2.2.3", + "version": "2.2.4", "license": "GPL-3.0", "dependencies": { "openapi-fetch": "^0.12.2" diff --git a/client/package.json b/client/package.json index d479076b..59587cd4 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@stacks/token-metadata-api-client", - "version": "2.2.3", + "version": "2.2.4", "description": "Client for the Stacks Token Metadata API", "author": "Stacks Labs", "keywords": [ diff --git a/package-lock.json b/package-lock.json index 0224b8f4..2afedd28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@stx-labs/token-metadata-api", - "version": "2.2.3", + "version": "2.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@stx-labs/token-metadata-api", - "version": "2.2.3", + "version": "2.2.4", "license": "GPL-3.0", "dependencies": { "@fastify/cors": "^11.2.0", diff --git a/package.json b/package.json index f97dd558..ee3a2db8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@stx-labs/token-metadata-api", "description": "A microservice that indexes metadata for all Fungible, Non-Fungible, and Semi-Fungible Tokens in the Stacks blockchain and exposes it via JSON REST API endpoints", - "version": "2.2.3", + "version": "2.2.4", "repository": { "type": "git", "url": "https://github.com/stx-labs/token-metadata-api.git"