fix(build): собирать deserialize.worker.cjs, перевести пакет на ESM-only — Piscina требует отдельный .cjs файл, а top-level await несовместим с CJS форматом
- tsup.config.ts: добавлен 3-й entry для src/workers/deserialize.worker.ts (CJS) → dist/deserialize.worker.cjs - tsup: main index и CLI переведены на ESM-only (TLA в ioredis/piscina импортах несовместим с CJS) - package.json exports/main/bin: указывают на .js (ESM) вместо .cjs - WorkerPool.resolveWorkerPath(): ищет worker в dist/ относительно src/ при запуске из источников (нужно для integration тестов) - ci.yml: добавлен step build @coopenomics/parser перед integration test — worker .cjs должен существовать
This commit is contained in:
@@ -79,6 +79,8 @@ jobs:
|
||||
with: { node-version: '20', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @coopenomics/coopos-ship-reader build
|
||||
# Parser2 build: нужен dist/deserialize.worker.cjs для Piscina worker pool
|
||||
- run: pnpm --filter @coopenomics/parser build
|
||||
# Запускаем nodeos вручную через docker run: образ dicoop/blockchain:v5.1.0-dev
|
||||
# не имеет CMD по умолчанию, и GitHub Actions services: не поддерживает
|
||||
# передачу команды контейнеру. Монтируем config.ini (SHiP на 8080) и genesis.json.
|
||||
|
||||
@@ -11,16 +11,15 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"parser": "./dist/cli/index.cjs"
|
||||
"parser": "./dist/cli/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -4,19 +4,50 @@
|
||||
* Использует Piscina — высокопроизводительный worker pool для Node.js.
|
||||
* Каждый worker держит собственный in-memory ABI-кэш, поэтому повторные
|
||||
* задания с одним и тем же abiJson не перепарсивают его.
|
||||
*
|
||||
* Важно: Piscina загружается через динамический import() из-за отсутствия
|
||||
* поля "exports" в package.json пакета — NodeNext resolution требует его.
|
||||
* Поэтому используется топ-левел await + явное приведение типа.
|
||||
*/
|
||||
|
||||
// Piscina: default import через dynamic import() — TS не может статически
|
||||
// проверить тип, поэтому используем топ-левел await + приведение типа.
|
||||
type PiscinaPool = {
|
||||
run(task: unknown): Promise<unknown>
|
||||
utilization: number
|
||||
destroy(): Promise<void>
|
||||
}
|
||||
type PiscinaCtor = new (opts: { filename: string; maxThreads?: number }) => PiscinaPool
|
||||
const { default: PiscinaClass } = await import('piscina') as unknown as { default: PiscinaCtor }
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { join, dirname, resolve } from 'node:path'
|
||||
import { existsSync } from 'node:fs'
|
||||
|
||||
// __dirname не доступен в ESM — восстанавливаем через import.meta.url
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
/**
|
||||
* Находит путь к скомпилированному worker'у.
|
||||
*
|
||||
* Сценарии:
|
||||
* 1. Production (dist/index.cjs) — worker рядом: dist/deserialize.worker.cjs
|
||||
* 2. Source mode (src/workers/WorkerPool.ts) — worker должен быть предварительно
|
||||
* собран в dist/workers/deserialize.worker.cjs (для тестов: build перед test:integration)
|
||||
*/
|
||||
function resolveWorkerPath(): string {
|
||||
const candidates = [
|
||||
// Production: рядом с WorkerPool
|
||||
join(__dirname, 'deserialize.worker.cjs'),
|
||||
// Source mode (tests): относительный путь к dist/
|
||||
resolve(__dirname, '../../dist/deserialize.worker.cjs'),
|
||||
resolve(__dirname, '../../dist/workers/deserialize.worker.cjs'),
|
||||
]
|
||||
for (const path of candidates) {
|
||||
if (existsSync(path)) return path
|
||||
}
|
||||
throw new Error(
|
||||
`Could not find deserialize.worker.cjs. Tried: ${candidates.join(', ')}. ` +
|
||||
`Run "pnpm build" first if running from source.`,
|
||||
)
|
||||
}
|
||||
|
||||
export interface DeserializeTask {
|
||||
/** Сырые байты action data или table row для декодирования. */
|
||||
rawBinary: Uint8Array
|
||||
@@ -28,22 +59,8 @@ export interface DeserializeTask {
|
||||
kind: 'action' | 'delta'
|
||||
}
|
||||
|
||||
interface PiscinaOptions {
|
||||
filename: string
|
||||
maxThreads?: number
|
||||
}
|
||||
|
||||
// CJS/ESM interop: у Piscina нет поля "exports" для NodeNext resolution,
|
||||
// поэтому TypeScript не может статически проверить тип импорта
|
||||
type PiscinaConstructor = new (opts: PiscinaOptions) => {
|
||||
run(task: unknown): Promise<unknown>
|
||||
utilization: number
|
||||
destroy(): Promise<void>
|
||||
}
|
||||
const { default: PiscinaClass } = await import('piscina') as unknown as { default: PiscinaConstructor }
|
||||
|
||||
export class WorkerPool {
|
||||
private pool: InstanceType<PiscinaConstructor>
|
||||
private pool: PiscinaPool
|
||||
|
||||
/**
|
||||
* @param maxThreads — максимум параллельных worker-потоков.
|
||||
@@ -53,7 +70,7 @@ export class WorkerPool {
|
||||
constructor(maxThreads = 2) {
|
||||
// Загружаем CJS-сборку worker'а, т.к. Piscina нативно работает с CJS
|
||||
this.pool = new PiscinaClass({
|
||||
filename: join(__dirname, 'deserialize.worker.cjs'),
|
||||
filename: resolveWorkerPath(),
|
||||
maxThreads,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import { defineConfig } from 'tsup'
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: { index: 'src/index.ts' },
|
||||
format: ['esm', 'cjs'],
|
||||
// ESM only: код использует top-level await (для динамической загрузки CJS-only
|
||||
// пакетов ioredis и piscina). TLA несовместим с CJS форматом.
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
@@ -11,9 +13,18 @@ export default defineConfig([
|
||||
},
|
||||
{
|
||||
entry: { 'cli/index': 'src/cli/index.ts' },
|
||||
format: ['cjs'],
|
||||
// ESM: CLI импортирует из src/ который использует top-level await
|
||||
format: ['esm'],
|
||||
sourcemap: true,
|
||||
target: 'node20',
|
||||
banner: { js: '#!/usr/bin/env node' },
|
||||
},
|
||||
// Piscina worker: отдельный CJS файл (Piscina не поддерживает ESM).
|
||||
// Размещается в dist/deserialize.worker.cjs — WorkerPool ищет по этому пути.
|
||||
{
|
||||
entry: { 'deserialize.worker': 'src/workers/deserialize.worker.ts' },
|
||||
format: ['cjs'],
|
||||
sourcemap: true,
|
||||
target: 'node20',
|
||||
},
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user