diff --git a/imports/startup/server/calcUnionAsync.js b/imports/startup/server/calcUnionAsync.js index 0eaa57f..b5ad54a 100644 --- a/imports/startup/server/calcUnionAsync.js +++ b/imports/startup/server/calcUnionAsync.js @@ -1,18 +1,24 @@ import path from 'path'; import { Worker } from 'worker_threads'; -// Meteor only bundles the npm packages it sees imported/required somewhere in -// its own compiled module graph. The actual usage of these three is inside -// private/workers/unionWorker.js, a plain asset file Meteor's bundler never -// traces (it's loaded at runtime via new Worker(), not import/require here). -// Without this, `meteor build` drops them from programs/server/node_modules -// and the worker fails at startup with "Cannot find module '@turf/circle'". -import '@turf/circle'; -import '@turf/union'; -import '@turf/truncate'; - const WORKER_PATH = path.resolve(process.cwd(), 'assets/app/workers/unionWorker.js'); +// The app's own npm dependencies (installed by `meteor build`) live in +// programs/server/npm/node_modules — a directory that's a SIBLING of +// programs/server/assets, not an ancestor of it. Node's require() only walks +// up parent directories looking for node_modules, so a plain +// require('@turf/circle') from the worker script (a raw asset, not part of +// Meteor's own module system that knows about this layout) can never find it +// and fails with "Cannot find module '@turf/circle'". Resolving the absolute +// path here, where Meteor's own module system already knows how to find it, +// and handing it to the worker sidesteps that entirely. +const NPM_MODULES = path.resolve(process.cwd(), 'programs/server/npm/node_modules'); +const turfPaths = { + circle: path.join(NPM_MODULES, '@turf/circle'), + union: path.join(NPM_MODULES, '@turf/union'), + truncate: path.join(NPM_MODULES, '@turf/truncate') +}; + /** * Circle-union of already-validated, already-decorated subscriptions, run in a * worker thread so the main event loop (DDP/HTTP) stays free regardless of @@ -21,7 +27,7 @@ const WORKER_PATH = path.resolve(process.cwd(), 'assets/app/workers/unionWorker. * are merged into it instead of a union computed from scratch. */ const calcUnionAsync = (subs, baseUnion = null) => new Promise((resolve, reject) => { - const worker = new Worker(WORKER_PATH, { workerData: { subs, baseUnion } }); + const worker = new Worker(WORKER_PATH, { workerData: { subs, baseUnion, turfPaths } }); worker.once('message', (msg) => { worker.terminate(); if (msg.error) { diff --git a/private/workers/unionWorker.js b/private/workers/unionWorker.js index f6fc9ec..34c8156 100644 --- a/private/workers/unionWorker.js +++ b/private/workers/unionWorker.js @@ -15,10 +15,20 @@ // subs gets merged into (the incremental-add fast path) instead of starting // from scratch — for a single new subscription this is one tunion() call // instead of redoing the whole chain over every subscription again. +// workerData.turfPaths gives absolute paths to @turf/circle, @turf/union and +// @turf/truncate — this file lives under programs/server/assets/, while the +// app's own npm deps live in the sibling programs/server/npm/node_modules, +// which plain require('@turf/circle') can never find by walking up parent +// directories. The caller resolves the paths (it's Meteor-compiled code that +// knows how to find them) and hands them over instead. const { parentPort, workerData } = require('worker_threads'); -const tcircle = require('@turf/circle').default; -const tunion = require('@turf/union').default; -const ttrunc = require('@turf/truncate').default; + +// eslint-disable-next-line import/no-dynamic-require +const tcircle = require(workerData.turfPaths.circle).default; +// eslint-disable-next-line import/no-dynamic-require +const tunion = require(workerData.turfPaths.union).default; +// eslint-disable-next-line import/no-dynamic-require +const ttrunc = require(workerData.turfPaths.truncate).default; const truncOptions = { precision: 6, coordinates: 2 };