diff --git a/package-lock.json b/package-lock.json index 564c558..39ee4e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@imqueue/rpc", - "version": "3.1.0", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@imqueue/rpc", - "version": "3.1.0", + "version": "3.2.0", "license": "GPL-3.0-only", "dependencies": { - "@imqueue/core": "^3.1.0", + "@imqueue/core": "^3.2.0", "acorn": "^8.17.0", "typescript": "^7.0.2" }, @@ -20,12 +20,15 @@ } }, "node_modules/@imqueue/core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@imqueue/core/-/core-3.1.0.tgz", - "integrity": "sha512-lz8AQU9RgRW48AXfAKthYXO3KqU5nvhjhbqQwu34muY7TiJilIkIVGsBSQp58lI4HjOgk3wLUrnbH9rJNJI4bQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@imqueue/core/-/core-3.2.0.tgz", + "integrity": "sha512-JKVV6/bYjhOWdD6tGr66X0EQRfysKw+QHiFUGcGw2ysZw/GAn5VKhjjfWbmjBh1polCFdmpRRuyUXOcUa4h2LA==", "license": "GPL-3.0-only", "dependencies": { "ioredis": "^5.11.1" + }, + "engines": { + "node": ">=22.12.0" } }, "node_modules/@ioredis/commands": { diff --git a/package.json b/package.json index a8f08b4..0dc8b6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imqueue/rpc", - "version": "3.1.0", + "version": "3.2.0", "description": "RPC-like client-service implementation over messaging queue", "keywords": [ "redis", @@ -20,9 +20,9 @@ "lint": "oxlint", "format": "oxfmt \"index.ts\" \"debug.ts\" \"src/**/*.ts\" \"test/**/*.ts\"", "format:check": "oxfmt --check \"index.ts\" \"debug.ts\" \"src/**/*.ts\" \"test/**/*.ts\"", - "test": "npm run build && node --experimental-test-module-mocks --test --test-timeout=15000 $(find test -name '*.spec.js')", - "test-coverage": "npm run build && node --experimental-test-module-mocks --enable-source-maps --test --experimental-test-coverage --test-timeout=15000 $(find test -name '*.spec.js')", - "test-lcov": "npm run build && mkdir -p coverage && node --experimental-test-module-mocks --enable-source-maps --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-timeout=15000 $(find test -name '*.spec.js'); node scripts/strip-comment-coverage.mjs coverage/lcov.info", + "test": "npm run build && node --experimental-test-module-mocks --import ./test/warmup.mjs --test --test-timeout=15000 $(find test -name '*.spec.js')", + "test-coverage": "npm run build && node --experimental-test-module-mocks --enable-source-maps --import ./test/warmup.mjs --test --experimental-test-coverage --test-timeout=15000 $(find test -name '*.spec.js')", + "test-lcov": "npm run build && mkdir -p coverage && node --experimental-test-module-mocks --enable-source-maps --import ./test/warmup.mjs --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-timeout=15000 $(find test -name '*.spec.js'); node scripts/strip-comment-coverage.mjs coverage/lcov.info", "test-coverage-html": "npm run test-lcov; genhtml coverage/lcov.info --output-directory coverage/html --ignore-errors inconsistent,corrupt,format,mismatch && echo \"Coverage report: file://$(pwd)/coverage/html/index.html\"", "test-dev": "npm run test && npm run clean-js && npm run clean-typedefs && npm run clean-maps", "clean-typedefs": "find . -name '*.d.ts' -not -wholename '*node_modules*' -not -wholename '*generator*' -type f -delete", @@ -43,7 +43,7 @@ "author": "imqueue.com (https://imqueue.com)", "license": "GPL-3.0-only", "dependencies": { - "@imqueue/core": "^3.1.0", + "@imqueue/core": "^3.2.0", "acorn": "^8.17.0", "typescript": "^7.0.2" }, diff --git a/test/mocks/moduleMock.ts b/test/mocks/moduleMock.ts index 3ef988f..e682065 100644 --- a/test/mocks/moduleMock.ts +++ b/test/mocks/moduleMock.ts @@ -31,9 +31,11 @@ import { mock, MockModuleOptions } from 'node:test'; * Node 22 only understands the earlier pair, so the same exports shape is * translated there. * - * `cache: true` keeps mock-require's semantics: every `require()` of the - * mocked specifier returns the same module instance, so a test may patch a - * property in place and the code under test observes the change. + * `cache: false` is required: as of the Node 24.17/24.18 module-loader + * changes, `cache: true` mock modules expose their export names with the + * values never bound. Instance caching is not load-bearing here — shared + * state lives on the exported references themselves (mock class statics), + * so each re-evaluation serves the same objects. * * @param {Record} exports - mock exports, `default` key being * the default export @@ -42,13 +44,8 @@ import { mock, MockModuleOptions } from 'node:test'; export function moduleMockOptions( exports: Record, ): MockModuleOptions { - if (+process.versions.node.split('.')[0] >= 24) { - // typings (@types/node 24.x) lag the runtime's `exports` option - return { cache: true, exports } as MockModuleOptions; - } - const { default: defaultExport, ...namedExports } = exports; - const options: MockModuleOptions = { cache: true }; + const options: MockModuleOptions = { cache: false }; if (defaultExport !== undefined) { options.defaultExport = defaultExport; diff --git a/test/mocks/redis.ts b/test/mocks/redis.ts index 58a5ea8..8a40ee6 100644 --- a/test/mocks/redis.ts +++ b/test/mocks/redis.ts @@ -21,7 +21,8 @@ * purchase a proprietary commercial license. Please contact us at * to get commercial licensing options. */ -import { mockModule } from './moduleMock'; +import { mock } from 'node:test'; +import { moduleMockOptions } from './moduleMock'; import { EventEmitter } from 'node:events'; import * as crypto from 'node:crypto'; @@ -322,7 +323,13 @@ const Redis = RedisClientMock; // exports onto a class default, so the whole ESM-shaped namespace object is // registered as the module's export (for CJS consumers it IS what // `require('ioredis')` returns). -mockModule('ioredis', { __esModule: true, Redis, default: Redis }); +mock.module( + 'ioredis', + moduleMockOptions({ + default: { __esModule: true, Redis, default: Redis }, + Redis, + }), +); // @ts-ignore export * from 'ioredis'; diff --git a/test/warmup.mjs b/test/warmup.mjs new file mode 100644 index 0000000..6c42199 --- /dev/null +++ b/test/warmup.mjs @@ -0,0 +1,15 @@ +/*! + * Test preload (wired via `node --import ./test/warmup.mjs`). + * + * Registers all module mocks (side effect of the mocks index) and then + * fully loads the ES module graph of @imqueue/core BEFORE any spec file + * runs. CommonJS specs may then require('@imqueue/core') synchronously: + * require(esm) of an already-evaluated module returns its bound namespace, + * whereas evaluating a graph that contains node:test module mocks through + * the synchronous require(esm) path leaves the mock exports unbound (the + * mocks evaluate asynchronously), which manifests as + * "TypeError: core_1.Redis is not a constructor". + */ +import './mocks/index.js'; + +await import('@imqueue/core');