@game-infra/valibot-to-csharp 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 CHANGED
@@ -8,36 +8,35 @@ We author JSON schemas in TypeScript (in editors and web tooling) but consume th
8
8
 
9
9
  ## Install
10
10
 
11
- This package is private and consumed as source from a sibling `game-infra` checkout (see `docs/CONSUMING.md` in the repo root). Add it to your game repo as a linked dependency:
11
+ This one is published, unlike the rest of the repo's tooling: a game repo that generates C# from its own schemas needs the generator without checking out `game-infra`.
12
+
13
+ ```sh
14
+ pnpm add -D @game-infra/valibot-to-csharp
15
+ ```
16
+
17
+ A sibling checkout still works and is what you want while changing the generator itself (see `docs/CONSUMING.md` in the repo root):
12
18
 
13
19
  ```jsonc
14
20
  // pnpm
15
21
  "devDependencies": {
16
22
  "@game-infra/valibot-to-csharp": "link:../../../game-infra/packages/valibot-to-csharp"
17
23
  }
18
-
19
- // npm
20
- "devDependencies": {
21
- "@game-infra/valibot-to-csharp": "file:../../../game-infra/packages/valibot-to-csharp"
22
- }
23
24
  ```
24
25
 
25
26
  ## CLI
26
27
 
27
- There is no published dist, so run the CLI straight from the sibling checkout's source with a TS runner such as tsx. Plain `node src/cli.ts` does not work: Node's type stripping refuses to map the `.js` relative import specifiers this repo uses onto their `.ts` sources.
28
-
29
28
  ```sh
30
- npx tsx ../game-infra/packages/valibot-to-csharp/src/cli.ts \
29
+ npx valibot-to-csharp \
31
30
  --input ./schemas/events.ts \
32
31
  --input ./schemas/common.ts \
33
32
  --output ./Generated \
34
33
  --namespace MyGame.Events
35
34
  ```
36
35
 
37
- Alternatively, build once inside `game-infra` (`pnpm --filter @game-infra/valibot-to-csharp build`) and run the compiled entry point, which keeps its shebang:
36
+ From a sibling checkout there is no `dist`, so run the source with a TS runner such as tsx. Plain `node src/cli.ts` does not work: Node's type stripping refuses to map the `.js` relative import specifiers this repo uses onto their `.ts` sources.
38
37
 
39
38
  ```sh
40
- node ../game-infra/packages/valibot-to-csharp/dist/cli.js --input ./schemas/events.ts --output ./Generated --namespace MyGame.Events
39
+ npx tsx ../game-infra/packages/valibot-to-csharp/src/cli.ts --input ./schemas/events.ts --output ./Generated --namespace MyGame.Events
41
40
  ```
42
41
 
43
42
  Flags:
@@ -89,28 +88,152 @@ import { emitFileHeader, emitModule, parseFiles } from "@game-infra/valibot-to-c
89
88
 
90
89
  const module = parseFiles(["./schemas/events.ts"]);
91
90
  const emit = emitModule(module, { namespace: "MyGame.Events" });
92
- const csSource = emitFileHeader(module.notes) + emit.source;
91
+ // Both note channels: what the parser could not read, and what the emitter could not
92
+ // render faithfully. Each one is something the generated file no longer carries.
93
+ const csSource = emitFileHeader([...module.notes, ...emit.notes]) + emit.source;
93
94
  ```
94
95
 
95
96
  ## Supported valibot surface
96
97
 
97
- | valibot | C# |
98
- | ------------------------------------ | -------------------------------------------------------- |
99
- | `string()` | `string` |
100
- | `number()` | `double` |
101
- | `boolean()` | `bool` |
102
- | `unknown()` | `JsonElement` |
103
- | `array(X)` | `IReadOnlyList<X>` |
104
- | `record(string(), V)` | `IReadOnlyDictionary<string, V>` |
105
- | `object({...})` | `public sealed record` |
106
- | `optional(X)` / `nullable(X)` | Nullable field (`X?`) |
107
- | `literal('a')` | String literal (merged into enums/variant tags) |
108
- | `picklist(['a','b'])` | `[JsonConverter(typeof(JsonStringEnumConverter))] enum` |
109
- | `union([literal(...), ...])` | Enum |
110
- | `union([string(), array(string())])` | `StringOrStringList` helper (auto-emitted) |
111
- | `union([{type:'A',...}, ...])` | `[JsonPolymorphic] abstract record` with derived records |
112
- | `variant('type', [...])` | Same as discriminated union |
113
- | `pipe(inner, ...validators)` | `inner` (validators are ignored) |
98
+ | valibot | C# |
99
+ | ----------------------------------------------- | -------------------------------------------------------- |
100
+ | `string()` | `string` |
101
+ | `number()` | `double` |
102
+ | `pipe(number(), integer(), minValue, maxValue)` | `int` when both bounds fit `Int32`, otherwise `long` |
103
+ | `boolean()` | `bool` |
104
+ | `unknown()` | `JsonElement` |
105
+ | `array(X)` | `IReadOnlyList<X>` |
106
+ | `record(string(), V)` | `IReadOnlyDictionary<string, V>` |
107
+ | `object`, `strictObject` | `public sealed record` (closed) |
108
+ | `looseObject` | `public sealed record` + `[JsonExtensionData]` bag |
109
+ | `nullable(X)` | Nullable field (`X?`) |
110
+ | `optional(X)` | Nullable field, omitted when null |
111
+ | `literal('a')` | String literal (merged into enums/variant tags) |
112
+ | `literal(1)` / `literal(true)` | Constant `int`/`long`/`double` / `bool` field |
113
+ | `picklist(['a','b'])` | `[JsonConverter(typeof(JsonStringEnumConverter))] enum` |
114
+ | `union([literal(...), ...])` | Enum (string literals only) |
115
+ | `union([string(), array(string())])` | `StringOrStringList` helper (auto-emitted) |
116
+ | `union([{type:'A',...}, ...])` | `[JsonPolymorphic] abstract record` with derived records |
117
+ | `variant('type', [...])` | Same as discriminated union |
118
+ | `pipe(inner, ...validators)` | `inner`, plus the numeric refinements above |
119
+
120
+ ### Numbers
121
+
122
+ A bare `number()` is a `double`, which is the honest mapping and the wrong one
123
+ for anything a digest is taken over: `double` round-trips `1e+21`, and .NET and
124
+ JavaScript disagree on how to spell that. Pipe it through `integer()` and the
125
+ emitted type is integral. It narrows to `int` only when `minValue` and `maxValue`
126
+ both bound it inside `System.Int32` — JavaScript integers run to 2^53, so an
127
+ unbounded one held in an `int` would refuse every value past two billion, which
128
+ is a whole class of documents rejected rather than a rounding error.
129
+
130
+ A numeric `literal(n)` is bounded by definition — the value is both bounds — so it
131
+ takes its type by the same rule: `literal(1)` is an `int`, `literal(3000000000)` a
132
+ `long`, and `literal(1.5)` a `double`.
133
+
134
+ ### Absent versus null
135
+
136
+ `optional(X)` and `nullable(X)` are both `X?` in C#, and they are not the same
137
+ fact on the wire: the first says the key may be absent, the second says the value
138
+ may be `null`. Optional fields therefore carry
139
+ `[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`, so serialising
140
+ one with no value omits the key instead of writing a `null` that a schema
141
+ accepting only absence would refuse. Nullable fields keep writing `null`.
142
+
143
+ ### Unknown keys
144
+
145
+ `object()` ignores entries its shape does not declare and `strictObject()` rejects
146
+ them, so a closed C# record loses nothing. `looseObject()` keeps them, so its record
147
+ carries a `[JsonExtensionData]` bag:
148
+
149
+ ```csharp
150
+ public sealed record Envelope(
151
+ [property: JsonPropertyName("id")] string Id
152
+ )
153
+ {
154
+ [JsonExtensionData]
155
+ public Dictionary<string, JsonElement>? AdditionalProperties { get; set; }
156
+ }
157
+ ```
158
+
159
+ The bag is declared in the record body rather than as a positional parameter:
160
+ `System.Text.Json` refuses an extension-data property bound to a constructor
161
+ parameter, and every positional record parameter is one.
162
+
163
+ ### Which field is the discriminator
164
+
165
+ A `variant('op', [...])` states its discriminator, and the emitter takes it at its
166
+ word: a member is tagged by its `op` literal and by nothing else. Only a bare
167
+ `union([...])`, which declares nothing, infers one — a string literal field,
168
+ preferring one named `type`.
169
+
170
+ The difference shows up the moment members share an envelope that carries a string
171
+ literal of its own:
172
+
173
+ ```ts
174
+ const envelope = { type: literal("op"), at: string() };
175
+ export const MoveOpSchema = object({ ...envelope, op: literal("move") });
176
+ export const HaltOpSchema = object({ ...envelope, op: literal("halt") });
177
+ export const OpSchema = variant("op", [MoveOpSchema, HaltOpSchema]);
178
+ ```
179
+
180
+ Tagging by `type` would give both members the `[JsonDerivedType]` value `"op"`, which
181
+ `System.Text.Json` refuses when it builds the converter, and would leave the real `op`
182
+ key off the wire entirely. Two members that do end up claiming the same tag — an
183
+ ambiguity in the schema itself — are reported in the generated header, and only the
184
+ first is emitted.
185
+
186
+ ### Referring to one member of a variant
187
+
188
+ A field pointing at a single member (`last: optional(MoveOpSchema)`) is typed as the
189
+ polymorphic **base**, not as the member:
190
+
191
+ ```csharp
192
+ [property: JsonPropertyName("last")] Op? Last
193
+ ```
194
+
195
+ The discriminator is written by `JsonPolymorphic` on the base and by nothing else, so
196
+ a field declared as the derived record would serialise with no `op` key at all. The
197
+ base deserialises straight back into the member; the widening is noted in the
198
+ generated file's header.
199
+
200
+ ### Shared fields on a union's base
201
+
202
+ Fields that every member of a `variant`/discriminated union declares identically — same name, same
203
+ nullability, same shape — are hoisted onto the abstract base record, and each member passes them
204
+ through as a plain constructor parameter. A shared envelope is then readable off the base type
205
+ instead of through a switch over every member:
206
+
207
+ ```csharp
208
+ foreach (var e in events) Console.WriteLine(e.Seq); // declared once, on MatchEvent
209
+ ```
210
+
211
+ A field only some members have, or one whose shape differs between them, stays on the members that
212
+ have it.
213
+
214
+ ### Shared field groups
215
+
216
+ Fields common to several schemas can live in a plain object literal and be
217
+ spread into each one; the parser resolves the spread against module-level
218
+ literals in the same file, and a later field of the same name wins as it does in
219
+ JavaScript.
220
+
221
+ ```ts
222
+ const eventEnvelope = { seq: seqSchema, createdAt: string() };
223
+
224
+ export const JoinedSchema = strictObject({
225
+ ...eventEnvelope,
226
+ type: literal("joined"),
227
+ });
228
+ ```
229
+
230
+ A spread it cannot resolve — a value from another module, a call, a `.entries`
231
+ access — contributes nothing rather than failing, so keep groups local and literal.
232
+ Every unresolved spread is reported in the generated file's header, so a missing
233
+ envelope is visible in the artifact rather than only in the diff of the C# it
234
+ produced. A field an unreadable expression overrides is dropped and reported the same
235
+ way: keeping the group's version would type the property as the shared shape while
236
+ the schema says something else.
114
237
 
115
238
  Cross-file imports (relative paths only) are followed automatically so imported schemas land in the same output file. Imports from `node_modules` are not traversed.
116
239
 
@@ -118,13 +241,18 @@ Cross-file imports (relative paths only) are followed automatically so imported
118
241
 
119
242
  - Not evaluated: the generator parses the source and never executes it. Schemas built at runtime (e.g. produced from a function) are invisible to it.
120
243
  - String literal kludge: if you write `string('foo')` inside a `union(...)` (a shorthand some schemas use as a literal placeholder), the parser treats it as `literal('foo')` and notes the substitution in the generated file.
121
- - Unsupported features: `lazy`, `tuple`, `intersect`, `custom` transforms, and non-string record keys. Heterogeneous unions with no common discriminator fall back to `JsonElement`.
244
+ - Unsupported features: `lazy`, `tuple`, `intersect`, `custom` transforms, and non-string record keys. Heterogeneous unions with no common discriminator fall back to `JsonElement`; a union mixing a numeric literal with a numeric schema (`union([literal(0), pipe(number(), integer())])`) is one of those, so express such a range as a single piped number with a `check` instead.
245
+ - A discriminated union's base record keeps its tag as a plain `[JsonIgnore]` property: `System.Text.Json` writes the discriminator from `JsonPolymorphic`, and a second property claiming that JSON name is a hard failure on both serialize and deserialize.
246
+ - A property whose C# name would equal its enclosing type's gets a `Value` suffix (`variant('op', ...)` named `Op` emits `OpValue`), because C# refuses the collision.
247
+ - A named alias that declares no C# type of its own — `array(...)`, `record(...)`, a bare primitive, an `optional`/`nullable` wrapper, or another such alias — is inlined wherever it is referenced, wrappers and all. `export const NoteSchema = nullable(string())` used as a field is a `string?`, not a reference to a `Note` type nothing declares.
248
+ - Anything dropped on the way is reported in the generated file's header, from both note channels. A generated file that lost a field says so at the top of itself.
122
249
  - Nested anonymous objects are named `<ParentType><FieldName>` to avoid collisions with sibling inline types.
123
250
  - `DTO` becomes `Dto` in emitted type names (e.g. `EventDefinitionDTOSchema` becomes `EventDefinitionDto`).
124
251
 
125
252
  ## Extension points
126
253
 
127
254
  - New valibot features: add a node kind to `src/ir.ts`, parse it in `src/parser.ts`, and teach `src/emitter.ts` how to render it. The IR is a closed union, so the compiler points at every switch that needs a new case.
255
+ - The emitter's supporting rules live beside it: `src/csharp.ts` holds everything about how a value spells itself in C# (numeric widths, property names, the file header, the hand-written `StringOrStringList` source), and `src/variants.ts` holds union classification (which field tags a member, what every member has in common). Neither needs a module to answer, so both are plain functions.
128
256
  - Naming: `--union-name` (CLI) / `unionRenames` (API) override the C# base-type name of a union or variant schema without touching the TS source.
129
257
 
130
258
  ## Dependencies
@@ -133,4 +261,4 @@ Cross-file imports (relative paths only) are followed automatically so imported
133
261
 
134
262
  ## Consumers
135
263
 
136
- The `*-schemas` contract packages in this repo, and game repos that need C# mirrors of their JSON contracts for .NET clients.
264
+ The `*-schemas` contract packages in this repo, and game repos that need C# mirrors of their JSON contracts for .NET clients — among them _Chaos Overlords: New Chrome_, whose MonoGame client mirrors the multiplayer server's valibot wire contracts.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * How a schema value spells itself in C#: numeric widths, property names, the file header,
3
+ * and the source of the one hand-written helper type the emitter can reach for.
4
+ *
5
+ * Kept apart from the emitter because none of it needs a module to answer — given a node or
6
+ * a name, the answer is the same every time.
7
+ */
8
+ import type { PrimitiveNode } from "./ir.js";
9
+ /** Property name for the `[JsonExtensionData]` bag a `looseObject` record carries. */
10
+ export declare const EXTENSION_DATA_PROPERTY = "AdditionalProperties";
11
+ /**
12
+ * The C# type for a `number()`.
13
+ *
14
+ * A plain number is `double`. An `integer()` is integral, and narrows to `int`
15
+ * only when both declared bounds fit `System.Int32`: JavaScript integers run to
16
+ * 2^53, so an unbounded one deserialised into an `int` would reject every value
17
+ * above two billion — the failure mode is a whole class of documents refused,
18
+ * not a rounding error, so the wider type is the safe default.
19
+ */
20
+ export declare function numericCsType(node: PrimitiveNode): string;
21
+ /**
22
+ * The C# type of a `literal(...)` value, by the JSON kind it stands for.
23
+ *
24
+ * A numeric literal is a `number()` whose bounds are the value itself, so it goes through
25
+ * {@link numericCsType} rather than repeating the Int32 fit test: `literal(3000000000)` is
26
+ * a `long` for the same reason `pipe(number(), integer(), minValue(3000000000))` is, and
27
+ * spelling it `double` would put a `3E+09` where the digest expects `3000000000`.
28
+ */
29
+ export declare function literalCsType(value: string | number | boolean): string;
30
+ /**
31
+ * The C# property name for a field of a given type.
32
+ *
33
+ * C# refuses a member whose name is its enclosing type's (CS0542), which a
34
+ * `variant("op", ...)` named `Op` walks straight into. The JSON name is carried
35
+ * by `JsonPropertyName` either way, so the C# name is free to move.
36
+ */
37
+ export declare function propertyNameIn(typeName: string, fieldName: string): string;
38
+ /**
39
+ * Render the file-level header: auto-generated banner, whatever the parse and the emit could
40
+ * not carry across, and the using-block. Split out so callers that want to embed the emitter
41
+ * output in larger files can skip it.
42
+ *
43
+ * Pass both {@link SchemaModule.notes} and {@link EmitResult.notes}: a field the parser could
44
+ * not read and a member the emitter had to drop are the same kind of fact to whoever opens
45
+ * the generated file, and the header is the only place either one surfaces.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * import { emitFileHeader, emitModule, parseFiles } from "@game-infra/valibot-to-csharp";
50
+ *
51
+ * const module = parseFiles(["./schemas/events.ts"]);
52
+ * const emit = emitModule(module, { namespace: "MyGame.Events" });
53
+ * const header = emitFileHeader([...module.notes, ...emit.notes]);
54
+ * // "// <auto-generated>..." plus the System.Text.Json using-block
55
+ * ```
56
+ */
57
+ export declare function emitFileHeader(notes: string[]): string;
58
+ /**
59
+ * The C# source of the `StringOrStringList` helper: the one type the emitter writes by hand
60
+ * rather than deriving from a schema. `union([string(), array(string())])` has no C# shape of
61
+ * its own, so it gets a record with implicit conversions and a converter that reads either
62
+ * form and writes back the one it was given.
63
+ */
64
+ export declare const STRING_OR_STRING_LIST_SOURCE = "[JsonConverter(typeof(StringOrStringListConverter))]\npublic sealed record StringOrStringList(IReadOnlyList<string> Values)\n{\n public static implicit operator StringOrStringList(string value) => new(new[] { value });\n public static implicit operator StringOrStringList(string[] values) => new(values);\n}\n\npublic sealed class StringOrStringListConverter : JsonConverter<StringOrStringList>\n{\n public override StringOrStringList Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)\n {\n if (reader.TokenType == JsonTokenType.String)\n {\n var single = reader.GetString() ?? string.Empty;\n return new StringOrStringList(new[] { single });\n }\n if (reader.TokenType == JsonTokenType.StartArray)\n {\n var list = new List<string>();\n while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)\n {\n list.Add(reader.GetString() ?? string.Empty);\n }\n return new StringOrStringList(list);\n }\n throw new JsonException(\"Expected string or array of strings for StringOrStringList.\");\n }\n\n public override void Write(Utf8JsonWriter writer, StringOrStringList value, JsonSerializerOptions options)\n {\n if (value.Values.Count == 1)\n {\n writer.WriteStringValue(value.Values[0]);\n return;\n }\n writer.WriteStartArray();\n foreach (var v in value.Values) writer.WriteStringValue(v);\n writer.WriteEndArray();\n }\n}";
65
+ //# sourceMappingURL=csharp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csharp.d.ts","sourceRoot":"","sources":["../src/csharp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAK9D;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAKzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAUtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAoBtD;AAED;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,qkDAuCvC,CAAC"}
package/dist/csharp.js ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * How a schema value spells itself in C#: numeric widths, property names, the file header,
3
+ * and the source of the one hand-written helper type the emitter can reach for.
4
+ *
5
+ * Kept apart from the emitter because none of it needs a module to answer — given a node or
6
+ * a name, the answer is the same every time.
7
+ */
8
+ import { toPascalCase } from "./naming.js";
9
+ /** Property name for the `[JsonExtensionData]` bag a `looseObject` record carries. */
10
+ export const EXTENSION_DATA_PROPERTY = "AdditionalProperties";
11
+ const INT32_MIN = -2_147_483_648;
12
+ const INT32_MAX = 2_147_483_647;
13
+ /**
14
+ * The C# type for a `number()`.
15
+ *
16
+ * A plain number is `double`. An `integer()` is integral, and narrows to `int`
17
+ * only when both declared bounds fit `System.Int32`: JavaScript integers run to
18
+ * 2^53, so an unbounded one deserialised into an `int` would reject every value
19
+ * above two billion — the failure mode is a whole class of documents refused,
20
+ * not a rounding error, so the wider type is the safe default.
21
+ */
22
+ export function numericCsType(node) {
23
+ if (!node.integer)
24
+ return "double";
25
+ const { min, max } = node;
26
+ const fitsInt32 = min !== undefined && max !== undefined && min >= INT32_MIN && max <= INT32_MAX;
27
+ return fitsInt32 ? "int" : "long";
28
+ }
29
+ /**
30
+ * The C# type of a `literal(...)` value, by the JSON kind it stands for.
31
+ *
32
+ * A numeric literal is a `number()` whose bounds are the value itself, so it goes through
33
+ * {@link numericCsType} rather than repeating the Int32 fit test: `literal(3000000000)` is
34
+ * a `long` for the same reason `pipe(number(), integer(), minValue(3000000000))` is, and
35
+ * spelling it `double` would put a `3E+09` where the digest expects `3000000000`.
36
+ */
37
+ export function literalCsType(value) {
38
+ if (typeof value === "string")
39
+ return "string";
40
+ if (typeof value === "boolean")
41
+ return "bool";
42
+ return numericCsType({
43
+ kind: "primitive",
44
+ type: "number",
45
+ integer: Number.isSafeInteger(value),
46
+ min: value,
47
+ max: value,
48
+ });
49
+ }
50
+ /**
51
+ * The C# property name for a field of a given type.
52
+ *
53
+ * C# refuses a member whose name is its enclosing type's (CS0542), which a
54
+ * `variant("op", ...)` named `Op` walks straight into. The JSON name is carried
55
+ * by `JsonPropertyName` either way, so the C# name is free to move.
56
+ */
57
+ export function propertyNameIn(typeName, fieldName) {
58
+ const propName = toPascalCase(fieldName);
59
+ return propName === typeName ? `${propName}Value` : propName;
60
+ }
61
+ /**
62
+ * Render the file-level header: auto-generated banner, whatever the parse and the emit could
63
+ * not carry across, and the using-block. Split out so callers that want to embed the emitter
64
+ * output in larger files can skip it.
65
+ *
66
+ * Pass both {@link SchemaModule.notes} and {@link EmitResult.notes}: a field the parser could
67
+ * not read and a member the emitter had to drop are the same kind of fact to whoever opens
68
+ * the generated file, and the header is the only place either one surfaces.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * import { emitFileHeader, emitModule, parseFiles } from "@game-infra/valibot-to-csharp";
73
+ *
74
+ * const module = parseFiles(["./schemas/events.ts"]);
75
+ * const emit = emitModule(module, { namespace: "MyGame.Events" });
76
+ * const header = emitFileHeader([...module.notes, ...emit.notes]);
77
+ * // "// <auto-generated>..." plus the System.Text.Json using-block
78
+ * ```
79
+ */
80
+ export function emitFileHeader(notes) {
81
+ const lines = [
82
+ "// <auto-generated>",
83
+ "// This file was generated by @game-infra/valibot-to-csharp.",
84
+ "// Do not edit by hand; regenerate from the valibot source instead.",
85
+ "// </auto-generated>",
86
+ ];
87
+ for (const note of notes) {
88
+ lines.push(`// ${note}`);
89
+ }
90
+ lines.push("");
91
+ // A file the compiler recognises as auto-generated is outside the project's nullable context, so
92
+ // every `T?` in it is an error (CS8669) unless the file turns the context on itself.
93
+ lines.push("#nullable enable");
94
+ lines.push("");
95
+ lines.push("using System.Collections.Generic;");
96
+ lines.push("using System.Text.Json;");
97
+ lines.push("using System.Text.Json.Serialization;");
98
+ lines.push("");
99
+ return lines.join("\n");
100
+ }
101
+ /**
102
+ * The C# source of the `StringOrStringList` helper: the one type the emitter writes by hand
103
+ * rather than deriving from a schema. `union([string(), array(string())])` has no C# shape of
104
+ * its own, so it gets a record with implicit conversions and a converter that reads either
105
+ * form and writes back the one it was given.
106
+ */
107
+ export const STRING_OR_STRING_LIST_SOURCE = `[JsonConverter(typeof(StringOrStringListConverter))]
108
+ public sealed record StringOrStringList(IReadOnlyList<string> Values)
109
+ {
110
+ public static implicit operator StringOrStringList(string value) => new(new[] { value });
111
+ public static implicit operator StringOrStringList(string[] values) => new(values);
112
+ }
113
+
114
+ public sealed class StringOrStringListConverter : JsonConverter<StringOrStringList>
115
+ {
116
+ public override StringOrStringList Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)
117
+ {
118
+ if (reader.TokenType == JsonTokenType.String)
119
+ {
120
+ var single = reader.GetString() ?? string.Empty;
121
+ return new StringOrStringList(new[] { single });
122
+ }
123
+ if (reader.TokenType == JsonTokenType.StartArray)
124
+ {
125
+ var list = new List<string>();
126
+ while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
127
+ {
128
+ list.Add(reader.GetString() ?? string.Empty);
129
+ }
130
+ return new StringOrStringList(list);
131
+ }
132
+ throw new JsonException("Expected string or array of strings for StringOrStringList.");
133
+ }
134
+
135
+ public override void Write(Utf8JsonWriter writer, StringOrStringList value, JsonSerializerOptions options)
136
+ {
137
+ if (value.Values.Count == 1)
138
+ {
139
+ writer.WriteStringValue(value.Values[0]);
140
+ return;
141
+ }
142
+ writer.WriteStartArray();
143
+ foreach (var v in value.Values) writer.WriteStringValue(v);
144
+ writer.WriteEndArray();
145
+ }
146
+ }`;
147
+ //# sourceMappingURL=csharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csharp.js","sourceRoot":"","sources":["../src/csharp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sFAAsF;AACtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE9D,MAAM,SAAS,GAAG,CAAC,aAAa,CAAC;AACjC,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,IAAmB;IAC/C,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IACnC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,IAAI,SAAS,CAAC;IACjG,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAC9C,OAAO,aAAa,CAAC;QACnB,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;QACpC,GAAG,EAAE,KAAK;QACV,GAAG,EAAE,KAAK;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,SAAiB;IAChE,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACzC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,MAAM,KAAK,GAAG;QACZ,qBAAqB;QACrB,gEAAgE;QAChE,uEAAuE;QACvE,sBAAsB;KACvB,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC3B,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,iGAAiG;IACjG,qFAAqF;IACrF,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuC1C,CAAC"}
package/dist/emitter.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SchemaModule } from "./ir.js";
2
+ export { emitFileHeader } from "./csharp.js";
2
3
  /**
3
4
  * Options accepted by {@link emitModule}.
4
5
  *
@@ -36,6 +37,13 @@ export interface EmitResult {
36
37
  source: string;
37
38
  /** True if any StringOrStringList helper was referenced. */
38
39
  usesStringOrStringList: boolean;
40
+ /**
41
+ * Shapes the emitter could not render faithfully — a dropped variant member, a widened
42
+ * field type, a reference it could not close. The caller renders these into the generated
43
+ * file's header alongside {@link SchemaModule.notes}, so a lossy translation says so in
44
+ * the artifact rather than only in whoever ran it.
45
+ */
46
+ notes: string[];
39
47
  }
40
48
  /**
41
49
  * Turn a {@link SchemaModule} into a C# source file body. The caller wraps
@@ -52,17 +60,4 @@ export interface EmitResult {
52
60
  * ```
53
61
  */
54
62
  export declare function emitModule(module: SchemaModule, opts: EmitOptions): EmitResult;
55
- /**
56
- * Render the file-level header: auto-generated banner + using-block. Split out
57
- * so callers that want to embed the emitter output in larger files can skip it.
58
- *
59
- * @example
60
- * ```ts
61
- * import { emitFileHeader } from "@game-infra/valibot-to-csharp";
62
- *
63
- * const header = emitFileHeader([]);
64
- * // "// <auto-generated>..." plus the System.Text.Json using-block
65
- * ```
66
- */
67
- export declare function emitFileHeader(notes: string[]): string;
68
63
  //# sourceMappingURL=emitter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,YAAY,EAGb,MAAM,SAAS,CAAC;AAGjB;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA0B9E;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAgBtD"}
1
+ {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAIV,YAAY,EAGb,MAAM,SAAS,CAAC;AAajB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,sBAAsB,EAAE,OAAO,CAAC;IAChC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA8B9E"}