@effected/yaml 0.4.0 → 0.5.1

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.
package/Yaml.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AliasExpansionBudgetExceeded, CollectionStyle, ScalarStyle, nodeToJsValue } from "./YamlNode.js";
1
+ import { AliasExpansionBudgetExceeded, CollectionStyle, QuoteStyle, ScalarStyle, nodeToJsValue } from "./YamlNode.js";
2
2
  import { buildAnchorMap } from "./internal/composer/anchors.js";
3
3
  import { composeAllDocuments, composeFirstDocument } from "./internal/composer/document.js";
4
4
  import { isFatalCode } from "./internal/diagnostics.js";
@@ -36,8 +36,8 @@ var YamlParseOptions = class extends Schema.Class("YamlParseOptions")({
36
36
  * Options controlling stringify behavior. All fields are omissible; absent
37
37
  * fields resolve to `indent` `2`, `lineWidth` `0`, `defaultScalarStyle`
38
38
  * `"plain"`, `defaultCollectionStyle` `"block"`, `sortKeys` `false`,
39
- * `indentSequences` `false`, `finalNewline` `true` and `forceDefaultStyles`
40
- * `false`.
39
+ * `indentSequences` `false`, `quoteStyle` `"single"`, `finalNewline` `true`
40
+ * and `forceDefaultStyles` `false`.
41
41
  *
42
42
  * `lineWidth` controls column-based scalar folding. The default `0` (and any
43
43
  * value `<= 0`) never wraps, emitting byte-identical output to the historic
@@ -54,6 +54,18 @@ var YamlParseOptions = class extends Schema.Class("YamlParseOptions")({
54
54
  * matching the `yaml` npm package's default output. Top-level sequences stay
55
55
  * at column zero in both modes.
56
56
  *
57
+ * `quoteStyle` selects the quote character used when a `plain`-styled scalar
58
+ * (the `defaultScalarStyle` default) turns out to require quoting: `"single"`
59
+ * (the default) emits `'@parcel/watcher'` — the kit's byte-compatible legacy
60
+ * form — while `"double"` emits `"@parcel/watcher"`, matching the `yaml` npm
61
+ * package's `singleQuote: false` output. It is a fallback selector only:
62
+ * scalars that need no quoting stay plain, and an explicit
63
+ * `defaultScalarStyle` of `"single-quoted"` or `"double-quoted"` still wins.
64
+ * On that plain fallback path, values carrying a tab, a carriage return or
65
+ * any other C0 control character are always emitted double-quoted whichever
66
+ * `quoteStyle` is set, since only double quotes can escape them into a form
67
+ * that round-trips exactly.
68
+ *
57
69
  * Construct with the validated `YamlStringifyOptions.make({ ... })` static —
58
70
  * the kit convention (never `new`). Call sites that take a
59
71
  * `YamlStringifyOptions` also accept a structurally-matching plain literal.
@@ -79,7 +91,7 @@ var YamlStringifyOptions = class extends Schema.Class("YamlStringifyOptions")({
79
91
  * (`>`) scalars at approximately that column, never block-literal (`|`).
80
92
  *
81
93
  * Takes effect only through {@link Yaml.stringify} and
82
- * {@link Yaml.stringifySync} — the two entry points that accept these
94
+ * {@link Yaml.stringifyResult} — the two entry points that accept these
83
95
  * options on the value path. The schema factories ({@link Yaml.fromString},
84
96
  * {@link Yaml.schema}, {@link Yaml.YamlFromString}) encode with default
85
97
  * stringify options (`lineWidth` `0`), so their output never folds. The
@@ -92,6 +104,17 @@ var YamlStringifyOptions = class extends Schema.Class("YamlStringifyOptions")({
92
104
  defaultCollectionStyle: Schema.optionalKey(CollectionStyle),
93
105
  sortKeys: Schema.optionalKey(Schema.Boolean),
94
106
  indentSequences: Schema.optionalKey(Schema.Boolean),
107
+ /**
108
+ * Quote style used when a `plain`-styled scalar requires quoting. Default
109
+ * `"single"` — the released byte-compatible behavior. `"double"` renders
110
+ * the same scalars double-quoted instead, matching the `yaml` npm
111
+ * package's `singleQuote: false` output.
112
+ *
113
+ * Affects only the plain fallback: scalars that need no quoting stay
114
+ * plain, and an explicit `defaultScalarStyle` of `"single-quoted"` or
115
+ * `"double-quoted"` is unaffected.
116
+ */
117
+ quoteStyle: Schema.optionalKey(QuoteStyle),
95
118
  finalNewline: Schema.optionalKey(Schema.Boolean),
96
119
  forceDefaultStyles: Schema.optionalKey(Schema.Boolean)
97
120
  }) {};
@@ -141,6 +164,7 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
141
164
  defaultCollectionStyle: options.defaultCollectionStyle,
142
165
  sortKeys: options.sortKeys,
143
166
  indentSequences: options.indentSequences,
167
+ quoteStyle: options.quoteStyle,
144
168
  finalNewline: options.finalNewline,
145
169
  forceDefaultStyles: options.forceDefaultStyles
146
170
  };
@@ -198,7 +222,7 @@ const stringifyDefectToError = (defect, value) => {
198
222
  * mode (fatal diagnostics, duplicate keys, a "billion laughs" blow-up) yields
199
223
  * a `Failure` carrying a typed {@link YamlParseError} — never a throw.
200
224
  */
201
- const parseSyncImpl = (text, options) => {
225
+ const parseResultImpl = (text, options) => {
202
226
  const doc = composeFirstDocument(text, toParseInput(options));
203
227
  const failures = failureRecords(doc, options?.uniqueKeys ?? true);
204
228
  if (failures.length > 0) return Result.fail(new YamlParseError({
@@ -219,7 +243,7 @@ const parseSyncImpl = (text, options) => {
219
243
  * recursion budget yields a `Failure` carrying a typed {@link YamlStringifyError},
220
244
  * never a thrown defect.
221
245
  */
222
- const stringifySyncImpl = (value, options) => {
246
+ const stringifyResultImpl = (value, options) => {
223
247
  try {
224
248
  return Result.succeed(stringifyValue(value, toStringifyInput(options)));
225
249
  } catch (defect) {
@@ -296,17 +320,11 @@ var Yaml = class Yaml {
296
320
  * resolved size grows exponentially relative to `maxAliasCount`) also
297
321
  * fails through {@link YamlParseError} with an `AliasCountExceeded`
298
322
  * diagnostic, never as an unhandled defect.
323
+ *
324
+ * Defined in terms of {@link Yaml.parseResult} — synchronous callers can
325
+ * use that variant directly.
299
326
  */
300
- static parse = Effect.fn("Yaml.parse")(function* (text, options) {
301
- const doc = composeFirstDocument(text, toParseInput(options));
302
- const failures = failureRecords(doc, options?.uniqueKeys ?? true);
303
- if (failures.length > 0) return yield* new YamlParseError({
304
- diagnostics: toDiagnostics(text, failures),
305
- input: text
306
- });
307
- const anchors = /* @__PURE__ */ new Map();
308
- return yield* extractDocumentValue(doc.contents, anchors, options?.maxAliasCount ?? 100, text);
309
- });
327
+ static parse = Effect.fn("Yaml.parse")((text, options) => Effect.fromResult(Yaml.parseResult(text, options)));
310
328
  /**
311
329
  * Parse a multi-document YAML stream into an array of plain JavaScript
312
330
  * values (one per document, in order). Any fatal diagnostic in any
@@ -339,6 +357,15 @@ var Yaml = class Yaml {
339
357
  * or on a value nested deeper than the stringifier's recursion budget
340
358
  * (`NestingDepthExceeded`) — both surface through the typed error channel
341
359
  * rather than as an unhandled stack-overflow defect.
360
+ *
361
+ * @remarks
362
+ * A `"<<"` object key is emitted **quoted** (`'<<': …`). This is the
363
+ * opposite of the document path ({@link YamlFormat.format} and
364
+ * `YamlDocument#stringify`), which leaves a parsed plain `<<` key unquoted
365
+ * so it keeps its merge-key meaning, and the asymmetry is deliberate: a
366
+ * `"<<"` key on a plain JavaScript object is an ordinary string key that
367
+ * never carried merge semantics, so emitting it plain would silently turn
368
+ * ordinary data into a merge directive.
342
369
  */
343
370
  static stringify = Effect.fn("Yaml.stringify")(function* (value, options) {
344
371
  return yield* stringifyOrFail(value, options);
@@ -346,8 +373,14 @@ var Yaml = class Yaml {
346
373
  /**
347
374
  * Synchronous single-document parse, returning a `Result` instead of
348
375
  * an `Effect`. A pure escape hatch for config-time callers that cannot
349
- * `await` an Effect (a `vitest.config.ts` is the motivating case): it runs
350
- * the same engine as {@link Yaml.parse} inline.
376
+ * `await` an Effect (a `vitest.config.ts` is the motivating case).
377
+ *
378
+ * @remarks
379
+ * This is the package's single parse path. {@link Yaml.parse} is defined in
380
+ * terms of it (`Effect.fromResult` behind the named span), so the two
381
+ * variants cannot diverge. Reach for the `Effect` variant inside Effect
382
+ * code — it carries the `Yaml.parse` tracing span — and for this one at
383
+ * synchronous boundaries.
351
384
  *
352
385
  * Preserves the package contract — malformed and adversarial input fails
353
386
  * typed, never as a defect. Fatal diagnostics, duplicate keys and a
@@ -359,7 +392,7 @@ var Yaml = class Yaml {
359
392
  * import { Yaml } from "@effected/yaml";
360
393
  * import { Result } from "effect";
361
394
  *
362
- * const result = Yaml.parseSync("name: Alice\nage: 30");
395
+ * const result = Yaml.parseResult("name: Alice\nage: 30");
363
396
  * if (Result.isSuccess(result)) {
364
397
  * result.success; // { name: "Alice", age: 30 }
365
398
  * } else {
@@ -369,8 +402,8 @@ var Yaml = class Yaml {
369
402
  *
370
403
  * @public
371
404
  */
372
- static parseSync(text, options) {
373
- return parseSyncImpl(text, options);
405
+ static parseResult(text, options) {
406
+ return parseResultImpl(text, options);
374
407
  }
375
408
  /**
376
409
  * Synchronous stringify, returning a `Result` instead of an `Effect`.
@@ -387,7 +420,7 @@ var Yaml = class Yaml {
387
420
  * import { Yaml } from "@effected/yaml";
388
421
  * import { Result } from "effect";
389
422
  *
390
- * const result = Yaml.stringifySync({ name: "Alice" });
423
+ * const result = Yaml.stringifyResult({ name: "Alice" });
391
424
  * if (Result.isFailure(result)) {
392
425
  * result.failure; // YamlStringifyError
393
426
  * } else {
@@ -397,8 +430,8 @@ var Yaml = class Yaml {
397
430
  *
398
431
  * @public
399
432
  */
400
- static stringifySync(value, options) {
401
- return stringifySyncImpl(value, options);
433
+ static stringifyResult(value, options) {
434
+ return stringifyResultImpl(value, options);
402
435
  }
403
436
  /**
404
437
  * Strip comments from YAML text. Without `replaceCh`, comment characters
package/YamlDocument.js CHANGED
@@ -96,7 +96,7 @@ var YamlDocument = class YamlDocument extends Schema.Class("YamlDocument")({
96
96
  * `YamlStringifyOptions.lineWidth` is not honored here: column-based
97
97
  * scalar folding exists only on the value path, through the entry points
98
98
  * that accept stringify options ({@link Yaml.stringify} and
99
- * {@link Yaml.stringifySync}). The
99
+ * {@link Yaml.stringifyResult}). The
100
100
  * document/node path threads `lineWidth` into its render context but
101
101
  * never reads it, so long scalars are emitted unfolded regardless of the
102
102
  * option. Callers that need folding should render the plain value
package/YamlFormat.js CHANGED
@@ -11,7 +11,8 @@ import { Effect, Schema } from "effect";
11
11
  //#region src/YamlFormat.ts
12
12
  /**
13
13
  * Options controlling formatting behavior: every {@link YamlStringifyOptions}
14
- * field (derived, not hand-duplicated — including `indentSequences`) plus
14
+ * field (derived, not hand-duplicated — including `indentSequences` and
15
+ * `quoteStyle`) plus
15
16
  * `preserveComments` (default `true`) and `range` (restrict edits to a
16
17
  * region; see the module-level remarks on the `range` parameter vs. this
17
18
  * field).
@@ -78,6 +79,7 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
78
79
  defaultCollectionStyle: options.defaultCollectionStyle,
79
80
  sortKeys: options.sortKeys,
80
81
  indentSequences: options.indentSequences,
82
+ quoteStyle: options.quoteStyle,
81
83
  finalNewline: options.finalNewline,
82
84
  forceDefaultStyles: options.forceDefaultStyles
83
85
  };
@@ -251,6 +253,14 @@ var YamlFormat = class YamlFormat {
251
253
  * `options?.range` when both are given; either accepts a plain
252
254
  * `{ offset, length }` object as well as a {@link YamlRange} instance, so
253
255
  * callers do not need `YamlRange.make(...)` for the common case.
256
+ *
257
+ * A plain `<<` mapping key is preserved unquoted, keeping its merge-key
258
+ * meaning (`tag:yaml.org,2002:merge`) — quoting it to `'<<'` would produce
259
+ * an ordinary string key that merges nothing, changing what the document
260
+ * means with no error raised. A key the author quoted explicitly keeps its
261
+ * quotes, since that is a literal string key they wrote deliberately. Note
262
+ * that {@link Yaml.stringify} is deliberately the other way round for a
263
+ * `"<<"` key on a plain JavaScript object.
254
264
  */
255
265
  static format(text, range, options) {
256
266
  const formatted = formatDocument(text, options);
package/YamlNode.js CHANGED
@@ -20,6 +20,16 @@ const ScalarStyle = Schema.Literals([
20
20
  */
21
21
  const CollectionStyle = Schema.Literals(["block", "flow"]);
22
22
  /**
23
+ * Quote characters available to the stringifier's plain-scalar fallback: the
24
+ * style a `plain`-styled scalar is rendered in when it turns out to require
25
+ * quoting. Referenced by the `quoteStyle` field of `YamlStringifyOptions`;
26
+ * unlike `ScalarStyle` it is a stringify-option vocabulary, never a property
27
+ * of a composed node.
28
+ *
29
+ * @public
30
+ */
31
+ const QuoteStyle = Schema.Literals(["single", "double"]);
32
+ /**
23
33
  * Block-scalar chomping indicators (`-` strip, default clip, `+` keep).
24
34
  * Referenced by the {@link YamlScalar} `chomp` field schema.
25
35
  *
@@ -393,4 +403,4 @@ function nodeToValue(node, anchors, budget, counting = false) {
393
403
  }
394
404
 
395
405
  //#endregion
396
- export { AliasExpansionBudgetExceeded, CollectionStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq, aliasExpansionLimit, nodeToJsValue };
406
+ export { AliasExpansionBudgetExceeded, CollectionStyle, QuoteStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq, aliasExpansionLimit, nodeToJsValue };
package/index.d.ts CHANGED
@@ -151,7 +151,7 @@ declare const YamlStringifyOptions_base: Schema.Class<YamlStringifyOptions, Sche
151
151
  * (`>`) scalars at approximately that column, never block-literal (`|`).
152
152
  *
153
153
  * Takes effect only through {@link Yaml.stringify} and
154
- * {@link Yaml.stringifySync} — the two entry points that accept these
154
+ * {@link Yaml.stringifyResult} — the two entry points that accept these
155
155
  * options on the value path. The schema factories ({@link Yaml.fromString},
156
156
  * {@link Yaml.schema}, {@link Yaml.YamlFromString}) encode with default
157
157
  * stringify options (`lineWidth` `0`), so their output never folds. The
@@ -164,6 +164,17 @@ declare const YamlStringifyOptions_base: Schema.Class<YamlStringifyOptions, Sche
164
164
  readonly defaultCollectionStyle: Schema.optionalKey<Schema.Literals<readonly ["block", "flow"]>>;
165
165
  readonly sortKeys: Schema.optionalKey<Schema.Boolean>;
166
166
  readonly indentSequences: Schema.optionalKey<Schema.Boolean>;
167
+ /**
168
+ * Quote style used when a `plain`-styled scalar requires quoting. Default
169
+ * `"single"` — the released byte-compatible behavior. `"double"` renders
170
+ * the same scalars double-quoted instead, matching the `yaml` npm
171
+ * package's `singleQuote: false` output.
172
+ *
173
+ * Affects only the plain fallback: scalars that need no quoting stay
174
+ * plain, and an explicit `defaultScalarStyle` of `"single-quoted"` or
175
+ * `"double-quoted"` is unaffected.
176
+ */
177
+ readonly quoteStyle: Schema.optionalKey<Schema.Literals<readonly ["single", "double"]>>;
167
178
  readonly finalNewline: Schema.optionalKey<Schema.Boolean>;
168
179
  readonly forceDefaultStyles: Schema.optionalKey<Schema.Boolean>;
169
180
  }>, {}>;
@@ -171,8 +182,8 @@ declare const YamlStringifyOptions_base: Schema.Class<YamlStringifyOptions, Sche
171
182
  * Options controlling stringify behavior. All fields are omissible; absent
172
183
  * fields resolve to `indent` `2`, `lineWidth` `0`, `defaultScalarStyle`
173
184
  * `"plain"`, `defaultCollectionStyle` `"block"`, `sortKeys` `false`,
174
- * `indentSequences` `false`, `finalNewline` `true` and `forceDefaultStyles`
175
- * `false`.
185
+ * `indentSequences` `false`, `quoteStyle` `"single"`, `finalNewline` `true`
186
+ * and `forceDefaultStyles` `false`.
176
187
  *
177
188
  * `lineWidth` controls column-based scalar folding. The default `0` (and any
178
189
  * value `<= 0`) never wraps, emitting byte-identical output to the historic
@@ -189,6 +200,18 @@ declare const YamlStringifyOptions_base: Schema.Class<YamlStringifyOptions, Sche
189
200
  * matching the `yaml` npm package's default output. Top-level sequences stay
190
201
  * at column zero in both modes.
191
202
  *
203
+ * `quoteStyle` selects the quote character used when a `plain`-styled scalar
204
+ * (the `defaultScalarStyle` default) turns out to require quoting: `"single"`
205
+ * (the default) emits `'@parcel/watcher'` — the kit's byte-compatible legacy
206
+ * form — while `"double"` emits `"@parcel/watcher"`, matching the `yaml` npm
207
+ * package's `singleQuote: false` output. It is a fallback selector only:
208
+ * scalars that need no quoting stay plain, and an explicit
209
+ * `defaultScalarStyle` of `"single-quoted"` or `"double-quoted"` still wins.
210
+ * On that plain fallback path, values carrying a tab, a carriage return or
211
+ * any other C0 control character are always emitted double-quoted whichever
212
+ * `quoteStyle` is set, since only double quotes can escape them into a form
213
+ * that round-trips exactly.
214
+ *
192
215
  * Construct with the validated `YamlStringifyOptions.make({ ... })` static —
193
216
  * the kit convention (never `new`). Call sites that take a
194
217
  * `YamlStringifyOptions` also accept a structurally-matching plain literal.
@@ -289,6 +312,9 @@ declare class Yaml {
289
312
  * resolved size grows exponentially relative to `maxAliasCount`) also
290
313
  * fails through {@link YamlParseError} with an `AliasCountExceeded`
291
314
  * diagnostic, never as an unhandled defect.
315
+ *
316
+ * Defined in terms of {@link Yaml.parseResult} — synchronous callers can
317
+ * use that variant directly.
292
318
  */
293
319
  static readonly parse: (text: string, options?: YamlParseOptions | undefined) => Effect.Effect<unknown, YamlParseError, never>;
294
320
  /**
@@ -308,13 +334,28 @@ declare class Yaml {
308
334
  * or on a value nested deeper than the stringifier's recursion budget
309
335
  * (`NestingDepthExceeded`) — both surface through the typed error channel
310
336
  * rather than as an unhandled stack-overflow defect.
337
+ *
338
+ * @remarks
339
+ * A `"<<"` object key is emitted **quoted** (`'<<': …`). This is the
340
+ * opposite of the document path ({@link YamlFormat.format} and
341
+ * `YamlDocument#stringify`), which leaves a parsed plain `<<` key unquoted
342
+ * so it keeps its merge-key meaning, and the asymmetry is deliberate: a
343
+ * `"<<"` key on a plain JavaScript object is an ordinary string key that
344
+ * never carried merge semantics, so emitting it plain would silently turn
345
+ * ordinary data into a merge directive.
311
346
  */
312
347
  static readonly stringify: (value: unknown, options?: YamlStringifyOptions | undefined) => Effect.Effect<string, YamlStringifyError, never>;
313
348
  /**
314
349
  * Synchronous single-document parse, returning a `Result` instead of
315
350
  * an `Effect`. A pure escape hatch for config-time callers that cannot
316
- * `await` an Effect (a `vitest.config.ts` is the motivating case): it runs
317
- * the same engine as {@link Yaml.parse} inline.
351
+ * `await` an Effect (a `vitest.config.ts` is the motivating case).
352
+ *
353
+ * @remarks
354
+ * This is the package's single parse path. {@link Yaml.parse} is defined in
355
+ * terms of it (`Effect.fromResult` behind the named span), so the two
356
+ * variants cannot diverge. Reach for the `Effect` variant inside Effect
357
+ * code — it carries the `Yaml.parse` tracing span — and for this one at
358
+ * synchronous boundaries.
318
359
  *
319
360
  * Preserves the package contract — malformed and adversarial input fails
320
361
  * typed, never as a defect. Fatal diagnostics, duplicate keys and a
@@ -326,7 +367,7 @@ declare class Yaml {
326
367
  * import { Yaml } from "@effected/yaml";
327
368
  * import { Result } from "effect";
328
369
  *
329
- * const result = Yaml.parseSync("name: Alice\nage: 30");
370
+ * const result = Yaml.parseResult("name: Alice\nage: 30");
330
371
  * if (Result.isSuccess(result)) {
331
372
  * result.success; // { name: "Alice", age: 30 }
332
373
  * } else {
@@ -336,7 +377,7 @@ declare class Yaml {
336
377
  *
337
378
  * @public
338
379
  */
339
- static parseSync(text: string, options?: YamlParseOptions): Result.Result<unknown, YamlParseError>;
380
+ static parseResult(text: string, options?: YamlParseOptions): Result.Result<unknown, YamlParseError>;
340
381
  /**
341
382
  * Synchronous stringify, returning a `Result` instead of an `Effect`.
342
383
  * The pure counterpart to {@link Yaml.stringify} for config-time callers
@@ -352,7 +393,7 @@ declare class Yaml {
352
393
  * import { Yaml } from "@effected/yaml";
353
394
  * import { Result } from "effect";
354
395
  *
355
- * const result = Yaml.stringifySync({ name: "Alice" });
396
+ * const result = Yaml.stringifyResult({ name: "Alice" });
356
397
  * if (Result.isFailure(result)) {
357
398
  * result.failure; // YamlStringifyError
358
399
  * } else {
@@ -362,7 +403,7 @@ declare class Yaml {
362
403
  *
363
404
  * @public
364
405
  */
365
- static stringifySync(value: unknown, options?: YamlStringifyOptions): Result.Result<string, YamlStringifyError>;
406
+ static stringifyResult(value: unknown, options?: YamlStringifyOptions): Result.Result<string, YamlStringifyError>;
366
407
  /**
367
408
  * Strip comments from YAML text. Without `replaceCh`, comment characters
368
409
  * are removed (line breaks are kept, so line numbers stay stable); with a
@@ -538,6 +579,22 @@ declare const CollectionStyle: Schema.Literals<readonly ["block", "flow"]>;
538
579
  * @public
539
580
  */
540
581
  type CollectionStyle = typeof CollectionStyle.Type;
582
+ /**
583
+ * Quote characters available to the stringifier's plain-scalar fallback: the
584
+ * style a `plain`-styled scalar is rendered in when it turns out to require
585
+ * quoting. Referenced by the `quoteStyle` field of `YamlStringifyOptions`;
586
+ * unlike `ScalarStyle` it is a stringify-option vocabulary, never a property
587
+ * of a composed node.
588
+ *
589
+ * @public
590
+ */
591
+ declare const QuoteStyle: Schema.Literals<readonly ["single", "double"]>;
592
+ /**
593
+ * The union of all fallback quote style string literals.
594
+ *
595
+ * @public
596
+ */
597
+ type QuoteStyle = typeof QuoteStyle.Type;
541
598
  /**
542
599
  * Block-scalar chomping indicators (`-` strip, default clip, `+` keep).
543
600
  * Referenced by the {@link YamlScalar} `chomp` field schema.
@@ -791,7 +848,7 @@ declare class YamlDocument extends YamlDocument_base {
791
848
  * `YamlStringifyOptions.lineWidth` is not honored here: column-based
792
849
  * scalar folding exists only on the value path, through the entry points
793
850
  * that accept stringify options ({@link Yaml.stringify} and
794
- * {@link Yaml.stringifySync}). The
851
+ * {@link Yaml.stringifyResult}). The
795
852
  * document/node path threads `lineWidth` into its render context but
796
853
  * never reads it, so long scalars are emitted unfolded regardless of the
797
854
  * option. Callers that need folding should render the plain value
@@ -826,6 +883,7 @@ declare const YamlFormattingOptions_base: Schema.Class<YamlFormattingOptions, Sc
826
883
  readonly defaultCollectionStyle: Schema.optionalKey<Schema.Literals<readonly ["block", "flow"]>>;
827
884
  readonly sortKeys: Schema.optionalKey<Schema.Boolean>;
828
885
  readonly indentSequences: Schema.optionalKey<Schema.Boolean>;
886
+ readonly quoteStyle: Schema.optionalKey<Schema.Literals<readonly ["single", "double"]>>;
829
887
  readonly finalNewline: Schema.optionalKey<Schema.Boolean>;
830
888
  readonly forceDefaultStyles: Schema.optionalKey<Schema.Boolean>;
831
889
  readonly preserveComments: Schema.optionalKey<Schema.Boolean>;
@@ -833,7 +891,8 @@ declare const YamlFormattingOptions_base: Schema.Class<YamlFormattingOptions, Sc
833
891
  }>, {}>;
834
892
  /**
835
893
  * Options controlling formatting behavior: every {@link YamlStringifyOptions}
836
- * field (derived, not hand-duplicated — including `indentSequences`) plus
894
+ * field (derived, not hand-duplicated — including `indentSequences` and
895
+ * `quoteStyle`) plus
837
896
  * `preserveComments` (default `true`) and `range` (restrict edits to a
838
897
  * region; see the module-level remarks on the `range` parameter vs. this
839
898
  * field).
@@ -898,6 +957,14 @@ declare class YamlFormat {
898
957
  * `options?.range` when both are given; either accepts a plain
899
958
  * `{ offset, length }` object as well as a {@link YamlRange} instance, so
900
959
  * callers do not need `YamlRange.make(...)` for the common case.
960
+ *
961
+ * A plain `<<` mapping key is preserved unquoted, keeping its merge-key
962
+ * meaning (`tag:yaml.org,2002:merge`) — quoting it to `'<<'` would produce
963
+ * an ordinary string key that merges nothing, changing what the document
964
+ * means with no error raised. A key the author quoted explicitly keeps its
965
+ * quotes, since that is a literal string key they wrote deliberately. Note
966
+ * that {@link Yaml.stringify} is deliberately the other way round for a
967
+ * `"<<"` key on a plain JavaScript object.
901
968
  */
902
969
  static format(text: string, range?: YamlRangeLike, options?: YamlFormattingOptions): ReadonlyArray<YamlEdit>;
903
970
  /**
@@ -1521,5 +1588,5 @@ declare class YamlVisitor {
1521
1588
  static visit(text: string, options?: YamlParseOptions): Stream.Stream<YamlVisitorEvent>;
1522
1589
  }
1523
1590
  //#endregion
1524
- export { CollectionStyle, ScalarChomp, ScalarStyle, Yaml, YamlAlias, type YamlBoundCodec, YamlComposerErrorCode, YamlDiagnostic, YamlDirective, YamlDocument, YamlEdit, YamlErrorCode, YamlFormat, YamlFormattingOptions, YamlLexErrorCode, YamlMap, YamlModificationError, YamlModifyErrorCode, YamlNode, YamlPair, YamlParseError, YamlParseErrorCode, YamlParseOptions, type YamlPath, YamlRange, type YamlRangeLike, YamlScalar, type YamlSegment, YamlSeq, YamlStringifyError, YamlStringifyErrorCode, YamlStringifyOptions, YamlVisitor, YamlVisitorEvent };
1591
+ export { CollectionStyle, QuoteStyle, ScalarChomp, ScalarStyle, Yaml, YamlAlias, type YamlBoundCodec, YamlComposerErrorCode, YamlDiagnostic, YamlDirective, YamlDocument, YamlEdit, YamlErrorCode, YamlFormat, YamlFormattingOptions, YamlLexErrorCode, YamlMap, YamlModificationError, YamlModifyErrorCode, YamlNode, YamlPair, YamlParseError, YamlParseErrorCode, YamlParseOptions, type YamlPath, YamlRange, type YamlRangeLike, YamlScalar, type YamlSegment, YamlSeq, YamlStringifyError, YamlStringifyErrorCode, YamlStringifyOptions, YamlVisitor, YamlVisitorEvent };
1525
1592
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CollectionStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq } from "./YamlNode.js";
1
+ import { CollectionStyle, QuoteStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq } from "./YamlNode.js";
2
2
  import { YamlComposerErrorCode, YamlDiagnostic, YamlErrorCode, YamlLexErrorCode, YamlModifyErrorCode, YamlParseErrorCode, YamlStringifyErrorCode } from "./YamlDiagnostic.js";
3
3
  import { Yaml, YamlParseError, YamlParseOptions, YamlStringifyError, YamlStringifyOptions } from "./Yaml.js";
4
4
  import { YamlDirective, YamlDocument } from "./YamlDocument.js";
@@ -6,4 +6,4 @@ import { YamlEdit, YamlRange } from "./YamlEdit.js";
6
6
  import { YamlFormat, YamlFormattingOptions, YamlModificationError } from "./YamlFormat.js";
7
7
  import { YamlVisitor, YamlVisitorEvent } from "./YamlVisitor.js";
8
8
 
9
- export { CollectionStyle, ScalarChomp, ScalarStyle, Yaml, YamlAlias, YamlComposerErrorCode, YamlDiagnostic, YamlDirective, YamlDocument, YamlEdit, YamlErrorCode, YamlFormat, YamlFormattingOptions, YamlLexErrorCode, YamlMap, YamlModificationError, YamlModifyErrorCode, YamlNode, YamlPair, YamlParseError, YamlParseErrorCode, YamlParseOptions, YamlRange, YamlScalar, YamlSeq, YamlStringifyError, YamlStringifyErrorCode, YamlStringifyOptions, YamlVisitor, YamlVisitorEvent };
9
+ export { CollectionStyle, QuoteStyle, ScalarChomp, ScalarStyle, Yaml, YamlAlias, YamlComposerErrorCode, YamlDiagnostic, YamlDirective, YamlDocument, YamlEdit, YamlErrorCode, YamlFormat, YamlFormattingOptions, YamlLexErrorCode, YamlMap, YamlModificationError, YamlModifyErrorCode, YamlNode, YamlPair, YamlParseError, YamlParseErrorCode, YamlParseOptions, YamlRange, YamlScalar, YamlSeq, YamlStringifyError, YamlStringifyErrorCode, YamlStringifyOptions, YamlVisitor, YamlVisitorEvent };
@@ -107,7 +107,10 @@ function requiresQuoting(s, ignoreType = false) {
107
107
  if (s.includes(" #") || s.includes(" #")) return true;
108
108
  const last = s[s.length - 1];
109
109
  if (last === " " || last === " ") return true;
110
- for (let i = 0; i < s.length; i++) if (isControlChar(s.charCodeAt(i))) return true;
110
+ for (let i = 0; i < s.length; i++) {
111
+ const code = s.charCodeAt(i);
112
+ if (code === 9 || code === 13 || isControlChar(code)) return true;
113
+ }
111
114
  return false;
112
115
  }
113
116
  /**
@@ -164,11 +167,13 @@ function renderSingleQuoted(s) {
164
167
  *
165
168
  * Multi-line strings are routed to block styles regardless of the requested
166
169
  * style (except double-quoted). For single-line strings, plain style delegates
167
- * to {@link requiresQuoting} and falls back to double-quoted when the value
168
- * would be ambiguous. Block literal and block folded styles are always
169
- * accepted for single-line strings even though the output is unusual.
170
+ * to {@link requiresQuoting} and falls back to `quoteStyle` (single-quoted by
171
+ * default, double-quoted when the caller asked for it) or to double-quoted
172
+ * regardless when the value needs YAML escapes single quotes cannot express.
173
+ * Block literal and block folded styles are always accepted for single-line
174
+ * strings even though the output is unusual.
170
175
  */
171
- function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition) {
176
+ function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition, quoteStyle = "single") {
172
177
  if (s.includes("\n")) {
173
178
  let hasControl = false;
174
179
  for (let i = 0; i < s.length; i++) {
@@ -202,7 +207,7 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
202
207
  if (requiresQuoting(s, ignoreType)) {
203
208
  if (s.includes(" ") || s.includes("\r")) return renderDoubleQuoted(s, canonical);
204
209
  for (let i = 0; i < s.length; i++) if (isControlChar(s.charCodeAt(i))) return renderDoubleQuoted(s, canonical);
205
- return renderSingleQuoted(s);
210
+ return quoteStyle === "double" ? renderDoubleQuoted(s, canonical) : renderSingleQuoted(s);
206
211
  }
207
212
  return s;
208
213
  case "single-quoted": return renderSingleQuoted(s);
@@ -257,6 +262,7 @@ function createContext(options) {
257
262
  defaultCollectionStyle: options?.defaultCollectionStyle ?? "block",
258
263
  sortKeys: options?.sortKeys ?? false,
259
264
  indentSequences: options?.indentSequences ?? false,
265
+ quoteStyle: options?.quoteStyle ?? "single",
260
266
  forceDefaultStyles: options?.forceDefaultStyles ?? false,
261
267
  seen: /* @__PURE__ */ new Set()
262
268
  };
@@ -276,7 +282,7 @@ function stringifyLines(value, ctx, depth, allowFold = true) {
276
282
  if (typeof value === "bigint") return [value.toString()];
277
283
  if (typeof value === "string") {
278
284
  const indentStr = " ".repeat(ctx.indent);
279
- const rendered = renderString(value, ctx.defaultScalarStyle, indentStr, false, ctx.forceDefaultStyles);
285
+ const rendered = renderString(value, ctx.defaultScalarStyle, indentStr, false, ctx.forceDefaultStyles, void 0, void 0, ctx.quoteStyle);
280
286
  return (allowFold && ctx.lineWidth > 0 ? foldRenderedScalar(rendered, indentStr, ctx.lineWidth) : rendered).split("\n");
281
287
  }
282
288
  if (Array.isArray(value)) {
@@ -341,10 +347,11 @@ function stringifyObjectLines(obj, ctx, depth) {
341
347
  const keys = Object.keys(obj);
342
348
  if (keys.length === 0) return ["{}"];
343
349
  if (ctx.sortKeys) keys.sort();
350
+ const renderPlainKey = (k) => renderString(k, "plain", "", false, false, void 0, void 0, ctx.quoteStyle);
344
351
  if (ctx.defaultCollectionStyle === "flow") return [`{${keys.map((k) => {
345
- return `${renderString(k, "plain", "")}: ${stringifyLines(obj[k], ctx, depth + 1, false).join(" ")}`;
352
+ return `${renderPlainKey(k)}: ${stringifyLines(obj[k], ctx, depth + 1, false).join(" ")}`;
346
353
  }).join(", ")}}`];
347
- const renderKey = (k) => k.includes("\n") ? renderDoubleQuoted(k, ctx.forceDefaultStyles) : renderString(k, "plain", "");
354
+ const renderKey = (k) => k.includes("\n") ? renderDoubleQuoted(k, ctx.forceDefaultStyles) : renderPlainKey(k);
348
355
  const pad = " ".repeat(ctx.indent);
349
356
  const lines = [];
350
357
  for (const k of keys) {
@@ -531,12 +538,45 @@ function stringifyScalarNodeLines(node, ctx) {
531
538
  else lines = ["null"];
532
539
  else if (typeof val === "boolean") lines = [val ? "true" : "false"];
533
540
  else if (typeof val === "number") lines = [node.raw !== void 0 ? node.raw : renderNumber(val)];
534
- else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition).split("\n");
541
+ else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition, ctx.quoteStyle).split("\n");
535
542
  else lines = [renderDoubleQuoted(String(val))];
536
543
  if (node.tag) lines[0] = `${node.tag} ${lines[0]}`;
537
544
  if (node.anchor) lines[0] = `&${node.anchor} ${lines[0]}`;
538
545
  return lines;
539
546
  }
547
+ /** The YAML merge key. */
548
+ const MERGE_KEY = "<<";
549
+ /**
550
+ * True when a mapping key must be emitted plain because quoting it would
551
+ * change what the document means.
552
+ *
553
+ * The merge key is the only scalar in this position where the plain and
554
+ * quoted forms resolve to different YAML types: a plain `<<` key resolves to
555
+ * `tag:yaml.org,2002:merge` and splices the aliased mapping into its parent,
556
+ * while `'<<'` is an ordinary string key that merges nothing. The difference
557
+ * is silent — the document still parses and still round-trips — so re-emitting
558
+ * a merge key quoted is a semantic rewrite of the kind the fidelity obligation
559
+ * forbids.
560
+ *
561
+ * Deliberately narrow. It fires only on a plain-styled scalar carrying no tag
562
+ * and no anchor, so an author's explicitly quoted `'<<'` (a literal string key
563
+ * they meant) and any tagged key keep the form they were written in. A key
564
+ * with no style at all counts as plain: an unstyled `<<` built by hand can
565
+ * only have been meant as a merge key, and preserving that meaning outranks
566
+ * normalizing its presentation.
567
+ */
568
+ function isPlainMergeKey(node) {
569
+ return node instanceof YamlScalar && node.value === MERGE_KEY && (node.style ?? "plain") === "plain" && node.tag === void 0 && node.anchor === void 0;
570
+ }
571
+ /**
572
+ * Renders a mapping key, honoring the keys that must stay plain before
573
+ * falling back to ordinary node rendering. Both the flow and the block
574
+ * mapping branches route through here so the two cannot disagree.
575
+ */
576
+ function stringifyMappingKeyLines(node, ctx, depth) {
577
+ if (isPlainMergeKey(node)) return [MERGE_KEY];
578
+ return stringifyNodeLines(node, ctx, depth);
579
+ }
540
580
  /**
541
581
  * Stringifies a YamlMap node into lines, using the node's collection style.
542
582
  */
@@ -556,7 +596,7 @@ function stringifyMapNodeLines(node, ctx, depth) {
556
596
  }
557
597
  if (style === "flow") {
558
598
  let line = `{${items.map((pair) => {
559
- return `${pair.key ? stringifyNodeLines(pair.key, ctx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null"}`;
599
+ return `${pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null"}`;
560
600
  }).join(", ")}}`;
561
601
  const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
562
602
  if (flowPrefix) line = `${flowPrefix} ${line}`;
@@ -603,7 +643,7 @@ function stringifyMapNodeLines(node, ctx, depth) {
603
643
  }
604
644
  let resolvedKeyStr;
605
645
  if (ctx.forceDefaultStyles && node.style === "flow" && node.sourceMultiline !== true && pair.key instanceof YamlScalar && (pair.key.style === "single-quoted" || pair.key.style === "double-quoted") && typeof pair.key.value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(pair.key.value)) resolvedKeyStr = pair.key.value;
606
- else resolvedKeyStr = pair.key ? stringifyNodeLines(pair.key, ctx, depth + 1).join(" ") : "null";
646
+ else resolvedKeyStr = pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null";
607
647
  const keyStr = resolvedKeyStr;
608
648
  const keyIsAnchoredOrTaggedEmpty = pair.key instanceof YamlScalar && pair.key.length === 0 && (pair.key.value === null || pair.key.value === void 0 || pair.key.value === "") && (pair.key.anchor !== void 0 || pair.key.tag !== void 0);
609
649
  const sep = pair.key instanceof YamlAlias || keyIsAnchoredOrTaggedEmpty ? " :" : ":";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/yaml",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "Zero-dependency YAML parsing, editing and formatting as Effect schemas.",
6
6
  "keywords": [
@@ -40,7 +40,7 @@
40
40
  "./package.json": "./package.json"
41
41
  },
42
42
  "peerDependencies": {
43
- "effect": "4.0.0-beta.99"
43
+ "effect": "4.0.0-beta.101"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=24.11.0"
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.58.11"
8
+ "packageVersion": "7.58.12"
9
9
  }
10
10
  ]
11
11
  }