@pipedog/core 0.1.0 → 0.2.0

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 (63) hide show
  1. package/dist/audio/resamplers/engine.d.ts +60 -0
  2. package/dist/audio/resamplers/engine.js +89 -0
  3. package/dist/audio/resamplers/sinc_engine.d.ts +10 -0
  4. package/dist/audio/resamplers/sinc_engine.js +235 -0
  5. package/dist/audio/resamplers/soxr_resampler.d.ts +10 -6
  6. package/dist/audio/resamplers/soxr_resampler.js +17 -7
  7. package/dist/audio/resamplers/soxr_stream_resampler.d.ts +9 -6
  8. package/dist/audio/resamplers/soxr_stream_resampler.js +24 -11
  9. package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +16 -8
  10. package/dist/audio/vad/vad_analyzer.d.ts +11 -3
  11. package/dist/audio/vad/vad_analyzer.js +72 -34
  12. package/dist/pipeline/worker.d.ts +7 -0
  13. package/dist/pipeline/worker.js +40 -23
  14. package/dist/utils/file_storage.d.ts +37 -21
  15. package/dist/utils/file_storage.js +87 -9
  16. package/dist/utils/frame_queue.js +2 -2
  17. package/dist/utils/image.d.ts +0 -1
  18. package/dist/utils/image.js +37 -4
  19. package/dist/utils/jpeg_encoder.d.ts +14 -0
  20. package/dist/utils/jpeg_encoder.js +315 -0
  21. package/dist/utils/text/transforms/acronyms.d.ts +21 -0
  22. package/dist/utils/text/transforms/acronyms.js +31 -0
  23. package/dist/utils/text/transforms/currency.d.ts +16 -0
  24. package/dist/utils/text/transforms/currency.js +56 -0
  25. package/dist/utils/text/transforms/dates.d.ts +18 -0
  26. package/dist/utils/text/transforms/dates.js +102 -0
  27. package/dist/utils/text/transforms/email.d.ts +17 -0
  28. package/dist/utils/text/transforms/email.js +35 -0
  29. package/dist/utils/text/transforms/index.d.ts +37 -0
  30. package/dist/utils/text/transforms/index.js +42 -0
  31. package/dist/utils/text/transforms/numbers.d.ts +25 -0
  32. package/dist/utils/text/transforms/numbers.js +59 -0
  33. package/dist/utils/text/transforms/percentages.d.ts +16 -0
  34. package/dist/utils/text/transforms/percentages.js +32 -0
  35. package/dist/utils/text/transforms/phone.d.ts +21 -0
  36. package/dist/utils/text/transforms/phone.js +39 -0
  37. package/dist/utils/text/transforms/pronunciations.js +2 -1
  38. package/dist/utils/text/transforms/replacements.d.ts +39 -0
  39. package/dist/utils/text/transforms/replacements.js +46 -0
  40. package/dist/utils/text/transforms/strip_markdown.d.ts +25 -0
  41. package/dist/utils/text/transforms/strip_markdown.js +60 -0
  42. package/dist/utils/text/transforms/units.d.ts +22 -0
  43. package/dist/utils/text/transforms/units.js +81 -0
  44. package/dist/utils/text/transforms/voice_formatter.d.ts +86 -0
  45. package/dist/utils/text/transforms/voice_formatter.js +98 -0
  46. package/dist/utils/text/unicode_digits.d.ts +3 -0
  47. package/dist/utils/text/unicode_digits.js +34 -0
  48. package/dist/utils/tracing/service_decorators.js +2 -2
  49. package/dist/utils/tracing/setup.d.ts +18 -6
  50. package/dist/utils/tracing/setup.js +63 -13
  51. package/dist/utils/tracing/turn_trace_observer.d.ts +5 -0
  52. package/dist/utils/tracing/turn_trace_observer.js +18 -4
  53. package/dist/utils/yaml.d.ts +151 -36
  54. package/dist/utils/yaml.js +527 -12
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/workers/proxy/index.d.ts +3 -0
  58. package/dist/workers/proxy/index.js +9 -0
  59. package/dist/workers/proxy/websocket/client.d.ts +110 -0
  60. package/dist/workers/proxy/websocket/client.js +196 -0
  61. package/dist/workers/proxy/websocket/index.d.ts +3 -0
  62. package/dist/workers/proxy/websocket/index.js +10 -0
  63. package/package.json +18 -3
@@ -0,0 +1,98 @@
1
+ // Copyright (c) 2024-2026, Daily
2
+ // Copyright (c) 2026, pipedog contributors
3
+ // SPDX-License-Identifier: BSD-2-Clause
4
+ //
5
+ // Ported from pipecat: src/pipecat/utils/text/transforms/voice_formatter.py
6
+ import { normalizeAcronyms } from "./acronyms.js";
7
+ import { expandCurrency } from "./currency.js";
8
+ import { normalizeDates } from "./dates.js";
9
+ import { emailToSpeech } from "./email.js";
10
+ import { expandNumbers } from "./numbers.js";
11
+ import { expandPercentages } from "./percentages.js";
12
+ import { expandPhoneNumbers } from "./phone.js";
13
+ import { replaceText } from "./replacements.js";
14
+ import { stripMarkdown } from "./strip_markdown.js";
15
+ import { expandUnits } from "./units.js";
16
+ /**
17
+ * Configurable bundle that applies a pipeline of voice-formatting transforms.
18
+ *
19
+ * Each option enables or disables one transform. Transforms are applied in a
20
+ * deliberate order: structural cleanup first, language expansions second, user
21
+ * replacements last.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const formatter = new VoiceFormatter({
26
+ * stripMarkdown: true,
27
+ * expandCurrency: true,
28
+ * numberDigitCutoff: 2025,
29
+ * customReplacements: [[/\bDr\./, "Doctor"]],
30
+ * });
31
+ * const tts = new CartesiaTTSService({
32
+ * textTransforms: [["*", formatter]],
33
+ * });
34
+ * ```
35
+ */
36
+ // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: adds Python's `__call__` signature to the class.
37
+ export class VoiceFormatter {
38
+ _transforms;
39
+ /** Initialize the voice formatter. */
40
+ constructor(options = {}) {
41
+ const call = async (text, textType) => self.transform(text, textType);
42
+ const self = Object.setPrototypeOf(call, new.target.prototype);
43
+ self._transforms = [];
44
+ if (options.stripMarkdown ?? true) {
45
+ self._transforms.push(stripMarkdown);
46
+ }
47
+ // emailToSpeech must run before expandPhoneNumbers (phone regex matches
48
+ // digit-only domains) and before normalizeAcronyms (all-caps local parts get
49
+ // letter-spaced, breaking the email pattern).
50
+ if (options.emailToSpeech ?? true) {
51
+ self._transforms.push(emailToSpeech);
52
+ }
53
+ if (options.expandPhoneNumbers ?? true) {
54
+ self._transforms.push(expandPhoneNumbers);
55
+ }
56
+ if (options.normalizeDates ?? true) {
57
+ self._transforms.push(normalizeDates);
58
+ }
59
+ if (options.expandCurrency ?? true) {
60
+ self._transforms.push(expandCurrency);
61
+ }
62
+ if (options.expandPercentages ?? true) {
63
+ self._transforms.push(expandPercentages);
64
+ }
65
+ // expandUnits must run before normalizeAcronyms: uppercase unit abbreviations
66
+ // like "MB" or "MPH" would be letter-spaced first and then not recognized.
67
+ if (options.expandUnits ?? true) {
68
+ self._transforms.push(expandUnits);
69
+ }
70
+ if (options.normalizeAcronyms ?? true) {
71
+ self._transforms.push(normalizeAcronyms);
72
+ }
73
+ if (options.expandNumbers ?? false) {
74
+ self._transforms.push(expandNumbers(options.numberDigitCutoff ?? null));
75
+ }
76
+ if (options.customReplacements && options.customReplacements.length > 0) {
77
+ self._transforms.push(replaceText(options.customReplacements));
78
+ }
79
+ // biome-ignore lint/correctness/noConstructorReturn: returning the callable is the point (see above).
80
+ return self;
81
+ }
82
+ /**
83
+ * Apply all configured transforms in order.
84
+ *
85
+ * Python's `__call__`; calling the instance runs this.
86
+ *
87
+ * @param text Input text to transform.
88
+ * @param textType Text type passed through to each transform.
89
+ * @returns Transformed text ready for TTS synthesis.
90
+ */
91
+ async transform(text, textType) {
92
+ for (const transform of this._transforms) {
93
+ text = await transform(text, textType);
94
+ }
95
+ return text;
96
+ }
97
+ }
98
+ //# sourceMappingURL=voice_formatter.js.map
@@ -0,0 +1,3 @@
1
+ /** Replace every Unicode decimal digit with its ASCII digit (`"٥٠"` → `"50"`). */
2
+ export declare function toAsciiDigits(text: string): string;
3
+ //# sourceMappingURL=unicode_digits.d.ts.map
@@ -0,0 +1,34 @@
1
+ // Copyright (c) 2026, pipedog contributors
2
+ // SPDX-License-Identifier: BSD-2-Clause
3
+ //
4
+ // Python's `\\d` (in str patterns) matches any Unicode decimal digit, and
5
+ // `int()` / `float()` accept them: `int("٥٠") == 50`. JavaScript's `\\p{Nd}`
6
+ // matches the same characters, but `Number` / `BigInt` only take ASCII digits.
7
+ // `toAsciiDigits` converts first, so ported code parses what Python parses.
8
+ //
9
+ // Generated from Python 3.14.5 unicodedata (Unicode 16.0.0):
10
+ // the code points whose decimal value is 0; each starts a run of ten digits 0-9.
11
+ const ZERO_CODE_POINTS = [
12
+ 0x30, 0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, 0xce6, 0xd66, 0xde6,
13
+ 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, 0x17e0, 0x1810, 0x1946, 0x19d0, 0x1a80, 0x1a90, 0x1b50,
14
+ 0x1bb0, 0x1c40, 0x1c50, 0xa620, 0xa8d0, 0xa900, 0xa9d0, 0xa9f0, 0xaa50, 0xabf0, 0xff10, 0x104a0,
15
+ 0x10d30, 0x10d40, 0x11066, 0x110f0, 0x11136, 0x111d0, 0x112f0, 0x11450, 0x114d0, 0x11650, 0x116c0,
16
+ 0x116d0, 0x116da, 0x11730, 0x118e0, 0x11950, 0x11bf0, 0x11c50, 0x11d50, 0x11da0, 0x11f50, 0x16130,
17
+ 0x16a60, 0x16ac0, 0x16b50, 0x16d70, 0x1ccf0, 0x1d7ce, 0x1d7d8, 0x1d7e2, 0x1d7ec, 0x1d7f6, 0x1e140,
18
+ 0x1e2f0, 0x1e4f0, 0x1e5f1, 0x1e950, 0x1fbf0,
19
+ ];
20
+ const DIGIT_VALUE = new Map();
21
+ for (const zero of ZERO_CODE_POINTS) {
22
+ for (let i = 0; i < 10; i++)
23
+ DIGIT_VALUE.set(zero + i, i);
24
+ }
25
+ /** Replace every Unicode decimal digit with its ASCII digit (`"٥٠"` → `"50"`). */
26
+ export function toAsciiDigits(text) {
27
+ let out = "";
28
+ for (const ch of text) {
29
+ const value = DIGIT_VALUE.get(ch.codePointAt(0));
30
+ out += value === undefined ? ch : String(value);
31
+ }
32
+ return out;
33
+ }
34
+ //# sourceMappingURL=unicode_digits.js.map
@@ -12,8 +12,8 @@
12
12
  *
13
13
  * Porting notes:
14
14
  * - Upstream decides at import time whether tracing is available and returns
15
- * a no-op decorator otherwise. Here OpenTelemetry is registered at runtime
16
- * (`registerOpenTelemetry()`), possibly after service classes are defined,
15
+ * a no-op decorator otherwise. Here OpenTelemetry is imported asynchronously
16
+ * or registered at runtime (see `setup.ts`), possibly after service classes are defined,
17
17
  * so the wrappers check `isTracingAvailable()` when they run instead. With
18
18
  * tracing unavailable they call straight through, as upstream's no-op does.
19
19
  * - Every decorator works three ways:
@@ -1,4 +1,4 @@
1
- export type OtelAttributeValue = string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
1
+ export type OtelAttributeValue = string | number | boolean | ReadonlyArray<string | null | undefined> | ReadonlyArray<number | null | undefined> | ReadonlyArray<boolean | null | undefined>;
2
2
  /** `SpanContext` from `@opentelemetry/api`. */
3
3
  export interface OtelSpanContext {
4
4
  traceId: string;
@@ -35,10 +35,10 @@ export interface OtelApi {
35
35
  /** The `@opentelemetry/sdk-trace-base` module (v2). */
36
36
  export interface OtelSdkTraceBase {
37
37
  BasicTracerProvider: new (config?: {
38
- resource?: unknown;
39
- spanProcessors?: unknown[];
38
+ resource?: any;
39
+ spanProcessors?: any[];
40
40
  }) => unknown;
41
- BatchSpanProcessor: new (exporter: unknown) => unknown;
41
+ BatchSpanProcessor: new (exporter: any) => unknown;
42
42
  ConsoleSpanExporter: new () => unknown;
43
43
  }
44
44
  /** The `@opentelemetry/resources` module (v2). */
@@ -55,8 +55,20 @@ export interface OpenTelemetryModules {
55
55
  resources?: OtelResources | null;
56
56
  }
57
57
  /**
58
- * Make OpenTelemetry available to Pipecat (upstream's optional import).
59
- * Pass `null` to disable tracing again.
58
+ * Wait for the automatic `import("@opentelemetry/api")` started when core
59
+ * loaded, and report whether tracing is available.
60
+ *
61
+ * `PipelineWorker` awaits this in its async setup, so a worker built before
62
+ * the import finished (e.g. at module top level) still traces; the turn trace
63
+ * observer and the service decorators check availability when they run.
64
+ *
65
+ * @returns True if tracing is available, False otherwise.
66
+ */
67
+ export declare function loadOpenTelemetry(): Promise<boolean>;
68
+ /**
69
+ * Make OpenTelemetry available to Pipecat explicitly, instead of (or before)
70
+ * the automatic import of `@opentelemetry/api`. Pass `null` to disable
71
+ * tracing again.
60
72
  */
61
73
  export declare function registerOpenTelemetry(modules: OpenTelemetryModules | null): void;
62
74
  /** The registered OpenTelemetry modules, or null if tracing is unavailable. */
@@ -10,33 +10,83 @@
10
10
  * OpenTelemetry tracing for Pipecat applications. It handles the optional
11
11
  * nature of OpenTelemetry dependencies and provides a safe setup process.
12
12
  *
13
- * Porting notes: upstream detects OpenTelemetry by trying to import it. Core
14
- * takes no third-party dependencies, so the OpenTelemetry JS modules are
15
- * handed in instead, once, at startup:
13
+ * Porting notes: upstream detects OpenTelemetry by trying to import it, and
14
+ * treats it as an optional extra (`pipecat-ai[tracing]`). Here
15
+ * `@opentelemetry/api` is an optional peer dependency of `@pipedog/core`:
16
+ *
17
+ * - When this module loads it starts a dynamic `import("@opentelemetry/api")`
18
+ * (no top-level await, so importing core never waits or throws). If the
19
+ * package is not installed the import fails quietly and tracing stays
20
+ * unavailable, exactly like upstream's `OPENTELEMETRY_AVAILABLE = False`.
21
+ * Because the import is asynchronous, `isTracingAvailable()` turns true a
22
+ * moment after core loads; `await loadOpenTelemetry()` waits for it.
23
+ * Nothing latches the early "unavailable": `PipelineWorker` awaits it in
24
+ * its async setup before creating the tracing observers.
25
+ * - `registerOpenTelemetry()` hands the modules in explicitly and
26
+ * synchronously (it wins over the automatic import). `@pipedog/tracing`'s
27
+ * `setupTracing()` does this, also registering `@opentelemetry/sdk-trace-base`
28
+ * and `@opentelemetry/resources`, which this module's `setupTracing()` needs.
29
+ *
30
+ * Without OpenTelemetry every tracing helper is a no-op, as upstream behaves.
16
31
  *
17
32
  * @example
18
33
  * import * as api from "@opentelemetry/api";
19
34
  * import * as sdk from "@opentelemetry/sdk-trace-base";
20
35
  * import * as resources from "@opentelemetry/resources";
21
- * import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
22
36
  *
23
37
  * registerOpenTelemetry({ api, sdk, resources });
24
- * setupTracing({
25
- * serviceName: "my-service",
26
- * exporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
27
- * });
28
- *
29
- * Until `registerOpenTelemetry()` is called, `isTracingAvailable()` is false and
30
- * every tracing helper is a no-op, as upstream behaves without OpenTelemetry.
38
+ * setupTracing({ serviceName: "my-service", exporter: new sdk.ConsoleSpanExporter() });
31
39
  */
32
40
  import { rethrowIfCancelled } from "@pipedog/asyncio";
33
41
  import { logger } from "../logger.js";
34
42
  let openTelemetry = null;
43
+ /** Set once `registerOpenTelemetry()` is called; the automatic import then leaves things alone. */
44
+ let registeredExplicitly = false;
45
+ /**
46
+ * Import `@opentelemetry/api` if it is installed (upstream's optional import).
47
+ *
48
+ * The specifier is a string literal so bundlers (esbuild/wrangler, Bun.build)
49
+ * bundle the package when it is installed; when it is not, they leave the
50
+ * `import()` in place and it rejects at runtime, which is caught here.
51
+ */
52
+ async function importOpenTelemetryApi() {
53
+ try {
54
+ const mod = (await import("@opentelemetry/api"));
55
+ // CommonJS interop (some bundlers) puts the exports on `default`.
56
+ const api = mod?.trace ? mod : mod?.default;
57
+ return api?.trace && api.context ? api : null;
58
+ }
59
+ catch (e) {
60
+ rethrowIfCancelled(e);
61
+ return null;
62
+ }
63
+ }
64
+ const autoLoad = importOpenTelemetryApi().then((api) => {
65
+ if (api && !registeredExplicitly) {
66
+ openTelemetry = { api };
67
+ }
68
+ });
69
+ /**
70
+ * Wait for the automatic `import("@opentelemetry/api")` started when core
71
+ * loaded, and report whether tracing is available.
72
+ *
73
+ * `PipelineWorker` awaits this in its async setup, so a worker built before
74
+ * the import finished (e.g. at module top level) still traces; the turn trace
75
+ * observer and the service decorators check availability when they run.
76
+ *
77
+ * @returns True if tracing is available, False otherwise.
78
+ */
79
+ export async function loadOpenTelemetry() {
80
+ await autoLoad;
81
+ return isTracingAvailable();
82
+ }
35
83
  /**
36
- * Make OpenTelemetry available to Pipecat (upstream's optional import).
37
- * Pass `null` to disable tracing again.
84
+ * Make OpenTelemetry available to Pipecat explicitly, instead of (or before)
85
+ * the automatic import of `@opentelemetry/api`. Pass `null` to disable
86
+ * tracing again.
38
87
  */
39
88
  export function registerOpenTelemetry(modules) {
89
+ registeredExplicitly = true;
40
90
  openTelemetry = modules;
41
91
  }
42
92
  /** The registered OpenTelemetry modules, or null if tracing is unavailable. */
@@ -38,6 +38,11 @@ export declare class TurnTraceObserver extends BaseObserver {
38
38
  protected _additionalSpanAttributes: Record<string, OtelAttributeValue>;
39
39
  /** Initialize the turn trace observer. */
40
40
  constructor(options: TurnTraceObserverOptions);
41
+ /**
42
+ * The turn tracer, resolved on first use if OpenTelemetry was not available
43
+ * yet when the observer was built (core imports it asynchronously).
44
+ */
45
+ protected _getTracer(): OtelTracer | null;
41
46
  /**
42
47
  * Handle latency measurement events.
43
48
  *
@@ -9,6 +9,10 @@
9
9
  * This module provides an observer that creates trace spans for each
10
10
  * conversation turn, integrating with the turn tracking system to provide
11
11
  * hierarchical tracing of conversation flows.
12
+ *
13
+ * Porting notes: upstream gets the tracer in `__init__`. Core imports
14
+ * OpenTelemetry asynchronously, so an observer built before that import
15
+ * finished gets its tracer on first use instead (`_getTracer()`).
12
16
  */
13
17
  import { StartFrame } from "../../frames/frames.js";
14
18
  import { BaseObserver, } from "../../observers/base_observer.js";
@@ -56,6 +60,14 @@ export class TurnTraceObserver extends BaseObserver {
56
60
  await this._handleLatencyMeasured(latencySeconds);
57
61
  });
58
62
  }
63
+ /**
64
+ * The turn tracer, resolved on first use if OpenTelemetry was not available
65
+ * yet when the observer was built (core imports it asynchronously).
66
+ */
67
+ _getTracer() {
68
+ this._tracer ??= getOpenTelemetry()?.api.trace.getTracer("pipecat.turn") ?? null;
69
+ return this._tracer;
70
+ }
59
71
  /**
60
72
  * Handle latency measurement events.
61
73
  *
@@ -90,7 +102,8 @@ export class TurnTraceObserver extends BaseObserver {
90
102
  * @param conversationId Optional custom ID for the conversation. If null, a UUID will be generated.
91
103
  */
92
104
  startConversationTracing(conversationId = null) {
93
- if (!isTracingAvailable() || !this._tracer) {
105
+ const tracer = this._getTracer();
106
+ if (!isTracingAvailable() || !tracer) {
94
107
  return;
95
108
  }
96
109
  // Generate a conversation ID if not provided
@@ -100,7 +113,7 @@ export class TurnTraceObserver extends BaseObserver {
100
113
  }
101
114
  this._conversationId = conversationId;
102
115
  // Create a new span for this conversation
103
- const conversationSpan = this._tracer.startSpan("conversation");
116
+ const conversationSpan = tracer.startSpan("conversation");
104
117
  this._conversationSpan = conversationSpan;
105
118
  // Set span attributes
106
119
  conversationSpan.setAttribute("conversation.id", conversationId);
@@ -142,7 +155,8 @@ export class TurnTraceObserver extends BaseObserver {
142
155
  }
143
156
  /** Handle a turn start event by creating a new span. */
144
157
  async _handleTurnStarted(turnNumber) {
145
- if (!isTracingAvailable() || !this._tracer) {
158
+ const tracer = this._getTracer();
159
+ if (!isTracingAvailable() || !tracer) {
146
160
  return;
147
161
  }
148
162
  if (turnNumber === 1 && !this._conversationSpan) {
@@ -154,7 +168,7 @@ export class TurnTraceObserver extends BaseObserver {
154
168
  parentContext = this._tracingContext.getConversationContext();
155
169
  }
156
170
  // Create a new span for this turn
157
- const currentSpan = this._tracer.startSpan("turn", undefined, parentContext ?? undefined);
171
+ const currentSpan = tracer.startSpan("turn", undefined, parentContext ?? undefined);
158
172
  this._currentSpan = currentSpan;
159
173
  this._currentTurnNumber = turnNumber;
160
174
  // Set span attributes
@@ -1,37 +1,138 @@
1
- /**
2
- * YAML loading helpers shared across Pipecat's YAML-based configuration.
3
- *
4
- * The main helper is `includeLoader`, which builds a loader class that
5
- * understands an `!include <relative-path>` tag so a document can pull in
6
- * other files:
7
- *
8
- * ```yaml
9
- * judge: !include judge_audio.yaml
10
- * task_messages: !include prompts/greeting.yaml
11
- * ```
12
- *
13
- * Include paths are not confined to the base directory; `..` and absolute
14
- * paths reach anywhere the process can read. Use these loaders only on
15
- * trusted documents.
16
- *
17
- * Not ported yet: Upstream builds on PyYAML loader classes and reads
18
- * included files from disk. Core takes no YAML dependency and has no
19
- * filesystem access (Workers), so only the interface is here; both functions
20
- * throw until a YAML parser and file reader are wired in.
21
- */
22
- /** A YAML node, as handed to a constructor. */
23
- export interface YamlNode {
24
- /** Whether the node is a scalar (as opposed to a sequence or mapping). */
25
- readonly isScalar: boolean;
26
- /** The scalar value, for scalar nodes. */
27
- readonly value?: unknown;
1
+ /** Position of a node in its document (0-based line and column, like PyYAML's `Mark`). */
2
+ export interface YamlMark {
3
+ line: number;
4
+ column: number;
5
+ }
6
+ /** Base class of the errors raised while loading YAML (`yaml.YAMLError`). */
7
+ export declare class YAMLError extends Error {
8
+ name: string;
28
9
  }
10
+ /** A YAML error tied to a place in the document (`yaml.MarkedYAMLError`). */
11
+ export declare class MarkedYAMLError extends YAMLError {
12
+ name: string;
13
+ readonly problem: string;
14
+ readonly problemMark: YamlMark | null;
15
+ constructor(problem: string, problemMark?: YamlMark | null);
16
+ }
17
+ /** The document is not valid YAML (`yaml.parser.ParserError` / `yaml.scanner.ScannerError`). */
18
+ export declare class ParserError extends MarkedYAMLError {
19
+ name: string;
20
+ }
21
+ /** A node could not be turned into a value (`yaml.constructor.ConstructorError`). */
22
+ export declare class ConstructorError extends MarkedYAMLError {
23
+ name: string;
24
+ }
25
+ /** A scalar node (`yaml.ScalarNode`). `value` is its text. */
26
+ export declare class ScalarNode {
27
+ readonly isScalar = true;
28
+ readonly tag: string;
29
+ readonly value: string;
30
+ readonly startMark: YamlMark | null;
31
+ /** How the scalar was written: `null` when plain, else `'`, `"`, `|` or `>`. */
32
+ readonly style: string | null;
33
+ constructor(tag: string, value: string, startMark: YamlMark | null, style?: string | null);
34
+ }
35
+ /** A sequence node (`yaml.SequenceNode`). */
36
+ export declare class SequenceNode {
37
+ readonly isScalar = false;
38
+ readonly tag: string;
39
+ readonly value: YamlNode[];
40
+ readonly startMark: YamlMark | null;
41
+ constructor(tag: string, value: YamlNode[], startMark: YamlMark | null);
42
+ }
43
+ /** A mapping node (`yaml.MappingNode`). `value` holds the key and value nodes in order. */
44
+ export declare class MappingNode {
45
+ readonly isScalar = false;
46
+ readonly tag: string;
47
+ value: [YamlNode, YamlNode][];
48
+ readonly startMark: YamlMark | null;
49
+ constructor(tag: string, value: [YamlNode, YamlNode][], startMark: YamlMark | null);
50
+ }
51
+ /** A YAML node, as handed to a constructor. */
52
+ export type YamlNode = ScalarNode | SequenceNode | MappingNode;
29
53
  /** A tag constructor registered on a loader class. */
30
- export type YamlConstructor = (loader: unknown, node: YamlNode) => unknown;
54
+ export type YamlConstructor = (loader: SafeLoader, node: YamlNode) => unknown;
55
+ /**
56
+ * Implicit resolvers by the first character of the plain scalars they apply
57
+ * to (`""` for the empty scalar, `null` for any): `[tag, pattern]` pairs,
58
+ * tried in order.
59
+ */
60
+ export type YamlImplicitResolvers = Map<string | null, [string, RegExp][]>;
31
61
  /** The loader-class shape the helpers work with (a `yaml.SafeLoader` subclass upstream). */
32
62
  export interface YamlLoaderClass {
63
+ new (stream: string): SafeLoader;
64
+ yamlConstructors: Map<string, YamlConstructor>;
65
+ yamlImplicitResolvers: YamlImplicitResolvers;
33
66
  /** Register a constructor for a tag on this loader class. */
34
67
  addConstructor(tag: string, fn: YamlConstructor): void;
68
+ /** Register an implicit resolver on this loader class. */
69
+ addImplicitResolver(tag: string, regexp: RegExp, first: Iterable<string> | null): void;
70
+ }
71
+ /**
72
+ * The safe loader (`yaml.SafeLoader`): loads standard YAML 1.1 tags into
73
+ * plain values. Subclass it and register constructors or resolvers on the
74
+ * subclass to extend it; registrations never reach the parent class.
75
+ */
76
+ export declare class SafeLoader {
77
+ #private;
78
+ static yamlConstructors: Map<string, YamlConstructor>;
79
+ static yamlImplicitResolvers: YamlImplicitResolvers;
80
+ /** Register `fn` as the constructor for `tag` on this class (copying the parent's table first). */
81
+ static addConstructor(tag: string, fn: YamlConstructor): void;
82
+ /**
83
+ * Resolve plain scalars matching `regexp` (and starting with one of `first`,
84
+ * or any character when null) to `tag` on this class.
85
+ */
86
+ static addImplicitResolver(tag: string, regexp: RegExp, first: Iterable<string> | null): void;
87
+ constructor(stream: string);
88
+ /** Load the stream's single document (`get_single_data`); `null` for an empty stream. */
89
+ getSingleData(): unknown;
90
+ /** Compose the stream's single document into nodes, resolving tags. */
91
+ getSingleNode(): YamlNode | null;
92
+ /** The tag a plain scalar resolves to under this class's implicit resolvers. */
93
+ resolveScalar(value: string): string;
94
+ /** Construct the value of a whole document. */
95
+ constructDocument(node: YamlNode): unknown;
96
+ /** Construct the value of `node` with the constructor registered for its tag. */
97
+ constructObject(node: YamlNode): unknown;
98
+ /** The text of a scalar node. */
99
+ constructScalar(node: YamlNode): string;
100
+ /** The values of a sequence node. */
101
+ constructSequence(node: YamlNode): unknown[];
102
+ /**
103
+ * The entries of a mapping node, as an object, after applying `<<` merge
104
+ * keys. Keys are scalars; a sequence or mapping key is an error, as an
105
+ * unhashable key is upstream.
106
+ */
107
+ constructMapping(node: YamlNode): Record<string, unknown>;
108
+ /** The entries of a sequence of one-entry mappings (`!!omap`, `!!pairs`) as `[key, value]` pairs. */
109
+ constructPairs(node: YamlNode): [unknown, unknown][];
110
+ }
111
+ /**
112
+ * Parse the single YAML document in `stream` (`yaml.load(stream, Loader)`).
113
+ *
114
+ * @param stream The YAML text.
115
+ * @param Loader The loader class to load with. Defaults to `SafeLoader`.
116
+ */
117
+ export declare function load(stream: string, Loader?: YamlLoaderClass): unknown;
118
+ /**
119
+ * Register an implicit resolver (`yaml.add_implicit_resolver`) on `Loader`.
120
+ *
121
+ * @param tag The tag plain scalars matching `regexp` resolve to.
122
+ * @param regexp The pattern a plain scalar must match.
123
+ * @param first The characters such scalars start with, or null for any.
124
+ */
125
+ export declare function addImplicitResolver(tag: string, regexp: RegExp, first?: Iterable<string> | null, options?: {
126
+ Loader?: YamlLoaderClass;
127
+ }): void;
128
+ /**
129
+ * Reads an included file as UTF-8 text. Throws if the file can't be read
130
+ * (Node's `ENOENT` error is upstream's `FileNotFoundError`).
131
+ */
132
+ export type YamlFileReader = (path: string) => string;
133
+ export interface IncludeOptions {
134
+ /** Reads `!include`d files, e.g. `(p) => readFileSync(p, "utf8")`. */
135
+ readFile: YamlFileReader;
35
136
  }
36
137
  /**
37
138
  * Register an `!include <relative-path>` constructor on `loaderClass`.
@@ -41,24 +142,38 @@ export interface YamlLoaderClass {
41
142
  * resolve against `baseDir` and may reach outside it.
42
143
  *
43
144
  * This mutates `loaderClass`. Register on a private subclass rather than on
44
- * the base loader itself so the constructor has no global side effects;
145
+ * `SafeLoader` itself so the constructor has no global side effects;
45
146
  * `includeLoader` does that for you.
46
147
  *
47
148
  * @param loaderClass The loader class to register the constructor on.
48
149
  * @param baseDir Directory that `!include` paths resolve against.
150
+ * @param options How to read the included files.
49
151
  */
50
- export declare function addIncludeConstructor(_loaderClass: YamlLoaderClass, _baseDir: string): void;
152
+ export declare function addIncludeConstructor(loaderClass: YamlLoaderClass, baseDir: string, options: IncludeOptions): void;
153
+ export interface IncludeLoaderOptions extends IncludeOptions {
154
+ /**
155
+ * Loader class to subclass. Defaults to `SafeLoader`; pass a custom
156
+ * `SafeLoader` subclass to keep its resolvers and constructors.
157
+ */
158
+ base?: YamlLoaderClass;
159
+ }
51
160
  /**
52
161
  * Build a loader class that resolves `!include` relative to `baseDir`.
53
162
  *
54
- * Returns a fresh subclass of `base` with the include constructor registered,
55
- * so `base` itself is left untouched.
163
+ * Returns a fresh subclass of `base` with the include constructor
164
+ * registered, so `base` itself is left untouched.
56
165
  *
57
166
  * @param baseDir Directory that `!include` paths resolve against, typically
58
167
  * the directory of the document being loaded.
59
- * @param base Loader class to subclass. Defaults to the safe loader; pass a
60
- * custom one to keep its resolvers and constructors.
61
- * @returns A loader class to load documents with.
168
+ * @param options How to read included files, and the loader class to subclass.
169
+ * @returns A loader class to pass to `load`.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * const data = load(readFileSync(path, "utf8"), includeLoader(dirname(path), {
174
+ * readFile: (p) => readFileSync(p, "utf8"),
175
+ * }));
176
+ * ```
62
177
  */
63
- export declare function includeLoader(_baseDir: string, _base?: YamlLoaderClass): YamlLoaderClass;
178
+ export declare function includeLoader(baseDir: string, options: IncludeLoaderOptions): YamlLoaderClass;
64
179
  //# sourceMappingURL=yaml.d.ts.map