@contractkit/plugin-csharp 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.turbo/turbo-build$colon$ci.log +13 -0
  2. package/.turbo/turbo-build.log +12 -0
  3. package/.turbo/turbo-format.log +34 -0
  4. package/.turbo/turbo-test.log +17 -0
  5. package/CHANGELOG.md +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +173 -0
  8. package/dist/codegen-client.d.ts +35 -0
  9. package/dist/codegen-client.d.ts.map +1 -0
  10. package/dist/codegen-models.d.ts +75 -0
  11. package/dist/codegen-models.d.ts.map +1 -0
  12. package/dist/codegen-sdk.d.ts +13 -0
  13. package/dist/codegen-sdk.d.ts.map +1 -0
  14. package/dist/hoist.d.ts +53 -0
  15. package/dist/hoist.d.ts.map +1 -0
  16. package/dist/index.d.ts +30 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +2569 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/naming.d.ts +89 -0
  21. package/dist/naming.d.ts.map +1 -0
  22. package/dist/runtime-converters.d.ts +15 -0
  23. package/dist/runtime-converters.d.ts.map +1 -0
  24. package/dist/runtime.d.ts +10 -0
  25. package/dist/runtime.d.ts.map +1 -0
  26. package/dist/scaffold.d.ts +26 -0
  27. package/dist/scaffold.d.ts.map +1 -0
  28. package/eslint.config.js +6 -0
  29. package/package.json +48 -0
  30. package/src/codegen-client.ts +680 -0
  31. package/src/codegen-models.ts +909 -0
  32. package/src/codegen-sdk.ts +52 -0
  33. package/src/hoist.ts +402 -0
  34. package/src/index.ts +373 -0
  35. package/src/naming.ts +262 -0
  36. package/src/runtime-converters.ts +147 -0
  37. package/src/runtime.ts +381 -0
  38. package/src/scaffold.ts +41 -0
  39. package/tests/codegen-client.test.ts +275 -0
  40. package/tests/codegen-models.test.ts +410 -0
  41. package/tests/helpers.ts +202 -0
  42. package/tests/hoist.test.ts +92 -0
  43. package/tests/index.test.ts +124 -0
  44. package/tests/naming.test.ts +133 -0
  45. package/tests/runtime.test.ts +104 -0
  46. package/tests/scaffold.test.ts +28 -0
  47. package/tsconfig.json +9 -0
  48. package/vitest.config.ts +14 -0
@@ -0,0 +1,13 @@
1
+ $ eslint --max-warnings=0 && pnpm run build
2
+ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration
3
+ CLI Building entry: src/index.ts
4
+ CLI Using tsconfig: tsconfig.json
5
+ CLI tsup v8.5.1
6
+ CLI Target: esnext
7
+ ESM Build start
8
+ ESM dist/index.js 93.08 KB
9
+ ESM dist/index.js.map 198.91 KB
10
+ ESM ⚡️ Build success in 28ms
11
+ DTS Build start
12
+ DTS ⚡️ Build success in 419ms
13
+ DTS dist/index.d.ts 1.50 KB
@@ -0,0 +1,12 @@
1
+ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration
2
+ CLI Building entry: src/index.ts
3
+ CLI Using tsconfig: tsconfig.json
4
+ CLI tsup v8.5.1
5
+ CLI Target: esnext
6
+ ESM Build start
7
+ ESM dist/index.js 93.08 KB
8
+ ESM dist/index.js.map 198.91 KB
9
+ ESM ⚡️ Build success in 19ms
10
+ DTS Build start
11
+ DTS ⚡️ Build success in 299ms
12
+ DTS dist/index.d.ts 1.50 KB
@@ -0,0 +1,34 @@
1
+ $ prettier --write .
2
+ CHANGELOG.md 9ms (unchanged)
3
+ dist/codegen-client.d.ts 22ms
4
+ dist/codegen-models.d.ts 7ms
5
+ dist/codegen-sdk.d.ts 1ms
6
+ dist/hoist.d.ts 5ms
7
+ dist/index.d.ts 3ms
8
+ dist/index.js 97ms
9
+ dist/naming.d.ts 6ms
10
+ dist/runtime-converters.d.ts 1ms
11
+ dist/runtime.d.ts 1ms
12
+ dist/scaffold.d.ts 2ms
13
+ eslint.config.js 1ms (unchanged)
14
+ package.json 2ms (unchanged)
15
+ README.md 20ms
16
+ src/codegen-client.ts 37ms
17
+ src/codegen-models.ts 40ms
18
+ src/codegen-sdk.ts 3ms (unchanged)
19
+ src/hoist.ts 17ms (unchanged)
20
+ src/index.ts 13ms (unchanged)
21
+ src/naming.ts 5ms (unchanged)
22
+ src/runtime-converters.ts 1ms (unchanged)
23
+ src/runtime.ts 1ms (unchanged)
24
+ src/scaffold.ts 1ms (unchanged)
25
+ tests/codegen-client.test.ts 13ms
26
+ tests/codegen-models.test.ts 32ms
27
+ tests/helpers.ts 8ms (unchanged)
28
+ tests/hoist.test.ts 6ms (unchanged)
29
+ tests/index.test.ts 6ms
30
+ tests/naming.test.ts 4ms (unchanged)
31
+ tests/runtime.test.ts 2ms (unchanged)
32
+ tests/scaffold.test.ts 1ms (unchanged)
33
+ tsconfig.json 1ms (unchanged)
34
+ vitest.config.ts 1ms (unchanged)
@@ -0,0 +1,17 @@
1
+ $ vitest run
2
+
3
+  RUN  v4.1.5 /Users/robert/projects/ContractKit/packages/plugin-csharp
4
+
5
+ ✓ tests/scaffold.test.ts (5 tests) 1ms
6
+ ✓ tests/naming.test.ts (19 tests) 2ms
7
+ ✓ tests/runtime.test.ts (14 tests) 2ms
8
+ ✓ tests/hoist.test.ts (10 tests) 2ms
9
+ ✓ tests/codegen-client.test.ts (26 tests) 5ms
10
+ ✓ tests/codegen-models.test.ts (63 tests) 6ms
11
+ ✓ tests/index.test.ts (10 tests) 7ms
12
+
13
+  Test Files  7 passed (7)
14
+  Tests  147 passed (147)
15
+  Start at  09:19:55
16
+  Duration  213ms (transform 426ms, setup 0ms, import 716ms, tests 25ms, environment 0ms)
17
+
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # @contractkit/plugin-csharp
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marooned Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @contractkit/plugin-csharp
2
+
3
+ Generates a C#/.NET SDK client from ContractKit `.ck` files.
4
+
5
+ The output is built on `System.Text.Json` and `HttpClient` from the shared framework and takes **no
6
+ NuGet dependencies at all**, so a generated SDK restores and builds with no feed reachable.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pnpm add -D @contractkit/plugin-csharp
12
+ ```
13
+
14
+ ## Configure
15
+
16
+ ```json
17
+ {
18
+ "plugins": {
19
+ "@contractkit/plugin-csharp": {
20
+ "baseDir": "clients/csharp/",
21
+ "namespace": "Acme.Sdk",
22
+ "sdkName": "AcmeSdk",
23
+ "scaffold": true
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ | Option | Default | Meaning |
30
+ | ----------------- | ----------------- | ----------------------------------------------------------------------- |
31
+ | `baseDir` | `csharp-sdk` | Output directory, relative to the config's `rootDir`. |
32
+ | `namespace` | `ContractKit.Sdk` | Root namespace for the generated sources. |
33
+ | `sdkName` | `Sdk` | Aggregator class name, and the assembly name when scaffolding. |
34
+ | `includeInternal` | `false` | Emit client methods for operations marked `internal`. |
35
+ | `scaffold` | `false` | Write `<SdkName>.csproj` once, as a user-owned file. Never overwritten. |
36
+
37
+ Config arrives as JSON, so it is validated at run time: an invalid namespace, a namespace segment
38
+ that is a C# keyword, or a non-boolean flag fails the build rather than emitting C# that cannot
39
+ compile.
40
+
41
+ ## What it emits
42
+
43
+ ```
44
+ <baseDir>/
45
+ Runtime/SdkRuntime.cs SdkHttp, SdkOptions, SdkException, SdkResponse, SdkPart
46
+ Runtime/Converters.cs SdkJson.Options and the scalar converters
47
+ Models/<File>.cs one file per contract file
48
+ Clients/<File>Client.cs one client per operations file
49
+ <SdkName>.cs the aggregator
50
+ <SdkName>.csproj only with `scaffold: true`, written once
51
+ ```
52
+
53
+ Generated sources are rewritten every run. The project file is not: it is emitted `ifAbsent`, so it
54
+ is created once and then belongs to you.
55
+
56
+ Every generated file opens with `// <auto-generated/>` and `#nullable enable`, and carries its own
57
+ `using` block, so the output does not depend on the project's implicit usings.
58
+
59
+ ## Using the client
60
+
61
+ ```csharp
62
+ using Acme.Sdk;
63
+ using Acme.Sdk.Runtime;
64
+
65
+ using var sdk = new AcmeSdk(new SdkOptions
66
+ {
67
+ BaseUrl = "https://api.example.com",
68
+ Headers = _ => ValueTask.FromResult<IReadOnlyDictionary<string, string>>(
69
+ new Dictionary<string, string> { ["authorization"] = $"Bearer {token}" }),
70
+ });
71
+
72
+ var payment = await sdk.Billing.GetPaymentAsync(paymentId);
73
+ ```
74
+
75
+ `Headers` is called once per request, so a token can be refreshed without rebuilding the SDK. Pass
76
+ your own `HttpClient` through `SdkOptions.HttpClient` to control handlers or retries; the SDK then
77
+ never disposes it.
78
+
79
+ A status outside 2xx that the contract does not declare throws `SdkException`, which derives from
80
+ `HttpRequestException` and carries the status, the raw body, and the body parsed as JSON.
81
+
82
+ Serialize a generated model by hand with `SdkJson.Options`: `decimal`, `bigint` and `duration` need
83
+ the converters registered there.
84
+
85
+ ## Type mapping
86
+
87
+ | `.ck` | C# | `.ck` | C# |
88
+ | ------------------------------------ | ----------------------- | --------------------------- | ---------------- |
89
+ | `string`, `email`, `url`, `interval` | `string` | `date` | `DateOnly` |
90
+ | `number` | `double` | `time` | `TimeOnly` |
91
+ | `int` | `long` | `datetime` | `DateTimeOffset` |
92
+ | `bigint` | `BigInteger` | `duration` | `TimeSpan` |
93
+ | `decimal` | `decimal` | `uuid` | `Guid` |
94
+ | `boolean` | `bool` | `binary` | `byte[]` |
95
+ | `array(T)` | `List<T>` | `null` | `object?` |
96
+ | `record(string, V)` | `Dictionary<string, V>` | `unknown`, `json`, `object` | `JsonElement` |
97
+
98
+ `int` is a JavaScript safe integer in the source language, which overflows a 32-bit `int`, so it
99
+ maps to `long`. `decimal` travels as a quoted JSON string and refuses to read an unquoted number,
100
+ matching the server's own schema. `duration` travels as ISO 8601 rather than the framework's
101
+ `d.hh:mm:ss` default. Both, and `bigint`, need the converters in `SdkJson.Options`.
102
+
103
+ ## Optional and nullable
104
+
105
+ Each property says for itself whether a null is written, so the two are not conflated:
106
+
107
+ | Contract | C# | On the wire |
108
+ | -------------- | ---------------------------------------------------------- | ----------------- |
109
+ | `x: T` | `public required T X { get; init; }` | always written |
110
+ | `x?: T` | `[JsonIgnore(WhenWritingNull)] public T? X { get; init; }` | omitted when null |
111
+ | `x: T \| null` | `public required T? X { get; init; }` | `null` is written |
112
+ | `x: T = v` | `public T X { get; init; } = v;` | always written |
113
+
114
+ A missing required property is a read error rather than a silent default. Every property is either
115
+ `required` or initialized, so the generated SDK compiles with warnings as errors.
116
+
117
+ ## Inheritance, and read versus Input variants
118
+
119
+ Bases are flattened into each record, applying the same later-wins override rule the inheritance
120
+ validator enforces. A contract with a `readonly` or `writeonly` field, or one reached by such a
121
+ contract, is emitted twice: `<Name>` for what a response carries and `<Name>Input` for what a
122
+ request sends. `format(input=)` and `format(output=)` rename the wire keys of each direction; a
123
+ model that is not split can only carry one key set, and asking for two is reported as a warning.
124
+
125
+ ## Unions and anonymous shapes
126
+
127
+ The `.ck` language lets a union, an enum, an object literal or a tuple appear anonymously inside a
128
+ field. C# needs a name for each, so a project-wide pass assigns one, named after the model and field
129
+ that hold it.
130
+
131
+ - A **plain union** becomes an abstract record closed by a private constructor, with one nested
132
+ member record each. Reading tries members in declaration order, which is what the server's own
133
+ schema does.
134
+ - A **discriminated union** becomes an interface its member records implement, with a converter that
135
+ dispatches on the tag. An interface rather than an abstract base, so one contract can belong to
136
+ several unions; the tag stays a real property rather than serializer metadata.
137
+ - A **tuple** becomes a record with a converter that reads and writes a JSON array, so it travels
138
+ correctly even nested inside a `List<>`.
139
+ - An **inline object** becomes a record, and an **inline enum** a C# enum.
140
+
141
+ ## Response shapes
142
+
143
+ A method returns the response body directly. Three things change that:
144
+
145
+ - A status declaring **response headers** returns `<Method>Result(Data, Headers)`, or
146
+ `<Method>Headers` when there is no body.
147
+ - An operation the client can observe at **several statuses** returns an abstract
148
+ `<Method>Response` with a `Status<code>` leaf each.
149
+ - A status declaring **several content types** returns one leaf per mime.
150
+
151
+ A status outside 2xx that the contract declares as an outcome is passed as an expected status and
152
+ comes back as a value; everything else throws `SdkException`, and the method documents which.
153
+
154
+ ## Scaffolding
155
+
156
+ `scaffold: true` writes `<SdkName>.csproj` once, targeting `net10.0` with nullable enabled and no
157
+ package reference. It is emitted `ifAbsent`, so it is created once and never regenerated: add a
158
+ package id, a version or an analyzer set and the next build leaves them alone.
159
+
160
+ ## Status and known limitations
161
+
162
+ 1. **Clients are not grouped by area.** One client per `.ck` file, as in the Python and Kotlin
163
+ SDKs. `keys.area` and `keys.subarea` are ignored.
164
+ 2. **A `binary` field inside a JSON body is base64.** No two ContractKit SDKs agree on this, so it
165
+ interoperates only with a service that agrees. Binary really travels as an
166
+ `application/octet-stream` body or a multipart part, both of which are exact.
167
+ 3. **`decimal` is the BCL's 28-digit type.** A contract decimal wider than that fails to read.
168
+ 4. **`DateTimeOffset` writes `+00:00`, not `Z`.** The generated server accepts both.
169
+ 5. **The three scalar converters are options-level.** Serialize a model yourself with
170
+ `SdkJson.Options`, or `decimal`, `bigint` and `duration` will not match the contract.
171
+ 6. **Only the first declared request mime is used**, since a method has one signature.
172
+ 7. **A type switch is not exhaustive.** C# has no compiler check that a union switch covers every
173
+ member, so each generated converter ends in a `default` that throws.
@@ -0,0 +1,35 @@
1
+ import type { ModelNode, OpOperationNode, OpRootNode, OpRouteNode, ParamSource } from '@contractkit/core';
2
+ import type { HoistResult } from './hoist.js';
3
+ export interface CSharpClientCodegenOptions {
4
+ namespace: string;
5
+ modelsWithInput: ReadonlySet<string>;
6
+ modelIndex?: ReadonlyMap<string, ModelNode>;
7
+ hoisted?: HoistResult;
8
+ includeInternal?: boolean;
9
+ warn?: (message: string) => void;
10
+ }
11
+ /** Whether the root has at least one operation eligible for client emission. */
12
+ export declare function hasPublicOperations(root: OpRootNode, includeInternal?: boolean): boolean;
13
+ export declare function deriveClientClassName(file: string): string;
14
+ export declare function deriveClientPropertyName(file: string): string;
15
+ /**
16
+ * Generate the client class for one operations file: one `Task`-returning method per public
17
+ * operation, plus the request and response shapes those methods name.
18
+ */
19
+ export declare function generateCSharpClient(root: OpRootNode, opts: CSharpClientCodegenOptions): string;
20
+ /**
21
+ * Render a route path as the `Path(...)` call that builds the URL.
22
+ *
23
+ * Literal segments stay string literals and dynamic ones go through `Segment(...)`, so exactly the
24
+ * values that came from the caller are percent-encoded. `params` says where a value lives: spread
25
+ * across the signature, or behind one `pathParams` argument when the route declares a model.
26
+ */
27
+ export declare function buildPathExpression(path: string, params?: ParamSource): string;
28
+ /**
29
+ * The SDK method name, in the same priority order every ContractKit SDK uses: an explicit `sdk:`,
30
+ * then the operation's `name:`, then a name inferred from the verb and path. C# spells it
31
+ * PascalCase with an `Async` suffix, which is what a .NET caller expects of a `Task`-returning
32
+ * method.
33
+ */
34
+ export declare function deriveMethodName(op: OpOperationNode, route: OpRouteNode): string;
35
+ //# sourceMappingURL=codegen-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-client.d.ts","sourceRoot":"","sources":["../src/codegen-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,SAAS,EACT,eAAe,EAIf,UAAU,EACV,WAAW,EACX,WAAW,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAY9C,MAAM,WAAW,0BAA0B;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAwBD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,UAAQ,GAAG,OAAO,CAOtF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,0BAA0B,GAAG,MAAM,CA2E/F;AAwZD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAY9E;AAkFD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAIhF"}
@@ -0,0 +1,75 @@
1
+ import type { ContractRootNode, ContractTypeNode, ModelNode, ScalarTypeNode } from '@contractkit/core';
2
+ import type { HoistResult } from './hoist.js';
3
+ export interface CSharpModelCodegenOptions {
4
+ /** Root namespace the SDK is generated into. Models land in `<namespace>.Models`. */
5
+ namespace: string;
6
+ /** Model names that have a distinct `Input` variant, including ones declared in other files. */
7
+ modelsWithInput?: ReadonlySet<string>;
8
+ /**
9
+ * Every model in the project, for flattening bases and intersections. Defaults to an index of
10
+ * this root's own models, which is enough for a single-file project and for unit tests.
11
+ */
12
+ modelIndex?: ReadonlyMap<string, ModelNode>;
13
+ /** Names assigned to anonymous types by {@link collectHoistedTypes}, across the whole project. */
14
+ hoisted?: HoistResult;
15
+ warn?: (message: string) => void;
16
+ }
17
+ /**
18
+ * Generate the C# models file for one contract root: a `sealed record` per model, plus `<Name>Input`
19
+ * variants, enums, aliases, and the records, interfaces and converters standing in for the unions
20
+ * and anonymous shapes this file owns.
21
+ *
22
+ * Every model in the project shares the single `<namespace>.Models` namespace, so a reference to a
23
+ * model declared in another `.ck` file needs no import and resolves by name alone. That is also what
24
+ * lets an interface declared in one file be implemented by a record generated in another.
25
+ */
26
+ export declare function generateCSharpModels(root: ContractRootNode, opts: CSharpModelCodegenOptions): string;
27
+ /**
28
+ * The complete set of model names that need a distinct `Input` variant: the ones passed in, plus
29
+ * the transitive closure over `models`.
30
+ *
31
+ * The hoisting pass and the renderer both have to agree on this — a hoisted shape whose Input twin
32
+ * one of them thinks is unnecessary would leave the other referring to a type nobody emitted.
33
+ */
34
+ export declare function resolveModelsWithInput(models: readonly ModelNode[], external?: ReadonlySet<string>): Set<string>;
35
+ interface RenderContext {
36
+ namespace: string;
37
+ modelsWithInput: ReadonlySet<string>;
38
+ modelIndex: ReadonlyMap<string, ModelNode>;
39
+ hoisted?: HoistResult;
40
+ /** `global using` alias lines this file has to emit above its own `using` block. */
41
+ globalAliases: string[];
42
+ /** When set, type names render fully qualified, as a `global using` alias target must be. */
43
+ qualify?: boolean;
44
+ warn?: (message: string) => void;
45
+ }
46
+ /** Build a rendering context for a file outside the models namespace, such as a client. */
47
+ export declare function createRenderContext(opts: CSharpModelCodegenOptions & {
48
+ modelsWithInput: ReadonlySet<string>;
49
+ }): RenderContext;
50
+ /**
51
+ * Assemble a generated C# file: header, nullable context, global aliases, usings, namespace, bodies.
52
+ *
53
+ * `// <auto-generated/>` turns the nullable context off, so `#nullable enable` follows it
54
+ * explicitly. A `global using` alias has to precede every ordinary `using` in its file, which is
55
+ * why the aliases are collected during rendering and emitted here rather than inline.
56
+ */
57
+ export declare function renderFile(namespaceName: string, globalAliases: readonly string[], usings: readonly string[], bodies: string[]): string;
58
+ /**
59
+ * Render a ContractKit type as its C# type expression. Never returns a nullable type unless the type
60
+ * itself is one — the caller appends `?` from the field's own `optional`/`nullable` flags.
61
+ *
62
+ * @param forInput - When true, a reference to a model or hoisted shape with an Input variant renders
63
+ * as `<Name>Input`.
64
+ * @throws {Error} Via the scalar renderer, if a scalar has no C# mapping.
65
+ */
66
+ export declare function renderCSharpType(type: ContractTypeNode, ctx: RenderContext, forInput?: boolean): string;
67
+ /**
68
+ * Map a ContractKit scalar to its C# type.
69
+ *
70
+ * @throws {Error} When a scalar has no mapping, so a scalar added to core fails the build here
71
+ * rather than emitting C# that does not compile.
72
+ */
73
+ export declare function renderScalar(name: ScalarTypeNode['name'], ctx: RenderContext): string;
74
+ export type { RenderContext };
75
+ //# sourceMappingURL=codegen-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-models.d.ts","sourceRoot":"","sources":["../src/codegen-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAa,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElH,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AAK3D,MAAM,WAAW,yBAAyB;IACtC,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,kGAAkG;IAClG,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAiBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,yBAAyB,GAAG,MAAM,CAwBpG;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAG3H;AAID,UAAU,aAAa;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oFAAoF;IACpF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,yBAAyB,GAAG;IAAE,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,aAAa,CAS7H;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAYvI;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,UAAQ,GAAG,MAAM,CAqDrG;AAqBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CA2CrF;AAooBD,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface SdkAggregatorClient {
2
+ className: string;
3
+ propertyName: string;
4
+ }
5
+ /**
6
+ * Generate the SDK entry point: one property per generated client, all sharing a single
7
+ * [SdkHttp] and therefore a single `HttpClient`.
8
+ *
9
+ * The Python SDK gives each sub-client its own connection pool; that is a bug worth not repeating,
10
+ * since a caller holding one SDK expects one set of connections.
11
+ */
12
+ export declare function generateSdkCs(namespaceName: string, sdkName: string, clients: readonly SdkAggregatorClient[]): string;
13
+ //# sourceMappingURL=codegen-sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-sdk.d.ts","sourceRoot":"","sources":["../src/codegen-sdk.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,CAqCrH"}
@@ -0,0 +1,53 @@
1
+ import type { ContractRootNode, ContractTypeNode, FieldNode, ModelNode } from '@contractkit/core';
2
+ /**
3
+ * C# needs a name for every shape a caller can hold. The `.ck` language does not: a union, an enum,
4
+ * or an object literal can appear anonymously inside a field. This pass walks every model in the
5
+ * project and assigns each such node a stable C# declaration, so the type renderer can emit a name
6
+ * and the file emitter can emit the declaration behind it.
7
+ *
8
+ * It runs once over all contract roots rather than per file, because a discriminated union declared
9
+ * in one file makes its member records, which may live in any other file, implement its interface.
10
+ */
11
+ export type HoistKind = 'enum' | 'record' | 'plainUnion' | 'discriminatedUnion' | 'tuple';
12
+ export interface HoistedMember {
13
+ /** The C# type of the member: a model name, or a hoisted declaration's name. */
14
+ typeName: string;
15
+ /** Nested record name inside a plain union's abstract record (`OfPayment`). */
16
+ wrapperName?: string;
17
+ /** Discriminator value for a discriminated union member. */
18
+ tag?: string;
19
+ type: ContractTypeNode;
20
+ }
21
+ export interface HoistedDecl {
22
+ kind: HoistKind;
23
+ name: string;
24
+ /** The `.ck` file whose models file carries this declaration. */
25
+ ownerFile: string;
26
+ /** Whether a distinct `<Name>Input` twin has to be emitted alongside it. */
27
+ needsInput: boolean;
28
+ /** Rendered references become `Name?` — the union had a `null` member. */
29
+ nullable?: boolean;
30
+ members?: HoistedMember[];
31
+ discriminator?: string;
32
+ fields?: FieldNode[];
33
+ values?: string[];
34
+ items?: ContractTypeNode[];
35
+ description?: string;
36
+ }
37
+ export interface HoistResult {
38
+ /** The declaration standing in for an anonymous node, keyed by AST node identity. */
39
+ byNode: Map<ContractTypeNode, HoistedDecl>;
40
+ byName: Map<string, HoistedDecl>;
41
+ /** Declarations each `.ck` file's models file has to emit, in collection order. */
42
+ byFile: Map<string, HoistedDecl[]>;
43
+ /** Model record name → the union interfaces it must declare it implements. */
44
+ memberships: Map<string, string[]>;
45
+ }
46
+ export interface HoistOptions {
47
+ modelIndex: ReadonlyMap<string, ModelNode>;
48
+ modelsWithInput: ReadonlySet<string>;
49
+ warn?: (message: string, file: string) => void;
50
+ }
51
+ /** Analyse every model in the project and name the anonymous types that need a C# declaration. */
52
+ export declare function collectHoistedTypes(roots: readonly ContractRootNode[], opts: HoistOptions): HoistResult;
53
+ //# sourceMappingURL=hoist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hoist.d.ts","sourceRoot":"","sources":["../src/hoist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAIlG;;;;;;;;GAQG;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,oBAAoB,GAAG,OAAO,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC1B,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,UAAU,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IACxB,qFAAqF;IACrF,MAAM,EAAE,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACjC,mFAAmF;IACnF,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACnC,8EAA8E;IAC9E,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC3C,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,WAAW,CAwBvG"}
@@ -0,0 +1,30 @@
1
+ import type { ContractKitPlugin } from '@contractkit/core';
2
+ export interface CSharpSdkPluginConfig {
3
+ /** Output directory relative to rootDir (default: "csharp-sdk") */
4
+ baseDir?: string;
5
+ /** Root namespace for the generated sources, e.g. "Acme.Sdk" (default: "ContractKit.Sdk") */
6
+ namespace?: string;
7
+ /** Aggregator class name (default: "Sdk"). Also the assembly name when scaffolding. */
8
+ sdkName?: string;
9
+ /**
10
+ * Whether to emit client methods for operations marked `internal`. Defaults to `false` —
11
+ * internal ops are omitted so consumers don't pick them up.
12
+ */
13
+ includeInternal?: boolean;
14
+ /** Emit `<SdkName>.csproj` once, as a user-owned file. Never overwritten. */
15
+ scaffold?: boolean;
16
+ }
17
+ /**
18
+ * Bumped when the C# codegen output shape changes in a way that should invalidate every per-file
19
+ * fingerprint, so a plugin upgrade forces full regeneration even when no `.ck` file has changed.
20
+ */
21
+ export declare const CSHARP_CODEGEN_VERSION = "1";
22
+ declare const plugin: ContractKitPlugin;
23
+ export default plugin;
24
+ export declare function createCSharpSdkPlugin(config: CSharpSdkPluginConfig, rootDir: string): ContractKitPlugin;
25
+ /**
26
+ * Reject config that would generate C# which cannot compile. These are runtime checks, not just
27
+ * types: config arrives as JSON, so the TypeScript interface constrains programmatic callers only.
28
+ */
29
+ export declare function assertValidConfig(config: CSharpSdkPluginConfig): void;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,iBAAiB,EAUpB,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,WAAW,qBAAqB;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAO1C,QAAA,MAAM,MAAM,EAAE,iBAMb,CAAC;AAEF,eAAe,MAAM,CAAC;AAEtB,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAOvG;AAKD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CA2BrE"}