osury 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 +112 -0
- package/bin/osury.mjs +103 -0
- package/package.json +60 -0
- package/src/Codegen.res.mjs +578 -0
- package/src/Errors.res.mjs +103 -0
- package/src/OpenAPIParser.res.mjs +103 -0
- package/src/Schema.gen.tsx +27 -0
- package/src/Schema.res.mjs +664 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# osury
|
|
2
|
+
|
|
3
|
+
Generate ReScript types with [Sury](https://github.com/DZakh/sury) schemas from OpenAPI specifications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- OpenAPI 3.x → ReScript types
|
|
8
|
+
- `@schema` annotations for Sury PPX validation
|
|
9
|
+
- `@genType` for TypeScript interop
|
|
10
|
+
- Union types extracted as proper variants with `@tag("_tag")`
|
|
11
|
+
- Automatic deduplication of identical union structures
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -D osury
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### CLI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Generate to default ./Generated.res
|
|
25
|
+
npx osury openapi.json
|
|
26
|
+
|
|
27
|
+
# Generate to specific file
|
|
28
|
+
npx osury openapi.json src/API.res
|
|
29
|
+
|
|
30
|
+
# With explicit output flag
|
|
31
|
+
npx osury generate openapi.json -o src/Schema.res
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Output Example
|
|
35
|
+
|
|
36
|
+
Input:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"components": {
|
|
40
|
+
"schemas": {
|
|
41
|
+
"User": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"id": { "type": "integer" },
|
|
45
|
+
"name": { "type": "string" },
|
|
46
|
+
"role": {
|
|
47
|
+
"anyOf": [
|
|
48
|
+
{ "$ref": "#/components/schemas/Admin" },
|
|
49
|
+
{ "$ref": "#/components/schemas/Guest" }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"required": ["id", "name"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Output:
|
|
61
|
+
```rescript
|
|
62
|
+
@genType
|
|
63
|
+
@tag("_tag")
|
|
64
|
+
@schema
|
|
65
|
+
type adminOrGuest = Admin(admin) | Guest(guest)
|
|
66
|
+
|
|
67
|
+
@genType
|
|
68
|
+
@schema
|
|
69
|
+
type user = {
|
|
70
|
+
id: int,
|
|
71
|
+
name: string,
|
|
72
|
+
role: option<adminOrGuest>
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Generated Annotations
|
|
77
|
+
|
|
78
|
+
| Annotation | Purpose |
|
|
79
|
+
|------------|---------|
|
|
80
|
+
| `@genType` | TypeScript type generation |
|
|
81
|
+
| `@schema` | Sury PPX validation schema |
|
|
82
|
+
| `@tag("_tag")` | Discriminated union support |
|
|
83
|
+
|
|
84
|
+
## Requirements
|
|
85
|
+
|
|
86
|
+
For the generated code to compile, your project needs:
|
|
87
|
+
|
|
88
|
+
- [rescript](https://rescript-lang.org/) >= 12.0
|
|
89
|
+
- [sury](https://github.com/DZakh/sury) >= 11.0 (for `@schema`)
|
|
90
|
+
- [sury-ppx](https://github.com/DZakh/sury) >= 11.0 (for `@schema` PPX)
|
|
91
|
+
- [gentype](https://github.com/rescript-lang/gentype) (for `@genType`)
|
|
92
|
+
|
|
93
|
+
## Type Mapping
|
|
94
|
+
|
|
95
|
+
| OpenAPI | ReScript |
|
|
96
|
+
|---------|----------|
|
|
97
|
+
| `string` | `string` |
|
|
98
|
+
| `number` | `float` |
|
|
99
|
+
| `integer` | `int` |
|
|
100
|
+
| `boolean` | `bool` |
|
|
101
|
+
| `array` | `array<T>` |
|
|
102
|
+
| `object` | `{ field: T }` |
|
|
103
|
+
| `$ref` | type reference |
|
|
104
|
+
| `anyOf` (nullable) | `option<T>` |
|
|
105
|
+
| `anyOf` (union) | variant type |
|
|
106
|
+
| `oneOf` (tagged) | poly variant |
|
|
107
|
+
| `additionalProperties` | `Dict.t<T>` |
|
|
108
|
+
| `default` value | field is required |
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
package/bin/osury.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as OpenAPIParser from "../src/OpenAPIParser.res.mjs";
|
|
4
|
+
import * as Codegen from "../src/Codegen.res.mjs";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log(`
|
|
12
|
+
osury - Generate ReScript types from OpenAPI schema
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
osury <input.json> [output.res]
|
|
16
|
+
osury generate <input.json> -o <output.res>
|
|
17
|
+
|
|
18
|
+
Arguments:
|
|
19
|
+
input.json Path to OpenAPI/JSON Schema file
|
|
20
|
+
output.res Output ReScript file (default: ./Generated.res)
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-o, --output Output file path
|
|
24
|
+
-h, --help Show this help
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
osury openapi.json
|
|
28
|
+
osury openapi.json src/API.res
|
|
29
|
+
osury generate ./schema.json -o ./src/Schema.res
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseArgs(args) {
|
|
34
|
+
const options = {
|
|
35
|
+
input: null,
|
|
36
|
+
output: "./Generated.res",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < args.length) {
|
|
41
|
+
const arg = args[i];
|
|
42
|
+
|
|
43
|
+
if (arg === "-h" || arg === "--help") {
|
|
44
|
+
printHelp();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} else if (arg === "-o" || arg === "--output") {
|
|
47
|
+
options.output = args[++i];
|
|
48
|
+
} else if (arg === "generate") {
|
|
49
|
+
// Skip 'generate' command word
|
|
50
|
+
} else if (!options.input) {
|
|
51
|
+
options.input = arg;
|
|
52
|
+
} else if (options.output === "./Generated.res") {
|
|
53
|
+
options.output = arg;
|
|
54
|
+
}
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return options;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function generate(inputPath, outputPath) {
|
|
62
|
+
// Read input file
|
|
63
|
+
if (!fs.existsSync(inputPath)) {
|
|
64
|
+
console.error(`Error: Input file not found: ${inputPath}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const doc = JSON.parse(fs.readFileSync(inputPath, "utf8"));
|
|
69
|
+
|
|
70
|
+
// Parse and generate
|
|
71
|
+
const result = OpenAPIParser.parseDocument(doc);
|
|
72
|
+
|
|
73
|
+
if (result.TAG === "Ok") {
|
|
74
|
+
const code = Codegen.generateModule(result._0);
|
|
75
|
+
|
|
76
|
+
// Ensure output directory exists
|
|
77
|
+
const outputDir = path.dirname(outputPath);
|
|
78
|
+
if (outputDir && !fs.existsSync(outputDir)) {
|
|
79
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(outputPath, code);
|
|
83
|
+
console.log(`Generated ${result._0.length} types to ${outputPath}`);
|
|
84
|
+
} else {
|
|
85
|
+
console.error("Parse errors:");
|
|
86
|
+
result._0.forEach((err) => {
|
|
87
|
+
const location = err.location?.path?.join(".") || "root";
|
|
88
|
+
console.error(` [${location}] ${err.kind.TAG}: ${err.kind._0 || ""}`);
|
|
89
|
+
});
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Main
|
|
95
|
+
const options = parseArgs(args);
|
|
96
|
+
|
|
97
|
+
if (!options.input) {
|
|
98
|
+
console.error("Error: Input file required\n");
|
|
99
|
+
printHelp();
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
generate(options.input, options.output);
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "osury",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"description": "Generate ReScript types with Sury schemas from OpenAPI specifications",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"osury": "./bin/osury.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/Codegen.res.mjs",
|
|
13
|
+
"src/Errors.res.mjs",
|
|
14
|
+
"src/OpenAPIParser.res.mjs",
|
|
15
|
+
"src/Schema.res.mjs",
|
|
16
|
+
"src/Schema.gen.tsx",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"res:clean": "rescript clean",
|
|
21
|
+
"res:build": "rescript",
|
|
22
|
+
"res:dev": "rescript watch",
|
|
23
|
+
"codegen": "node scripts/codegen.mjs",
|
|
24
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
25
|
+
"prepublishOnly": "npm run res:build && npm test"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@rescript/core": "^1.6.1",
|
|
29
|
+
"gentype": "^4.5.0",
|
|
30
|
+
"jest": "^30.2.0",
|
|
31
|
+
"rescript": "^12.1.0",
|
|
32
|
+
"sury": "^11.0.0-alpha.4",
|
|
33
|
+
"sury-ppx": "^11.0.0-alpha.2"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"sury": ">=11.0.0-alpha.4",
|
|
37
|
+
"sury-ppx": ">=11.0.0-alpha.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"sury": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"sury-ppx": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"openapi",
|
|
49
|
+
"rescript",
|
|
50
|
+
"codegen",
|
|
51
|
+
"sury",
|
|
52
|
+
"schema",
|
|
53
|
+
"typescript",
|
|
54
|
+
"types"
|
|
55
|
+
],
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/anthropic/osury"
|
|
59
|
+
}
|
|
60
|
+
}
|