@hasagi/schema 0.7.0 → 0.7.1

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 ADDED
@@ -0,0 +1,46 @@
1
+ # @hasagi/schema
2
+
3
+ The schema / type-generation toolkit behind Hasagi. It reads the League of Legends client's own API
4
+ description (the LCU "help" schema, exposed by a running client), builds an OpenAPI v3 (swagger)
5
+ document from it, and generates the TypeScript definitions that power typed LCU requests and events.
6
+
7
+ This is the engine used by the [`@hasagi/cli`](https://www.npmjs.com/package/@hasagi/cli) `schema`
8
+ command and the generated types shipped in
9
+ [`@hasagi/core/types`](https://www.npmjs.com/package/@hasagi/core) and
10
+ [`@hasagi/types`](https://www.npmjs.com/package/@hasagi/types).
11
+
12
+ > Most users don't need this package directly — use `hasagi schema` (from `@hasagi/cli`) to generate
13
+ > types, or just consume the pre-generated types. Use `@hasagi/schema` only if you're building your
14
+ > own generation pipeline.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @hasagi/schema
20
+ ```
21
+
22
+ ## API
23
+
24
+ ```ts
25
+ import { getExtendedHelp, getSwagger, getTypeScript } from "@hasagi/schema";
26
+
27
+ // 1. Fetch and normalize the LCU help schema (requires a running League client).
28
+ const xhelp = await getExtendedHelp();
29
+
30
+ // 2. Build an OpenAPI v3 (swagger) document.
31
+ const swagger = await getSwagger(xhelp);
32
+
33
+ // 3. Generate the TypeScript definitions.
34
+ const { lcuTypes, lcuEndpoints, lcuEvents } = await getTypeScript(swagger, xhelp /*, namespace? */);
35
+ ```
36
+
37
+ - **`getExtendedHelp()`** — retrieves and normalizes the LCU's API description from a running client.
38
+ - **`getSwagger(xhelp)`** — produces an OpenAPI v3 schema object (with helpers for generating types).
39
+ - **`getTypeScript(swagger, xhelp, namespace?)`** — returns the generated `lcuTypes`, `lcuEndpoints`
40
+ and `lcuEvents` source strings. Pass a `namespace` to wrap the generated types.
41
+
42
+ ## Disclaimer
43
+
44
+ Hasagi is not endorsed by Riot Games and does not reflect the views or opinions of Riot Games or
45
+ anyone officially involved in producing or managing Riot Games properties. Riot Games and all
46
+ associated properties are trademarks or registered trademarks of Riot Games, Inc.
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@hasagi/schema",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "keywords": [
5
5
  "hasagi"
6
6
  ],
7
7
  "author": "dysolix",
8
8
  "license": "MIT",
9
9
  "dependencies": {
10
- "@hasagi/core": "^0.6.3",
11
- "axios": "^1.7.9"
10
+ "@hasagi/core": "^0.7.0",
11
+ "axios": "^1.17.0"
12
12
  },
13
13
  "exports": {
14
14
  ".": "./index.js"
@@ -17,6 +17,6 @@
17
17
  "type": "module",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "https://github.com/dysolix/hasagi-schema.git"
20
+ "url": "git+https://github.com/dysolix/hasagi-schema.git"
21
21
  }
22
22
  }
@@ -1,3 +0,0 @@
1
- import { ExtendedHelp } from "./types.js";
2
- import { OpenAPIObject } from "./open-api-types.js";
3
- export declare function generateOpenAPIv3(schema: ExtendedHelp): Promise<OpenAPIObject>;
@@ -1,243 +0,0 @@
1
- import HasagiLiteClient from "@hasagi/core";
2
- function getRootType(input) {
3
- const isObject = input.fields.length > 0;
4
- const isEnum = input.values.length > 0;
5
- const required = [];
6
- const properties = {};
7
- input.fields.forEach(f => {
8
- if (properties[f.name] !== undefined) {
9
- console.log(`Duplicate field '${f.name}' in type '${input.name}'`);
10
- return;
11
- }
12
- properties[f.name] = getType(f);
13
- if (!f.optional)
14
- required.push(f.name);
15
- });
16
- return {
17
- type: isEnum ? "string" : "object",
18
- description: input.description,
19
- properties: isObject ? properties : undefined,
20
- enum: isEnum ? input.values.sort((v1, v2) => v2.value - v1.value).map(v => v.name) : undefined,
21
- additionalProperties: !isObject && !isEnum,
22
- required: required.length > 0 ? required : undefined
23
- };
24
- }
25
- function getType(input) {
26
- const type = typeof input === "string" ? input : input.type.type;
27
- switch (type) {
28
- case "string":
29
- return { type: "string" };
30
- case "uint8":
31
- case "uint16":
32
- case "uint32":
33
- case "uint64":
34
- return { type: "integer", format: type, minimum: 0 };
35
- case "int8":
36
- case "int16":
37
- case "int32":
38
- case "int64":
39
- return { type: "integer", format: type };
40
- case "bool":
41
- return { type: "boolean" };
42
- case "double":
43
- case "float":
44
- return { type: "number", format: type };
45
- case "vector":
46
- return { type: "array", items: getType(input.type.elementType) };
47
- case "map": {
48
- return { type: "object", additionalProperties: getType(input.type.elementType) };
49
- }
50
- case "object":
51
- return { type: "object", additionalProperties: true };
52
- case "":
53
- return undefined;
54
- default:
55
- return { $ref: `#/components/schemas/${type}` };
56
- }
57
- }
58
- export async function generateOpenAPIv3(schema) {
59
- const client = new HasagiLiteClient();
60
- await client.connect();
61
- const region = await client.request({ method: "get", url: "/riotclient/region-locale" });
62
- const { version } = await client.request({ method: "get", url: "/system/v1/builds" });
63
- const functionsWithMissingData = [];
64
- const openAPISchema = {
65
- openapi: "3.0.0",
66
- info: {
67
- title: "LCU SCHEMA",
68
- description: "",
69
- version
70
- },
71
- components: { schemas: {} },
72
- paths: {}
73
- };
74
- let tags = [];
75
- schema.types.forEach(type => openAPISchema.components.schemas[type.name] = getRootType(type));
76
- schema.functions.forEach(func => {
77
- if (func.overridden) {
78
- functionsWithMissingData.push(func.name);
79
- }
80
- if (func.method === null || func.path === null) {
81
- console.log(`Function '${func.name}' does not have a http method or path.`);
82
- return;
83
- }
84
- openAPISchema.paths[func.path] = openAPISchema.paths[func.path] ?? {};
85
- const operation = endpointToOperation(func, openAPISchema);
86
- // @ts-expect-error
87
- openAPISchema.paths[func.path][func.method.toLowerCase()] = operation;
88
- });
89
- let tagCount = {};
90
- Object.entries(openAPISchema.paths).forEach(([path, methods]) => {
91
- Object.values(methods).forEach((operation) => {
92
- operation.tags?.forEach(tag => tagCount[tag] = (tagCount[tag] ?? 0) + 1);
93
- });
94
- });
95
- Object.entries(openAPISchema.paths).forEach(([path, methods]) => {
96
- Object.values(methods).forEach((operation) => {
97
- operation.tags = operation.tags?.filter(tag => tagCount[tag] > 1 || tag.startsWith("Plugin")) ?? [];
98
- if ((operation.tags?.length ?? 0) < 1) {
99
- operation.tags = [path.split("/")[1]];
100
- }
101
- if (operation.tags.length === 0)
102
- operation.tags.push("other");
103
- operation.tags = operation.tags.map(tag => tag.startsWith("Plugin") ? tag : tag.toLowerCase());
104
- });
105
- });
106
- tagCount = {};
107
- Object.entries(openAPISchema.paths).forEach(([path, methods]) => {
108
- Object.values(methods).forEach((operation) => {
109
- operation.tags?.forEach(tag => tagCount[tag] = (tagCount[tag] ?? 0) + 1);
110
- });
111
- });
112
- Object.entries(openAPISchema.paths).forEach(([path, methods]) => {
113
- Object.values(methods).forEach((operation) => {
114
- operation.tags = operation.tags?.filter(tag => tagCount[tag] > 1 || tag.startsWith("Plugin")) ?? [];
115
- if (operation.tags.length === 0)
116
- operation.tags.push("other");
117
- operation.tags = operation.tags
118
- .map(tag => tag.startsWith("Plugin") ? tag : tag.toLowerCase())
119
- .map(tag => tag.replace("$", ""));
120
- tags.push(...operation.tags.filter(tag => !tags.includes(tag)));
121
- });
122
- });
123
- openAPISchema.tags = tags.map(tag => {
124
- return {
125
- name: tag
126
- };
127
- }).sort((t1, t2) => {
128
- if (t1.name.includes("Plugin") && !t2.name.includes("Plugin"))
129
- return 1;
130
- if (!t1.name.includes("Plugin") && t2.name.includes("Plugin"))
131
- return -1;
132
- if (t1.name.includes("Plugin") && t2.name.includes("Plugin")) {
133
- if (t1.name.includes("Plugin lol") && !t2.name.includes("Plugin lol"))
134
- return 1;
135
- if (!t1.name.includes("Plugin lol") && t2.name.includes("Plugin lol"))
136
- return -1;
137
- }
138
- if (t1.name == "other" && t2.name != "other")
139
- return 1;
140
- if (t1.name != "other" && t2.name == "other")
141
- return -1;
142
- return t1.name.localeCompare(t2.name);
143
- });
144
- let i = 1;
145
- openAPISchema.info.description =
146
- `
147
- Auto-generated using LCU's /help endpoint.
148
- The following endpoints are not entirely auto-generated because their /help response is missing necessary fields:
149
-
150
- ${functionsWithMissingData.map(func => `${i++}.\t${func}`).join("\n")}
151
-
152
- ### Disclaimer
153
-
154
- dysolix.dev is not endorsed by Riot Games and does not reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties.
155
- Riot Games and all associated properties are trademarks or registered trademarks of Riot Games, Inc`;
156
- return openAPISchema;
157
- }
158
- function endpointToOperation(endpoint, schema) {
159
- let parameters = undefined;
160
- let response = { description: "Success response" };
161
- let requestBody = undefined;
162
- parameters = endpoint.pathParams?.map(param => {
163
- const p = endpoint.arguments.find(arg => arg.name.replace("+", "") === param.replace("+", ""));
164
- return {
165
- in: "path",
166
- name: param,
167
- required: true,
168
- schema: p === undefined ? { type: "string" } : getType(p)
169
- };
170
- }) ?? [];
171
- const nonPathParam = endpoint.arguments.slice(endpoint.pathParams?.length ?? 0);
172
- if (nonPathParam.length > 1) {
173
- //console.log(`Endpoint '${endpoint.name}' has more than one non-path parameter.`)
174
- nonPathParam.forEach(arg => {
175
- parameters.push({
176
- in: "query",
177
- name: arg.name,
178
- schema: getType(arg),
179
- required: !arg.optional
180
- });
181
- });
182
- }
183
- else if (endpoint.method === "GET" || endpoint.method === "DELETE" || endpoint.method === "HEAD" || endpoint.method === "OPTIONS" || endpoint.method === "TRACE") {
184
- const params = endpoint.arguments.slice(parameters.length).flatMap(arg => {
185
- const type = getType(arg);
186
- if ("$ref" in type) {
187
- const schemaObject = schema.components.schemas[type.$ref.split("/").at(-1)];
188
- if (schemaObject.properties)
189
- return Object.entries(schemaObject.properties).map(entry => {
190
- return {
191
- in: "query",
192
- name: entry[0],
193
- type: entry[1],
194
- };
195
- });
196
- return {
197
- in: "query",
198
- schema: type,
199
- name: arg.name
200
- };
201
- }
202
- else {
203
- return {
204
- in: "query",
205
- required: !arg.optional,
206
- name: arg.name,
207
- schema: getType(arg)
208
- };
209
- }
210
- });
211
- parameters.push(...params);
212
- }
213
- else {
214
- const bodyType = endpoint.arguments.slice(parameters.length).at(0);
215
- if (bodyType)
216
- requestBody = {
217
- content: {
218
- "application/json": {
219
- schema: getType(bodyType)
220
- }
221
- }
222
- };
223
- }
224
- const returnType = getType({ type: endpoint.returns });
225
- response = returnType !== undefined ? { content: { "application/json": { schema: returnType } }, description: "Success response" } : { description: "Success response" };
226
- const tags = [];
227
- if (endpoint.path?.startsWith("/lol-"))
228
- tags.push("Plugin " + endpoint.path.split("/")[1]);
229
- else if (endpoint.path?.startsWith("/{plugin}"))
230
- tags.push("Plugin Asset Serving");
231
- else
232
- tags.push(endpoint.path.split("/")[1]);
233
- return {
234
- operationId: endpoint.name,
235
- description: endpoint.description,
236
- tags: endpoint.tags.filter(tag => !["Plugins", "$remoting-binding-module"].includes(tag)),
237
- parameters,
238
- requestBody,
239
- responses: {
240
- "2XX": response
241
- },
242
- };
243
- }