@effected/templates 0.1.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.
package/index.d.ts ADDED
@@ -0,0 +1,812 @@
1
+ import { Context, Data, Effect, FileSystem, Layer, Option, Result, Schema } from "effect";
2
+ //#region src/CommentStyle.d.ts
3
+ declare const CommentStyle_base: Schema.Class<CommentStyle, Schema.Struct<{
4
+ /** Opens the comment. Non-empty, single-line. */
5
+ readonly prefix: Schema.String;
6
+ /** Closes the comment, for wrapped styles. Omitted for line styles. */
7
+ readonly suffix: Schema.optionalKey<Schema.String>;
8
+ }>, {}>;
9
+ /**
10
+ * How a managed section's markers are commented out in a given file format.
11
+ *
12
+ * @remarks
13
+ * A **line** style carries only a `prefix` (`#`, `//`); a **wrapped** style
14
+ * carries a `suffix` as well (`<!--` … `-->`). The wrapped form is what makes
15
+ * managed sections representable in Markdown, HTML and XML, which the two-way
16
+ * literal union this replaces could not express.
17
+ *
18
+ * The preset set is a convenience, not a closed world: a format nobody
19
+ * anticipated is one `CommentStyle.make({ prefix: "%" })` away.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { CommentStyle } from "@effected/templates";
24
+ *
25
+ * CommentStyle.hash; // # …
26
+ * CommentStyle.html; // <!-- … -->
27
+ * CommentStyle.make({ prefix: "(*", suffix: "*)" }); // ML, no preset needed
28
+ * ```
29
+ *
30
+ * @public
31
+ */
32
+ declare class CommentStyle extends CommentStyle_base {
33
+ /** Shell, YAML, TOML, Python, Dockerfile, `.env`. */
34
+ static readonly hash: CommentStyle;
35
+ /** JavaScript, TypeScript, C, Go, Rust, JSONC. */
36
+ static readonly slash: CommentStyle;
37
+ /** INI, Lisp, assembly. */
38
+ static readonly semicolon: CommentStyle;
39
+ /** SQL, Lua, Haskell. */
40
+ static readonly dash: CommentStyle;
41
+ /** Markdown, HTML, XML — the wrapped style v3 could not represent. */
42
+ static readonly html: CommentStyle;
43
+ /** CSS, and the block form of every C-family language. */
44
+ static readonly block: CommentStyle;
45
+ /**
46
+ * Every preset, in the order a reader would expect: line styles first,
47
+ * then wrapped.
48
+ *
49
+ * @remarks
50
+ * A grouped static over variants of one concept that live in one module
51
+ * and reach nothing heavier than each other — nothing sits behind a preset
52
+ * but a two-field object, so this is not the namespace-object hazard.
53
+ */
54
+ static readonly presets: ReadonlyArray<CommentStyle>;
55
+ /**
56
+ * A stable string identity, for keying a plain `Map`.
57
+ *
58
+ * @remarks
59
+ * The `NUL` separator is not cosmetic: without it `{ prefix: "ab" }`
60
+ * and `{ prefix: "a", suffix: "b" }` would produce the same id and two
61
+ * genuinely different styles would collide in a lookup table. `NUL`
62
+ * cannot occur in a delimiter, which the single-line check guarantees is
63
+ * printable-ish, so the encoding is unambiguous.
64
+ */
65
+ get id(): string;
66
+ /** True when the style wraps its content, i.e. it carries a suffix. */
67
+ get isWrapped(): boolean;
68
+ }
69
+ //#endregion
70
+ //#region src/Section.d.ts
71
+ /**
72
+ * The name identifying a managed section, as it literally appears in the
73
+ * file's markers.
74
+ *
75
+ * @remarks
76
+ * Keys are **case-sensitive** and rendered verbatim: the key a consumer
77
+ * declares is the key the marker carries. A file already containing
78
+ * `SAVVY-LINT` markers is managed by declaring the key `"SAVVY-LINT"`.
79
+ *
80
+ * The character set is bounded so a key can always be read back out of a
81
+ * marker unambiguously — whitespace in particular would make the scanner's
82
+ * key capture ambiguous against the marker phrase that follows it.
83
+ *
84
+ * This is a checked string rather than a branded one **deliberately**: a
85
+ * brand would force every call site through a decode step to spell
86
+ * `SectionId.make({ key: "example-tool", … })`, and the validation a brand
87
+ * would carry is already enforced by the check at construction.
88
+ *
89
+ * @public
90
+ */
91
+ declare const SectionKey: Schema.String;
92
+ declare const SectionId_base: Schema.Class<SectionId, Schema.Struct<{
93
+ /** The section's name, exactly as it appears in the markers. */
94
+ readonly key: Schema.String;
95
+ /** How this section's markers are commented out. */
96
+ readonly commentStyle: typeof CommentStyle;
97
+ }>, {}>;
98
+ /**
99
+ * What identifies a managed section inside a document: its key and the
100
+ * comment style its markers are written in.
101
+ *
102
+ * @remarks
103
+ * `commentStyle` is **required, with no default**. A defaulted style is how a
104
+ * caller who forgets the argument writes `#` markers into a TypeScript file —
105
+ * a syntax error in the user's own source, produced silently by an omission.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { CommentStyle, SectionId } from "@effected/templates";
110
+ *
111
+ * const ToolSection = SectionId.make({
112
+ * key: "example-tool",
113
+ * commentStyle: CommentStyle.hash,
114
+ * });
115
+ *
116
+ * const block = ToolSection.section("echo hello");
117
+ * ```
118
+ *
119
+ * @public
120
+ */
121
+ declare class SectionId extends SectionId_base {
122
+ /** Pair this identity with the content a tool wants inside it. */
123
+ section(content: string): Section;
124
+ }
125
+ declare const Section_base: Schema.Class<Section, Schema.Struct<{
126
+ /** The section's name, exactly as it appears in the markers. */
127
+ readonly key: Schema.String;
128
+ /** How this section's markers are commented out. */
129
+ readonly commentStyle: typeof CommentStyle;
130
+ /** Everything between the markers, exclusive of the boundary line breaks. */
131
+ readonly content: Schema.String;
132
+ }>, {}>;
133
+ /**
134
+ * A managed section: an identity plus the content its owner wants between the
135
+ * markers.
136
+ *
137
+ * @remarks
138
+ * Equality is **structural and whitespace-significant**. The v3 model this
139
+ * replaces compared trimmed, whitespace-collapsed content, which silently
140
+ * swallowed any template change that altered only indentation — the change
141
+ * compared equal, reported `Unchanged`, and never reached the file. The one
142
+ * normalization this package applies is to line endings, and it happens at
143
+ * parse time rather than inside equality, so `Equal.equals` stays honest for
144
+ * a consumer comparing two sections directly.
145
+ *
146
+ * @public
147
+ */
148
+ declare class Section extends Section_base {
149
+ /** This section's identity, without its content. */
150
+ get id(): SectionId;
151
+ /** The same section carrying different content. */
152
+ withContent(content: string): Section;
153
+ }
154
+ declare const PlacedSection_base: Schema.Class<PlacedSection, Schema.Struct<{
155
+ /** The section, with line endings already normalized to `\n`. */
156
+ readonly section: typeof Section;
157
+ /** Offset of the begin marker's first character. */
158
+ readonly start: Schema.Number;
159
+ /** Offset one past the end marker's last character. */
160
+ readonly end: Schema.Number;
161
+ /** 1-based line of the begin marker. */
162
+ readonly line: Schema.Number;
163
+ }>, {}>;
164
+ /**
165
+ * A managed section as found in a document, carrying the span it occupies.
166
+ *
167
+ * @remarks
168
+ * `start` and `end` bound the **whole** block, from the first character of
169
+ * the begin marker to one past the last character of the end marker, so
170
+ * `text.slice(start, end)` is exactly the block as written. `line` is 1-based
171
+ * and points at the begin marker, which is what a diagnostic needs.
172
+ *
173
+ * @public
174
+ */
175
+ declare class PlacedSection extends PlacedSection_base {}
176
+ //#endregion
177
+ //#region src/SectionDialect.d.ts
178
+ /**
179
+ * The line ending a document uses.
180
+ *
181
+ * @public
182
+ */
183
+ type Eol = "\n" | "\r\n";
184
+ declare const SectionRenderError_base: Schema.Class<SectionRenderError, Schema.TaggedStruct<"SectionRenderError", {
185
+ /**
186
+ * `markerInContent` — the content carries a line the scanner would read as
187
+ * a marker, which would move the block boundary and let the next sync
188
+ * consume user text. `unknownCommentStyle` — the section's comment style
189
+ * is not in the dialect's set, so the block would be written into a
190
+ * document where the scanner could never find it again, growing a
191
+ * duplicate on every run. `duplicateDeclaration` — the same identity was
192
+ * declared twice in one call, so the caller stated two intentions for one
193
+ * block and any choice between them would be a guess; this is the
194
+ * caller-side twin of the document-side `duplicateSection`.
195
+ */
196
+ readonly reason: Schema.Literals<readonly ["markerInContent", "unknownCommentStyle", "duplicateDeclaration"]>;
197
+ /** The key of the section that could not be rendered. */
198
+ readonly key: Schema.String;
199
+ }>, import("effect/Cause").YieldableError>;
200
+ /**
201
+ * Raised when a section cannot be turned into marker-delimited text.
202
+ *
203
+ * @remarks
204
+ * Both reasons describe a document this package would be unable to read back
205
+ * correctly, so rendering refuses rather than writing something it cannot
206
+ * re-parse.
207
+ *
208
+ * @public
209
+ */
210
+ declare class SectionRenderError extends SectionRenderError_base {
211
+ get message(): string;
212
+ }
213
+ declare const SectionDialect_base: Schema.Class<SectionDialect, Schema.Struct<{
214
+ /**
215
+ * The phrase between the key and the closing rule.
216
+ *
217
+ * @remarks
218
+ * Letters, digits, spaces and underscores only. Dashes are excluded so a
219
+ * phrase can never contain the `---` rule and make a marker ambiguous
220
+ * against itself.
221
+ */
222
+ readonly phrase: Schema.String;
223
+ /** Which comment styles the document scanner recognizes. At least one. */
224
+ readonly styles: Schema.$Array<typeof CommentStyle>;
225
+ }>, {}>;
226
+ /**
227
+ * The marker vocabulary: what phrase delimits a managed section, and which
228
+ * comment styles a document is scanned for.
229
+ *
230
+ * @remarks
231
+ * `styles` exists because reconciliation must **recognize sections it does
232
+ * not own** — a foreign tool's block in the same file is preserved verbatim
233
+ * and must never be mistaken for prose. That set cannot be derived from the
234
+ * caller's own sections, because a foreign block's style may appear nowhere
235
+ * in them.
236
+ *
237
+ * One phrase per dialect is deliberate: two marker families in one document
238
+ * make parsing ambiguous for no benefit anyone has asked for.
239
+ *
240
+ * @public
241
+ */
242
+ declare class SectionDialect extends SectionDialect_base {
243
+ /**
244
+ * The zero-configuration dialect: the phrase `MANAGED SECTION` and every
245
+ * preset comment style.
246
+ */
247
+ static readonly default: SectionDialect;
248
+ /** True when a section written in this style can be scanned back. */
249
+ recognizes(style: CommentStyle): boolean;
250
+ /** The opening marker line for an identity, without a line break. */
251
+ beginMarker(id: SectionId): string;
252
+ /** The closing marker line for an identity, without a line break. */
253
+ endMarker(id: SectionId): string;
254
+ /**
255
+ * A section as marker-delimited text, or a typed refusal.
256
+ *
257
+ * @remarks
258
+ * Fails rather than producing a document it could not read back: see
259
+ * {@link SectionRenderError}. Content is emitted with `eol` throughout,
260
+ * so a section rendered into a CRLF document stays CRLF.
261
+ */
262
+ render(section: Section, eol?: Eol): Result.Result<string, SectionRenderError>;
263
+ /**
264
+ * True when `text` contains a line this dialect would read as a marker.
265
+ *
266
+ * @internal
267
+ */
268
+ containsMarker(text: string): boolean;
269
+ /**
270
+ * One compiled scanner per recognized comment style, memoized.
271
+ *
272
+ * @remarks
273
+ * Each pattern is anchored per line, bounds the key with an explicit
274
+ * character class, and carries no nested quantifier, so scanning is linear
275
+ * in document length. Every caller-supplied fragment — prefix, suffix,
276
+ * phrase — is regex-escaped before interpolation.
277
+ *
278
+ * @internal
279
+ */
280
+ matchers(): ReadonlyArray<{
281
+ readonly style: CommentStyle;
282
+ readonly regex: RegExp;
283
+ }>;
284
+ private marker;
285
+ }
286
+ //#endregion
287
+ //#region src/SectionOutcome.d.ts
288
+ /**
289
+ * What a sync did to one declared section.
290
+ *
291
+ * @remarks
292
+ * `Updated` carries **both sides** rather than a diff. The model this replaces
293
+ * computed a set difference over lines, which deduplicated, ignored order, and
294
+ * reported a pure line reordering as no change at all — a diff that could say
295
+ * "nothing changed" about changed content. Every consumer of that model read
296
+ * only the tag, so the honest primitive is the pair; a caller wanting a
297
+ * rendering diffs `before.content` against `after.content` with the diff
298
+ * library of its choice.
299
+ *
300
+ * @public
301
+ */
302
+ type SyncOutcome = Data.TaggedEnum<{
303
+ /** The section was not in the document and has been added. */
304
+ readonly Created: {
305
+ readonly section: Section;
306
+ };
307
+ /** The section was present with different content and has been rewritten. */
308
+ readonly Updated: {
309
+ readonly before: Section;
310
+ readonly after: Section;
311
+ };
312
+ /** The section was already exactly as declared. */
313
+ readonly Unchanged: {
314
+ readonly section: Section;
315
+ };
316
+ }>;
317
+ /**
318
+ * Constructors and matchers for `SyncOutcome` — e.g.
319
+ * `SyncOutcome.Created({ section })`, `SyncOutcome.$is("Updated")`.
320
+ *
321
+ * @public
322
+ */
323
+ declare const SyncOutcome: {
324
+ readonly $is: <Tag extends "Created" | "Unchanged" | "Updated">(tag: Tag) => (u: unknown) => u is Extract<{
325
+ readonly _tag: "Created";
326
+ readonly section: Section;
327
+ }, {
328
+ readonly _tag: Tag;
329
+ }> | Extract<{
330
+ readonly _tag: "Unchanged";
331
+ readonly section: Section;
332
+ }, {
333
+ readonly _tag: Tag;
334
+ }> | Extract<{
335
+ readonly _tag: "Updated";
336
+ readonly before: Section;
337
+ readonly after: Section;
338
+ }, {
339
+ readonly _tag: Tag;
340
+ }>;
341
+ readonly $match: {
342
+ <Cases extends {
343
+ readonly Created: (args: {
344
+ readonly _tag: "Created";
345
+ readonly section: Section;
346
+ }) => any;
347
+ readonly Unchanged: (args: {
348
+ readonly _tag: "Unchanged";
349
+ readonly section: Section;
350
+ }) => any;
351
+ readonly Updated: (args: {
352
+ readonly _tag: "Updated";
353
+ readonly before: Section;
354
+ readonly after: Section;
355
+ }) => any;
356
+ }>(cases: Cases): (value: {
357
+ readonly _tag: "Created";
358
+ readonly section: Section;
359
+ } | {
360
+ readonly _tag: "Unchanged";
361
+ readonly section: Section;
362
+ } | {
363
+ readonly _tag: "Updated";
364
+ readonly before: Section;
365
+ readonly after: Section;
366
+ }) => import("effect/Unify").Unify<ReturnType<Cases["Created" | "Unchanged" | "Updated"]>>;
367
+ <Cases extends {
368
+ readonly Created: (args: {
369
+ readonly _tag: "Created";
370
+ readonly section: Section;
371
+ }) => any;
372
+ readonly Unchanged: (args: {
373
+ readonly _tag: "Unchanged";
374
+ readonly section: Section;
375
+ }) => any;
376
+ readonly Updated: (args: {
377
+ readonly _tag: "Updated";
378
+ readonly before: Section;
379
+ readonly after: Section;
380
+ }) => any;
381
+ }>(value: {
382
+ readonly _tag: "Created";
383
+ readonly section: Section;
384
+ } | {
385
+ readonly _tag: "Unchanged";
386
+ readonly section: Section;
387
+ } | {
388
+ readonly _tag: "Updated";
389
+ readonly before: Section;
390
+ readonly after: Section;
391
+ }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Created" | "Unchanged" | "Updated"]>>;
392
+ };
393
+ readonly Created: Data.TaggedEnum.ConstructorFrom<{
394
+ readonly _tag: "Created";
395
+ readonly section: Section;
396
+ }, "_tag">;
397
+ readonly Unchanged: Data.TaggedEnum.ConstructorFrom<{
398
+ readonly _tag: "Unchanged";
399
+ readonly section: Section;
400
+ }, "_tag">;
401
+ readonly Updated: Data.TaggedEnum.ConstructorFrom<{
402
+ readonly _tag: "Updated";
403
+ readonly before: Section;
404
+ readonly after: Section;
405
+ }, "_tag">;
406
+ };
407
+ /**
408
+ * What a check found, without touching the document.
409
+ *
410
+ * @remarks
411
+ * Three flat variants rather than the `Found({ isUpToDate, diff })` +
412
+ * `NotFound` pair they replace: a boolean beside a diff that already encodes
413
+ * the same fact is two sources of truth for one question, and it forces every
414
+ * caller through a nested branch.
415
+ *
416
+ * @public
417
+ */
418
+ type CheckOutcome = Data.TaggedEnum<{
419
+ /** No section with this identity is in the document. */
420
+ readonly Absent: {
421
+ readonly id: SectionId;
422
+ };
423
+ /** The document already says exactly what was declared. */
424
+ readonly UpToDate: {
425
+ readonly section: Section;
426
+ };
427
+ /** The document has this section, with different content. */
428
+ readonly Drifted: {
429
+ readonly onDisk: Section;
430
+ readonly expected: Section;
431
+ };
432
+ }>;
433
+ /**
434
+ * Constructors and matchers for `CheckOutcome` — e.g.
435
+ * `CheckOutcome.Absent({ id })`, `CheckOutcome.$is("Drifted")`.
436
+ *
437
+ * @public
438
+ */
439
+ declare const CheckOutcome: {
440
+ readonly $is: <Tag extends "Absent" | "Drifted" | "UpToDate">(tag: Tag) => (u: unknown) => u is Extract<{
441
+ readonly _tag: "Absent";
442
+ readonly id: SectionId;
443
+ }, {
444
+ readonly _tag: Tag;
445
+ }> | Extract<{
446
+ readonly _tag: "Drifted";
447
+ readonly onDisk: Section;
448
+ readonly expected: Section;
449
+ }, {
450
+ readonly _tag: Tag;
451
+ }> | Extract<{
452
+ readonly _tag: "UpToDate";
453
+ readonly section: Section;
454
+ }, {
455
+ readonly _tag: Tag;
456
+ }>;
457
+ readonly $match: {
458
+ <Cases extends {
459
+ readonly Absent: (args: {
460
+ readonly _tag: "Absent";
461
+ readonly id: SectionId;
462
+ }) => any;
463
+ readonly Drifted: (args: {
464
+ readonly _tag: "Drifted";
465
+ readonly onDisk: Section;
466
+ readonly expected: Section;
467
+ }) => any;
468
+ readonly UpToDate: (args: {
469
+ readonly _tag: "UpToDate";
470
+ readonly section: Section;
471
+ }) => any;
472
+ }>(cases: Cases): (value: {
473
+ readonly _tag: "Absent";
474
+ readonly id: SectionId;
475
+ } | {
476
+ readonly _tag: "Drifted";
477
+ readonly onDisk: Section;
478
+ readonly expected: Section;
479
+ } | {
480
+ readonly _tag: "UpToDate";
481
+ readonly section: Section;
482
+ }) => import("effect/Unify").Unify<ReturnType<Cases["Absent" | "Drifted" | "UpToDate"]>>;
483
+ <Cases extends {
484
+ readonly Absent: (args: {
485
+ readonly _tag: "Absent";
486
+ readonly id: SectionId;
487
+ }) => any;
488
+ readonly Drifted: (args: {
489
+ readonly _tag: "Drifted";
490
+ readonly onDisk: Section;
491
+ readonly expected: Section;
492
+ }) => any;
493
+ readonly UpToDate: (args: {
494
+ readonly _tag: "UpToDate";
495
+ readonly section: Section;
496
+ }) => any;
497
+ }>(value: {
498
+ readonly _tag: "Absent";
499
+ readonly id: SectionId;
500
+ } | {
501
+ readonly _tag: "Drifted";
502
+ readonly onDisk: Section;
503
+ readonly expected: Section;
504
+ } | {
505
+ readonly _tag: "UpToDate";
506
+ readonly section: Section;
507
+ }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Absent" | "Drifted" | "UpToDate"]>>;
508
+ };
509
+ readonly Absent: Data.TaggedEnum.ConstructorFrom<{
510
+ readonly _tag: "Absent";
511
+ readonly id: SectionId;
512
+ }, "_tag">;
513
+ readonly Drifted: Data.TaggedEnum.ConstructorFrom<{
514
+ readonly _tag: "Drifted";
515
+ readonly onDisk: Section;
516
+ readonly expected: Section;
517
+ }, "_tag">;
518
+ readonly UpToDate: Data.TaggedEnum.ConstructorFrom<{
519
+ readonly _tag: "UpToDate";
520
+ readonly section: Section;
521
+ }, "_tag">;
522
+ };
523
+ //#endregion
524
+ //#region src/SectionDocument.d.ts
525
+ declare const SectionParseError_base: Schema.Class<SectionParseError, Schema.TaggedStruct<"SectionParseError", {
526
+ /** Which ambiguity was found. */
527
+ readonly reason: Schema.Literals<readonly ["unterminatedSection", "orphanedEnd", "overlappingSections", "duplicateSection"]>;
528
+ /** 1-based line of the offending marker. */
529
+ readonly line: Schema.Number;
530
+ /** The section key involved, when the failure names one. */
531
+ readonly key: Schema.optionalKey<Schema.String>;
532
+ /** The file the document came from. Absent for a document parsed from a string. */
533
+ readonly path: Schema.optionalKey<Schema.String>;
534
+ }>, import("effect/Cause").YieldableError>;
535
+ /**
536
+ * Raised when a document's managed-section structure cannot be read without
537
+ * guessing.
538
+ *
539
+ * @remarks
540
+ * Every reason names an ambiguity whose silent resolution corrupts a file.
541
+ * A skipped unterminated marker makes the next write append a **second** copy
542
+ * of the section; a duplicate means every sync updates the first copy while
543
+ * the stale second one lives on disk forever. Malformed input therefore fails
544
+ * through the typed channel with the line that identifies it, and the user
545
+ * fixes their file.
546
+ *
547
+ * @public
548
+ */
549
+ declare class SectionParseError extends SectionParseError_base {
550
+ get message(): string;
551
+ /**
552
+ * The same failure, attributed to a file.
553
+ *
554
+ * @remarks
555
+ * The pure core has no path to report; the service that read the file
556
+ * attaches one on the way out.
557
+ */
558
+ static at(path: string, error: SectionParseError): SectionParseError;
559
+ }
560
+ /**
561
+ * The result of reconciling a declared set of sections against a document.
562
+ *
563
+ * @remarks
564
+ * A plain interface rather than a `Schema.Class`: `outcomes` holds
565
+ * `SyncOutcome` values, which are a `Data.TaggedEnum` and therefore not
566
+ * a schema, so the class form would be schema-shaped in name only.
567
+ *
568
+ * @public
569
+ */
570
+ interface SectionReconciliation {
571
+ /** The document as it should now read. */
572
+ readonly text: string;
573
+ /** One outcome per declared section, in declared order. */
574
+ readonly outcomes: ReadonlyArray<SyncOutcome>;
575
+ /** Whether `text` differs from the document it was reconciled against. */
576
+ readonly changed: boolean;
577
+ }
578
+ declare const SectionDocument_base: Schema.Class<SectionDocument, Schema.Struct<{
579
+ /** The document's source text, exactly as parsed. */
580
+ readonly text: Schema.String;
581
+ /** The marker vocabulary this document was read with. */
582
+ readonly dialect: typeof SectionDialect;
583
+ /** Every managed section found, in document order. */
584
+ readonly sections: Schema.$Array<typeof PlacedSection>;
585
+ /** The document's dominant line ending. */
586
+ readonly eol: Schema.Literals<readonly ["\n", "\r\n"]>;
587
+ }>, {}>;
588
+ /**
589
+ * A parsed document: its text, the dialect it was read with, and every
590
+ * managed section found in it.
591
+ *
592
+ * @remarks
593
+ * This is the package's pure core. Parsing, inspection, reconciliation and
594
+ * removal are all string-to-string, so every interesting invariant —
595
+ * idempotency, text preservation, ordering normalization, line-ending
596
+ * handling, marker-injection refusal — is assertable from a string literal
597
+ * with no layer, no runtime and no filesystem.
598
+ *
599
+ * @example
600
+ * ```ts
601
+ * import { CommentStyle, SectionDocument, SectionId } from "@effected/templates";
602
+ * import { Result } from "effect";
603
+ *
604
+ * const doc = SectionDocument.parseResult(source);
605
+ * if (Result.isSuccess(doc)) {
606
+ * const id = SectionId.make({ key: "example-tool", commentStyle: CommentStyle.hash });
607
+ * const next = doc.success.reconcile([id.section("echo hello")]);
608
+ * }
609
+ * ```
610
+ *
611
+ * @public
612
+ */
613
+ declare class SectionDocument extends SectionDocument_base {
614
+ /**
615
+ * Parse a document. The synchronous primitive.
616
+ *
617
+ * @remarks
618
+ * Pure computation exposes the sync form as the primitive; {@link SectionDocument.parse}
619
+ * derives from this and adds only the tracing span, so the two cannot drift.
620
+ * Synchronous callers — a lint hook, a build plugin — use this directly and
621
+ * never build an Effect runtime.
622
+ */
623
+ static parseResult(text: string, dialect?: SectionDialect): Result.Result<SectionDocument, SectionParseError>;
624
+ /**
625
+ * Parse a document, in `Effect`.
626
+ *
627
+ * @remarks
628
+ * Defined in terms of {@link SectionDocument.parseResult} — synchronous
629
+ * callers can use that variant directly.
630
+ */
631
+ static readonly parse: (text: string, dialect?: SectionDialect | undefined) => Effect.Effect<SectionDocument, SectionParseError, never>;
632
+ /** The section with this identity, if the document has one. */
633
+ read(id: SectionId): Option.Option<Section>;
634
+ /** Whether the document carries a section with this identity. */
635
+ has(id: SectionId): boolean;
636
+ /**
637
+ * Compare a declared section against the document, changing nothing.
638
+ *
639
+ * @remarks
640
+ * Total: there is no way for a comparison to fail. Line endings are
641
+ * normalized on both sides, so a CRLF document does not report drift
642
+ * against LF content forever.
643
+ */
644
+ check(section: Section): CheckOutcome;
645
+ /**
646
+ * Fit a declared set of sections into this document.
647
+ *
648
+ * @remarks
649
+ * Declared sections come back **in declared order** — the ordering
650
+ * normalization is a guarantee consumers depend on, not a side effect.
651
+ * Text outside a managed span and sections this dialect does not own are
652
+ * preserved byte-for-byte. Nothing is rendered into the output until every
653
+ * declared section has rendered successfully, so a refusal leaves the
654
+ * document untouched.
655
+ */
656
+ reconcile(sections: ReadonlyArray<Section>): Result.Result<SectionReconciliation, SectionRenderError>;
657
+ /**
658
+ * The document with this section removed, or `none` if it was not there.
659
+ *
660
+ * @remarks
661
+ * The blank lines around the removed block collapse into a single
662
+ * separator, so repeated removals never accumulate gaps.
663
+ */
664
+ remove(id: SectionId): Option.Option<string>;
665
+ }
666
+ //#endregion
667
+ //#region src/ManagedSection.d.ts
668
+ declare const SectionFileError_base: Schema.Class<SectionFileError, Schema.TaggedStruct<"SectionFileError", {
669
+ /** The file the operation was against. */
670
+ readonly path: Schema.String;
671
+ /** Which half of the read-modify-write failed. */
672
+ readonly operation: Schema.Literals<readonly ["read", "write"]>;
673
+ /** The underlying failure, preserved structurally. */
674
+ readonly cause: Schema.Defect;
675
+ }>, import("effect/Cause").YieldableError>;
676
+ /**
677
+ * Raised when the file behind a managed-section operation could not be read or
678
+ * written.
679
+ *
680
+ * @remarks
681
+ * The underlying `PlatformError` is preserved structurally in `cause` rather
682
+ * than stringified. `operation` is what a bare platform error cannot tell a
683
+ * caller: these operations are read-modify-write, so "it failed" is ambiguous
684
+ * about whether the document was even seen.
685
+ *
686
+ * A **missing** file is not an error — it reads as a document with no
687
+ * sections, and a sync creates it.
688
+ *
689
+ * @public
690
+ */
691
+ declare class SectionFileError extends SectionFileError_base {
692
+ get message(): string;
693
+ }
694
+ /**
695
+ * The {@link ManagedSection} service shape.
696
+ *
697
+ * @remarks
698
+ * Exported so a consumer can type a function against the shape without naming
699
+ * the service class, and so the surface is a reviewable declaration rather
700
+ * than whatever the implementation happened to return.
701
+ *
702
+ * Every member is **data-first**: the path comes first and nothing is dual.
703
+ * The model this replaces made all seven members dual, which doubled the
704
+ * declared surface for a data-last form no call site used — and a dual member
705
+ * cannot be stubbed with a one-line override in {@link ManagedSection.layerTest},
706
+ * because the override has to satisfy both overloads.
707
+ *
708
+ * @public
709
+ */
710
+ interface ManagedSectionShape {
711
+ /** The section with this identity, if the file has one. */
712
+ readonly read: (path: string, id: SectionId) => Effect.Effect<Option.Option<Section>, SectionParseError | SectionFileError>;
713
+ /** Every managed section in the file, in document order. */
714
+ readonly readAll: (path: string) => Effect.Effect<ReadonlyArray<Section>, SectionParseError | SectionFileError>;
715
+ /** Whether the file carries a section with this identity. */
716
+ readonly isManaged: (path: string, id: SectionId) => Effect.Effect<boolean, SectionParseError | SectionFileError>;
717
+ /** Make one section say what it should, writing only if that changes the file. */
718
+ readonly sync: (path: string, section: Section) => Effect.Effect<SyncOutcome, SectionParseError | SectionRenderError | SectionFileError>;
719
+ /**
720
+ * Make a whole set of sections say what they should, in declared order.
721
+ *
722
+ * @remarks
723
+ * The declared order is written into the file — see
724
+ * {@link SectionDocument.reconcile}. Writes only if the text changes.
725
+ */
726
+ readonly syncAll: (path: string, sections: ReadonlyArray<Section>) => Effect.Effect<ReadonlyArray<SyncOutcome>, SectionParseError | SectionRenderError | SectionFileError>;
727
+ /** Compare one section against the file, changing nothing. */
728
+ readonly check: (path: string, section: Section) => Effect.Effect<CheckOutcome, SectionParseError | SectionFileError>;
729
+ /** Compare a set of sections against the file from a single read. */
730
+ readonly checkAll: (path: string, sections: ReadonlyArray<Section>) => Effect.Effect<ReadonlyArray<CheckOutcome>, SectionParseError | SectionFileError>;
731
+ /** Remove a section. `false` when it was not there. */
732
+ readonly remove: (path: string, id: SectionId) => Effect.Effect<boolean, SectionParseError | SectionFileError>;
733
+ }
734
+ /**
735
+ * How a `ManagedSection` layer reads and writes markers.
736
+ *
737
+ * @public
738
+ */
739
+ interface ManagedSectionOptions {
740
+ /** Defaults to {@link SectionDialect.default}. */
741
+ readonly dialect?: SectionDialect;
742
+ }
743
+ declare const ManagedSection_base: Context.ServiceClass<ManagedSection, "@effected/templates/ManagedSection", ManagedSectionShape>;
744
+ /**
745
+ * Managed sections in files: read, compare, sync and remove delimited blocks
746
+ * whose surrounding content belongs to the user.
747
+ *
748
+ * @remarks
749
+ * A thin shell over {@link SectionDocument}: every member reads the file, runs
750
+ * the pure core, and writes back only when the text actually changed. All the
751
+ * interesting behavior — and all the interesting tests — live in the pure core.
752
+ *
753
+ * `FileSystem` is required in `R` and discharged by the consumer's platform
754
+ * layer at the edge. No `Path` is required: paths are handed to `FileSystem`
755
+ * untouched.
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * import { CommentStyle, ManagedSection, SectionId } from "@effected/templates";
760
+ * import { Effect } from "effect";
761
+ *
762
+ * const Base = SectionId.make({ key: "base", commentStyle: CommentStyle.hash });
763
+ * const Tool = SectionId.make({ key: "tool", commentStyle: CommentStyle.hash });
764
+ *
765
+ * const program = Effect.gen(function* () {
766
+ * const sections = yield* ManagedSection;
767
+ * // Declared order is written into the file, so `base` precedes `tool`.
768
+ * return yield* sections.syncAll(".husky/pre-commit", [
769
+ * Base.section("preamble"),
770
+ * Tool.section("run-the-tool"),
771
+ * ]);
772
+ * });
773
+ * ```
774
+ *
775
+ * @public
776
+ */
777
+ declare class ManagedSection extends ManagedSection_base {
778
+ /**
779
+ * The default layer, reading and writing {@link SectionDialect.default}
780
+ * markers.
781
+ *
782
+ * @remarks
783
+ * A bound `const`, not a function, so it memoizes by reference.
784
+ */
785
+ static readonly layer: Layer.Layer<ManagedSection, never, FileSystem.FileSystem>;
786
+ /**
787
+ * A layer with a custom marker dialect.
788
+ *
789
+ * @remarks
790
+ * A parameterized layer factory mints a **fresh reference per call**, and
791
+ * layers memoize by reference — bind the result to a `const` and reuse it
792
+ * rather than calling `layerWith(...)` at each composition site.
793
+ */
794
+ static readonly layerWith: (options: ManagedSectionOptions) => Layer.Layer<ManagedSection, never, FileSystem.FileSystem>;
795
+ /**
796
+ * A test double. Members a suite does not stub **die when called**, rather
797
+ * than returning a plausible-looking answer that makes a wrong test pass.
798
+ */
799
+ static readonly makeTest: (overrides?: Partial<ManagedSectionShape>) => ManagedSectionShape;
800
+ /**
801
+ * The test layer: {@link ManagedSection.makeTest} behind `Layer.succeed`, so
802
+ * a suite provides only the members it exercises.
803
+ *
804
+ * @remarks
805
+ * A parameterized layer factory mints a fresh reference per call — bind it
806
+ * to a `const` rather than calling it at each composition site.
807
+ */
808
+ static readonly layerTest: (overrides?: Partial<ManagedSectionShape>) => Layer.Layer<ManagedSection>;
809
+ }
810
+ //#endregion
811
+ export { CheckOutcome, CommentStyle, type Eol, ManagedSection, type ManagedSectionOptions, type ManagedSectionShape, PlacedSection, Section, SectionDialect, SectionDocument, SectionFileError, SectionId, SectionKey, SectionParseError, type SectionReconciliation, SectionRenderError, SyncOutcome };
812
+ //# sourceMappingURL=index.d.ts.map