@effected/toml 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.
- package/README.md +6 -4
- package/Toml.js +52 -2
- package/TomlNode.js +3 -2
- package/index.d.ts +68 -8
- package/internal/diagnostics.js +0 -2
- package/internal/parser.js +25 -29
- package/internal/scanner.js +31 -17
- package/package.json +5 -4
- package/tsdoc-metadata.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@effected/toml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
|
-
Zero-dependency TOML 1.
|
|
8
|
+
Zero-dependency TOML 1.1.0 parsing, editing and formatting expressed as Effect schemas and pure functions. Parse TOML into plain values or a byte-exact linear CST, compute comment-preserving edits, format, modify by path, walk a document as a `Stream`, and decode straight into a validated domain schema.
|
|
9
9
|
|
|
10
10
|
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
11
|
> development against a single pinned Effect v4 beta. Packages graduate to
|
|
@@ -23,7 +23,7 @@ Zero-dependency TOML 1.0.0 parsing, editing and formatting expressed as Effect s
|
|
|
23
23
|
|
|
24
24
|
TOML is the config format behind `Cargo.toml`, `pyproject.toml` and a large share of the Rust and Python toolchains — files that people maintain by hand and comment heavily. Most JavaScript TOML libraries round-trip through a plain-object model, which loses every comment and blank line the moment you write the file back out.
|
|
25
25
|
|
|
26
|
-
The engine here is written from scratch against the TOML 1.
|
|
26
|
+
The engine here is written from scratch against the TOML 1.1.0 spec, and it is the only format package in this repo that vendors no upstream code at all. Parsing accepts everything 1.1 added — newlines, comments and trailing commas inside inline tables, optional seconds in times, and the `\e` and `\xHH` string escapes — while `Toml.stringify` deliberately emits 1.0 spellings, which are valid 1.1, so documents this package writes stay readable by 1.0-only consumers. It parses into a lossless linear CST whose expression spans tile the source byte-exact, so `TomlDocument.parse(text).stringify()` reproduces the original text exactly for any valid document. There is no separate re-serialization path that can drift from the source. Edits are byte-minimal splices against that CST, so formatting and path-based modification preserve every comment and every byte you did not touch.
|
|
27
27
|
|
|
28
28
|
The value model is honest about TOML's types rather than flattening them into JavaScript's. Integers past ±(2^53 − 1) decode to `bigint` instead of silently losing precision, and TOML's four date-time types decode to four calendar-validated value classes instead of a `Date` that cannot represent a local time. TOML has no null, so `Toml.stringify` on a value containing `null` fails with a structured `UnsupportedValue` diagnostic naming the offending path rather than dropping the key. Every fallible entry point carries a typed error built from `TomlDiagnostic`, and nesting-depth guards on both the parse and the stringify side mean hostile input fails through that channel rather than as a stack overflow.
|
|
29
29
|
|
|
@@ -39,6 +39,8 @@ pnpm add @effected/toml effect
|
|
|
39
39
|
|
|
40
40
|
Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package itself adds no other runtime dependencies.
|
|
41
41
|
|
|
42
|
+
All `@effected/*` packages are ESM-only: the exports maps publish only `import` conditions, so `require()` — including tools that resolve in CJS mode — fails with Node's `ERR_PACKAGE_PATH_NOT_EXPORTED` rather than loading a CJS build that does not exist. Import from an ES module.
|
|
43
|
+
|
|
42
44
|
## Quick start
|
|
43
45
|
|
|
44
46
|
Compose your schema with `Toml.schema` to decode TOML straight into a validated domain value, or reach for the pre-bound `Toml.TomlFromString` codec when you just want the plain value:
|
|
@@ -133,7 +135,7 @@ Effect.runPromise(Effect.result(Toml.stringify({ a: null }))).then(console.log);
|
|
|
133
135
|
|
|
134
136
|
## Conformance
|
|
135
137
|
|
|
136
|
-
The engine runs the [toml-test](https://github.com/toml-lang/toml-test) 1.
|
|
138
|
+
The engine runs the [toml-test](https://github.com/toml-lang/toml-test) compliance corpus — its `files-toml-1.1.0` subset, 214 valid cases and 467 invalid ones — to a 100% pass rate with no skip list, and a differential property suite cross-checks it against an independent TOML parser. Both oracles are devDependency-only; neither reaches your runtime.
|
|
137
139
|
|
|
138
140
|
## License
|
|
139
141
|
|
package/Toml.js
CHANGED
|
@@ -11,6 +11,13 @@ import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effec
|
|
|
11
11
|
* Options controlling stringify behavior. The only knob is `newline` —
|
|
12
12
|
* omitted, it resolves to `"\n"`.
|
|
13
13
|
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Stringify deliberately emits only TOML 1.0.0 spellings — seconds always
|
|
16
|
+
* present in times, no `\e`/`\xHH` escapes, single-line inline tables — even
|
|
17
|
+
* though {@link Toml.parse} accepts the full TOML 1.1.0 grammar. Every 1.0
|
|
18
|
+
* document is valid 1.1, so this conservative-write/liberal-read asymmetry
|
|
19
|
+
* keeps emitted documents readable by 1.0-only consumers.
|
|
20
|
+
*
|
|
14
21
|
* @public
|
|
15
22
|
*/
|
|
16
23
|
var TomlStringifyOptions = class extends Schema.Class("TomlStringifyOptions")({ newline: Schema.optionalKey(Schema.Literals(["\n", "\r\n"])) }) {};
|
|
@@ -84,7 +91,7 @@ const stringifyOrFail = (value, options) => Effect.try({
|
|
|
84
91
|
* channels — including the hardening guards (nesting-depth caps on both
|
|
85
92
|
* sides, circular-reference detection on encode) that keep malformed or
|
|
86
93
|
* adversarial input on the typed channel instead of surfacing as an
|
|
87
|
-
* unhandled defect. `parse` takes no options: TOML 1.
|
|
94
|
+
* unhandled defect. `parse` takes no options: TOML 1.1.0 parsing has no
|
|
88
95
|
* knobs.
|
|
89
96
|
*
|
|
90
97
|
* @example
|
|
@@ -103,7 +110,7 @@ const stringifyOrFail = (value, options) => Effect.try({
|
|
|
103
110
|
var Toml = class Toml {
|
|
104
111
|
constructor() {}
|
|
105
112
|
/**
|
|
106
|
-
* Parse a TOML 1.
|
|
113
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value: tables and
|
|
107
114
|
* inline tables become plain objects (`__proto__` lands as an own data
|
|
108
115
|
* property), arrays become plain arrays, integers become `number` (or
|
|
109
116
|
* `bigint` past 2^53) and date-times become the four `TomlDateTime`
|
|
@@ -159,6 +166,49 @@ var Toml = class Toml {
|
|
|
159
166
|
static schema(target) {
|
|
160
167
|
return Toml.TomlFromString.pipe(Schema.decodeTo(target));
|
|
161
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Bind a target schema to the TOML codec once, yielding the composed
|
|
171
|
+
* schema plus pre-derived `decode`/`encode` directions — the
|
|
172
|
+
* {@link Toml.schema} composition without the generic `Schema` machinery
|
|
173
|
+
* at every use site. Binds the plain form only: TOML 1.1.0 parsing on
|
|
174
|
+
* decode, default stringify options on encode.
|
|
175
|
+
*
|
|
176
|
+
* Both directions fail with `Schema.SchemaError`, exactly as
|
|
177
|
+
* `Schema.decodeEffect`/`Schema.encodeEffect` over {@link Toml.schema}
|
|
178
|
+
* would; the target's decoding/encoding service requirements flow through.
|
|
179
|
+
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* Schema-producing: each call composes a fresh schema and derives both
|
|
182
|
+
* directions from it. Bind the result to a `const` — that single binding is
|
|
183
|
+
* the point.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* import { Toml } from "@effected/toml";
|
|
188
|
+
* import { Effect, Schema } from "effect";
|
|
189
|
+
*
|
|
190
|
+
* const Config = Schema.Struct({ name: Schema.String });
|
|
191
|
+
* const config = Toml.bind(Config);
|
|
192
|
+
*
|
|
193
|
+
* const program = Effect.gen(function* () {
|
|
194
|
+
* const value = yield* config.decode('name = "Alice"');
|
|
195
|
+
* const text = yield* config.encode(value);
|
|
196
|
+
* return [value, text] as const;
|
|
197
|
+
* });
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @param target - The domain schema decoded values must satisfy.
|
|
201
|
+
* @returns A {@link TomlBoundCodec} carrying the composed schema and its
|
|
202
|
+
* two pre-bound directions.
|
|
203
|
+
*/
|
|
204
|
+
static bind(target) {
|
|
205
|
+
const schema = Toml.schema(target);
|
|
206
|
+
return {
|
|
207
|
+
schema,
|
|
208
|
+
decode: Schema.decodeEffect(schema),
|
|
209
|
+
encode: Schema.encodeEffect(schema)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
162
212
|
};
|
|
163
213
|
|
|
164
214
|
//#endregion
|
package/TomlNode.js
CHANGED
|
@@ -98,7 +98,7 @@ var TomlDateTimeLiteral = class extends Schema.TaggedClass()("TomlDateTimeLitera
|
|
|
98
98
|
length: Schema.Number
|
|
99
99
|
}) {};
|
|
100
100
|
/**
|
|
101
|
-
* An array value node. Heterogeneous per TOML
|
|
101
|
+
* An array value node. Heterogeneous per TOML; may span multiple lines
|
|
102
102
|
* (the span covers brackets, inner newlines and inner comments).
|
|
103
103
|
*
|
|
104
104
|
* @public
|
|
@@ -121,7 +121,8 @@ var TomlInlineEntry = class extends Schema.TaggedClass()("TomlInlineEntry", {
|
|
|
121
121
|
length: Schema.Number
|
|
122
122
|
}) {};
|
|
123
123
|
/**
|
|
124
|
-
* An inline table value node (`{ k = v, ... }`).
|
|
124
|
+
* An inline table value node (`{ k = v, ... }`). May span multiple lines
|
|
125
|
+
* since TOML 1.1 (the span covers braces, inner newlines and inner comments).
|
|
125
126
|
*
|
|
126
127
|
* @public
|
|
127
128
|
*/
|
package/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ type TomlLexErrorCode = typeof TomlLexErrorCode.Type;
|
|
|
17
17
|
*
|
|
18
18
|
* @public
|
|
19
19
|
*/
|
|
20
|
-
declare const TomlParseErrorCode: Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
20
|
+
declare const TomlParseErrorCode: Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>;
|
|
21
21
|
/**
|
|
22
22
|
* The union of all parser-stage error code string literals.
|
|
23
23
|
*
|
|
@@ -54,7 +54,7 @@ type TomlStringifyErrorCode = typeof TomlStringifyErrorCode.Type;
|
|
|
54
54
|
*
|
|
55
55
|
* @public
|
|
56
56
|
*/
|
|
57
|
-
declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
57
|
+
declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>, Schema.Literals<readonly ["DuplicateKey", "TableRedefined", "ArrayOfTablesConflict", "DottedKeyConflict", "InlineTableExtended"]>, Schema.Literals<readonly ["CircularReference", "UnsupportedValue", "IntegerOutOfRange", "NestingDepthExceeded"]>]>;
|
|
58
58
|
/**
|
|
59
59
|
* The union of all TOML error code string literals.
|
|
60
60
|
*
|
|
@@ -62,7 +62,7 @@ declare const TomlErrorCode: Schema.Union<readonly [Schema.Literals<readonly ["I
|
|
|
62
62
|
*/
|
|
63
63
|
type TomlErrorCode = typeof TomlErrorCode.Type;
|
|
64
64
|
declare const TomlDiagnostic_base: Schema.Class<TomlDiagnostic, Schema.Struct<{
|
|
65
|
-
readonly code: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "
|
|
65
|
+
readonly code: Schema.Union<readonly [Schema.Literals<readonly ["InvalidUtf8", "UnterminatedString", "InvalidEscape", "InvalidUnicodeEscape", "ControlCharacterInString", "ControlCharacterInComment", "InvalidCharacter", "BareCarriageReturn"]>, Schema.Literals<readonly ["ExpectedKey", "ExpectedEquals", "ExpectedValue", "ExpectedNewline", "ExpectedTableHeaderClose", "UnterminatedArray", "UnterminatedInlineTable", "InvalidValue", "InvalidNumber", "IntegerOutOfRange", "InvalidDateTime", "NestingDepthExceeded"]>, Schema.Literals<readonly ["DuplicateKey", "TableRedefined", "ArrayOfTablesConflict", "DottedKeyConflict", "InlineTableExtended"]>, Schema.Literals<readonly ["CircularReference", "UnsupportedValue", "IntegerOutOfRange", "NestingDepthExceeded"]>]>;
|
|
66
66
|
readonly message: Schema.String;
|
|
67
67
|
readonly offset: Schema.Number;
|
|
68
68
|
readonly length: Schema.Number;
|
|
@@ -104,6 +104,13 @@ declare const TomlStringifyOptions_base: Schema.Class<TomlStringifyOptions, Sche
|
|
|
104
104
|
* Options controlling stringify behavior. The only knob is `newline` —
|
|
105
105
|
* omitted, it resolves to `"\n"`.
|
|
106
106
|
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* Stringify deliberately emits only TOML 1.0.0 spellings — seconds always
|
|
109
|
+
* present in times, no `\e`/`\xHH` escapes, single-line inline tables — even
|
|
110
|
+
* though {@link Toml.parse} accepts the full TOML 1.1.0 grammar. Every 1.0
|
|
111
|
+
* document is valid 1.1, so this conservative-write/liberal-read asymmetry
|
|
112
|
+
* keeps emitted documents readable by 1.0-only consumers.
|
|
113
|
+
*
|
|
107
114
|
* @public
|
|
108
115
|
*/
|
|
109
116
|
declare class TomlStringifyOptions extends TomlStringifyOptions_base {}
|
|
@@ -135,6 +142,22 @@ declare const TomlStringifyError_base: Schema.Class<TomlStringifyError, Schema.T
|
|
|
135
142
|
declare class TomlStringifyError extends TomlStringifyError_base {
|
|
136
143
|
get message(): string;
|
|
137
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* A domain codec pre-bound to its two directions, returned by
|
|
147
|
+
* {@link Toml.bind}: the composed `schema` (what {@link Toml.schema} returns)
|
|
148
|
+
* plus `decode` and `encode` functions derived from it once, so callers need
|
|
149
|
+
* no generic `Schema` machinery at the use site.
|
|
150
|
+
*
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
153
|
+
interface TomlBoundCodec<T, RD = never, RE = never> {
|
|
154
|
+
/** The composed codec decoding a TOML `string` straight into `T`. */
|
|
155
|
+
readonly schema: Schema.Codec<T, string, RD, RE>;
|
|
156
|
+
/** Decode TOML text into a validated `T`. */
|
|
157
|
+
readonly decode: (text: string) => Effect.Effect<T, Schema.SchemaError, RD>;
|
|
158
|
+
/** Encode a `T` back to canonical TOML text. */
|
|
159
|
+
readonly encode: (value: T) => Effect.Effect<string, Schema.SchemaError, RE>;
|
|
160
|
+
}
|
|
138
161
|
/**
|
|
139
162
|
* Static entry points for TOML parsing, stringification and the schema
|
|
140
163
|
* factories. Not instantiable.
|
|
@@ -144,7 +167,7 @@ declare class TomlStringifyError extends TomlStringifyError_base {
|
|
|
144
167
|
* channels — including the hardening guards (nesting-depth caps on both
|
|
145
168
|
* sides, circular-reference detection on encode) that keep malformed or
|
|
146
169
|
* adversarial input on the typed channel instead of surfacing as an
|
|
147
|
-
* unhandled defect. `parse` takes no options: TOML 1.
|
|
170
|
+
* unhandled defect. `parse` takes no options: TOML 1.1.0 parsing has no
|
|
148
171
|
* knobs.
|
|
149
172
|
*
|
|
150
173
|
* @example
|
|
@@ -163,7 +186,7 @@ declare class TomlStringifyError extends TomlStringifyError_base {
|
|
|
163
186
|
declare class Toml {
|
|
164
187
|
private constructor();
|
|
165
188
|
/**
|
|
166
|
-
* Parse a TOML 1.
|
|
189
|
+
* Parse a TOML 1.1.0 document into a plain JavaScript value: tables and
|
|
167
190
|
* inline tables become plain objects (`__proto__` lands as an own data
|
|
168
191
|
* property), arrays become plain arrays, integers become `number` (or
|
|
169
192
|
* `bigint` past 2^53) and date-times become the four `TomlDateTime`
|
|
@@ -208,6 +231,42 @@ declare class Toml {
|
|
|
208
231
|
* {@link Toml.fromString}).
|
|
209
232
|
*/
|
|
210
233
|
static schema<T, E, RD = never, RE = never>(target: Schema.Codec<T, E, RD, RE>): Schema.Codec<T, string, RD, RE>;
|
|
234
|
+
/**
|
|
235
|
+
* Bind a target schema to the TOML codec once, yielding the composed
|
|
236
|
+
* schema plus pre-derived `decode`/`encode` directions — the
|
|
237
|
+
* {@link Toml.schema} composition without the generic `Schema` machinery
|
|
238
|
+
* at every use site. Binds the plain form only: TOML 1.1.0 parsing on
|
|
239
|
+
* decode, default stringify options on encode.
|
|
240
|
+
*
|
|
241
|
+
* Both directions fail with `Schema.SchemaError`, exactly as
|
|
242
|
+
* `Schema.decodeEffect`/`Schema.encodeEffect` over {@link Toml.schema}
|
|
243
|
+
* would; the target's decoding/encoding service requirements flow through.
|
|
244
|
+
*
|
|
245
|
+
* @remarks
|
|
246
|
+
* Schema-producing: each call composes a fresh schema and derives both
|
|
247
|
+
* directions from it. Bind the result to a `const` — that single binding is
|
|
248
|
+
* the point.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* import { Toml } from "@effected/toml";
|
|
253
|
+
* import { Effect, Schema } from "effect";
|
|
254
|
+
*
|
|
255
|
+
* const Config = Schema.Struct({ name: Schema.String });
|
|
256
|
+
* const config = Toml.bind(Config);
|
|
257
|
+
*
|
|
258
|
+
* const program = Effect.gen(function* () {
|
|
259
|
+
* const value = yield* config.decode('name = "Alice"');
|
|
260
|
+
* const text = yield* config.encode(value);
|
|
261
|
+
* return [value, text] as const;
|
|
262
|
+
* });
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* @param target - The domain schema decoded values must satisfy.
|
|
266
|
+
* @returns A {@link TomlBoundCodec} carrying the composed schema and its
|
|
267
|
+
* two pre-bound directions.
|
|
268
|
+
*/
|
|
269
|
+
static bind<T, E, RD = never, RE = never>(target: Schema.Codec<T, E, RD, RE>): TomlBoundCodec<T, RD, RE>;
|
|
211
270
|
}
|
|
212
271
|
//#endregion
|
|
213
272
|
//#region src/TomlDateTime.d.ts
|
|
@@ -385,7 +444,7 @@ declare const TomlArray_base: Schema.Class<TomlArray, Schema.TaggedStruct<"TomlA
|
|
|
385
444
|
readonly length: Schema.Number;
|
|
386
445
|
}>, {}>;
|
|
387
446
|
/**
|
|
388
|
-
* An array value node. Heterogeneous per TOML
|
|
447
|
+
* An array value node. Heterogeneous per TOML; may span multiple lines
|
|
389
448
|
* (the span covers brackets, inner newlines and inner comments).
|
|
390
449
|
*
|
|
391
450
|
* @public
|
|
@@ -410,7 +469,8 @@ declare const TomlInlineTable_base: Schema.Class<TomlInlineTable, Schema.TaggedS
|
|
|
410
469
|
readonly length: Schema.Number;
|
|
411
470
|
}>, {}>;
|
|
412
471
|
/**
|
|
413
|
-
* An inline table value node (`{ k = v, ... }`).
|
|
472
|
+
* An inline table value node (`{ k = v, ... }`). May span multiple lines
|
|
473
|
+
* since TOML 1.1 (the span covers braces, inner newlines and inner comments).
|
|
414
474
|
*
|
|
415
475
|
* @public
|
|
416
476
|
*/
|
|
@@ -893,5 +953,5 @@ declare class TomlVisitor {
|
|
|
893
953
|
static visit(text: string): Stream.Stream<TomlVisitorEvent, TomlParseError>;
|
|
894
954
|
}
|
|
895
955
|
//#endregion
|
|
896
|
-
export { Toml, TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlDiagnostic, TomlDocument, TomlEdit, TomlErrorCode, TomlExpression, TomlFloat, TomlFormat, TomlFormattingOptions, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlLexErrorCode, TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlModificationError, TomlOffsetDateTime, TomlParseError, TomlParseErrorCode, type TomlPath, TomlRange, type TomlRangeLike, type TomlSegment, TomlSemanticErrorCode, TomlString, TomlStringStyle, TomlStringifyError, TomlStringifyErrorCode, TomlStringifyOptions, TomlTableHeader, TomlTrivia, TomlValueNode, TomlVisitor, TomlVisitorEvent };
|
|
956
|
+
export { Toml, TomlArray, TomlArrayTableHeader, TomlBoolean, type TomlBoundCodec, TomlDateTimeLiteral, TomlDiagnostic, TomlDocument, TomlEdit, TomlErrorCode, TomlExpression, TomlFloat, TomlFormat, TomlFormattingOptions, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlLexErrorCode, TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlModificationError, TomlOffsetDateTime, TomlParseError, TomlParseErrorCode, type TomlPath, TomlRange, type TomlRangeLike, type TomlSegment, TomlSemanticErrorCode, TomlString, TomlStringStyle, TomlStringifyError, TomlStringifyErrorCode, TomlStringifyOptions, TomlTableHeader, TomlTrivia, TomlValueNode, TomlVisitor, TomlVisitorEvent };
|
|
897
957
|
//# sourceMappingURL=index.d.ts.map
|
package/internal/diagnostics.js
CHANGED
package/internal/parser.js
CHANGED
|
@@ -105,8 +105,12 @@ const parseLineEnd = (source, pos) => {
|
|
|
105
105
|
end: after
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Skip whitespace, newlines and comments inside array brackets or inline-table
|
|
110
|
+
* braces — the grammar's `ws-comment-newline`, shared by both container
|
|
111
|
+
* parsers since TOML 1.1 let inline tables span lines.
|
|
112
|
+
*/
|
|
113
|
+
const skipValueGap = (source, pos) => {
|
|
110
114
|
let i = pos;
|
|
111
115
|
for (;;) {
|
|
112
116
|
i = scanWhitespace(source, i);
|
|
@@ -122,12 +126,6 @@ const skipArrayGap = (source, pos) => {
|
|
|
122
126
|
return i;
|
|
123
127
|
}
|
|
124
128
|
};
|
|
125
|
-
/** Guard the inline-table single-line rule and unterminated end-of-input. */
|
|
126
|
-
const checkInsideInlineTable = (source, pos, openPos) => {
|
|
127
|
-
if (pos >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, pos - openPos);
|
|
128
|
-
const code = source.charCodeAt(pos);
|
|
129
|
-
if (code === LF || code === CR) raise("NewlineInInlineTable", "inline tables must fit on a single line", pos, 1);
|
|
130
|
-
};
|
|
131
129
|
/** Whether a token that classified as `number` spells a float (never called on hex/oct/bin). */
|
|
132
130
|
const isFloatToken = (token) => !/^0[xob]/.test(token) && (/[.eE]/.test(token) || token.includes("inf") || token.includes("nan"));
|
|
133
131
|
/** An array value starting at `[`; `depth` is this array's own nesting count. */
|
|
@@ -136,7 +134,7 @@ const parseArray = (source, openPos, depth) => {
|
|
|
136
134
|
const items = [];
|
|
137
135
|
let i = openPos + 1;
|
|
138
136
|
for (;;) {
|
|
139
|
-
i =
|
|
137
|
+
i = skipValueGap(source, i);
|
|
140
138
|
if (i >= source.length) raise("UnterminatedArray", "array not closed before end of input", openPos, i - openPos);
|
|
141
139
|
if (source.charCodeAt(i) === RIGHT_BRACKET) {
|
|
142
140
|
i += 1;
|
|
@@ -144,7 +142,7 @@ const parseArray = (source, openPos, depth) => {
|
|
|
144
142
|
}
|
|
145
143
|
const item = parseValue(source, i, depth);
|
|
146
144
|
items.push(item.node);
|
|
147
|
-
i =
|
|
145
|
+
i = skipValueGap(source, item.end);
|
|
148
146
|
const code = source.charCodeAt(i);
|
|
149
147
|
if (code === COMMA) {
|
|
150
148
|
i += 1;
|
|
@@ -166,28 +164,29 @@ const parseArray = (source, openPos, depth) => {
|
|
|
166
164
|
end: i
|
|
167
165
|
};
|
|
168
166
|
};
|
|
169
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* An inline table starting at `{`; `depth` is this table's own nesting count.
|
|
169
|
+
* TOML 1.1 grammar: `ws-comment-newline` may sit before each key-value,
|
|
170
|
+
* before each separator and before the closing brace, and a trailing comma is
|
|
171
|
+
* allowed — but `keyval-sep` is still ws-only, so no newline may split a key
|
|
172
|
+
* from its `=` or the `=` from its value.
|
|
173
|
+
*/
|
|
170
174
|
const parseInlineTable = (source, openPos, depth) => {
|
|
171
175
|
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, openPos);
|
|
172
176
|
const entries = [];
|
|
173
|
-
let i =
|
|
174
|
-
checkInsideInlineTable(source, i, openPos);
|
|
175
|
-
if (source.charCodeAt(i) === RIGHT_BRACE) return {
|
|
176
|
-
node: new TomlInlineTable({
|
|
177
|
-
entries,
|
|
178
|
-
offset: openPos,
|
|
179
|
-
length: i + 1 - openPos
|
|
180
|
-
}),
|
|
181
|
-
end: i + 1
|
|
182
|
-
};
|
|
177
|
+
let i = openPos + 1;
|
|
183
178
|
for (;;) {
|
|
179
|
+
i = skipValueGap(source, i);
|
|
180
|
+
if (i >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, i - openPos);
|
|
181
|
+
if (source.charCodeAt(i) === RIGHT_BRACE) {
|
|
182
|
+
i += 1;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
184
185
|
const entryStart = i;
|
|
185
186
|
const keyPath = parseKeyPath(source, i);
|
|
186
187
|
i = scanWhitespace(source, keyPath.end);
|
|
187
|
-
checkInsideInlineTable(source, i, openPos);
|
|
188
188
|
if (source.charCodeAt(i) !== EQUALS) raise("ExpectedEquals", "expected = after key", i, 1);
|
|
189
189
|
i = scanWhitespace(source, i + 1);
|
|
190
|
-
checkInsideInlineTable(source, i, openPos);
|
|
191
190
|
const value = parseValue(source, i, depth);
|
|
192
191
|
entries.push(new TomlInlineEntry({
|
|
193
192
|
keyPath: keyPath.node,
|
|
@@ -195,20 +194,17 @@ const parseInlineTable = (source, openPos, depth) => {
|
|
|
195
194
|
offset: entryStart,
|
|
196
195
|
length: value.end - entryStart
|
|
197
196
|
}));
|
|
198
|
-
i =
|
|
199
|
-
checkInsideInlineTable(source, i, openPos);
|
|
197
|
+
i = skipValueGap(source, value.end);
|
|
200
198
|
const code = source.charCodeAt(i);
|
|
201
199
|
if (code === RIGHT_BRACE) {
|
|
202
200
|
i += 1;
|
|
203
201
|
break;
|
|
204
202
|
}
|
|
205
203
|
if (code === COMMA) {
|
|
206
|
-
|
|
207
|
-
i = scanWhitespace(source, i + 1);
|
|
208
|
-
checkInsideInlineTable(source, i, openPos);
|
|
209
|
-
if (source.charCodeAt(i) === RIGHT_BRACE) raise("TrailingCommaInInlineTable", "inline tables may not end with a trailing comma", commaPos, 1);
|
|
204
|
+
i += 1;
|
|
210
205
|
continue;
|
|
211
206
|
}
|
|
207
|
+
if (i >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, i - openPos);
|
|
212
208
|
raise("UnterminatedInlineTable", "expected , or } in inline table", i, 1);
|
|
213
209
|
}
|
|
214
210
|
return {
|
package/internal/scanner.js
CHANGED
|
@@ -17,6 +17,7 @@ const RIGHT_BRACKET = 93;
|
|
|
17
17
|
const RIGHT_BRACE = 125;
|
|
18
18
|
const LOWER_U = 117;
|
|
19
19
|
const UPPER_U = 85;
|
|
20
|
+
const LOWER_X = 120;
|
|
20
21
|
const BOM = 65279;
|
|
21
22
|
const raise = (code, message, offset, length) => {
|
|
22
23
|
throw new RawTomlError({
|
|
@@ -36,7 +37,7 @@ const isBareKeyChar = (code) => code >= 97 && code <= 122 || code >= 65 && code
|
|
|
36
37
|
/** The number of code units to skip for a single leading U+FEFF BOM. */
|
|
37
38
|
const skipBom = (source) => source.charCodeAt(0) === BOM ? 1 : 0;
|
|
38
39
|
/**
|
|
39
|
-
* Reject a document carrying U+FFFD REPLACEMENT CHARACTER. TOML
|
|
40
|
+
* Reject a document carrying U+FFFD REPLACEMENT CHARACTER. TOML requires
|
|
40
41
|
* a valid UTF-8 document, but this engine receives an already-decoded JS
|
|
41
42
|
* string — after Node's lossy utf8 decode, U+FFFD is the only surviving
|
|
42
43
|
* evidence of a malformed byte sequence. The grammar technically admits
|
|
@@ -106,20 +107,29 @@ const simpleEscape = (code) => {
|
|
|
106
107
|
case 110: return "\n";
|
|
107
108
|
case 102: return "\f";
|
|
108
109
|
case 114: return "\r";
|
|
110
|
+
case 101: return "\x1B";
|
|
109
111
|
case QUOTE: return "\"";
|
|
110
112
|
case BACKSLASH: return "\\";
|
|
111
113
|
default: return;
|
|
112
114
|
}
|
|
113
115
|
};
|
|
114
116
|
const HEX_DIGITS = /^[0-9A-Fa-f]+$/;
|
|
117
|
+
const ESCAPE_LETTERS = {
|
|
118
|
+
2: "x",
|
|
119
|
+
4: "u",
|
|
120
|
+
8: "U"
|
|
121
|
+
};
|
|
115
122
|
/**
|
|
116
|
-
* Decode a `\uXXXX` / `\UXXXXXXXX` escape at `backslash`. The code
|
|
117
|
-
* be a Unicode scalar value: surrogates and values above U+10FFFF
|
|
123
|
+
* Decode a `\xHH` / `\uXXXX` / `\UXXXXXXXX` escape at `backslash`. The code
|
|
124
|
+
* point must be a Unicode scalar value: surrogates and values above U+10FFFF
|
|
125
|
+
* are errors. `\xHH` (TOML 1.1) covers U+0000–U+00FF — the escape is a
|
|
126
|
+
* grammar-sibling of `basic-unescaped`, so the decoded character bypasses the
|
|
127
|
+
* control-character ban by design.
|
|
118
128
|
*/
|
|
119
129
|
const decodeUnicodeEscape = (source, backslash, width) => {
|
|
120
130
|
const start = backslash + 2;
|
|
121
131
|
const hex = source.slice(start, start + width);
|
|
122
|
-
if (hex.length < width || !HEX_DIGITS.test(hex)) return raise("InvalidUnicodeEscape", `\\${width
|
|
132
|
+
if (hex.length < width || !HEX_DIGITS.test(hex)) return raise("InvalidUnicodeEscape", `\\${ESCAPE_LETTERS[width]} escape requires ${width} hexadecimal digits`, backslash, width + 2);
|
|
123
133
|
const codePoint = Number.parseInt(hex, 16);
|
|
124
134
|
if (codePoint >= 55296 && codePoint <= 57343 || codePoint > 1114111) return raise("InvalidUnicodeEscape", `${codeLabel(codePoint)} is not a Unicode scalar value`, backslash, width + 2);
|
|
125
135
|
return {
|
|
@@ -127,6 +137,8 @@ const decodeUnicodeEscape = (source, backslash, width) => {
|
|
|
127
137
|
end: start + width
|
|
128
138
|
};
|
|
129
139
|
};
|
|
140
|
+
/** The escape width for a unicode-style escape letter, or undefined. */
|
|
141
|
+
const unicodeEscapeWidth = (code) => code === LOWER_X ? 2 : code === LOWER_U ? 4 : code === UPPER_U ? 8 : void 0;
|
|
130
142
|
/** Scan a single-line basic string starting at the opening `"`. */
|
|
131
143
|
const scanBasicString = (source, pos) => {
|
|
132
144
|
let out = "";
|
|
@@ -143,11 +155,12 @@ const scanBasicString = (source, pos) => {
|
|
|
143
155
|
out += source.slice(chunkStart, i);
|
|
144
156
|
const next = source.charCodeAt(i + 1);
|
|
145
157
|
const mapped = simpleEscape(next);
|
|
158
|
+
const width = unicodeEscapeWidth(next);
|
|
146
159
|
if (mapped !== void 0) {
|
|
147
160
|
out += mapped;
|
|
148
161
|
i += 2;
|
|
149
|
-
} else if (
|
|
150
|
-
const decoded = decodeUnicodeEscape(source, i,
|
|
162
|
+
} else if (width !== void 0) {
|
|
163
|
+
const decoded = decodeUnicodeEscape(source, i, width);
|
|
151
164
|
out += String.fromCodePoint(decoded.codePoint);
|
|
152
165
|
i = decoded.end;
|
|
153
166
|
} else return raise("InvalidEscape", `invalid escape sequence \\${source[i + 1] ?? ""}`, i, 2);
|
|
@@ -234,8 +247,9 @@ const scanMultilineBasicString = (source, pos) => {
|
|
|
234
247
|
chunkStart = i;
|
|
235
248
|
continue;
|
|
236
249
|
}
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
const width = unicodeEscapeWidth(next);
|
|
251
|
+
if (width !== void 0) {
|
|
252
|
+
const decoded = decodeUnicodeEscape(source, i, width);
|
|
239
253
|
out += String.fromCodePoint(decoded.codePoint);
|
|
240
254
|
i = decoded.end;
|
|
241
255
|
chunkStart = i;
|
|
@@ -319,10 +333,10 @@ const scanTokenSpan = (source, pos) => {
|
|
|
319
333
|
}
|
|
320
334
|
return i;
|
|
321
335
|
};
|
|
322
|
-
const OFFSET_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})
|
|
323
|
-
const LOCAL_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})
|
|
336
|
+
const OFFSET_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?([Zz]|[+-][0-9]{2}:[0-9]{2})$/;
|
|
337
|
+
const LOCAL_DATE_TIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?$/;
|
|
324
338
|
const LOCAL_DATE = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
|
|
325
|
-
const LOCAL_TIME = /^([0-9]{2}):([0-9]{2})
|
|
339
|
+
const LOCAL_TIME = /^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?$/;
|
|
326
340
|
/**
|
|
327
341
|
* Scan a non-string scalar value token. Stops at whitespace, newlines, `,`,
|
|
328
342
|
* `]`, `}` and `#` — with the G5 extension: a token that scanned as a full
|
|
@@ -409,13 +423,13 @@ const classifyValueToken = (token, offset) => {
|
|
|
409
423
|
const length = Math.max(token.length, 1);
|
|
410
424
|
let match = OFFSET_DATE_TIME.exec(token);
|
|
411
425
|
if (match !== null) {
|
|
412
|
-
const [, y = "", mo = "", d = "", h = "", mi = "", s
|
|
426
|
+
const [, y = "", mo = "", d = "", h = "", mi = "", s, fraction, offsetText = ""] = match;
|
|
413
427
|
const year = Number(y);
|
|
414
428
|
const month = Number(mo);
|
|
415
429
|
const day = Number(d);
|
|
416
430
|
const hour = Number(h);
|
|
417
431
|
const minute = Number(mi);
|
|
418
|
-
const second = Number(s);
|
|
432
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
419
433
|
validateDate(year, month, day, offset, length);
|
|
420
434
|
validateTime(hour, minute, second, offset, length);
|
|
421
435
|
const offsetMinutes = decodeOffsetMinutes(offsetText, offset, length);
|
|
@@ -432,13 +446,13 @@ const classifyValueToken = (token, offset) => {
|
|
|
432
446
|
}
|
|
433
447
|
match = LOCAL_DATE_TIME.exec(token);
|
|
434
448
|
if (match !== null) {
|
|
435
|
-
const [, y = "", mo = "", d = "", h = "", mi = "", s
|
|
449
|
+
const [, y = "", mo = "", d = "", h = "", mi = "", s, fraction] = match;
|
|
436
450
|
const year = Number(y);
|
|
437
451
|
const month = Number(mo);
|
|
438
452
|
const day = Number(d);
|
|
439
453
|
const hour = Number(h);
|
|
440
454
|
const minute = Number(mi);
|
|
441
|
-
const second = Number(s);
|
|
455
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
442
456
|
validateDate(year, month, day, offset, length);
|
|
443
457
|
validateTime(hour, minute, second, offset, length);
|
|
444
458
|
return new TomlLocalDateTime({
|
|
@@ -466,10 +480,10 @@ const classifyValueToken = (token, offset) => {
|
|
|
466
480
|
}
|
|
467
481
|
match = LOCAL_TIME.exec(token);
|
|
468
482
|
if (match !== null) {
|
|
469
|
-
const [, h = "", mi = "", s
|
|
483
|
+
const [, h = "", mi = "", s, fraction] = match;
|
|
470
484
|
const hour = Number(h);
|
|
471
485
|
const minute = Number(mi);
|
|
472
|
-
const second = Number(s);
|
|
486
|
+
const second = s === void 0 ? 0 : Number(s);
|
|
473
487
|
validateTime(hour, minute, second, offset, length);
|
|
474
488
|
return new TomlLocalTime({
|
|
475
489
|
hour,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/toml",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "TOML 1.
|
|
5
|
+
"description": "TOML 1.1.0 parsing, editing and formatting as Effect schemas: typed diagnostics, a lossless CST and first-class date-time values.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"toml",
|
|
8
8
|
"parser",
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./index.d.ts",
|
|
35
|
-
"import": "./index.js"
|
|
35
|
+
"import": "./index.js",
|
|
36
|
+
"default": "./index.js"
|
|
36
37
|
},
|
|
37
38
|
"./package.json": "./package.json"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
|
-
"effect": "4.0.0-beta.
|
|
41
|
+
"effect": "4.0.0-beta.99"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=24.11.0"
|