@cushin/api-codegen 1.1.0 → 1.1.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/dist/cli.js +419 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +424 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -185,9 +185,9 @@ var APIClient = class {
|
|
|
185
185
|
})();
|
|
186
186
|
return this.refreshPromise;
|
|
187
187
|
}
|
|
188
|
-
buildPath(
|
|
189
|
-
if (!params) return
|
|
190
|
-
let finalPath =
|
|
188
|
+
buildPath(path12, params) {
|
|
189
|
+
if (!params) return path12;
|
|
190
|
+
let finalPath = path12;
|
|
191
191
|
Object.entries(params).forEach(([key, value]) => {
|
|
192
192
|
finalPath = finalPath.replace(
|
|
193
193
|
`:${key}`,
|
|
@@ -219,7 +219,7 @@ var APIClient = class {
|
|
|
219
219
|
}
|
|
220
220
|
async request(endpoint, params, query, body) {
|
|
221
221
|
try {
|
|
222
|
-
const
|
|
222
|
+
const path12 = this.buildPath(endpoint.path, params);
|
|
223
223
|
const client = this.getClientForEndpoint(endpoint);
|
|
224
224
|
const options = {
|
|
225
225
|
method: endpoint.method
|
|
@@ -243,7 +243,7 @@ var APIClient = class {
|
|
|
243
243
|
options.json = body;
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
|
-
const response = await client(
|
|
246
|
+
const response = await client(path12, options);
|
|
247
247
|
const data = await response.json();
|
|
248
248
|
if (endpoint.response) {
|
|
249
249
|
return endpoint.response.parse(data);
|
|
@@ -719,7 +719,7 @@ import { apiConfig } from '${relativePath}';
|
|
|
719
719
|
|
|
720
720
|
|
|
721
721
|
// Re-export endpoint configuration types
|
|
722
|
-
export type { APIConfig, APIEndpoint, HTTPMethod } from '
|
|
722
|
+
export type { APIConfig, APIEndpoint, HTTPMethod } from './schema';
|
|
723
723
|
|
|
724
724
|
/**
|
|
725
725
|
* Type helper to extract params schema from an endpoint
|
|
@@ -812,8 +812,8 @@ var ClientGenerator = class extends BaseGenerator {
|
|
|
812
812
|
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
813
813
|
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
814
814
|
return `${useClientDirective ? "'use client';\n" : ""}
|
|
815
|
-
import { createAPIClient } from '
|
|
816
|
-
import type { AuthCallbacks } from '
|
|
815
|
+
import { createAPIClient } from './core';
|
|
816
|
+
import type { AuthCallbacks } from './core';
|
|
817
817
|
import { apiConfig } from '${relativePath}';
|
|
818
818
|
import { z } from 'zod';
|
|
819
819
|
|
|
@@ -881,7 +881,7 @@ export type { AuthCallbacks };
|
|
|
881
881
|
`;
|
|
882
882
|
}
|
|
883
883
|
generateServerClientContent() {
|
|
884
|
-
return `import { createAPIClient } from '
|
|
884
|
+
return `import { createAPIClient } from './core';
|
|
885
885
|
import { apiConfig } from '../config/endpoints';
|
|
886
886
|
import type { APIEndpoints } from './types';
|
|
887
887
|
|
|
@@ -1177,6 +1177,419 @@ ${this.generatePrefetchFunctions()}
|
|
|
1177
1177
|
};`;
|
|
1178
1178
|
}
|
|
1179
1179
|
};
|
|
1180
|
+
var SchemaGenerator = class extends BaseGenerator {
|
|
1181
|
+
async generate() {
|
|
1182
|
+
const content = this.generateContent();
|
|
1183
|
+
const outputPath = path6.join(this.context.config.outputDir, "schema.ts");
|
|
1184
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
1185
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
1186
|
+
}
|
|
1187
|
+
generateContent() {
|
|
1188
|
+
const content = `// Auto-generated schema definitions
|
|
1189
|
+
|
|
1190
|
+
import type { z } from 'zod';
|
|
1191
|
+
|
|
1192
|
+
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1193
|
+
|
|
1194
|
+
export interface APIEndpoint {
|
|
1195
|
+
path: string;
|
|
1196
|
+
method: HTTPMethod;
|
|
1197
|
+
baseUrl?: string;
|
|
1198
|
+
params?: z.ZodType<any>;
|
|
1199
|
+
query?: z.ZodType<any>;
|
|
1200
|
+
body?: z.ZodType<any>;
|
|
1201
|
+
response: z.ZodType<any>;
|
|
1202
|
+
tags?: string[];
|
|
1203
|
+
description?: string;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export interface APIConfig {
|
|
1207
|
+
baseUrl?: string;
|
|
1208
|
+
endpoints: Record<string, APIEndpoint>;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export type EndpointConfig<
|
|
1212
|
+
TPath extends string = string,
|
|
1213
|
+
TMethod extends HTTPMethod = HTTPMethod,
|
|
1214
|
+
TParams = undefined,
|
|
1215
|
+
TQuery = undefined,
|
|
1216
|
+
TBody = undefined,
|
|
1217
|
+
TResponse = any,
|
|
1218
|
+
> = {
|
|
1219
|
+
path: TPath;
|
|
1220
|
+
method: TMethod;
|
|
1221
|
+
baseUrl?: string;
|
|
1222
|
+
params?: z.ZodType<TParams>;
|
|
1223
|
+
query?: z.ZodType<TQuery>;
|
|
1224
|
+
body?: z.ZodType<TBody>;
|
|
1225
|
+
response: z.ZodType<TResponse>;
|
|
1226
|
+
tags?: string[];
|
|
1227
|
+
description?: string;
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Helper function to define API configuration with type safety
|
|
1232
|
+
*/
|
|
1233
|
+
export function defineConfig<T extends APIConfig>(config: T): T {
|
|
1234
|
+
return config;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Helper function to define a single endpoint with type inference
|
|
1239
|
+
*/
|
|
1240
|
+
export function defineEndpoint<
|
|
1241
|
+
TPath extends string,
|
|
1242
|
+
TMethod extends HTTPMethod,
|
|
1243
|
+
TParams = undefined,
|
|
1244
|
+
TQuery = undefined,
|
|
1245
|
+
TBody = undefined,
|
|
1246
|
+
TResponse = any,
|
|
1247
|
+
>(
|
|
1248
|
+
config: EndpointConfig<TPath, TMethod, TParams, TQuery, TBody, TResponse>,
|
|
1249
|
+
): EndpointConfig<TPath, TMethod, TParams, TQuery, TBody, TResponse> {
|
|
1250
|
+
return config;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Helper to define multiple endpoints
|
|
1255
|
+
*/
|
|
1256
|
+
export function defineEndpoints<
|
|
1257
|
+
T extends Record<string, APIEndpoint>,
|
|
1258
|
+
>(endpoints: T): T {
|
|
1259
|
+
return endpoints;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
`;
|
|
1263
|
+
return content;
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
var CoreGenerator = class extends BaseGenerator {
|
|
1267
|
+
async generate() {
|
|
1268
|
+
const content = this.generateContent();
|
|
1269
|
+
const outputPath = path6.join(this.context.config.outputDir, "core.ts");
|
|
1270
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
1271
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
1272
|
+
}
|
|
1273
|
+
generateContent() {
|
|
1274
|
+
const content = `// Auto-generated schema definitions
|
|
1275
|
+
|
|
1276
|
+
import ky, { HTTPError } from "ky";
|
|
1277
|
+
import type { APIConfig, APIEndpoint } from "../config/schema.js";
|
|
1278
|
+
|
|
1279
|
+
export interface AuthTokens {
|
|
1280
|
+
accessToken: string;
|
|
1281
|
+
refreshToken?: string;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
export interface AuthCallbacks {
|
|
1285
|
+
getTokens: () => Promise<AuthTokens | null>;
|
|
1286
|
+
onAuthError?: () => void;
|
|
1287
|
+
onRefreshToken?: () => Promise<void>;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
export class APIError extends Error {
|
|
1291
|
+
constructor(
|
|
1292
|
+
message: string,
|
|
1293
|
+
public status: number,
|
|
1294
|
+
public response?: any,
|
|
1295
|
+
) {
|
|
1296
|
+
super(message);
|
|
1297
|
+
this.name = "APIError";
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
export class AuthError extends APIError {
|
|
1302
|
+
constructor(message: string = "Authentication failed") {
|
|
1303
|
+
super(message, 401);
|
|
1304
|
+
this.name = "AuthError";
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
export class APIClient {
|
|
1309
|
+
private client: typeof ky;
|
|
1310
|
+
private isRefreshing = false;
|
|
1311
|
+
private refreshPromise: Promise<void> | null = null;
|
|
1312
|
+
private hooks: any;
|
|
1313
|
+
|
|
1314
|
+
constructor(
|
|
1315
|
+
private config: APIConfig,
|
|
1316
|
+
private authCallbacks?: AuthCallbacks,
|
|
1317
|
+
) {
|
|
1318
|
+
this.hooks = {
|
|
1319
|
+
beforeRequest: [
|
|
1320
|
+
async (request: Request) => {
|
|
1321
|
+
const tokens = await this.authCallbacks?.getTokens();
|
|
1322
|
+
if (tokens?.accessToken) {
|
|
1323
|
+
request.headers.set(
|
|
1324
|
+
"Authorization",
|
|
1325
|
+
\`Bearer \${tokens.accessToken}\`,
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
],
|
|
1330
|
+
beforeRetry: [
|
|
1331
|
+
async ({ request, error, retryCount }: any) => {
|
|
1332
|
+
if (error instanceof HTTPError && error.response.status === 401) {
|
|
1333
|
+
if (retryCount === 1 && this.authCallbacks) {
|
|
1334
|
+
try {
|
|
1335
|
+
await this.refreshTokens();
|
|
1336
|
+
const tokens = await this.authCallbacks.getTokens();
|
|
1337
|
+
if (tokens?.accessToken) {
|
|
1338
|
+
request.headers.set(
|
|
1339
|
+
"Authorization",
|
|
1340
|
+
\`Bearer \${tokens.accessToken}\`,
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
} catch (refreshError) {
|
|
1344
|
+
this.authCallbacks.onAuthError?.();
|
|
1345
|
+
throw new AuthError();
|
|
1346
|
+
}
|
|
1347
|
+
} else {
|
|
1348
|
+
this.authCallbacks?.onAuthError?.();
|
|
1349
|
+
throw new AuthError();
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
],
|
|
1354
|
+
beforeError: [
|
|
1355
|
+
async (error: any) => {
|
|
1356
|
+
const { response } = error;
|
|
1357
|
+
if (response?.body) {
|
|
1358
|
+
try {
|
|
1359
|
+
const body = await response.json();
|
|
1360
|
+
error.message =
|
|
1361
|
+
(body as Error).message || \`HTTP \${response.status}\`;
|
|
1362
|
+
} catch {
|
|
1363
|
+
// Keep original message
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return error;
|
|
1367
|
+
},
|
|
1368
|
+
],
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
this.client = ky.create({
|
|
1372
|
+
prefixUrl: this.config.baseUrl,
|
|
1373
|
+
headers: {
|
|
1374
|
+
"Content-Type": "application/json",
|
|
1375
|
+
},
|
|
1376
|
+
retry: {
|
|
1377
|
+
limit: 2,
|
|
1378
|
+
methods: ["get", "post", "put", "delete", "patch"],
|
|
1379
|
+
statusCodes: [401],
|
|
1380
|
+
},
|
|
1381
|
+
hooks: this.hooks,
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private async refreshTokens(): Promise<void> {
|
|
1386
|
+
if (!this.authCallbacks) {
|
|
1387
|
+
throw new AuthError("No auth callbacks provided");
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (this.isRefreshing && this.refreshPromise) {
|
|
1391
|
+
return this.refreshPromise;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
this.isRefreshing = true;
|
|
1395
|
+
|
|
1396
|
+
this.refreshPromise = (async () => {
|
|
1397
|
+
try {
|
|
1398
|
+
if (this.authCallbacks?.onRefreshToken) {
|
|
1399
|
+
await this.authCallbacks.onRefreshToken();
|
|
1400
|
+
} else {
|
|
1401
|
+
throw new AuthError("No refresh token handler provided");
|
|
1402
|
+
}
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
throw error;
|
|
1405
|
+
} finally {
|
|
1406
|
+
this.isRefreshing = false;
|
|
1407
|
+
this.refreshPromise = null;
|
|
1408
|
+
}
|
|
1409
|
+
})();
|
|
1410
|
+
|
|
1411
|
+
return this.refreshPromise;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
private buildPath(path: string, params?: Record<string, any>): string {
|
|
1415
|
+
if (!params) return path;
|
|
1416
|
+
|
|
1417
|
+
let finalPath = path;
|
|
1418
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
1419
|
+
finalPath = finalPath.replace(
|
|
1420
|
+
\`:\${key}\`,
|
|
1421
|
+
encodeURIComponent(String(value)),
|
|
1422
|
+
);
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
return finalPath;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private getEndpointBaseUrl(endpoint: APIEndpoint): string {
|
|
1429
|
+
return endpoint.baseUrl || this.config.baseUrl!;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
private getClientForEndpoint(endpoint: APIEndpoint): typeof ky {
|
|
1433
|
+
const endpointBaseUrl = this.getEndpointBaseUrl(endpoint);
|
|
1434
|
+
|
|
1435
|
+
if (endpointBaseUrl === this.config.baseUrl) {
|
|
1436
|
+
return this.client;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
return ky.create({
|
|
1440
|
+
prefixUrl: endpointBaseUrl,
|
|
1441
|
+
headers: {
|
|
1442
|
+
"Content-Type": "application/json",
|
|
1443
|
+
},
|
|
1444
|
+
retry: {
|
|
1445
|
+
limit: 2,
|
|
1446
|
+
methods: ["get", "post", "put", "delete", "patch"],
|
|
1447
|
+
statusCodes: [401],
|
|
1448
|
+
},
|
|
1449
|
+
hooks: this.hooks,
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
async request<T>(
|
|
1454
|
+
endpoint: APIEndpoint,
|
|
1455
|
+
params?: Record<string, any>,
|
|
1456
|
+
query?: Record<string, any>,
|
|
1457
|
+
body?: any,
|
|
1458
|
+
): Promise<T> {
|
|
1459
|
+
try {
|
|
1460
|
+
const path = this.buildPath(endpoint.path, params);
|
|
1461
|
+
const client = this.getClientForEndpoint(endpoint);
|
|
1462
|
+
|
|
1463
|
+
const options: Record<string, any> = {
|
|
1464
|
+
method: endpoint.method,
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
if (query && Object.keys(query).length > 0) {
|
|
1468
|
+
const searchParams = new URLSearchParams();
|
|
1469
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
1470
|
+
if (value !== undefined && value !== null) {
|
|
1471
|
+
searchParams.append(key, String(value));
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
if (searchParams.toString()) {
|
|
1475
|
+
options.searchParams = searchParams;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (body && endpoint.method !== "GET") {
|
|
1480
|
+
if (endpoint.body) {
|
|
1481
|
+
const validatedBody = endpoint.body.parse(body);
|
|
1482
|
+
options.json = validatedBody;
|
|
1483
|
+
} else {
|
|
1484
|
+
options.json = body;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
const response = await client(path, options);
|
|
1489
|
+
const data = await response.json();
|
|
1490
|
+
|
|
1491
|
+
if (endpoint.response) {
|
|
1492
|
+
return endpoint.response.parse(data);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return data as T;
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
if (error instanceof HTTPError) {
|
|
1498
|
+
const errorData = await error.response.json().catch(() => ({}));
|
|
1499
|
+
throw new APIError(
|
|
1500
|
+
errorData.message || error.message,
|
|
1501
|
+
error.response.status,
|
|
1502
|
+
errorData,
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (error instanceof AuthError) {
|
|
1507
|
+
throw error;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
throw new APIError(
|
|
1511
|
+
error instanceof Error ? error.message : "Network error",
|
|
1512
|
+
0,
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
updateAuthCallbacks(authCallbacks: AuthCallbacks) {
|
|
1518
|
+
this.authCallbacks = authCallbacks;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
async refreshAuth(): Promise<void> {
|
|
1522
|
+
if (!this.authCallbacks) {
|
|
1523
|
+
throw new AuthError("No auth callbacks provided");
|
|
1524
|
+
}
|
|
1525
|
+
await this.refreshTokens();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
generateMethods() {
|
|
1529
|
+
const methods: any = {};
|
|
1530
|
+
|
|
1531
|
+
Object.entries(this.config.endpoints).forEach(([name, endpoint]) => {
|
|
1532
|
+
if (endpoint.method === "GET") {
|
|
1533
|
+
if (endpoint.params && endpoint.query) {
|
|
1534
|
+
methods[name] = (params: any, query?: any): Promise<any> => {
|
|
1535
|
+
return this.request(endpoint, params, query);
|
|
1536
|
+
};
|
|
1537
|
+
} else if (endpoint.params) {
|
|
1538
|
+
methods[name] = (params: any): Promise<any> => {
|
|
1539
|
+
return this.request(endpoint, params);
|
|
1540
|
+
};
|
|
1541
|
+
} else if (endpoint.query) {
|
|
1542
|
+
methods[name] = (query?: any): Promise<any> => {
|
|
1543
|
+
return this.request(endpoint, undefined, query);
|
|
1544
|
+
};
|
|
1545
|
+
} else {
|
|
1546
|
+
methods[name] = (): Promise<any> => {
|
|
1547
|
+
return this.request(endpoint);
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
} else {
|
|
1551
|
+
if (endpoint.params && endpoint.body) {
|
|
1552
|
+
methods[name] = (params: any, body: any): Promise<any> => {
|
|
1553
|
+
return this.request(endpoint, params, undefined, body);
|
|
1554
|
+
};
|
|
1555
|
+
} else if (endpoint.params) {
|
|
1556
|
+
methods[name] = (params: any): Promise<any> => {
|
|
1557
|
+
return this.request(endpoint, params);
|
|
1558
|
+
};
|
|
1559
|
+
} else if (endpoint.body) {
|
|
1560
|
+
methods[name] = (body: any): Promise<any> => {
|
|
1561
|
+
return this.request(endpoint, undefined, undefined, body);
|
|
1562
|
+
};
|
|
1563
|
+
} else {
|
|
1564
|
+
methods[name] = (): Promise<any> => {
|
|
1565
|
+
return this.request(endpoint);
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
return methods;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
export function createAPIClient(
|
|
1576
|
+
config: APIConfig,
|
|
1577
|
+
authCallbacks?: AuthCallbacks,
|
|
1578
|
+
) {
|
|
1579
|
+
const instance = new APIClient(config, authCallbacks);
|
|
1580
|
+
const methods = instance.generateMethods();
|
|
1581
|
+
|
|
1582
|
+
return {
|
|
1583
|
+
...methods,
|
|
1584
|
+
refreshAuth: () => instance.refreshAuth(),
|
|
1585
|
+
updateAuthCallbacks: (newCallbacks: AuthCallbacks) =>
|
|
1586
|
+
instance.updateAuthCallbacks(newCallbacks),
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
`;
|
|
1590
|
+
return content;
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1180
1593
|
|
|
1181
1594
|
// src/generators/index.ts
|
|
1182
1595
|
var CodeGenerator = class {
|
|
@@ -1192,6 +1605,8 @@ var CodeGenerator = class {
|
|
|
1192
1605
|
getGenerators() {
|
|
1193
1606
|
const generators = [];
|
|
1194
1607
|
generators.push(new TypesGenerator(this.context));
|
|
1608
|
+
generators.push(new SchemaGenerator(this.context));
|
|
1609
|
+
generators.push(new CoreGenerator(this.context));
|
|
1195
1610
|
if (this.context.config.generateClient) {
|
|
1196
1611
|
generators.push(new ClientGenerator(this.context));
|
|
1197
1612
|
}
|