ebely 0.0.1 → 0.0.2
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/bin/ebely.mjs +70 -0
- package/dist/index.d.ts +273 -0
- package/dist/index.js +722 -0
- package/dist/index.mjs +691 -0
- package/package.json +26 -7
- package/skills/ebely-setup/SKILL.md +29 -0
- package/skills/ebely-write-tests/SKILL.md +28 -0
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.changeset/fast-goats-doubt.md +0 -5
- package/.github/workflows/main.yml +0 -21
- package/.github/workflows/publish.yml +0 -36
- package/index.ts +0 -3
- package/tsconfig.json +0 -21
package/dist/index.js
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// index.ts
|
|
21
|
+
var evely_exports = {};
|
|
22
|
+
__export(evely_exports, {
|
|
23
|
+
ApiResponse: () => ApiResponse,
|
|
24
|
+
BaseStore: () => BaseStore,
|
|
25
|
+
EbelyAssertionError: () => EbelyAssertionError,
|
|
26
|
+
HookRegistry: () => HookRegistry,
|
|
27
|
+
generateClient: () => generateClient
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(evely_exports);
|
|
30
|
+
|
|
31
|
+
// src/base-store.ts
|
|
32
|
+
var BaseStore = class {
|
|
33
|
+
store = /* @__PURE__ */ new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Типизированный доступ к эндпоинтам ИЗНУТРИ методов-сценариев
|
|
36
|
+
* («actions»): `this.api.<группа>.<метод>(...)`. Само значение
|
|
37
|
+
* подставляет генерируемый `World` (для user-store — клиент этого
|
|
38
|
+
* пользователя, для world-store — анонимный клиент), поэтому здесь это
|
|
39
|
+
* объявление без инициализатора. Тип `Api` пользователь задаёт вторым
|
|
40
|
+
* дженериком, передавая сгенерированный `WorldApi`:
|
|
41
|
+
*
|
|
42
|
+
* import type { WorldApi } from './generated'
|
|
43
|
+
* class UserStore extends BaseStore<Vars, WorldApi> {
|
|
44
|
+
* async fullRegister(args: { email: string; password: string }) {
|
|
45
|
+
* await this.api.auth.register({ body: args })
|
|
46
|
+
* await this.api.auth.confirm({ body: { code: '0000' } })
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* (Тип импортируется как `import type` → рантайм-цикла нет, ровно как
|
|
51
|
+
* у `hooks.ts`; см. ARCHITECTURE.md §7–§8.)
|
|
52
|
+
*/
|
|
53
|
+
api;
|
|
54
|
+
/** Сохранить внутреннюю переменную пользователя. */
|
|
55
|
+
set(args) {
|
|
56
|
+
this.store.set(args.key, args.value);
|
|
57
|
+
}
|
|
58
|
+
/** Прочитать внутреннюю переменную пользователя (undefined, если не задана). */
|
|
59
|
+
get(args) {
|
|
60
|
+
return this.store.get(args.key);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/response.ts
|
|
65
|
+
var EbelyAssertionError = class extends Error {
|
|
66
|
+
constructor(message) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.name = "EbelyAssertionError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function safeJson(value) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.stringify(value) ?? String(value);
|
|
74
|
+
} catch {
|
|
75
|
+
return String(value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function matchPartial(args) {
|
|
79
|
+
const { actual, expected, path = "" } = args;
|
|
80
|
+
if (Array.isArray(expected)) {
|
|
81
|
+
if (!Array.isArray(actual))
|
|
82
|
+
return { path, expected, actual };
|
|
83
|
+
for (let i = 0; i < expected.length; i++) {
|
|
84
|
+
const m = matchPartial({
|
|
85
|
+
actual: actual[i],
|
|
86
|
+
expected: expected[i],
|
|
87
|
+
path: `${path}[${i}]`
|
|
88
|
+
});
|
|
89
|
+
if (m)
|
|
90
|
+
return m;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (expected !== null && typeof expected === "object") {
|
|
95
|
+
if (actual === null || typeof actual !== "object" || Array.isArray(actual)) {
|
|
96
|
+
return { path, expected, actual };
|
|
97
|
+
}
|
|
98
|
+
const exp = expected;
|
|
99
|
+
const act = actual;
|
|
100
|
+
for (const key of Object.keys(exp)) {
|
|
101
|
+
const m = matchPartial({
|
|
102
|
+
actual: act[key],
|
|
103
|
+
expected: exp[key],
|
|
104
|
+
path: path ? `${path}.${key}` : key
|
|
105
|
+
});
|
|
106
|
+
if (m)
|
|
107
|
+
return m;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return Object.is(actual, expected) ? null : { path, expected, actual };
|
|
112
|
+
}
|
|
113
|
+
function assertResponse(args) {
|
|
114
|
+
const { actualStatus, actualBody, expectedStatus, expectedBody } = args;
|
|
115
|
+
if (actualStatus !== expectedStatus) {
|
|
116
|
+
throw new EbelyAssertionError(
|
|
117
|
+
`\u041E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441 ${expectedStatus}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D ${actualStatus}.
|
|
118
|
+
\u0422\u0435\u043B\u043E \u043E\u0442\u0432\u0435\u0442\u0430: ${safeJson(actualBody)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (expectedBody === void 0)
|
|
122
|
+
return;
|
|
123
|
+
const mismatch = matchPartial({ actual: actualBody, expected: expectedBody });
|
|
124
|
+
if (mismatch) {
|
|
125
|
+
throw new EbelyAssertionError(
|
|
126
|
+
`\u0422\u0435\u043B\u043E \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u043F\u043E \u043F\u0443\u0442\u0438 "${mismatch.path || "<root>"}": \u043E\u0436\u0438\u0434\u0430\u043B\u043E\u0441\u044C ${safeJson(mismatch.expected)}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E ${safeJson(mismatch.actual)}.
|
|
127
|
+
\u041F\u043E\u043B\u043D\u043E\u0435 \u0442\u0435\u043B\u043E: ${safeJson(actualBody)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
var ApiResponse = class {
|
|
132
|
+
/** Фактический HTTP-статус ответа. */
|
|
133
|
+
status;
|
|
134
|
+
/** Распарсенное тело ответа. */
|
|
135
|
+
body;
|
|
136
|
+
constructor(args) {
|
|
137
|
+
this.status = args.status;
|
|
138
|
+
this.body = args.body;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Проверяет статус и (опционально) часть тела ответа.
|
|
142
|
+
*
|
|
143
|
+
* @param status Ожидаемый статус. Подсказывается интеллисенсом из
|
|
144
|
+
* swagger; незадекларированный литерал — ошибка типов. Чтобы
|
|
145
|
+
* проверить статус, которого нет в схеме (например `400`),
|
|
146
|
+
* используйте `res.assert(400 as any, ...)`.
|
|
147
|
+
* @param expectedBody Необязательная часть тела: проверяются только
|
|
148
|
+
* переданные поля (глубоко-частично), остальные игнорируются.
|
|
149
|
+
* @returns тот же объект ответа — удобно читать `.body` после проверки.
|
|
150
|
+
*/
|
|
151
|
+
assert(status, expectedBody) {
|
|
152
|
+
assertResponse({
|
|
153
|
+
actualStatus: this.status,
|
|
154
|
+
actualBody: this.body,
|
|
155
|
+
expectedStatus: status,
|
|
156
|
+
expectedBody
|
|
157
|
+
});
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/hooks.ts
|
|
163
|
+
var HookRegistry = class {
|
|
164
|
+
beforeMap = /* @__PURE__ */ new Map();
|
|
165
|
+
afterMap = /* @__PURE__ */ new Map();
|
|
166
|
+
/** Зарегистрировать `before`-хук на ключ `"<группа>.<метод>"`. */
|
|
167
|
+
before(args) {
|
|
168
|
+
const list = this.beforeMap.get(args.key) ?? [];
|
|
169
|
+
list.push(args.fn);
|
|
170
|
+
this.beforeMap.set(args.key, list);
|
|
171
|
+
}
|
|
172
|
+
/** Зарегистрировать `after`-хук на ключ `"<группа>.<метод>"`. */
|
|
173
|
+
after(args) {
|
|
174
|
+
const list = this.afterMap.get(args.key) ?? [];
|
|
175
|
+
list.push(args.fn);
|
|
176
|
+
this.afterMap.set(args.key, list);
|
|
177
|
+
}
|
|
178
|
+
/** Прогнать все `before`-хуки ключа по очереди (await на async). */
|
|
179
|
+
async runBefore(args) {
|
|
180
|
+
for (const fn of this.beforeMap.get(args.key) ?? []) {
|
|
181
|
+
await fn({ request: args.request, ctx: args.ctx });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Прогнать все `after`-хуки ключа по очереди (await на async). */
|
|
185
|
+
async runAfter(args) {
|
|
186
|
+
for (const fn of this.afterMap.get(args.key) ?? []) {
|
|
187
|
+
await fn({ request: args.request, response: args.response, ctx: args.ctx });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/generate-client.ts
|
|
193
|
+
var import_promises2 = require("fs/promises");
|
|
194
|
+
var import_node_path2 = require("path");
|
|
195
|
+
|
|
196
|
+
// src/generator/schema.ts
|
|
197
|
+
function schemaToType(args) {
|
|
198
|
+
const { schema, spec, indent = 0 } = args;
|
|
199
|
+
if (!schema)
|
|
200
|
+
return "unknown";
|
|
201
|
+
if (typeof schema.$ref === "string") {
|
|
202
|
+
const resolved = resolveRef({ ref: schema.$ref, spec });
|
|
203
|
+
return schemaToType({ schema: resolved, spec, indent });
|
|
204
|
+
}
|
|
205
|
+
for (const key of ["allOf", "oneOf", "anyOf"]) {
|
|
206
|
+
if (Array.isArray(schema[key])) {
|
|
207
|
+
const joiner = key === "allOf" ? " & " : " | ";
|
|
208
|
+
return schema[key].map((s) => schemaToType({ schema: s, spec, indent })).join(joiner);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (Array.isArray(schema.enum)) {
|
|
212
|
+
return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
213
|
+
}
|
|
214
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
215
|
+
const rendered = types.map((type) => {
|
|
216
|
+
switch (type) {
|
|
217
|
+
case "string":
|
|
218
|
+
return "string";
|
|
219
|
+
case "number":
|
|
220
|
+
case "integer":
|
|
221
|
+
return "number";
|
|
222
|
+
case "boolean":
|
|
223
|
+
return "boolean";
|
|
224
|
+
case "null":
|
|
225
|
+
return "null";
|
|
226
|
+
case "array":
|
|
227
|
+
return `Array<${schemaToType({ schema: schema.items, spec, indent })}>`;
|
|
228
|
+
case "object": {
|
|
229
|
+
const props = schema.properties ?? {};
|
|
230
|
+
const required = schema.required ?? [];
|
|
231
|
+
const keys = Object.keys(props);
|
|
232
|
+
if (keys.length === 0)
|
|
233
|
+
return "Record<string, unknown>";
|
|
234
|
+
const pad = " ".repeat(indent + 1);
|
|
235
|
+
const closePad = " ".repeat(indent);
|
|
236
|
+
const lines = keys.map((k) => {
|
|
237
|
+
const optional = required.includes(k) ? "" : "?";
|
|
238
|
+
const valueType = schemaToType({ schema: props[k], spec, indent: indent + 1 });
|
|
239
|
+
return `${pad}${JSON.stringify(k)}${optional}: ${valueType}`;
|
|
240
|
+
});
|
|
241
|
+
return `{
|
|
242
|
+
${lines.join("\n")}
|
|
243
|
+
${closePad}}`;
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return "unknown";
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
return rendered.join(" | ");
|
|
250
|
+
}
|
|
251
|
+
function resolveRef(args) {
|
|
252
|
+
const { ref, spec } = args;
|
|
253
|
+
if (!ref.startsWith("#/"))
|
|
254
|
+
return void 0;
|
|
255
|
+
return ref.slice(2).split("/").reduce((acc, part) => acc ? acc[part] : void 0, spec);
|
|
256
|
+
}
|
|
257
|
+
function pickResponseSchema(args) {
|
|
258
|
+
const { responses } = args;
|
|
259
|
+
const status = Object.keys(responses ?? {}).find((s) => s.startsWith("2")) ?? "default";
|
|
260
|
+
return responses?.[status]?.content?.["application/json"]?.schema;
|
|
261
|
+
}
|
|
262
|
+
function collectResponseSchemas(args) {
|
|
263
|
+
const { responses } = args;
|
|
264
|
+
const out = [];
|
|
265
|
+
for (const key of Object.keys(responses ?? {})) {
|
|
266
|
+
const status = Number(key);
|
|
267
|
+
if (!Number.isInteger(status))
|
|
268
|
+
continue;
|
|
269
|
+
out.push({
|
|
270
|
+
status,
|
|
271
|
+
schema: responses[key]?.content?.["application/json"]?.schema
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/generator/operations.ts
|
|
278
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
279
|
+
function collectOperations(args) {
|
|
280
|
+
const { spec } = args;
|
|
281
|
+
const operations = [];
|
|
282
|
+
for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
283
|
+
for (const method of HTTP_METHODS) {
|
|
284
|
+
const op = pathItem[method];
|
|
285
|
+
if (!op)
|
|
286
|
+
continue;
|
|
287
|
+
const operationId = op.operationId ?? `${method}${path}`;
|
|
288
|
+
const dotIndex = operationId.indexOf(".");
|
|
289
|
+
const group = dotIndex === -1 ? "default" : operationId.slice(0, dotIndex);
|
|
290
|
+
const name = dotIndex === -1 ? operationId : operationId.slice(dotIndex + 1);
|
|
291
|
+
const parameters = op.parameters ?? [];
|
|
292
|
+
const pathParams = parameters.filter((p) => p.in === "path").map((p) => p.name);
|
|
293
|
+
const queryParams = parameters.filter((p) => p.in === "query").map((p) => p.name);
|
|
294
|
+
const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
|
|
295
|
+
const responseSchema = pickResponseSchema({ responses: op.responses, spec });
|
|
296
|
+
const responseType = schemaToType({ schema: responseSchema, spec, indent: 3 });
|
|
297
|
+
const declared = collectResponseSchemas({ responses: op.responses, spec });
|
|
298
|
+
const responses = declared.length > 0 ? declared.map((r) => ({
|
|
299
|
+
status: r.status,
|
|
300
|
+
bodyType: schemaToType({ schema: r.schema, spec, indent: 4 })
|
|
301
|
+
})) : [{ status: 200, bodyType: responseType }];
|
|
302
|
+
operations.push({
|
|
303
|
+
group,
|
|
304
|
+
name,
|
|
305
|
+
method,
|
|
306
|
+
path,
|
|
307
|
+
pathParams,
|
|
308
|
+
queryParams,
|
|
309
|
+
bodyType: bodySchema ? schemaToType({ schema: bodySchema, spec, indent: 4 }) : null,
|
|
310
|
+
bodyRequired: Boolean(op.requestBody?.required),
|
|
311
|
+
responseType,
|
|
312
|
+
responses,
|
|
313
|
+
summary: op.summary
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return operations;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/generator/render.ts
|
|
321
|
+
function buildInputType(op) {
|
|
322
|
+
const parts = [];
|
|
323
|
+
if (op.pathParams.length > 0) {
|
|
324
|
+
const fields = op.pathParams.map((p) => `${JSON.stringify(p)}: string`).join("; ");
|
|
325
|
+
parts.push(`path: { ${fields} }`);
|
|
326
|
+
}
|
|
327
|
+
if (op.queryParams.length > 0) {
|
|
328
|
+
const fields = op.queryParams.map((p) => `${JSON.stringify(p)}?: string | number | boolean`).join("; ");
|
|
329
|
+
parts.push(`query?: { ${fields} }`);
|
|
330
|
+
}
|
|
331
|
+
if (op.bodyType) {
|
|
332
|
+
parts.push(`body${op.bodyRequired ? "" : "?"}: ${op.bodyType}`);
|
|
333
|
+
}
|
|
334
|
+
if (parts.length === 0)
|
|
335
|
+
return { type: "{}", optional: true };
|
|
336
|
+
const optional = op.pathParams.length === 0 && (!op.bodyType || !op.bodyRequired);
|
|
337
|
+
return { type: `{ ${parts.join("; ")} }`, optional };
|
|
338
|
+
}
|
|
339
|
+
function buildStatusMapType(op) {
|
|
340
|
+
const entries = op.responses.map((r) => `${r.status}: ${r.bodyType}`);
|
|
341
|
+
return `{ ${entries.join("; ")} }`;
|
|
342
|
+
}
|
|
343
|
+
function methodReturnType(args) {
|
|
344
|
+
const { op, mode } = args;
|
|
345
|
+
if (mode === "frontend")
|
|
346
|
+
return `Promise<${op.responseType}>`;
|
|
347
|
+
return `Promise<ApiResponse<${buildStatusMapType(op)}>>`;
|
|
348
|
+
}
|
|
349
|
+
function hookKey(op) {
|
|
350
|
+
return `${op.group}.${op.name}`;
|
|
351
|
+
}
|
|
352
|
+
function hookBodyType(op) {
|
|
353
|
+
return op.bodyType ?? "undefined";
|
|
354
|
+
}
|
|
355
|
+
function renderMethod(args) {
|
|
356
|
+
const { op, mode } = args;
|
|
357
|
+
const { type, optional } = buildInputType(op);
|
|
358
|
+
const inputParam = `input${optional ? "?" : ""}: ${type}`;
|
|
359
|
+
const doc = op.summary ? `
|
|
360
|
+
/** ${op.summary} */` : "";
|
|
361
|
+
const call = `request({
|
|
362
|
+
method: ${JSON.stringify(op.method.toUpperCase())},
|
|
363
|
+
path: ${JSON.stringify(op.path)},
|
|
364
|
+
opKey: ${JSON.stringify(hookKey(op))},
|
|
365
|
+
input: input as RequestInput,
|
|
366
|
+
})`;
|
|
367
|
+
if (mode === "frontend") {
|
|
368
|
+
return `${doc}
|
|
369
|
+
${JSON.stringify(op.name)}: (${inputParam}): Promise<${op.responseType}> =>
|
|
370
|
+
${call} as Promise<${op.responseType}>,`;
|
|
371
|
+
}
|
|
372
|
+
const map = buildStatusMapType(op);
|
|
373
|
+
return `${doc}
|
|
374
|
+
${JSON.stringify(op.name)}: async (${inputParam}): Promise<ApiResponse<${map}>> => {
|
|
375
|
+
const res = await ${call}
|
|
376
|
+
return new ApiResponse(res) as unknown as ApiResponse<${map}>
|
|
377
|
+
},`;
|
|
378
|
+
}
|
|
379
|
+
function renderImports(args) {
|
|
380
|
+
const { mode, userStoreImport, configImport } = args;
|
|
381
|
+
const values = mode === "frontend" ? "BaseStore, HookRegistry" : "BaseStore, ApiResponse, HookRegistry";
|
|
382
|
+
return `import { ${values} } from ${JSON.stringify(userStoreImport)}
|
|
383
|
+
import type { BeforeHook, AfterHook } from ${JSON.stringify(userStoreImport)}
|
|
384
|
+
import { ebely } from ${JSON.stringify(configImport)}`;
|
|
385
|
+
}
|
|
386
|
+
function renderRequestTail(mode) {
|
|
387
|
+
if (mode === "frontend") {
|
|
388
|
+
return {
|
|
389
|
+
returnType: "unknown",
|
|
390
|
+
ret: ` if (!response.ok) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
\`Request \${method} \${resolvedPath} failed with \${response.status}: \${text}\`,
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return data`
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
returnType: "{ status: number; body: unknown }",
|
|
401
|
+
ret: ` return { status: response.status, body: data }`
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function groupOperations(operations) {
|
|
405
|
+
const groups = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const op of operations) {
|
|
407
|
+
const list = groups.get(op.group) ?? [];
|
|
408
|
+
list.push(op);
|
|
409
|
+
groups.set(op.group, list);
|
|
410
|
+
}
|
|
411
|
+
return groups;
|
|
412
|
+
}
|
|
413
|
+
function renderApiType(args) {
|
|
414
|
+
const { groups, mode } = args;
|
|
415
|
+
const blocks = [...groups.entries()].map(([group, ops]) => {
|
|
416
|
+
const leaves = ops.map((op) => {
|
|
417
|
+
const { type, optional } = buildInputType(op);
|
|
418
|
+
const inputParam = `input${optional ? "?" : ""}: ${type}`;
|
|
419
|
+
return ` ${JSON.stringify(op.name)}: (${inputParam}) => ${methodReturnType({ op, mode })}`;
|
|
420
|
+
});
|
|
421
|
+
return ` ${JSON.stringify(group)}: {
|
|
422
|
+
${leaves.join("\n")}
|
|
423
|
+
}`;
|
|
424
|
+
});
|
|
425
|
+
return `export type WorldApi = {
|
|
426
|
+
${blocks.join("\n")}
|
|
427
|
+
}`;
|
|
428
|
+
}
|
|
429
|
+
function renderHookTreeType(groups) {
|
|
430
|
+
const blocks = [...groups.entries()].map(([group, ops]) => {
|
|
431
|
+
const leaves = ops.map((op) => {
|
|
432
|
+
const body = hookBodyType(op);
|
|
433
|
+
return ` ${JSON.stringify(op.name)}: {
|
|
434
|
+
before(fn: BeforeHook<Store, ${body}>): void
|
|
435
|
+
after(fn: AfterHook<Store, ${body}, ${op.responseType}>): void
|
|
436
|
+
}`;
|
|
437
|
+
});
|
|
438
|
+
return ` ${JSON.stringify(group)}: {
|
|
439
|
+
${leaves.join("\n")}
|
|
440
|
+
}`;
|
|
441
|
+
});
|
|
442
|
+
return `type EbelyHookTree<Store extends BaseStore> = {
|
|
443
|
+
${blocks.join("\n")}
|
|
444
|
+
}`;
|
|
445
|
+
}
|
|
446
|
+
function renderHookTreeBuilder(groups) {
|
|
447
|
+
const blocks = [...groups.entries()].map(([group, ops]) => {
|
|
448
|
+
const leaves = ops.map((op) => {
|
|
449
|
+
const key = JSON.stringify(hookKey(op));
|
|
450
|
+
return ` ${JSON.stringify(op.name)}: {
|
|
451
|
+
before: (fn: BeforeHook<Store>) => r.before({ key: ${key}, fn }),
|
|
452
|
+
after: (fn: AfterHook<Store>) => r.after({ key: ${key}, fn }),
|
|
453
|
+
},`;
|
|
454
|
+
});
|
|
455
|
+
return ` ${JSON.stringify(group)}: {
|
|
456
|
+
${leaves.join("\n")}
|
|
457
|
+
},`;
|
|
458
|
+
});
|
|
459
|
+
return ` private buildHookTree(): EbelyHookTree<Store> {
|
|
460
|
+
const r = this.hookRegistry
|
|
461
|
+
return {
|
|
462
|
+
${blocks.join("\n")}
|
|
463
|
+
} as unknown as EbelyHookTree<Store>
|
|
464
|
+
}`;
|
|
465
|
+
}
|
|
466
|
+
function renderApiTreeBuilder(args) {
|
|
467
|
+
const { groups, mode } = args;
|
|
468
|
+
const groupBlocks = [...groups.entries()].map(([group, ops]) => {
|
|
469
|
+
const methods = ops.map((op) => renderMethod({ op, mode }));
|
|
470
|
+
return ` ${JSON.stringify(group)}: {
|
|
471
|
+
${methods.join("\n")}
|
|
472
|
+
},`;
|
|
473
|
+
});
|
|
474
|
+
return ` /** \u0414\u0435\u0440\u0435\u0432\u043E \u0442\u0438\u043F\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0432\u044B\u0437\u043E\u0432\u043E\u0432 \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u043E\u0432 \u043F\u043E\u0432\u0435\u0440\u0445 \u043E\u0434\u043D\u043E\u0433\u043E \`request\`. */
|
|
475
|
+
private buildApiTree(request: RequestFn) {
|
|
476
|
+
return {
|
|
477
|
+
${groupBlocks.join("\n")}
|
|
478
|
+
}
|
|
479
|
+
}`;
|
|
480
|
+
}
|
|
481
|
+
function renderClient(args) {
|
|
482
|
+
const { spec, operations, mode, userStoreImport, configImport } = args;
|
|
483
|
+
const basePath = spec.servers?.[0]?.url ?? "";
|
|
484
|
+
const groups = groupOperations(operations);
|
|
485
|
+
const tail = renderRequestTail(mode);
|
|
486
|
+
return `// AUTO-GENERATED by ebely (generateClient) \u2014 \u041D\u0415 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.
|
|
487
|
+
// \u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A: ${spec.info?.title ?? "OpenAPI spec"} v${spec.info?.version ?? "?"}
|
|
488
|
+
// \u0420\u0435\u0436\u0438\u043C \u043A\u043B\u0438\u0435\u043D\u0442\u0430: ${mode}
|
|
489
|
+
// \u041F\u0435\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F: pnpm run client:generate
|
|
490
|
+
|
|
491
|
+
${renderImports({ mode, userStoreImport, configImport })}
|
|
492
|
+
|
|
493
|
+
type RequestInput = {
|
|
494
|
+
path?: Record<string, string>
|
|
495
|
+
query?: Record<string, string | number | boolean | undefined>
|
|
496
|
+
body?: unknown
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
type RequestFn = (req: {
|
|
500
|
+
method: string
|
|
501
|
+
path: string
|
|
502
|
+
opKey: string
|
|
503
|
+
input?: RequestInput
|
|
504
|
+
}) => Promise<${tail.returnType}>
|
|
505
|
+
|
|
506
|
+
export type CreateUserArgs = {
|
|
507
|
+
/** \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u0443\u0434\u0443\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0442\u044C\u0441\u044F \u043A\u043E \u0432\u0441\u0435\u043C \u0437\u0430\u043F\u0440\u043E\u0441\u0430\u043C \u044D\u0442\u043E\u0433\u043E \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. */
|
|
508
|
+
headers?: Record<string, string>
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
${renderApiType({ groups, mode })}
|
|
512
|
+
|
|
513
|
+
${renderHookTreeType(groups)}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* \u0422\u0438\u043F \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u0445\u0443\u043A\u043E\u0432 \u0434\u043B\u044F \u042D\u0422\u041E\u0413\u041E \u0431\u044D\u043A\u0435\u043D\u0434\u0430. \u041E\u0431\u044A\u044F\u0432\u0438\u0442\u0435 \u0445\u0443\u043A\u0438 \u0432 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u043C
|
|
517
|
+
* \u0444\u0430\u0439\u043B\u0435 \u0438 \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435 \u043E\u0434\u043D\u043E\u0439 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u0432 \u043A\u043E\u043D\u0444\u0438\u0433 (\`EbelyConfig.hooks\`).
|
|
518
|
+
* \u041F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0421\u0412\u041E\u0419 \u043A\u043B\u0430\u0441\u0441 store \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u043C, \u0447\u0442\u043E\u0431\u044B \`ctx\` \u0431\u044B\u043B \u0442\u0438\u043F\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D:
|
|
519
|
+
*
|
|
520
|
+
* import type { UserStore } from './userStore'
|
|
521
|
+
* export const hooks: Hooks<UserStore> = (h) => {
|
|
522
|
+
* h.posts.create.after(({ response, ctx }) => { \u2026 })
|
|
523
|
+
* }
|
|
524
|
+
*
|
|
525
|
+
* (\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u041D\u0415 \u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u0438\u0437 \`ebely.userStore\` \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E: \u044D\u0442\u043E \u0441\u043E\u0437\u0434\u0430\u043B\u043E
|
|
526
|
+
* \u0431\u044B \u0446\u0438\u043A\u043B \u0442\u0438\u043F\u043E\u0432 \`ebely\` \u21C4 \`Hooks\`, \u0442.\u043A. \`hooks\` \u043B\u0435\u0436\u0438\u0442 \u0432\u043D\u0443\u0442\u0440\u0438 \`ebely\`.)
|
|
527
|
+
*/
|
|
528
|
+
export type Hooks<Store extends BaseStore = BaseStore> = (
|
|
529
|
+
h: EbelyHookTree<Store>,
|
|
530
|
+
) => void
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* \u0411\u0430\u0437\u0430 \`World\` \u2014 \u044D\u0442\u043E \u0441\u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \`ebely.worldStore\` (\u0438\u043B\u0438 \u043F\u0443\u0441\u0442\u043E\u0439
|
|
534
|
+
* \`BaseStore\`, \u0435\u0441\u043B\u0438 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D). \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \`world.<\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439>()\` \u0438
|
|
535
|
+
* \`world.get/set\` \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0438 \u0442\u0438\u043F\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0440\u043E\u0432\u043D\u043E \u043A\u0430\u043A \u0443 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F,
|
|
536
|
+
* \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0431\u043B\u0430\u0441\u0442\u044C \u2014 \u0432\u0435\u0441\u044C \u043C\u0438\u0440. \u0422\u0438\u043F \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \`ebely\` \u0442\u0435\u043C \u0436\u0435 \u043F\u0440\u0438\u0451\u043C\u043E\u043C, \u0447\u0442\u043E
|
|
537
|
+
* \u0438 \`Store\` (\u043D\u0438\u043A\u0430\u043A\u0438\u0445 \u0440\u0430\u043D\u0442\u0430\u0439\u043C-\u0443\u0441\u043B\u043E\u0432\u0438\u0439 \u2014 \u0441\u043C. ARCHITECTURE.md \xA78).
|
|
538
|
+
*/
|
|
539
|
+
type ConfiguredWorldStore =
|
|
540
|
+
typeof ebely extends { worldStore: new () => infer I extends BaseStore }
|
|
541
|
+
? I
|
|
542
|
+
: BaseStore
|
|
543
|
+
const WorldStoreBase = ((ebely as { worldStore?: new () => BaseStore })
|
|
544
|
+
.worldStore ?? BaseStore) as new () => ConfiguredWorldStore
|
|
545
|
+
|
|
546
|
+
export class World<
|
|
547
|
+
Store extends BaseStore = InstanceType<typeof ebely.userStore>,
|
|
548
|
+
> extends WorldStoreBase {
|
|
549
|
+
/**
|
|
550
|
+
* \u041E\u0431\u0449\u0438\u0439 \u0440\u0435\u0435\u0441\u0442\u0440 \u0445\u0443\u043A\u043E\u0432. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u2014 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0430\u044F (\u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0438\u0437 \u043A\u043E\u043D\u0444\u0438\u0433\u0430),
|
|
551
|
+
* \u043D\u043E \`ctx\` \u043F\u043E\u0434\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432 \u043C\u043E\u043C\u0435\u043D\u0442 \u0437\u0430\u043F\u0440\u043E\u0441\u0430 = store \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0433\u043E \u044E\u0437\u0435\u0440\u0430.
|
|
552
|
+
*/
|
|
553
|
+
private hookRegistry = new HookRegistry()
|
|
554
|
+
|
|
555
|
+
constructor(
|
|
556
|
+
public args: {
|
|
557
|
+
/** URL \u0431\u044D\u043A\u0435\u043D\u0434\u0430. \u0415\u0441\u043B\u0438 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F ebely.url \u0438\u0437 \u043A\u043E\u043D\u0444\u0438\u0433\u0430. */
|
|
558
|
+
url?: string
|
|
559
|
+
/** \u041A\u043B\u0430\u0441\u0441-\u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435. \u0415\u0441\u043B\u0438 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F ebely.userStore. */
|
|
560
|
+
store?: new () => Store
|
|
561
|
+
} = {},
|
|
562
|
+
) {
|
|
563
|
+
super()
|
|
564
|
+
const registrar = (ebely as { hooks?: (h: unknown) => void }).hooks
|
|
565
|
+
if (registrar) registrar(this.buildHookTree())
|
|
566
|
+
// world-store \u0445\u043E\u0434\u0438\u0442 \u0410\u041D\u041E\u041D\u0418\u041C\u041D\u042B\u041C \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u043C (\u0431\u0435\u0437 per-user \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u043E\u0432);
|
|
567
|
+
// ctx \u0445\u0443\u043A\u043E\u0432 \u0434\u043B\u044F \u0442\u0430\u043A\u0438\u0445 \u0432\u044B\u0437\u043E\u0432\u043E\u0432 = \u0441\u0430\u043C world.
|
|
568
|
+
;(this as unknown as { api: unknown }).api = this.buildApiTree(
|
|
569
|
+
this.makeRequest({ headers: {}, store: this }),
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** \u0411\u0430\u0437\u043E\u0432\u044B\u0439 URL \u0441 \u0443\u0447\u0451\u0442\u043E\u043C server.url \u0438\u0437 \u0441\u0445\u0435\u043C\u044B. */
|
|
574
|
+
private baseUrl(): string {
|
|
575
|
+
return (this.args.url ?? ebely.url).replace(/\\/$/, '') + ${JSON.stringify(basePath)}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
${renderHookTreeBuilder(groups)}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* \u0421\u043E\u0437\u0434\u0430\u0451\u0442 \`request\`, \u0437\u0430\u043C\u043A\u043D\u0443\u0442\u044B\u0439 \u043D\u0430 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438 \u0438 \`store\` (\u043E\u043D \u0436\u0435 \`ctx\`
|
|
582
|
+
* \u0445\u0443\u043A\u043E\u0432). \u041E\u0434\u0438\u043D \u0438 \u0442\u043E\u0442 \u0436\u0435 \u0434\u0432\u0438\u0436\u043E\u043A \u0438 \u0434\u043B\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F, \u0438 \u0434\u043B\u044F world.
|
|
583
|
+
*/
|
|
584
|
+
private makeRequest(cfg: {
|
|
585
|
+
headers: Record<string, string>
|
|
586
|
+
store: BaseStore
|
|
587
|
+
}): RequestFn {
|
|
588
|
+
const baseUrl = this.baseUrl()
|
|
589
|
+
const registry = this.hookRegistry
|
|
590
|
+
const { headers: baseHeaders, store } = cfg
|
|
591
|
+
|
|
592
|
+
return async (req): Promise<${tail.returnType}> => {
|
|
593
|
+
const { method, path, opKey, input } = req
|
|
594
|
+
|
|
595
|
+
const hookReq = {
|
|
596
|
+
method,
|
|
597
|
+
path,
|
|
598
|
+
pathParams: { ...(input?.path ?? {}) },
|
|
599
|
+
query: { ...(input?.query ?? {}) },
|
|
600
|
+
body: input?.body,
|
|
601
|
+
headers: { ...baseHeaders },
|
|
602
|
+
}
|
|
603
|
+
await registry.runBefore({ key: opKey, request: hookReq, ctx: store })
|
|
604
|
+
|
|
605
|
+
let resolvedPath = path
|
|
606
|
+
for (const [k, v] of Object.entries(hookReq.pathParams)) {
|
|
607
|
+
resolvedPath = resolvedPath.replace(
|
|
608
|
+
\`{\${k}}\`,
|
|
609
|
+
encodeURIComponent(String(v)),
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const url = new URL(baseUrl + resolvedPath)
|
|
614
|
+
for (const [k, v] of Object.entries(hookReq.query)) {
|
|
615
|
+
if (v !== undefined) url.searchParams.set(k, String(v))
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const hasBody = hookReq.body !== undefined
|
|
619
|
+
const response = await fetch(url, {
|
|
620
|
+
method,
|
|
621
|
+
headers: {
|
|
622
|
+
...(hasBody ? { 'content-type': 'application/json' } : {}),
|
|
623
|
+
...hookReq.headers,
|
|
624
|
+
},
|
|
625
|
+
body: hasBody ? JSON.stringify(hookReq.body) : undefined,
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
const text = await response.text()
|
|
629
|
+
let data: unknown
|
|
630
|
+
try {
|
|
631
|
+
data = text ? JSON.parse(text) : undefined
|
|
632
|
+
} catch {
|
|
633
|
+
data = text
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
await registry.runAfter({
|
|
637
|
+
key: opKey,
|
|
638
|
+
request: hookReq,
|
|
639
|
+
response: { status: response.status, body: data },
|
|
640
|
+
ctx: store,
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
${tail.ret}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
${renderApiTreeBuilder({ groups, mode })}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* \u0421\u043E\u0437\u0434\u0430\u0451\u0442 \xAB\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F\xBB \u2014 \u0438\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u043D\u0430\u0431\u043E\u0440 \u0442\u0438\u043F\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0432\u044B\u0437\u043E\u0432\u043E\u0432
|
|
651
|
+
* \u044D\u043D\u0434\u043F\u043E\u0438\u043D\u0442\u043E\u0432, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043F\u043E\u0434 \u043A\u0430\u043F\u043E\u0442\u043E\u043C \u0445\u043E\u0434\u0438\u0442 fetch-\u0437\u0430\u043F\u0440\u043E\u0441\u0430\u043C\u0438. \`store.api\`
|
|
652
|
+
* \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043D\u0430 \u0442\u043E \u0436\u0435 \u0434\u0435\u0440\u0435\u0432\u043E, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043C\u0435\u0442\u043E\u0434\u044B-\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438 \u044D\u0442\u043E\u0433\u043E store
|
|
653
|
+
* (\`fullRegister\` \u0438 \u0442.\u043F.) \u0445\u043E\u0434\u044F\u0442 \u043E\u0442 \u043B\u0438\u0446\u0430 \u0438\u043C\u0435\u043D\u043D\u043E \u044D\u0442\u043E\u0433\u043E \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F.
|
|
654
|
+
*/
|
|
655
|
+
createUser(userArgs: CreateUserArgs = {}) {
|
|
656
|
+
const StoreClass =
|
|
657
|
+
this.args.store ?? (ebely.userStore as unknown as new () => Store)
|
|
658
|
+
const store = new StoreClass()
|
|
659
|
+
const tree = this.buildApiTree(
|
|
660
|
+
this.makeRequest({ headers: { ...userArgs.headers }, store }),
|
|
661
|
+
)
|
|
662
|
+
;(store as unknown as { api: unknown }).api = tree
|
|
663
|
+
return Object.assign(store, tree)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/generator/swagger.ts
|
|
670
|
+
var import_promises = require("fs/promises");
|
|
671
|
+
var import_node_path = require("path");
|
|
672
|
+
async function loadSpec(args) {
|
|
673
|
+
const { swagger } = args;
|
|
674
|
+
if (swagger.pathToFile !== void 0) {
|
|
675
|
+
const specPath = (0, import_node_path.resolve)(process.cwd(), swagger.pathToFile);
|
|
676
|
+
return JSON.parse(await (0, import_promises.readFile)(specPath, "utf8"));
|
|
677
|
+
}
|
|
678
|
+
const response = await fetch(swagger.url);
|
|
679
|
+
if (!response.ok) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C swagger \u043F\u043E ${swagger.url}: ${response.status} ${response.statusText}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
return await response.json();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/generate-client.ts
|
|
688
|
+
async function generateClient(args) {
|
|
689
|
+
const {
|
|
690
|
+
swagger,
|
|
691
|
+
generateClientTo,
|
|
692
|
+
mode = "test",
|
|
693
|
+
userStoreImport = "ebely",
|
|
694
|
+
configImport = "./ebely"
|
|
695
|
+
} = args;
|
|
696
|
+
const outPath = (0, import_node_path2.resolve)(process.cwd(), generateClientTo);
|
|
697
|
+
const spec = await loadSpec({ swagger });
|
|
698
|
+
const operations = collectOperations({ spec });
|
|
699
|
+
const source = renderClient({
|
|
700
|
+
spec,
|
|
701
|
+
operations,
|
|
702
|
+
mode,
|
|
703
|
+
userStoreImport,
|
|
704
|
+
configImport
|
|
705
|
+
});
|
|
706
|
+
await (0, import_promises2.writeFile)(outPath, source, "utf8");
|
|
707
|
+
console.log(`
|
|
708
|
+
\u2705 Typed client written to:
|
|
709
|
+
${outPath}
|
|
710
|
+
|
|
711
|
+
Endpoints: ${operations.length}
|
|
712
|
+
`);
|
|
713
|
+
return { outPath, operations: operations.length };
|
|
714
|
+
}
|
|
715
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
716
|
+
0 && (module.exports = {
|
|
717
|
+
ApiResponse,
|
|
718
|
+
BaseStore,
|
|
719
|
+
EbelyAssertionError,
|
|
720
|
+
HookRegistry,
|
|
721
|
+
generateClient
|
|
722
|
+
});
|