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/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..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", @@ -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..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" @@ -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/src/token-processor/queue/job/update-token-supply-job.ts b/src/token-processor/queue/job/update-token-supply-job.ts index 4129d861..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,7 +47,9 @@ export class UpdateTokenSupplyJob extends Job { await this.handleFt(client, token); break; case DbTokenType.nft: - throw new Error(`UpdateTokenSupplyJob does not support NFTs`); + throw new UserError( + `NFTs do not have a total supply. Malformed token ${contract.principal}#${token.token_number} (id=${token.id})` + ); case DbTokenType.sft: await this.handleSft(client, token); break; @@ -62,7 +64,8 @@ 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`); + // Not supported. + return 'UpdateTokenSupplyJob'; case DbTokenType.sft: return `SFT SUPPLY ${this.contract.principal}#${this.token.token_number} (id=${this.token.id})`; } 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`); }