@juspay/neurolink 10.8.13 → 10.8.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/browser/neurolink.min.js +484 -664
  3. package/dist/cli/factories/sagemakerCommandFactory.js +2 -11
  4. package/dist/features/ppt/slideGenerator.js +10 -18
  5. package/dist/lib/features/ppt/slideGenerator.js +10 -18
  6. package/dist/lib/processors/document/ExcelProcessor.js +11 -19
  7. package/dist/lib/processors/document/WordProcessor.js +3 -11
  8. package/dist/lib/processors/media/AudioProcessor.js +3 -12
  9. package/dist/lib/processors/media/VideoProcessor.d.ts +11 -0
  10. package/dist/lib/processors/media/VideoProcessor.js +33 -24
  11. package/dist/lib/providers/sagemaker/client.js +2 -11
  12. package/dist/lib/server/voice/voiceWebSocketHandler.js +3 -12
  13. package/dist/lib/services/server/ai/observability/instrumentation.js +2 -11
  14. package/dist/lib/tasks/backends/bullmqBackend.js +2 -10
  15. package/dist/lib/utils/tryImport.d.ts +35 -0
  16. package/dist/lib/utils/tryImport.js +95 -0
  17. package/dist/processors/document/ExcelProcessor.js +11 -19
  18. package/dist/processors/document/WordProcessor.js +3 -11
  19. package/dist/processors/media/AudioProcessor.js +3 -12
  20. package/dist/processors/media/VideoProcessor.d.ts +11 -0
  21. package/dist/processors/media/VideoProcessor.js +33 -24
  22. package/dist/providers/sagemaker/client.js +2 -11
  23. package/dist/server/voice/voiceWebSocketHandler.js +3 -12
  24. package/dist/services/server/ai/observability/instrumentation.js +2 -11
  25. package/dist/tasks/backends/bullmqBackend.js +2 -10
  26. package/dist/utils/tryImport.d.ts +35 -0
  27. package/dist/utils/tryImport.js +94 -0
  28. package/package.json +9 -5
@@ -2,19 +2,10 @@ import chalk from "chalk";
2
2
  import ora from "ora";
3
3
  import inquirer from "inquirer";
4
4
  async function loadSageMakerControl() {
5
- try {
6
- return await import(/* @vite-ignore */ "@aws-sdk/client-sagemaker");
7
- }
8
- catch (err) {
9
- const e = err instanceof Error ? err : null;
10
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
11
- e.message.includes("client-sagemaker")) {
12
- throw new Error('SageMaker setup requires "@aws-sdk/client-sagemaker". Install it with:\n pnpm add @aws-sdk/client-sagemaker', { cause: err });
13
- }
14
- throw err;
15
- }
5
+ return tryImport("@aws-sdk/client-sagemaker", "SageMaker setup");
16
6
  }
17
7
  import { logger } from "../../lib/utils/logger.js";
8
+ import { tryImport } from "../../lib/utils/tryImport.js";
18
9
  import { checkSageMakerConfiguration, getSageMakerConfig, getConfigurationSummary, clearConfigurationCache, } from "../../lib/providers/sagemaker/config.js";
19
10
  import { AmazonSageMakerProvider } from "../../lib/providers/sagemaker/index.js";
20
11
  import { runQuickDiagnostics, formatDiagnosticReport, } from "../../lib/providers/sagemaker/diagnostics.js";
@@ -21,26 +21,18 @@ export async function loadPptxGenJS() {
21
21
  if (_pptxGenJS) {
22
22
  return _pptxGenJS;
23
23
  }
24
- try {
25
- const mod = await import(/* @vite-ignore */ "pptxgenjs");
26
- // ESM/CJS interop: pptxgenjs v4 may double-wrap the default export.
27
- // The runtime shape is genuinely dynamic, so probe it as `unknown`.
28
- const rawDefault = mod.default;
29
- const Ctor = typeof rawDefault === "function"
30
- ? rawDefault
31
- : rawDefault.default;
32
- _pptxGenJS = Ctor;
33
- return _pptxGenJS;
34
- }
35
- catch (err) {
36
- const e = err instanceof Error ? err : null;
37
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("pptxgenjs")) {
38
- throw new Error('PPT generation requires the "pptxgenjs" package. Install it with:\n pnpm add pptxgenjs', { cause: err });
39
- }
40
- throw err;
41
- }
24
+ const mod = await tryImport("pptxgenjs", "PPT generation");
25
+ // ESM/CJS interop: pptxgenjs v4 may double-wrap the default export.
26
+ // The runtime shape is genuinely dynamic, so probe it as `unknown`.
27
+ const rawDefault = mod.default;
28
+ const Ctor = typeof rawDefault === "function"
29
+ ? rawDefault
30
+ : rawDefault.default;
31
+ _pptxGenJS = Ctor;
32
+ return _pptxGenJS;
42
33
  }
43
34
  import { logger } from "../../utils/logger.js";
35
+ import { tryImport } from "../../utils/tryImport.js";
44
36
  import { withTimeout, ErrorFactory, NeuroLinkError, } from "../../utils/errorHandling.js";
45
37
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../../observability/index.js";
46
38
  import { LAYOUT_POSITIONS, renderTitleSlide, renderSectionHeaderSlide, renderThankYouSlide, renderContentSlide, renderImageSlide, renderTwoColumnSlide, renderThreeColumnSlide, renderQuoteSlide, renderStatisticsSlide, renderChartSlide, renderTableSlide, renderTimelineSlide, renderProcessFlowSlide, renderComparisonSlide, renderFeaturesSlide, renderTeamSlide, renderConclusionSlide, renderDashboardSlide, renderMixedContentSlide, renderStatsGridSlide, renderIconGridSlide, } from "./slideRenderers.js";
@@ -21,26 +21,18 @@ export async function loadPptxGenJS() {
21
21
  if (_pptxGenJS) {
22
22
  return _pptxGenJS;
23
23
  }
24
- try {
25
- const mod = await import(/* @vite-ignore */ "pptxgenjs");
26
- // ESM/CJS interop: pptxgenjs v4 may double-wrap the default export.
27
- // The runtime shape is genuinely dynamic, so probe it as `unknown`.
28
- const rawDefault = mod.default;
29
- const Ctor = typeof rawDefault === "function"
30
- ? rawDefault
31
- : rawDefault.default;
32
- _pptxGenJS = Ctor;
33
- return _pptxGenJS;
34
- }
35
- catch (err) {
36
- const e = err instanceof Error ? err : null;
37
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("pptxgenjs")) {
38
- throw new Error('PPT generation requires the "pptxgenjs" package. Install it with:\n pnpm add pptxgenjs', { cause: err });
39
- }
40
- throw err;
41
- }
24
+ const mod = await tryImport("pptxgenjs", "PPT generation");
25
+ // ESM/CJS interop: pptxgenjs v4 may double-wrap the default export.
26
+ // The runtime shape is genuinely dynamic, so probe it as `unknown`.
27
+ const rawDefault = mod.default;
28
+ const Ctor = typeof rawDefault === "function"
29
+ ? rawDefault
30
+ : rawDefault.default;
31
+ _pptxGenJS = Ctor;
32
+ return _pptxGenJS;
42
33
  }
43
34
  import { logger } from "../../utils/logger.js";
35
+ import { tryImport } from "../../utils/tryImport.js";
44
36
  import { withTimeout, ErrorFactory, NeuroLinkError, } from "../../utils/errorHandling.js";
45
37
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../../observability/index.js";
46
38
  import { NeuroLink } from "../../neurolink.js";
@@ -38,30 +38,22 @@
38
38
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
39
39
  import { SIZE_LIMITS } from "../config/index.js";
40
40
  import { FileErrorCode } from "../errors/index.js";
41
+ import { tryImport } from "../../utils/tryImport.js";
41
42
  let _exceljs = null;
42
43
  async function loadExcelJS() {
43
44
  if (_exceljs) {
44
45
  return _exceljs;
45
46
  }
46
- try {
47
- const mod = await import(/* @vite-ignore */ "exceljs");
48
- // exceljs is a CommonJS module. Under Node ESM (and some bundlers) the
49
- // `Workbook` constructor is exposed at runtime on the namespace's `default`
50
- // export rather than on the namespace itself — so a bare
51
- // `new ExcelJS.Workbook()` throws "ExcelJS.Workbook is not a constructor"
52
- // (TS still types it as present via esModuleInterop, masking the bug).
53
- // Normalise here so the constructor is reachable regardless of interop style.
54
- const ns = mod;
55
- _exceljs = (ns.Workbook ? ns : (ns.default ?? ns));
56
- return _exceljs;
57
- }
58
- catch (err) {
59
- const e = err instanceof Error ? err : null;
60
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("exceljs")) {
61
- throw new Error('Excel file processing requires the "exceljs" package. Install it with:\n pnpm add exceljs', { cause: err });
62
- }
63
- throw err;
64
- }
47
+ const mod = await tryImport("exceljs", "Excel file processing");
48
+ // exceljs is a CommonJS module. Under Node ESM (and some bundlers) the
49
+ // `Workbook` constructor is exposed at runtime on the namespace's `default`
50
+ // export rather than on the namespace itself — so a bare
51
+ // `new ExcelJS.Workbook()` throws "ExcelJS.Workbook is not a constructor"
52
+ // (TS still types it as present via esModuleInterop, masking the bug).
53
+ // Normalise here so the constructor is reachable regardless of interop style.
54
+ const ns = mod;
55
+ _exceljs = (ns.Workbook ? ns : (ns.default ?? ns));
56
+ return _exceljs;
65
57
  }
66
58
  // Re-export for consumers who import from this module
67
59
  // Import for local use
@@ -34,22 +34,14 @@
34
34
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
35
35
  import { SIZE_LIMITS } from "../config/index.js";
36
36
  import { FileErrorCode } from "../errors/index.js";
37
+ import { tryImport } from "../../utils/tryImport.js";
37
38
  let _mammoth = null;
38
39
  async function loadMammoth() {
39
40
  if (_mammoth) {
40
41
  return _mammoth;
41
42
  }
42
- try {
43
- _mammoth = await import(/* @vite-ignore */ "mammoth");
44
- return _mammoth;
45
- }
46
- catch (err) {
47
- const e = err instanceof Error ? err : null;
48
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("mammoth")) {
49
- throw new Error('Word document processing requires the "mammoth" package. Install it with:\n pnpm add mammoth', { cause: err });
50
- }
51
- throw err;
52
- }
43
+ _mammoth = await tryImport("mammoth", "Word document processing");
44
+ return _mammoth;
53
45
  }
54
46
  // Re-export for consumers who import from this module
55
47
  // Import for local use
@@ -41,23 +41,14 @@ import { SIZE_LIMITS_MB } from "../config/index.js";
41
41
  import { FileErrorCode } from "../errors/index.js";
42
42
  import { withTimeout } from "../../utils/timeout.js";
43
43
  import { formatMediaDuration } from "../../utils/mediaDuration.js";
44
+ import { tryImport } from "../../utils/tryImport.js";
44
45
  let _musicMetadata = null;
45
46
  async function loadMusicMetadata() {
46
47
  if (_musicMetadata) {
47
48
  return _musicMetadata;
48
49
  }
49
- try {
50
- _musicMetadata = await import(/* @vite-ignore */ "music-metadata");
51
- return _musicMetadata;
52
- }
53
- catch (err) {
54
- const e = err instanceof Error ? err : null;
55
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
56
- e.message.includes("music-metadata")) {
57
- throw new Error('Audio processing requires the "music-metadata" package. Install it with:\n pnpm add music-metadata', { cause: err });
58
- }
59
- throw err;
60
- }
50
+ _musicMetadata = await tryImport("music-metadata", "Audio processing");
51
+ return _musicMetadata;
61
52
  }
62
53
  // =============================================================================
63
54
  // TYPES
@@ -45,6 +45,17 @@
45
45
  */
46
46
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
47
47
  import type { FileInfo, ProcessedVideo, ProcessorFileProcessingResult, ProcessOptions } from "../../types/index.js";
48
+ /**
49
+ * Narrow a loaded `fluent-ffmpeg` export to the shape this file actually uses:
50
+ * a callable carrying the `ffprobe` and `setFfmpegPath` statics.
51
+ *
52
+ * `tryImport` proves only that the package RESOLVES. Callers invoke the export
53
+ * and reach straight for its statics, so a package whose shape changed (ESM
54
+ * rewrite, major bump, a shim in node_modules) would otherwise surface as
55
+ * "Cannot read properties of undefined (reading 'ffprobe')" from inside
56
+ * probeVideo — blaming the call site instead of the package that is wrong.
57
+ */
58
+ export declare function assertFluentFfmpegShape(mod: unknown): asserts mod is typeof import("fluent-ffmpeg");
48
59
  /**
49
60
  * Video Processor - extracts metadata, keyframes, and subtitles from video files.
50
61
  *
@@ -55,39 +55,48 @@ import { SIZE_LIMITS_MB } from "../config/index.js";
55
55
  import { FileErrorCode } from "../errors/index.js";
56
56
  import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
57
57
  import { logger } from "../../utils/logger.js";
58
+ import { tryImport } from "../../utils/tryImport.js";
59
+ /**
60
+ * Narrow a loaded `fluent-ffmpeg` export to the shape this file actually uses:
61
+ * a callable carrying the `ffprobe` and `setFfmpegPath` statics.
62
+ *
63
+ * `tryImport` proves only that the package RESOLVES. Callers invoke the export
64
+ * and reach straight for its statics, so a package whose shape changed (ESM
65
+ * rewrite, major bump, a shim in node_modules) would otherwise surface as
66
+ * "Cannot read properties of undefined (reading 'ffprobe')" from inside
67
+ * probeVideo — blaming the call site instead of the package that is wrong.
68
+ */
69
+ export function assertFluentFfmpegShape(mod) {
70
+ // Not `Partial<typeof import("fluent-ffmpeg")>`: Partial maps over properties
71
+ // and drops the call signature, so `typeof x === "function"` would narrow the
72
+ // result to `never`. Probe the statics structurally instead.
73
+ const statics = mod;
74
+ if (typeof mod !== "function" ||
75
+ typeof statics.ffprobe !== "function" ||
76
+ typeof statics.setFfmpegPath !== "function") {
77
+ throw new Error(`The installed "fluent-ffmpeg" package does not export a callable with ` +
78
+ `ffprobe and setFfmpegPath statics (got ${typeof mod}). ` +
79
+ `Reinstall a compatible version:\n pnpm add fluent-ffmpeg`);
80
+ }
81
+ }
58
82
  // fluent-ffmpeg's default export is callable + has static methods — avoid caching
59
83
  // the module type (it confuses TS); Node's module cache handles dedup.
60
84
  async function loadFluentFfmpeg() {
61
- try {
62
- const mod = await import(/* @vite-ignore */ "fluent-ffmpeg");
63
- return mod.default;
64
- }
65
- catch (err) {
66
- const e = err instanceof Error ? err : null;
67
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
68
- e.message.includes("fluent-ffmpeg")) {
69
- throw new Error('Video processing requires the "fluent-ffmpeg" package. Install it with:\n pnpm add fluent-ffmpeg', { cause: err });
70
- }
71
- throw err;
72
- }
85
+ // fluent-ffmpeg is CJS (`export =`), so `typeof import(...)` describes the
86
+ // callable itself and carries no `default`. Under Node ESM the namespace
87
+ // still wraps it, so ask for that shape explicitly.
88
+ const mod = await tryImport("fluent-ffmpeg", "Video processing");
89
+ const ffmpeg = mod.default;
90
+ assertFluentFfmpegShape(ffmpeg);
91
+ return ffmpeg;
73
92
  }
74
93
  let _mediabunny = null;
75
94
  async function loadMediaBunny() {
76
95
  if (_mediabunny) {
77
96
  return _mediabunny;
78
97
  }
79
- try {
80
- _mediabunny = await import(/* @vite-ignore */ "mediabunny");
81
- return _mediabunny;
82
- }
83
- catch (err) {
84
- const e = err instanceof Error ? err : null;
85
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
86
- e.message.includes("mediabunny")) {
87
- throw new Error('Video processing requires the "mediabunny" package. Install it with:\n pnpm add mediabunny', { cause: err });
88
- }
89
- throw err;
90
- }
98
+ _mediabunny = await tryImport("mediabunny", "Video processing");
99
+ return _mediabunny;
91
100
  }
92
101
  // =============================================================================
93
102
  // FFMPEG PATH INITIALIZATION
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { handleSageMakerError, SageMakerError, isRetryableError, getRetryDelay, } from "./errors.js";
8
8
  import { logger } from "../../utils/logger.js";
9
+ import { tryImport } from "../../utils/tryImport.js";
9
10
  /**
10
11
  * Lazily load `@aws-sdk/client-sagemaker-runtime`.
11
12
  *
@@ -16,17 +17,7 @@ import { logger } from "../../utils/logger.js";
16
17
  * error instead of a raw resolution failure.
17
18
  */
18
19
  async function loadSageMakerRuntime() {
19
- try {
20
- return await import(/* @vite-ignore */ "@aws-sdk/client-sagemaker-runtime");
21
- }
22
- catch (err) {
23
- const e = err instanceof Error ? err : null;
24
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
25
- e.message.includes("client-sagemaker-runtime")) {
26
- throw new Error('SageMaker inference requires "@aws-sdk/client-sagemaker-runtime". Install it with:\n pnpm add @aws-sdk/client-sagemaker-runtime', { cause: err });
27
- }
28
- throw err;
29
- }
20
+ return tryImport("@aws-sdk/client-sagemaker-runtime", "SageMaker inference");
30
21
  }
31
22
  /**
32
23
  * Enhanced SageMaker Runtime client with retry logic and error handling
@@ -5,20 +5,11 @@ import { timingSafeEqualString } from "./tokenCompare.js";
5
5
  import { CartesiaStream } from "../../adapters/tts/cartesiaHandler.js";
6
6
  import { NeuroLink } from "../../neurolink.js";
7
7
  import { logger } from "../../utils/logger.js";
8
+ import { tryImport } from "../../utils/tryImport.js";
8
9
  import { withTimeout } from "../../utils/async/withTimeout.js";
9
10
  async function loadCobra(accessKey) {
10
- try {
11
- const mod = (await import(/* @vite-ignore */ "@picovoice/cobra-node"));
12
- return new mod.Cobra(accessKey);
13
- }
14
- catch (err) {
15
- const e = err instanceof Error ? err : null;
16
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
17
- e.message.includes("cobra-node")) {
18
- throw new Error('Voice activity detection requires "@picovoice/cobra-node". Install it with:\n pnpm add @picovoice/cobra-node', { cause: err });
19
- }
20
- throw err;
21
- }
11
+ const mod = await tryImport("@picovoice/cobra-node", "Voice activity detection");
12
+ return new mod.Cobra(accessKey);
22
13
  }
23
14
  const SONIOX_URL = process.env.SONIOX_WS_URL ?? "wss://stt-rt.soniox.com/transcribe-websocket";
24
15
  function getRequiredEnv(name) {
@@ -20,6 +20,7 @@ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semanti
20
20
  import { AsyncLocalStorage } from "async_hooks";
21
21
  import { extractMcpErrorText } from "../../../../utils/mcpErrorText.js";
22
22
  import { logger } from "../../../../utils/logger.js";
23
+ import { tryImport } from "../../../../utils/tryImport.js";
23
24
  import { LANGFUSE_ATTR } from "../../../../telemetry/attributes.js";
24
25
  const LOG_PREFIX = "[OpenTelemetry]";
25
26
  function createOtelResource(config, serviceName) {
@@ -568,17 +569,7 @@ class ContextEnricher {
568
569
  }
569
570
  }
570
571
  async function createLangfuseProcessor(config) {
571
- let mod;
572
- try {
573
- mod = await import(/* @vite-ignore */ "@langfuse/otel");
574
- }
575
- catch (err) {
576
- const e = err instanceof Error ? err : null;
577
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("langfuse")) {
578
- throw new Error('Langfuse observability requires "@langfuse/otel". Install it with:\n pnpm add @langfuse/otel', { cause: err });
579
- }
580
- throw err;
581
- }
572
+ const mod = await tryImport("@langfuse/otel", "Langfuse observability");
582
573
  return new mod.LangfuseSpanProcessor({
583
574
  publicKey: config.publicKey,
584
575
  secretKey: config.secretKey,
@@ -7,19 +7,11 @@
7
7
  * - Survives process restarts (Redis-persisted)
8
8
  */
9
9
  import { logger } from "../../utils/logger.js";
10
+ import { tryImport } from "../../utils/tryImport.js";
10
11
  import { TaskError } from "../errors.js";
11
12
  import { TASK_DEFAULTS, } from "../../types/index.js";
12
13
  async function loadBullMQ() {
13
- try {
14
- return await import(/* @vite-ignore */ "bullmq");
15
- }
16
- catch (err) {
17
- const e = err instanceof Error ? err : null;
18
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("bullmq")) {
19
- throw new Error('BullMQ task backend requires the "bullmq" package. Install it with:\n pnpm add bullmq', { cause: err });
20
- }
21
- throw err;
22
- }
14
+ return tryImport("bullmq", "BullMQ task backend");
23
15
  }
24
16
  const QUEUE_NAME = "neurolink-tasks";
25
17
  export class BullMQBackend {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Dynamically import an optional dependency, converting a missing package into
3
+ * an actionable error.
4
+ *
5
+ * NeuroLink declares 42 `optionalDependencies`, and each loader had grown its
6
+ * own copy of the same try/catch: check `err.code`, substring-match the package
7
+ * name, rethrow a friendlier Error. The copies had drifted — some said
8
+ * `pnpm add`, one said `npm install`, and the phrasing varied per call site.
9
+ *
10
+ * Only missing-module failures are intercepted. A package that is installed but
11
+ * throws while evaluating (syntax error, ESM/CJS interop, a broken transitive
12
+ * dep) rethrows unchanged — turning those into "please install X" would send
13
+ * the caller after a package they already have. The same applies to specifiers
14
+ * that are not installable packages at all (relative paths, file/data URLs):
15
+ * they rethrow untouched rather than producing an unrunnable install command.
16
+ *
17
+ * @param pkg - Module specifier. The install hint is only produced for bare
18
+ * package names; anything else rethrows the loader's own error.
19
+ * @param feature - Human-readable capability name, used to open the message.
20
+ * @returns The imported module namespace, typed as `T` (`unknown` if the
21
+ * caller does not supply a type argument, since the shape cannot be known
22
+ * for a dynamic specifier).
23
+ * @throws The loader's original error, or an `Error` naming the package and
24
+ * the `pnpm add` command when a bare package is genuinely absent. The
25
+ * original failure is always preserved on `cause`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const ExcelJS = await tryImport<typeof import("exceljs")>(
30
+ * "exceljs",
31
+ * "Excel file processing",
32
+ * );
33
+ * ```
34
+ */
35
+ export declare function tryImport<T = unknown>(pkg: string, feature: string): Promise<T>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Dynamically import an optional dependency, converting a missing package into
3
+ * an actionable error.
4
+ *
5
+ * NeuroLink declares 42 `optionalDependencies`, and each loader had grown its
6
+ * own copy of the same try/catch: check `err.code`, substring-match the package
7
+ * name, rethrow a friendlier Error. The copies had drifted — some said
8
+ * `pnpm add`, one said `npm install`, and the phrasing varied per call site.
9
+ *
10
+ * Only missing-module failures are intercepted. A package that is installed but
11
+ * throws while evaluating (syntax error, ESM/CJS interop, a broken transitive
12
+ * dep) rethrows unchanged — turning those into "please install X" would send
13
+ * the caller after a package they already have. The same applies to specifiers
14
+ * that are not installable packages at all (relative paths, file/data URLs):
15
+ * they rethrow untouched rather than producing an unrunnable install command.
16
+ *
17
+ * @param pkg - Module specifier. The install hint is only produced for bare
18
+ * package names; anything else rethrows the loader's own error.
19
+ * @param feature - Human-readable capability name, used to open the message.
20
+ * @returns The imported module namespace, typed as `T` (`unknown` if the
21
+ * caller does not supply a type argument, since the shape cannot be known
22
+ * for a dynamic specifier).
23
+ * @throws The loader's original error, or an `Error` naming the package and
24
+ * the `pnpm add` command when a bare package is genuinely absent. The
25
+ * original failure is always preserved on `cause`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const ExcelJS = await tryImport<typeof import("exceljs")>(
30
+ * "exceljs",
31
+ * "Excel file processing",
32
+ * );
33
+ * ```
34
+ */
35
+ export async function tryImport(pkg, feature) {
36
+ try {
37
+ return (await import(/* @vite-ignore */ pkg));
38
+ }
39
+ catch (err) {
40
+ if (isMissingModule(err, pkg)) {
41
+ throw new Error(`${feature} requires the "${pkg}" package. Install it with:\n pnpm add ${pkg}`, { cause: err });
42
+ }
43
+ throw err;
44
+ }
45
+ }
46
+ /**
47
+ * True only when `err` is the module loader failing to resolve `pkg` itself.
48
+ *
49
+ * The package name is matched inside quotes because that is how both loaders
50
+ * format it — ESM `Cannot find package 'x' imported from …`, CJS
51
+ * `Cannot find module 'x'`. Matching the bare substring instead (as the
52
+ * hand-rolled copies did) also matches a *transitive* dependency whose path
53
+ * happens to contain the name, which would blame the wrong package.
54
+ *
55
+ * The specifier must also be a bare package name. Without that check a missing
56
+ * relative path or file URL is rewritten into `pnpm add ./fixtures/thing.mjs`,
57
+ * which is not a command anyone can run — and this helper is already called
58
+ * with `file:` and `data:` URLs, so that path is reachable rather than
59
+ * theoretical.
60
+ */
61
+ function isMissingModule(err, pkg) {
62
+ if (!(err instanceof Error)) {
63
+ return false;
64
+ }
65
+ if (!isBarePackageSpecifier(pkg)) {
66
+ return false;
67
+ }
68
+ const code = err.code;
69
+ if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
70
+ return false;
71
+ }
72
+ return err.message.includes(`'${pkg}'`) || err.message.includes(`"${pkg}"`);
73
+ }
74
+ /**
75
+ * True for specifiers that name an installable package — `exceljs`,
76
+ * `@scope/pkg`, `pkg/sub/path`.
77
+ *
78
+ * False for the two things an install hint cannot help with: filesystem paths
79
+ * (`./x`, `../x`, `/x`, and Windows `C:\x`, which the scheme test below also
80
+ * rejects) and any URL (`file:`, `data:`, `http:`), including the `node:`
81
+ * builtins, which are never installed.
82
+ */
83
+ function isBarePackageSpecifier(specifier) {
84
+ if (specifier.length === 0) {
85
+ return false;
86
+ }
87
+ if (specifier.startsWith("/") ||
88
+ specifier.startsWith("./") ||
89
+ specifier.startsWith("../")) {
90
+ return false;
91
+ }
92
+ // RFC 3986 scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":"
93
+ return !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(specifier);
94
+ }
95
+ //# sourceMappingURL=tryImport.js.map
@@ -38,30 +38,22 @@
38
38
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
39
39
  import { SIZE_LIMITS } from "../config/index.js";
40
40
  import { FileErrorCode } from "../errors/index.js";
41
+ import { tryImport } from "../../utils/tryImport.js";
41
42
  let _exceljs = null;
42
43
  async function loadExcelJS() {
43
44
  if (_exceljs) {
44
45
  return _exceljs;
45
46
  }
46
- try {
47
- const mod = await import(/* @vite-ignore */ "exceljs");
48
- // exceljs is a CommonJS module. Under Node ESM (and some bundlers) the
49
- // `Workbook` constructor is exposed at runtime on the namespace's `default`
50
- // export rather than on the namespace itself — so a bare
51
- // `new ExcelJS.Workbook()` throws "ExcelJS.Workbook is not a constructor"
52
- // (TS still types it as present via esModuleInterop, masking the bug).
53
- // Normalise here so the constructor is reachable regardless of interop style.
54
- const ns = mod;
55
- _exceljs = (ns.Workbook ? ns : (ns.default ?? ns));
56
- return _exceljs;
57
- }
58
- catch (err) {
59
- const e = err instanceof Error ? err : null;
60
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("exceljs")) {
61
- throw new Error('Excel file processing requires the "exceljs" package. Install it with:\n pnpm add exceljs', { cause: err });
62
- }
63
- throw err;
64
- }
47
+ const mod = await tryImport("exceljs", "Excel file processing");
48
+ // exceljs is a CommonJS module. Under Node ESM (and some bundlers) the
49
+ // `Workbook` constructor is exposed at runtime on the namespace's `default`
50
+ // export rather than on the namespace itself — so a bare
51
+ // `new ExcelJS.Workbook()` throws "ExcelJS.Workbook is not a constructor"
52
+ // (TS still types it as present via esModuleInterop, masking the bug).
53
+ // Normalise here so the constructor is reachable regardless of interop style.
54
+ const ns = mod;
55
+ _exceljs = (ns.Workbook ? ns : (ns.default ?? ns));
56
+ return _exceljs;
65
57
  }
66
58
  // Re-export for consumers who import from this module
67
59
  // Import for local use
@@ -34,22 +34,14 @@
34
34
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
35
35
  import { SIZE_LIMITS } from "../config/index.js";
36
36
  import { FileErrorCode } from "../errors/index.js";
37
+ import { tryImport } from "../../utils/tryImport.js";
37
38
  let _mammoth = null;
38
39
  async function loadMammoth() {
39
40
  if (_mammoth) {
40
41
  return _mammoth;
41
42
  }
42
- try {
43
- _mammoth = await import(/* @vite-ignore */ "mammoth");
44
- return _mammoth;
45
- }
46
- catch (err) {
47
- const e = err instanceof Error ? err : null;
48
- if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("mammoth")) {
49
- throw new Error('Word document processing requires the "mammoth" package. Install it with:\n pnpm add mammoth', { cause: err });
50
- }
51
- throw err;
52
- }
43
+ _mammoth = await tryImport("mammoth", "Word document processing");
44
+ return _mammoth;
53
45
  }
54
46
  // Re-export for consumers who import from this module
55
47
  // Import for local use
@@ -41,23 +41,14 @@ import { SIZE_LIMITS_MB } from "../config/index.js";
41
41
  import { FileErrorCode } from "../errors/index.js";
42
42
  import { withTimeout } from "../../utils/timeout.js";
43
43
  import { formatMediaDuration } from "../../utils/mediaDuration.js";
44
+ import { tryImport } from "../../utils/tryImport.js";
44
45
  let _musicMetadata = null;
45
46
  async function loadMusicMetadata() {
46
47
  if (_musicMetadata) {
47
48
  return _musicMetadata;
48
49
  }
49
- try {
50
- _musicMetadata = await import(/* @vite-ignore */ "music-metadata");
51
- return _musicMetadata;
52
- }
53
- catch (err) {
54
- const e = err instanceof Error ? err : null;
55
- if (e?.code === "ERR_MODULE_NOT_FOUND" &&
56
- e.message.includes("music-metadata")) {
57
- throw new Error('Audio processing requires the "music-metadata" package. Install it with:\n pnpm add music-metadata', { cause: err });
58
- }
59
- throw err;
60
- }
50
+ _musicMetadata = await tryImport("music-metadata", "Audio processing");
51
+ return _musicMetadata;
61
52
  }
62
53
  // =============================================================================
63
54
  // TYPES
@@ -45,6 +45,17 @@
45
45
  */
46
46
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
47
47
  import type { FileInfo, ProcessedVideo, ProcessorFileProcessingResult, ProcessOptions } from "../../types/index.js";
48
+ /**
49
+ * Narrow a loaded `fluent-ffmpeg` export to the shape this file actually uses:
50
+ * a callable carrying the `ffprobe` and `setFfmpegPath` statics.
51
+ *
52
+ * `tryImport` proves only that the package RESOLVES. Callers invoke the export
53
+ * and reach straight for its statics, so a package whose shape changed (ESM
54
+ * rewrite, major bump, a shim in node_modules) would otherwise surface as
55
+ * "Cannot read properties of undefined (reading 'ffprobe')" from inside
56
+ * probeVideo — blaming the call site instead of the package that is wrong.
57
+ */
58
+ export declare function assertFluentFfmpegShape(mod: unknown): asserts mod is typeof import("fluent-ffmpeg");
48
59
  /**
49
60
  * Video Processor - extracts metadata, keyframes, and subtitles from video files.
50
61
  *