@f3liz/rescript-autogen-openapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +98 -0
  3. package/lib/es6/src/Codegen.mjs +423 -0
  4. package/lib/es6/src/Types.mjs +20 -0
  5. package/lib/es6/src/core/CodegenUtils.mjs +186 -0
  6. package/lib/es6/src/core/DocOverride.mjs +399 -0
  7. package/lib/es6/src/core/FileSystem.mjs +78 -0
  8. package/lib/es6/src/core/IRBuilder.mjs +201 -0
  9. package/lib/es6/src/core/OpenAPIParser.mjs +168 -0
  10. package/lib/es6/src/core/Pipeline.mjs +150 -0
  11. package/lib/es6/src/core/ReferenceResolver.mjs +41 -0
  12. package/lib/es6/src/core/Result.mjs +378 -0
  13. package/lib/es6/src/core/SchemaIR.mjs +355 -0
  14. package/lib/es6/src/core/SchemaIRParser.mjs +490 -0
  15. package/lib/es6/src/core/SchemaRefResolver.mjs +146 -0
  16. package/lib/es6/src/core/SchemaRegistry.mjs +92 -0
  17. package/lib/es6/src/core/SpecDiffer.mjs +251 -0
  18. package/lib/es6/src/core/SpecMerger.mjs +237 -0
  19. package/lib/es6/src/generators/ComponentSchemaGenerator.mjs +125 -0
  20. package/lib/es6/src/generators/DiffReportGenerator.mjs +155 -0
  21. package/lib/es6/src/generators/EndpointGenerator.mjs +172 -0
  22. package/lib/es6/src/generators/IRToSuryGenerator.mjs +233 -0
  23. package/lib/es6/src/generators/IRToTypeGenerator.mjs +241 -0
  24. package/lib/es6/src/generators/IRToTypeScriptGenerator.mjs +143 -0
  25. package/lib/es6/src/generators/ModuleGenerator.mjs +285 -0
  26. package/lib/es6/src/generators/SchemaCodeGenerator.mjs +77 -0
  27. package/lib/es6/src/generators/ThinWrapperGenerator.mjs +97 -0
  28. package/lib/es6/src/generators/TypeScriptDtsGenerator.mjs +172 -0
  29. package/lib/es6/src/generators/TypeScriptWrapperGenerator.mjs +145 -0
  30. package/lib/es6/src/types/CodegenError.mjs +79 -0
  31. package/lib/es6/src/types/Config.mjs +42 -0
  32. package/lib/es6/src/types/GenerationContext.mjs +24 -0
  33. package/package.json +44 -0
  34. package/rescript.json +20 -0
  35. package/src/Codegen.res +222 -0
  36. package/src/Types.res +195 -0
  37. package/src/core/CodegenUtils.res +130 -0
  38. package/src/core/DocOverride.res +504 -0
  39. package/src/core/FileSystem.res +62 -0
  40. package/src/core/IRBuilder.res +66 -0
  41. package/src/core/OpenAPIParser.res +144 -0
  42. package/src/core/Pipeline.res +51 -0
  43. package/src/core/ReferenceResolver.res +41 -0
  44. package/src/core/Result.res +187 -0
  45. package/src/core/SchemaIR.res +258 -0
  46. package/src/core/SchemaIRParser.res +360 -0
  47. package/src/core/SchemaRefResolver.res +143 -0
  48. package/src/core/SchemaRegistry.res +107 -0
  49. package/src/core/SpecDiffer.res +270 -0
  50. package/src/core/SpecMerger.res +245 -0
  51. package/src/generators/ComponentSchemaGenerator.res +127 -0
  52. package/src/generators/DiffReportGenerator.res +152 -0
  53. package/src/generators/EndpointGenerator.res +172 -0
  54. package/src/generators/IRToSuryGenerator.res +199 -0
  55. package/src/generators/IRToTypeGenerator.res +199 -0
  56. package/src/generators/IRToTypeScriptGenerator.res +72 -0
  57. package/src/generators/ModuleGenerator.res +362 -0
  58. package/src/generators/SchemaCodeGenerator.res +83 -0
  59. package/src/generators/ThinWrapperGenerator.res +124 -0
  60. package/src/generators/TypeScriptDtsGenerator.res +193 -0
  61. package/src/generators/TypeScriptWrapperGenerator.res +166 -0
  62. package/src/types/CodegenError.res +82 -0
  63. package/src/types/Config.res +89 -0
  64. package/src/types/GenerationContext.res +23 -0
@@ -0,0 +1,124 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // ThinWrapperGenerator.res - Generate ReScript thin wrappers with pipe-first ergonomics
4
+ open Types
5
+
6
+ let clientTypeCode = `
7
+ |type client = {
8
+ | baseUrl: string,
9
+ | token: option<string>,
10
+ | fetch: ${CodegenUtils.fetchTypeSignature},
11
+ |}
12
+ |`->CodegenUtils.trimMargin
13
+
14
+ let generateConnectFunction = (title) =>
15
+ `
16
+ |/** Create a client for ${title} */
17
+ |let connect = (~baseUrl: string, ~token: option<string>=?, ~fetch: ${CodegenUtils.fetchTypeSignature}, ()): client => {
18
+ | baseUrl,
19
+ | token,
20
+ | fetch,
21
+ |}
22
+ |`->CodegenUtils.trimMargin
23
+
24
+ let generateWrapperFunction = (~endpoint: endpoint, ~generatedModuleName: string) => {
25
+ let operationName = CodegenUtils.generateOperationName(
26
+ endpoint.operationId,
27
+ endpoint.path,
28
+ endpoint.method,
29
+ )
30
+ let hasRequestBody = endpoint.requestBody->Option.isSome
31
+ let docComment = endpoint.summary->Option.mapOr("", summary => {
32
+ let descriptionPart = endpoint.description->Option.mapOr("", description =>
33
+ description == summary ? "" : " - " ++ description
34
+ )
35
+ ` /** ${summary}${descriptionPart} */\n`
36
+ })
37
+
38
+ let signature = hasRequestBody
39
+ ? `let ${operationName} = (request: ${generatedModuleName}.${operationName}Request, ~client: client)`
40
+ : `let ${operationName} = (~client: client)`
41
+
42
+ let callArguments = hasRequestBody ? "~body=request, " : ""
43
+
44
+ `${docComment} ${signature}: promise<${generatedModuleName}.${operationName}Response> =>
45
+ ${generatedModuleName}.${operationName}(${callArguments}~fetch=client.fetch)`
46
+ }
47
+
48
+ let generateWrapper = (
49
+ ~spec: openAPISpec,
50
+ ~endpoints,
51
+ ~extensionEndpoints=[],
52
+ ~outputDir,
53
+ ~wrapperModuleName="Wrapper",
54
+ ~generatedModulePrefix="",
55
+ ~baseModulePrefix="",
56
+ ) => {
57
+ let extensionOperationIds =
58
+ extensionEndpoints->Array.reduce(Dict.make(), (acc, endpoint) => {
59
+ let name = CodegenUtils.generateOperationName(
60
+ endpoint.operationId,
61
+ endpoint.path,
62
+ endpoint.method,
63
+ )
64
+ Dict.set(acc, name, true)
65
+ acc
66
+ })
67
+
68
+ let hasExtensions = Array.length(extensionEndpoints) > 0
69
+
70
+ let allEndpoints = Array.concat(
71
+ hasExtensions
72
+ ? endpoints->Array.filter(endpoint => {
73
+ let name = CodegenUtils.generateOperationName(
74
+ endpoint.operationId,
75
+ endpoint.path,
76
+ endpoint.method,
77
+ )
78
+ !Dict.has(extensionOperationIds, name)
79
+ })
80
+ : endpoints,
81
+ extensionEndpoints,
82
+ )
83
+
84
+ let endpointsByTag = OpenAPIParser.groupByTag(allEndpoints)
85
+
86
+ let modulesCode =
87
+ Dict.keysToArray(endpointsByTag)
88
+ ->Array.map(tag => {
89
+ let moduleName = CodegenUtils.toPascalCase(tag)
90
+ let wrapperFunctions =
91
+ Dict.get(endpointsByTag, tag)
92
+ ->Option.getOr([])
93
+ ->Array.map(endpoint => {
94
+ let operationName = CodegenUtils.generateOperationName(
95
+ endpoint.operationId,
96
+ endpoint.path,
97
+ endpoint.method,
98
+ )
99
+ let isExtension = hasExtensions && Dict.has(extensionOperationIds, operationName)
100
+ let prefix =
101
+ (!isExtension && baseModulePrefix != "") ? baseModulePrefix : generatedModulePrefix
102
+
103
+ let targetModuleName = prefix != "" ? `${prefix}${moduleName}` : moduleName
104
+ generateWrapperFunction(~endpoint, ~generatedModuleName=targetModuleName)
105
+ })
106
+ ->Array.join("\n\n")
107
+
108
+ `module ${moduleName} = {\n${wrapperFunctions}\n}`
109
+ })
110
+ ->Array.join("\n\n")
111
+
112
+ let fileContent = `// Generated thin wrapper
113
+
114
+ ${clientTypeCode}
115
+
116
+ ${generateConnectFunction(spec.info.title)}
117
+
118
+ ${modulesCode}`
119
+
120
+ Pipeline.fromFilesAndWarnings(
121
+ [{path: FileSystem.makePath(outputDir, `${wrapperModuleName}.res`), content: fileContent}],
122
+ [],
123
+ )
124
+ }
@@ -0,0 +1,193 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // TypeScriptDtsGenerator.res - Generate TypeScript .d.ts definition files
4
+ open Types
5
+
6
+ let getFirstJsonSchema = (contentDict: dict<mediaType>): option<jsonSchema> =>
7
+ Dict.keysToArray(contentDict)
8
+ ->Array.get(0)
9
+ ->Option.flatMap(contentType => Dict.get(contentDict, contentType))
10
+ ->Option.flatMap(mediaType => mediaType.schema)
11
+
12
+ let generateTypeScriptType = (name, description, schema) => {
13
+ let (irType, _) = SchemaIRParser.parseJsonSchema(schema)
14
+ IRToTypeScriptGenerator.generateNamedType(~namedSchema={name, description, type_: irType})
15
+ }
16
+
17
+ // Generate TypeScript interface for request type
18
+ let generateRequestInterface = (~endpoint: endpoint, ~functionName) => {
19
+ let requestTypeName = `${CodegenUtils.toPascalCase(functionName)}Request`
20
+ endpoint.requestBody->Option.flatMap(body =>
21
+ getFirstJsonSchema(body.content)->Option.map(schema =>
22
+ generateTypeScriptType(requestTypeName, body.description, schema)
23
+ )
24
+ )
25
+ }
26
+
27
+ // Generate TypeScript interface for response type
28
+ let generateResponseInterface = (~endpoint: endpoint, ~functionName) => {
29
+ let responseTypeName = `${CodegenUtils.toPascalCase(functionName)}Response`
30
+ let successCodes = ["200", "201", "202", "204"]
31
+ let successResponse = successCodes
32
+ ->Array.filterMap(code => Dict.get(endpoint.responses, code))
33
+ ->Array.get(0)
34
+
35
+ successResponse
36
+ ->Option.flatMap(response =>
37
+ response.content
38
+ ->Option.flatMap(getFirstJsonSchema)
39
+ ->Option.map(schema => generateTypeScriptType(responseTypeName, response.description->Some, schema))
40
+ )
41
+ ->Option.getOr(`export type ${responseTypeName} = void;`)
42
+ }
43
+
44
+ // Generate method signature for endpoint in an interface
45
+ let generateMethodSignature = (~endpoint: endpoint, ~functionName) => {
46
+ let params = endpoint.requestBody->Option.isSome
47
+ ? `client: MisskeyClient, request: ${CodegenUtils.toPascalCase(functionName)}Request`
48
+ : "client: MisskeyClient"
49
+
50
+ let docLines = endpoint.summary->Option.mapOr([], summary => {
51
+ let lines = [" /**", ` * ${summary}`]
52
+ endpoint.description->Option.forEach(description => {
53
+ if description != summary {
54
+ lines->Array.push(` * ${description}`)
55
+ }
56
+ })
57
+ lines->Array.push(" */")
58
+ lines
59
+ })
60
+
61
+ let code = `
62
+ |${docLines->Array.join("\n")}
63
+ | ${functionName}(${params}): Promise<${CodegenUtils.toPascalCase(functionName)}Response>;`
64
+
65
+ code->CodegenUtils.trimMargin
66
+ }
67
+
68
+ // Generate .d.ts file for a module (grouped by tag)
69
+ let generateModuleDts = (~moduleName, ~endpoints: array<endpoint>) => {
70
+ let interfaces =
71
+ endpoints
72
+ ->Array.map(endpoint => {
73
+ let functionName = CodegenUtils.generateOperationName(
74
+ endpoint.operationId,
75
+ endpoint.path,
76
+ endpoint.method,
77
+ )
78
+ let requestPart =
79
+ generateRequestInterface(~endpoint, ~functionName)->Option.getOr("")
80
+ let responsePart = generateResponseInterface(~endpoint, ~functionName)
81
+ [requestPart, responsePart]->Array.filter(s => s != "")->Array.join("\n")
82
+ })
83
+ ->Array.join("\n\n")
84
+
85
+ let methodSignatures =
86
+ endpoints
87
+ ->Array.map(endpoint =>
88
+ generateMethodSignature(
89
+ ~endpoint,
90
+ ~functionName=CodegenUtils.generateOperationName(
91
+ endpoint.operationId,
92
+ endpoint.path,
93
+ endpoint.method,
94
+ ),
95
+ )
96
+ )
97
+ ->Array.join("\n")
98
+
99
+ let header = `
100
+ |// TypeScript definitions for ${moduleName}
101
+ |// Generated by @f3liz/rescript-autogen-openapi
102
+ |// DO NOT EDIT
103
+ |
104
+ |import { MisskeyClient } from './index';
105
+ |import * as ComponentSchemas from './ComponentSchemas';
106
+ |`->CodegenUtils.trimMargin
107
+
108
+ `
109
+ |${header}
110
+ |
111
+ |${interfaces}
112
+ |
113
+ |export interface ${moduleName}Module {
114
+ |${methodSignatures}
115
+ |}
116
+ |
117
+ |export const ${moduleName}: ${moduleName}Module;
118
+ |`->CodegenUtils.trimMargin
119
+ }
120
+
121
+ // Generate ComponentSchemas.d.ts
122
+ let generateComponentSchemasDts = (~schemas: Dict.t<jsonSchema>) => {
123
+ let content =
124
+ Dict.toArray(schemas)
125
+ ->Array.map(((name, schema)) => generateTypeScriptType(name, schema.description, schema))
126
+ ->Array.join("\n\n")
127
+
128
+ `
129
+ |// TypeScript definitions for ComponentSchemas
130
+ |// Generated by @f3liz/rescript-autogen-openapi
131
+ |// DO NOT EDIT
132
+ |
133
+ |${content}
134
+ |`->CodegenUtils.trimMargin
135
+ }
136
+
137
+ // Generate main index.d.ts with MisskeyClient class
138
+ let generateIndexDts = (~moduleNames) => {
139
+ let imports = moduleNames->Array.map(m => `import { ${m}Module } from './${m}';`)->Array.join("\n")
140
+ let exports = moduleNames->Array.map(m => `export const ${m}: ${m}Module;`)->Array.join("\n")
141
+
142
+ `
143
+ |// TypeScript definitions
144
+ |// Generated by @f3liz/rescript-autogen-openapi
145
+ |// DO NOT EDIT
146
+ |
147
+ |${imports}
148
+ |
149
+ |export class MisskeyClient {
150
+ | constructor(baseUrl: string, token?: string);
151
+ | readonly baseUrl: string;
152
+ | readonly token?: string;
153
+ |}
154
+ |
155
+ |${exports}
156
+ |`->CodegenUtils.trimMargin
157
+ }
158
+
159
+ // Generate all .d.ts files for a spec
160
+ let generate = (~spec: openAPISpec, ~endpoints, ~outputDir): Pipeline.generationOutput => {
161
+ let endpointsByTag = OpenAPIParser.groupByTag(endpoints)
162
+ let moduleNames = []
163
+ let files =
164
+ Dict.toArray(endpointsByTag)
165
+ ->Array.filterMap(((tag, tagEndpoints)) =>
166
+ if Array.length(tagEndpoints) > 0 {
167
+ let name = CodegenUtils.toPascalCase(tag)
168
+ moduleNames->Array.push(name)
169
+ Some({
170
+ FileSystem.path: FileSystem.makePath(outputDir, `types/${name}.d.ts`),
171
+ content: generateModuleDts(~moduleName=name, ~endpoints=tagEndpoints),
172
+ })
173
+ } else {
174
+ None
175
+ }
176
+ )
177
+
178
+ spec.components
179
+ ->Option.flatMap(c => c.schemas)
180
+ ->Option.forEach(schemas =>
181
+ files->Array.push({
182
+ path: FileSystem.makePath(outputDir, "types/ComponentSchemas.d.ts"),
183
+ content: generateComponentSchemasDts(~schemas=schemas),
184
+ })
185
+ )
186
+
187
+ files->Array.push({
188
+ path: FileSystem.makePath(outputDir, "types/index.d.ts"),
189
+ content: generateIndexDts(~moduleNames),
190
+ })
191
+
192
+ {files, warnings: []}
193
+ }
@@ -0,0 +1,166 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // TypeScriptWrapperGenerator.res - Generate TypeScript/JavaScript wrapper
4
+ open Types
5
+
6
+ let misskeyClientJsCode = `
7
+ |export class MisskeyClient {
8
+ | constructor(baseUrl, token) {
9
+ | this.baseUrl = baseUrl;
10
+ | this.token = token;
11
+ | }
12
+ |
13
+ | async _fetch(url, method, body) {
14
+ | const headers = { 'Content-Type': 'application/json' };
15
+ | if (this.token) {
16
+ | headers['Authorization'] = \`Bearer \${this.token}\`;
17
+ | }
18
+ | const response = await fetch(this.baseUrl + url, {
19
+ | method,
20
+ | headers,
21
+ | body: body ? JSON.stringify(body) : undefined,
22
+ | });
23
+ | return response.json();
24
+ | }
25
+ |}`->CodegenUtils.trimMargin
26
+
27
+ let generateWrapperMjs = (~endpoints, ~generatedModulePath) => {
28
+ let endpointsByTag = OpenAPIParser.groupByTag(endpoints)
29
+ let tags = Dict.keysToArray(endpointsByTag)
30
+
31
+ let imports =
32
+ tags
33
+ ->Array.map(tag => {
34
+ let moduleName = CodegenUtils.toPascalCase(tag)
35
+ `import * as ${moduleName} from '${generatedModulePath}/${moduleName}.mjs';`
36
+ })
37
+ ->Array.join("\n")
38
+
39
+ let wrappers =
40
+ tags
41
+ ->Array.map(tag => {
42
+ let moduleName = CodegenUtils.toPascalCase(tag)
43
+ let methods =
44
+ Dict.get(endpointsByTag, tag)
45
+ ->Option.getOr([])
46
+ ->Array.map(endpoint => {
47
+ let functionName = CodegenUtils.generateOperationName(
48
+ endpoint.operationId,
49
+ endpoint.path,
50
+ endpoint.method,
51
+ )
52
+ let hasRequestBody = endpoint.requestBody->Option.isSome
53
+ let bodyArg = hasRequestBody ? "body: request, " : ""
54
+ `
55
+ | async ${functionName}(client${hasRequestBody ? ", request" : ""}) {
56
+ | return ${moduleName}.${functionName}({
57
+ | ${bodyArg}fetch: (url, method, body) => client._fetch(url, method, body)
58
+ | });
59
+ | },`
60
+ })
61
+ ->Array.join("\n")
62
+ `
63
+ |export const ${moduleName} = {
64
+ |${methods}
65
+ |};`
66
+ })
67
+ ->Array.join("\n\n")
68
+
69
+ `
70
+ |// Generated wrapper
71
+ |${imports}
72
+ |
73
+ |${misskeyClientJsCode}
74
+ |
75
+ |${wrappers}
76
+ |`->CodegenUtils.trimMargin
77
+ }
78
+
79
+ let generateWrapperDts = (~endpoints) => {
80
+ let endpointsByTag = OpenAPIParser.groupByTag(endpoints)
81
+ let tags = Dict.keysToArray(endpointsByTag)
82
+
83
+ let imports =
84
+ tags
85
+ ->Array.map(tag => {
86
+ let moduleName = CodegenUtils.toPascalCase(tag)
87
+ let typesToImport =
88
+ Dict.get(endpointsByTag, tag)
89
+ ->Option.getOr([])
90
+ ->Array.flatMap(endpoint => {
91
+ let pascalName = CodegenUtils.toPascalCase(
92
+ CodegenUtils.generateOperationName(endpoint.operationId, endpoint.path, endpoint.method),
93
+ )
94
+ if endpoint.requestBody->Option.isSome {
95
+ [` ${pascalName}Request,`, ` ${pascalName}Response,`]
96
+ } else {
97
+ [` ${pascalName}Response,`]
98
+ }
99
+ })
100
+ ->Array.join("\n")
101
+ `import type {
102
+ ${typesToImport}
103
+ } from '../types/${moduleName}.d.ts';`
104
+ })
105
+ ->Array.join("\n")
106
+
107
+ let namespaces =
108
+ tags
109
+ ->Array.map(tag => {
110
+ let moduleName = CodegenUtils.toPascalCase(tag)
111
+ let functions =
112
+ Dict.get(endpointsByTag, tag)
113
+ ->Option.getOr([])
114
+ ->Array.map(endpoint => {
115
+ let functionName = CodegenUtils.generateOperationName(
116
+ endpoint.operationId,
117
+ endpoint.path,
118
+ endpoint.method,
119
+ )
120
+ let pascalName = CodegenUtils.toPascalCase(functionName)
121
+ let docComment = endpoint.summary->Option.mapOr("", summary => {
122
+ let descriptionPart = endpoint.description->Option.mapOr("", description =>
123
+ description == summary ? "" : " - " ++ description
124
+ )
125
+ ` /** ${summary}${descriptionPart} */\n`
126
+ })
127
+ let requestParam = endpoint.requestBody->Option.isSome ? `, request: ${pascalName}Request` : ""
128
+ `${docComment} export function ${functionName}(client: MisskeyClient${requestParam}): Promise<${pascalName}Response>;`
129
+ })
130
+ ->Array.join("\n")
131
+ `
132
+ |export namespace ${moduleName} {
133
+ |${functions}
134
+ |}`
135
+ })
136
+ ->Array.join("\n\n")
137
+
138
+ `
139
+ |// Generated TypeScript definitions for wrapper
140
+ |${imports}
141
+ |
142
+ |export class MisskeyClient {
143
+ | constructor(baseUrl: string, token?: string);
144
+ | readonly baseUrl: string;
145
+ | readonly token?: string;
146
+ |}
147
+ |
148
+ |${namespaces}
149
+ |`->CodegenUtils.trimMargin
150
+ }
151
+
152
+ let generate = (~endpoints, ~outputDir, ~generatedModulePath="../generated") => {
153
+ Pipeline.fromFilesAndWarnings(
154
+ [
155
+ {
156
+ FileSystem.path: FileSystem.makePath(outputDir, "wrapper/index.mjs"),
157
+ content: generateWrapperMjs(~endpoints, ~generatedModulePath),
158
+ },
159
+ {
160
+ path: FileSystem.makePath(outputDir, "wrapper/index.d.ts"),
161
+ content: generateWrapperDts(~endpoints),
162
+ },
163
+ ],
164
+ [],
165
+ )
166
+ }
@@ -0,0 +1,82 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // Error.res - Compact error and warning types with helpers
4
+
5
+ // Error context for debugging (defined here to avoid circular dependency)
6
+ type context = {
7
+ path: string,
8
+ operation: string,
9
+ schema: option<JSON.t>, // Use JSON.t to avoid circular dependency with Types.jsonSchema
10
+ }
11
+
12
+ // Structured error types (keep original names for backward compat)
13
+ type t =
14
+ | SpecResolutionError({url: string, message: string})
15
+ | SchemaParseError({context: context, reason: string})
16
+ | ReferenceError({ref: string, context: context})
17
+ | ValidationError({schema: string, input: JSON.t, issues: array<string>})
18
+ | CircularSchemaError({ref: string, depth: int, path: string})
19
+ | FileWriteError({filePath: string, message: string})
20
+ | InvalidConfigError({field: string, message: string})
21
+ | UnknownError({message: string, context: option<context>})
22
+
23
+ // Warning types
24
+ module Warning = {
25
+ type t =
26
+ | FallbackToJson({reason: string, context: context})
27
+ | UnsupportedFeature({feature: string, fallback: string, location: string})
28
+ | DepthLimitReached({depth: int, path: string})
29
+ | MissingSchema({ref: string, location: string})
30
+ | IntersectionNotFullySupported({location: string, note: string})
31
+ | ComplexUnionSimplified({location: string, types: string})
32
+
33
+ let toString = w =>
34
+ switch w {
35
+ | FallbackToJson({reason, context}) =>
36
+ `⚠️ Falling back to JSON.t at '${context.path}' (${context.operation}): ${reason}`
37
+ | UnsupportedFeature({feature, fallback, location}) =>
38
+ `⚠️ Unsupported feature '${feature}' at '${location}', using fallback: ${fallback}`
39
+ | DepthLimitReached({depth, path}) =>
40
+ `⚠️ Depth limit ${depth->Int.toString} reached at '${path}', using simplified type`
41
+ | MissingSchema({ref, location}) =>
42
+ `⚠️ Schema reference '${ref}' not found at '${location}'`
43
+ | IntersectionNotFullySupported({location, note}) =>
44
+ `⚠️ Intersection type at '${location}' not fully supported: ${note}`
45
+ | ComplexUnionSimplified({location, types}) =>
46
+ `⚠️ Complex union at '${location}' simplified (types: ${types})`
47
+ }
48
+
49
+ let print = warnings =>
50
+ if Array.length(warnings) > 0 {
51
+ Console.log("\n⚠️ Warnings:")
52
+ warnings->Array.forEach(w => Console.log(toString(w)))
53
+ }
54
+ }
55
+
56
+ // Error helpers
57
+ let toString = e =>
58
+ switch e {
59
+ | SpecResolutionError({url, message}) => `Failed to resolve spec from '${url}': ${message}`
60
+ | SchemaParseError({context, reason}) =>
61
+ `Failed to parse schema at '${context.path}' (${context.operation}): ${reason}`
62
+ | ReferenceError({ref, context}) =>
63
+ `Failed to resolve reference '${ref}' at '${context.path}' (${context.operation})`
64
+ | ValidationError({schema, issues}) =>
65
+ `Validation failed for schema '${schema}': ${issues->Array.join(", ")}`
66
+ | CircularSchemaError({ref, depth, path}) =>
67
+ `Circular schema detected for '${ref}' at depth ${depth->Int.toString} (path: ${path})`
68
+ | FileWriteError({filePath, message}) => `Failed to write file '${filePath}': ${message}`
69
+ | InvalidConfigError({field, message}) => `Invalid configuration for field '${field}': ${message}`
70
+ | UnknownError({message, context}) =>
71
+ switch context {
72
+ | Some(ctx) => `Unknown error at '${ctx.path}' (${ctx.operation}): ${message}`
73
+ | None => `Unknown error: ${message}`
74
+ }
75
+ }
76
+
77
+ // Create context helper
78
+ let makeContext = (~path, ~operation, ~schema=?, ()) => {
79
+ path,
80
+ operation,
81
+ schema,
82
+ }
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // Config.res - Generation configuration types
4
+
5
+ type generationStrategy =
6
+ | Separate
7
+ | SharedBase
8
+
9
+ type breakingChangeHandling =
10
+ | Error
11
+ | Warn
12
+ | Ignore
13
+
14
+ type forkSpecConfig = {
15
+ name: string,
16
+ specPath: string,
17
+ }
18
+
19
+ type generationTargets = {
20
+ rescriptApi: bool, // Generate base ReScript API (always true by default)
21
+ rescriptWrapper: bool, // Generate ReScript thin wrapper (pipe-first)
22
+ typescriptDts: bool, // Generate .d.ts type definitions
23
+ typescriptWrapper: bool, // Generate TypeScript/JavaScript wrapper
24
+ }
25
+
26
+ type t = {
27
+ specPath: string,
28
+ forkSpecs: option<array<forkSpecConfig>>,
29
+ outputDir: string,
30
+ strategy: generationStrategy,
31
+ modulePerTag: bool,
32
+ includeTags: option<array<string>>,
33
+ excludeTags: option<array<string>>,
34
+ generateDiffReport: bool,
35
+ breakingChangeHandling: breakingChangeHandling,
36
+ generateDocOverrides: option<bool>,
37
+ docOverrideDir: option<string>,
38
+ targets: option<generationTargets>, // Generation targets (what to generate)
39
+ dtsOutputDir: option<string>, // Output directory for .d.ts files (default: "types")
40
+ wrapperOutputDir: option<string>, // Output directory for wrapper files (default: "wrapper")
41
+ baseInstanceName: option<string>, // Subdirectory name for base instance (e.g., "misskey-io")
42
+ baseModulePrefix: option<string>, // Module prefix for base instance (e.g., "MisskeyIo")
43
+ }
44
+
45
+ // Default configuration
46
+ let make = (
47
+ ~specPath,
48
+ ~outputDir,
49
+ ~strategy=SharedBase,
50
+ ~modulePerTag=true,
51
+ ~includeTags=?,
52
+ ~excludeTags=?,
53
+ ~generateDiffReport=true,
54
+ ~breakingChangeHandling=Warn,
55
+ ~forkSpecs=?,
56
+ ~generateDocOverrides=?,
57
+ ~docOverrideDir=?,
58
+ ~targets=?,
59
+ ~dtsOutputDir=?,
60
+ ~wrapperOutputDir=?,
61
+ ~baseInstanceName=?,
62
+ ~baseModulePrefix=?,
63
+ (),
64
+ ) => {
65
+ specPath,
66
+ outputDir,
67
+ strategy,
68
+ modulePerTag,
69
+ includeTags,
70
+ excludeTags,
71
+ generateDiffReport,
72
+ breakingChangeHandling,
73
+ forkSpecs,
74
+ generateDocOverrides,
75
+ docOverrideDir,
76
+ targets,
77
+ dtsOutputDir,
78
+ wrapperOutputDir,
79
+ baseInstanceName,
80
+ baseModulePrefix,
81
+ }
82
+
83
+ // Default generation targets
84
+ let defaultTargets = (): generationTargets => {
85
+ rescriptApi: true,
86
+ rescriptWrapper: false,
87
+ typescriptDts: false,
88
+ typescriptWrapper: false,
89
+ }
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ // GenerationContext.res - Shared context type for IR code generators
4
+
5
+ type t = {
6
+ mutable warnings: array<CodegenError.Warning.t>,
7
+ path: string,
8
+ insideComponentSchemas: bool, // Whether we're generating inside ComponentSchemas module
9
+ availableSchemas: option<array<string>>, // Schemas available in current module (for fork schemas)
10
+ modulePrefix: string, // Module prefix for qualified references (e.g., "MisskeyIo")
11
+ }
12
+
13
+ let make = (~path, ~insideComponentSchemas=false, ~availableSchemas=?, ~modulePrefix="", ()): t => {
14
+ warnings: [],
15
+ path,
16
+ insideComponentSchemas,
17
+ availableSchemas,
18
+ modulePrefix,
19
+ }
20
+
21
+ let addWarning = (ctx: t, warning: CodegenError.Warning.t): unit => {
22
+ ctx.warnings->Array.push(warning)
23
+ }