rastack 0.0.16

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 (140) hide show
  1. package/CHANGELOG.md +43013 -0
  2. package/dist/.prettierrc +7 -0
  3. package/dist/compile/analyze.d.ts +12 -0
  4. package/dist/compile/analyze.js +233 -0
  5. package/dist/compile/index.d.ts +19 -0
  6. package/dist/compile/index.js +107 -0
  7. package/dist/compile/manifest.d.ts +4 -0
  8. package/dist/compile/manifest.js +12 -0
  9. package/dist/compile/model.d.ts +54 -0
  10. package/dist/compile/model.js +7 -0
  11. package/dist/compile/openapi.d.ts +11 -0
  12. package/dist/compile/openapi.js +233 -0
  13. package/dist/compile/program.d.ts +10 -0
  14. package/dist/compile/program.js +61 -0
  15. package/dist/csv-schema.d.ts +28 -0
  16. package/dist/csv-schema.js +162 -0
  17. package/dist/define/index.d.ts +117 -0
  18. package/dist/define/index.js +74 -0
  19. package/dist/entity-generation/delete-method.d.ts +1 -0
  20. package/dist/entity-generation/delete-method.js +31 -0
  21. package/dist/entity-generation/form.d.ts +13 -0
  22. package/dist/entity-generation/form.js +136 -0
  23. package/dist/entity-generation/get-method.d.ts +1 -0
  24. package/dist/entity-generation/get-method.js +67 -0
  25. package/dist/entity-generation/imports.d.ts +5 -0
  26. package/dist/entity-generation/imports.js +91 -0
  27. package/dist/entity-generation/sync-method.d.ts +30 -0
  28. package/dist/entity-generation/sync-method.js +158 -0
  29. package/dist/entity-generation/update-method.d.ts +1 -0
  30. package/dist/entity-generation/update-method.js +29 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +53 -0
  33. package/dist/rad-compile.d.ts +11 -0
  34. package/dist/rad-compile.js +82 -0
  35. package/dist/rad-wasm-build.d.ts +14 -0
  36. package/dist/rad-wasm-build.js +74 -0
  37. package/dist/rad.d.ts +17 -0
  38. package/dist/rad.js +88 -0
  39. package/dist/scan.d.ts +12 -0
  40. package/dist/scan.js +79 -0
  41. package/dist/schema/camel-to-pastel.d.ts +1 -0
  42. package/dist/schema/camel-to-pastel.js +7 -0
  43. package/dist/schema/capitalise-first-letter.d.ts +1 -0
  44. package/dist/schema/capitalise-first-letter.js +7 -0
  45. package/dist/schema/extract-response.d.ts +2 -0
  46. package/dist/schema/extract-response.js +30 -0
  47. package/dist/schema/fetch-schema.d.ts +7 -0
  48. package/dist/schema/fetch-schema.js +23 -0
  49. package/dist/schema/remove-non-model-paths.d.ts +2 -0
  50. package/dist/schema/remove-non-model-paths.js +15 -0
  51. package/dist/schema/to-camel-case.d.ts +1 -0
  52. package/dist/schema/to-camel-case.js +11 -0
  53. package/dist/schema/to-pastel-case.d.ts +1 -0
  54. package/dist/schema/to-pastel-case.js +10 -0
  55. package/dist/schema-convert.d.ts +1 -0
  56. package/dist/schema-convert.js +63 -0
  57. package/dist/schema-entities.d.ts +14 -0
  58. package/dist/schema-entities.js +293 -0
  59. package/dist/schema-fetch.d.ts +1 -0
  60. package/dist/schema-fetch.js +81 -0
  61. package/dist/schema-full.d.ts +1 -0
  62. package/dist/schema-full.js +45 -0
  63. package/dist/schema-index.d.ts +1 -0
  64. package/dist/schema-index.js +34 -0
  65. package/dist/schema-params.d.ts +1 -0
  66. package/dist/schema-params.js +100 -0
  67. package/dist/seed.d.ts +17 -0
  68. package/dist/seed.js +252 -0
  69. package/hooks/form/form.ts +207 -0
  70. package/hooks/form/index.ts +8 -0
  71. package/hooks/form/interfaces.ts +217 -0
  72. package/hooks/form/structure.ts +39 -0
  73. package/hooks/form/validate-schema.ts +49 -0
  74. package/hooks/index.ts +3 -0
  75. package/hooks/query/api.ts +42 -0
  76. package/hooks/query/delete.ts +45 -0
  77. package/hooks/query/fetch.ts +48 -0
  78. package/hooks/query/index.ts +21 -0
  79. package/hooks/query/interfaces.ts +111 -0
  80. package/hooks/query/list.ts +286 -0
  81. package/hooks/query/update.ts +88 -0
  82. package/hooks/query/url.ts +31 -0
  83. package/hooks/real-time/index.ts +1 -0
  84. package/hooks/real-time/pusher.ts +43 -0
  85. package/jest.config.cjs +6 -0
  86. package/package.json +57 -0
  87. package/provider/index.ts +13 -0
  88. package/provider/provider.tsx +187 -0
  89. package/provider/types.ts +114 -0
  90. package/provider/warehouse.ts +167 -0
  91. package/provider/wasm.ts +124 -0
  92. package/runtime.ts +3 -0
  93. package/src/.prettierrc +7 -0
  94. package/src/compile/analyze.ts +224 -0
  95. package/src/compile/index.ts +86 -0
  96. package/src/compile/manifest.ts +10 -0
  97. package/src/compile/model.ts +69 -0
  98. package/src/compile/openapi.ts +266 -0
  99. package/src/compile/program.ts +40 -0
  100. package/src/csv-schema.ts +187 -0
  101. package/src/define/index.ts +162 -0
  102. package/src/entity-generation/delete-method.ts +37 -0
  103. package/src/entity-generation/form.ts +169 -0
  104. package/src/entity-generation/get-method.ts +80 -0
  105. package/src/entity-generation/imports.ts +110 -0
  106. package/src/entity-generation/sync-method.ts +222 -0
  107. package/src/entity-generation/update-method.ts +38 -0
  108. package/src/index.ts +77 -0
  109. package/src/rad-compile.ts +88 -0
  110. package/src/rad-wasm-build.ts +102 -0
  111. package/src/rad.ts +101 -0
  112. package/src/scan.ts +89 -0
  113. package/src/schema/camel-to-pastel.ts +3 -0
  114. package/src/schema/capitalise-first-letter.ts +3 -0
  115. package/src/schema/extract-response.ts +38 -0
  116. package/src/schema/fetch-schema.ts +12 -0
  117. package/src/schema/remove-non-model-paths.ts +13 -0
  118. package/src/schema/to-camel-case.ts +7 -0
  119. package/src/schema/to-pastel-case.ts +6 -0
  120. package/src/schema-convert.ts +61 -0
  121. package/src/schema-entities.ts +401 -0
  122. package/src/schema-fetch.ts +82 -0
  123. package/src/schema-full.ts +38 -0
  124. package/src/schema-index.ts +29 -0
  125. package/src/schema-params.ts +116 -0
  126. package/src/seed.ts +297 -0
  127. package/sync/engine.ts +392 -0
  128. package/sync/hooks.ts +450 -0
  129. package/sync/index.ts +8 -0
  130. package/sync/persistence.ts +237 -0
  131. package/sync/provider.tsx +91 -0
  132. package/sync/registry.ts +36 -0
  133. package/sync/store.ts +126 -0
  134. package/sync/transactions.ts +300 -0
  135. package/sync/types.ts +94 -0
  136. package/test/compile.spec.ts +192 -0
  137. package/test/csv-schema.spec.ts +143 -0
  138. package/test/schema-entities.spec.ts +688 -0
  139. package/tsconfig.json +24 -0
  140. package/types.ts +19 -0
@@ -0,0 +1,401 @@
1
+ import { mkdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import {
4
+ OperationObject,
5
+ ParameterObject,
6
+ PathsObject,
7
+ RequestBodyObject,
8
+ } from "@ts-stack/openapi-spec";
9
+ import { extractResponseBodySchema } from "./schema/extract-response";
10
+ import { fetchSchema } from "./schema/fetch-schema";
11
+ import { removeNonModelPaths } from "./schema/remove-non-model-paths";
12
+ import { camelToPastel } from "./schema/camel-to-pastel";
13
+ import { generateGetAPIMethod } from "./entity-generation/get-method";
14
+ import { generateUpdateAPIMethod } from "./entity-generation/update-method";
15
+ import {
16
+ generateFolderImports,
17
+ generateImportStatements,
18
+ generateIndexImports,
19
+ generateKeyImports,
20
+ } from "./entity-generation/imports";
21
+ import { generateUpdateFormMethod } from "./entity-generation/form";
22
+ import { generateDeleteAPIMethod } from "./entity-generation/delete-method";
23
+ import {
24
+ EntitySyncMeta,
25
+ generateConfirmedMethod,
26
+ generateEntityDescriptor,
27
+ generateSyncDeleteMethod,
28
+ generateSyncListMethod,
29
+ generateSyncRecordMethod,
30
+ generateSyncUpdateMethod,
31
+ } from "./entity-generation/sync-method";
32
+ import { toCamelCase } from "./schema/to-camel-case";
33
+
34
+ function pathSegments(pathKey: string): string[] {
35
+ return pathKey.replace(/\/$/, "").split("/");
36
+ }
37
+
38
+ /**
39
+ * Pre-pass over the OpenAPI paths: collects, per entity, the sync mode
40
+ * (from rastack's `x-rastack-sync` vendor extension), confirmed operations,
41
+ * the list/detail URLs, the row schema, and the list/detail operation ids
42
+ * (used to wire cache invalidation for non-sync entities).
43
+ */
44
+ export function collectEntityMeta(paths: PathsObject): {
45
+ [indexKey: string]: EntitySyncMeta;
46
+ } {
47
+ const meta: { [indexKey: string]: EntitySyncMeta } = {};
48
+
49
+ Object.keys(paths).forEach((pathKey) => {
50
+ const appName = pathKey.split("/")[2];
51
+ const modelName = pathKey.split("/")[4];
52
+ const indexKey = `${appName}|${modelName}`;
53
+ if (!meta[indexKey]) {
54
+ meta[indexKey] = { sync: false, confirmedOperationIds: new Set() };
55
+ }
56
+ const entityMeta = meta[indexKey];
57
+
58
+ const segments = pathSegments(pathKey);
59
+ const isListPath = segments.length === 5;
60
+ const isDetailPath = segments.length === 6 && segments[5].startsWith("{");
61
+ if (isListPath) {
62
+ entityMeta.listUrl = pathKey;
63
+ }
64
+ if (isDetailPath) {
65
+ entityMeta.detailUrl = pathKey;
66
+ entityMeta.detailParam = segments[5].slice(1, -1);
67
+ }
68
+
69
+ Object.keys(paths[pathKey]).forEach((method) => {
70
+ // @ts-ignore
71
+ const operation = paths[pathKey][method] as OperationObject;
72
+ const syncExtension = (operation as any)["x-rastack-sync"];
73
+ if (syncExtension === "local" || syncExtension === "confirmed") {
74
+ entityMeta.sync = true;
75
+ }
76
+ if ((operation as any)["x-rastack-sync-load"] === "lazy") {
77
+ entityMeta.lazy = true;
78
+ }
79
+ if ((operation as any)["x-rastack-sync-client-ids"] === true) {
80
+ entityMeta.clientIds = true;
81
+ }
82
+ if (syncExtension === "confirmed" && operation.operationId) {
83
+ entityMeta.confirmedOperationIds.add(operation.operationId as string);
84
+ }
85
+
86
+ const [responseBodySchema, isList] =
87
+ extractResponseBodySchema(operation);
88
+ const operationId = (operation.operationId as string) || "";
89
+ const isPostList =
90
+ method === "post" &&
91
+ (operationId.includes("List") || operationId.includes("list"));
92
+
93
+ if (method === "get" || isPostList) {
94
+ if (isList || isPostList) {
95
+ if (isListPath && !entityMeta.listOperationId) {
96
+ entityMeta.listOperationId = operationId;
97
+ }
98
+ } else if (isDetailPath && !entityMeta.detailOperationId) {
99
+ entityMeta.detailOperationId = operationId;
100
+ }
101
+ if (responseBodySchema && !entityMeta.rowSchema) {
102
+ entityMeta.rowSchema = `I${responseBodySchema}`;
103
+ }
104
+ }
105
+ });
106
+ });
107
+
108
+ return meta;
109
+ }
110
+
111
+ export async function generateFolders(
112
+ entitiesPath: string,
113
+ paths: PathsObject,
114
+ ): Promise<void> {
115
+ Object.keys(paths).forEach((key) => {
116
+ const appName = key.split("/")[2];
117
+ const modelName = key.split("/")[4];
118
+ const appFolder = join(entitiesPath, appName);
119
+ const modelFolder = join(appFolder, modelName);
120
+
121
+ mkdirSync(appFolder, { recursive: true });
122
+ mkdirSync(modelFolder, { recursive: true });
123
+ });
124
+ }
125
+
126
+ export async function generateFiles(
127
+ entitiesPath: string,
128
+ paths: PathsObject,
129
+ ): Promise<void> {
130
+ const uniqueOperationNames: string[] = [];
131
+ const modelFileImports: { [key: string]: Set<string> } = {};
132
+ const modelFileData: { [key: string]: string } = {};
133
+ const keysFileData: { [key: string]: string[] } = {};
134
+ const modelImportsFileData: { [key: string]: string[] } = {};
135
+ const formMutationOperations: { [key: string]: any } = {};
136
+ const entityMeta = collectEntityMeta(paths);
137
+
138
+ Object.keys(paths).forEach((pathKey) => {
139
+ const appName = pathKey.split("/")[2];
140
+ const modelName = pathKey.split("/")[4];
141
+
142
+ const indexKey = `${appName}|${modelName}`;
143
+ if (!formMutationOperations[indexKey]) {
144
+ formMutationOperations[indexKey] = {
145
+ post: {
146
+ operationId: undefined,
147
+ schema: undefined,
148
+ },
149
+ put: {
150
+ operationId: undefined,
151
+ schema: undefined,
152
+ },
153
+ patch: {
154
+ operationId: undefined,
155
+ schema: undefined,
156
+ },
157
+ };
158
+ }
159
+ if (!modelImportsFileData[appName]) {
160
+ modelImportsFileData[appName] = [modelName];
161
+ } else {
162
+ if (!modelImportsFileData[appName].includes(modelName)) {
163
+ modelImportsFileData[appName].push(modelName);
164
+ }
165
+ }
166
+
167
+ Object.keys(paths[pathKey]).forEach((method) => {
168
+ // @ts-ignore
169
+ const methodInformation = paths[pathKey][method] as OperationObject;
170
+
171
+ const methodParameters =
172
+ methodInformation.parameters as ParameterObject[];
173
+ const pathParameters = methodParameters
174
+ ? methodParameters
175
+ .filter((item) => item.required)
176
+ .filter((item) => item.in === "path")
177
+ .map((item) => item.name)
178
+ : [];
179
+
180
+ const requestBody = methodInformation.requestBody as RequestBodyObject;
181
+ // @ts-ignore
182
+ const requestBodySchema = requestBody?.content?.[
183
+ "application/json"
184
+ ]?.schema["$ref"]?.replace("#/components/schemas/", "");
185
+
186
+ let [responseBodySchema, isList] =
187
+ extractResponseBodySchema(methodInformation);
188
+
189
+ const requiredParameters = methodParameters
190
+ ? methodParameters
191
+ .filter((item) => item.required)
192
+ .map((item) => item.name)
193
+ : [];
194
+
195
+ const operationId = methodInformation.operationId as string;
196
+
197
+ if (!uniqueOperationNames.includes(operationId)) {
198
+ uniqueOperationNames.push(operationId);
199
+
200
+ let apiMethods = "";
201
+ let importStatements = new Set<string>();
202
+
203
+ const isPostListEndpoint =
204
+ method === "post" &&
205
+ (operationId.includes("List") || operationId.includes("list"));
206
+ const isListEndpoint = isList || isPostListEndpoint;
207
+
208
+ const meta = entityMeta[indexKey];
209
+ const isSyncEntity = meta?.sync === true;
210
+ const isConfirmedOperation =
211
+ (methodInformation as any)["x-rastack-sync"] === "confirmed" &&
212
+ method !== "get";
213
+ const modelCamel = toCamelCase(modelName);
214
+ const listKeyRef = meta?.listOperationId
215
+ ? `${modelCamel}QueriesKeys.${meta.listOperationId}`
216
+ : undefined;
217
+ const itemKeyRef = meta?.detailOperationId
218
+ ? `${modelCamel}QueriesKeys.${meta.detailOperationId}`
219
+ : undefined;
220
+
221
+ if (isConfirmedOperation) {
222
+ [apiMethods, importStatements] = generateConfirmedMethod(
223
+ pathKey,
224
+ method,
225
+ operationId,
226
+ requestBodySchema ? `I${requestBodySchema}` : undefined,
227
+ responseBodySchema ? `I${responseBodySchema}` : undefined,
228
+ );
229
+
230
+ formMutationOperations[indexKey] = {
231
+ ...formMutationOperations[indexKey],
232
+ [pathKey]: {
233
+ [method]: {
234
+ operationId: camelToPastel(operationId),
235
+ schema: requestBodySchema ? `I${requestBodySchema}` : "",
236
+ },
237
+ },
238
+ };
239
+ } else if (method === "get" || isPostListEndpoint) {
240
+ if (isSyncEntity && isListEndpoint) {
241
+ [apiMethods, importStatements] = generateSyncListMethod(
242
+ modelName,
243
+ operationId,
244
+ meta.rowSchema,
245
+ );
246
+ } else if (isSyncEntity && pathKey.includes("{")) {
247
+ const urlParams =
248
+ pathKey.match(/{([^}]+)}/g)?.map((param) => param.slice(1, -1)) ||
249
+ [];
250
+ [apiMethods, importStatements] = generateSyncRecordMethod(
251
+ modelName,
252
+ operationId,
253
+ meta.rowSchema,
254
+ pathParameters.length > 0 ? pathParameters : urlParams,
255
+ );
256
+ } else {
257
+ [apiMethods, importStatements] = generateGetAPIMethod(
258
+ pathKey,
259
+ modelName,
260
+ operationId,
261
+ responseBodySchema ? `I${responseBodySchema}` : undefined,
262
+ isListEndpoint,
263
+ pathParameters,
264
+ requiredParameters,
265
+ method,
266
+ requestBodySchema !== undefined ? requestBodySchema : "",
267
+ );
268
+ }
269
+ } else if (
270
+ method === "post" ||
271
+ method === "put" ||
272
+ method === "patch"
273
+ ) {
274
+ if (isSyncEntity) {
275
+ [apiMethods, importStatements] = generateSyncUpdateMethod(
276
+ pathKey,
277
+ method,
278
+ modelName,
279
+ operationId,
280
+ requestBodySchema ? `I${requestBodySchema}` : undefined,
281
+ responseBodySchema ? `I${responseBodySchema}` : undefined,
282
+ pathParameters,
283
+ );
284
+ } else {
285
+ [apiMethods, importStatements] = generateUpdateAPIMethod(
286
+ pathKey,
287
+ method,
288
+ operationId,
289
+ requestBodySchema ? `I${requestBodySchema}` : undefined,
290
+ responseBodySchema ? `I${responseBodySchema}` : undefined,
291
+ listKeyRef,
292
+ itemKeyRef,
293
+ );
294
+ }
295
+
296
+ formMutationOperations[indexKey] = {
297
+ ...formMutationOperations[indexKey],
298
+ [pathKey]: {
299
+ [method]: {
300
+ operationId: camelToPastel(operationId),
301
+ schema: requestBodySchema ? `I${requestBodySchema}` : "",
302
+ },
303
+ },
304
+ };
305
+ } else if (method === "delete") {
306
+ if (isSyncEntity) {
307
+ [apiMethods, importStatements] = generateSyncDeleteMethod(
308
+ pathKey,
309
+ modelName,
310
+ operationId,
311
+ );
312
+ } else {
313
+ [apiMethods, importStatements] = generateDeleteAPIMethod(
314
+ pathKey,
315
+ operationId,
316
+ listKeyRef,
317
+ );
318
+ }
319
+ }
320
+
321
+ modelFileData[indexKey] = (modelFileData[indexKey] || "") + apiMethods;
322
+ modelFileImports[indexKey] = new Set([
323
+ ...(modelFileImports[indexKey] || []),
324
+ ...importStatements,
325
+ ]);
326
+ keysFileData[indexKey] = [
327
+ ...(keysFileData[indexKey] || []),
328
+ operationId,
329
+ ];
330
+ }
331
+ });
332
+ });
333
+
334
+ Object.keys(modelFileData).forEach((key) => {
335
+ const [appName, modelName] = key.split("|");
336
+ const modelFolder = join(entitiesPath, appName, modelName);
337
+ const apiFile = join(modelFolder, "api.ts");
338
+ const indexFile = join(modelFolder, "index.ts");
339
+ const keyImports = generateKeyImports(modelName, keysFileData[key]);
340
+
341
+ const [updateFormMethod, importStatements] = generateUpdateFormMethod(
342
+ appName,
343
+ modelName,
344
+ formMutationOperations[key],
345
+ );
346
+ const indexKey = `${appName}|${modelName}`;
347
+ modelFileImports[indexKey] = new Set([
348
+ ...(modelFileImports[indexKey] || []),
349
+ ...importStatements,
350
+ ]);
351
+
352
+ let entityDescriptor = "";
353
+ if (entityMeta[key]?.sync) {
354
+ const [descriptorCode, descriptorImports] = generateEntityDescriptor(
355
+ appName,
356
+ modelName,
357
+ entityMeta[key],
358
+ );
359
+ entityDescriptor = descriptorCode;
360
+ modelFileImports[indexKey] = new Set([
361
+ ...(modelFileImports[indexKey] || []),
362
+ ...descriptorImports,
363
+ ]);
364
+ }
365
+
366
+ const importData = generateImportStatements(
367
+ modelFileImports[key],
368
+ modelName,
369
+ );
370
+
371
+ const apiFileText = `${importData}\n${keyImports}\n${entityDescriptor}${modelFileData[key]}\n${updateFormMethod}`;
372
+ const indexImports = generateIndexImports(modelName, keysFileData[key]);
373
+
374
+ writeFileSync(apiFile, apiFileText, { flag: "w" });
375
+ writeFileSync(indexFile, indexImports, { flag: "w" });
376
+ });
377
+
378
+ // create an index file with all the file names per model
379
+ Object.keys(modelImportsFileData).forEach((key) => {
380
+ const modelFolder = join(entitiesPath, key);
381
+ const indexFile = join(modelFolder, "index.ts");
382
+
383
+ const indexImports = generateFolderImports(modelImportsFileData[key]);
384
+ writeFileSync(indexFile, indexImports, { flag: "w" });
385
+ });
386
+ }
387
+
388
+ export async function main(url: string, entitiesPath: string): Promise<void> {
389
+ console.log("Generating Entities");
390
+ const data = await fetchSchema(url);
391
+ const filteredPaths = removeNonModelPaths(data.paths);
392
+ await generateFolders(entitiesPath, filteredPaths);
393
+ await generateFiles(entitiesPath, filteredPaths);
394
+ }
395
+
396
+ if (require.main === module) {
397
+ const args: string[] = process.argv.slice(2);
398
+ const url = args[0];
399
+ const entitiesPath = args[1];
400
+ main(url, entitiesPath);
401
+ }
@@ -0,0 +1,82 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import axios, { AxiosResponse } from "axios";
4
+
5
+ interface OpenAPISchema {
6
+ components: { schemas: Record<string, any> };
7
+ }
8
+
9
+ async function fetchSchema(
10
+ url: string,
11
+ schemaPath: string,
12
+ ): Promise<OpenAPISchema> {
13
+ const response: AxiosResponse<OpenAPISchema> = await axios.get(url);
14
+ writeFileSync(schemaPath, JSON.stringify(response.data, null, 2));
15
+ return response.data;
16
+ }
17
+
18
+ async function generateFiles(
19
+ basePath: string,
20
+ components: { schemas: Record<string, any> },
21
+ ): Promise<void> {
22
+ const schemas = components.schemas;
23
+ Object.keys(schemas).forEach((key) => {
24
+ const schema = schemas[key];
25
+ let schemaKey = key;
26
+ if (key.endsWith("Enum")) {
27
+ schemaKey = key.replace("Enum", "");
28
+ schemaKey = `E${schemaKey}`;
29
+ } else {
30
+ schemaKey = `I${schemaKey}`;
31
+ }
32
+ const schemaFolderPath = join(basePath, "api-schema");
33
+ const fileName = `${schemaFolderPath}/${schemaKey}.json`;
34
+ writeFileSync(fileName, JSON.stringify(schema, null, 2));
35
+ });
36
+ }
37
+
38
+ async function replaceReferences(basePath: string): Promise<void> {
39
+ const schemaFolderPath = join(basePath, "api-schema");
40
+ const schemaFiles = readdirSync(schemaFolderPath);
41
+
42
+ schemaFiles.forEach((file) => {
43
+ const filePath = join(schemaFolderPath, file);
44
+ const fileContent = JSON.parse(readFileSync(filePath, "utf8"));
45
+ let convertedSchema = JSON.stringify(fileContent);
46
+
47
+ const searchPattern = /"#\/components\/schemas\/([A-Za-z0-9_-]+)"/g;
48
+
49
+ convertedSchema = convertedSchema.replace(
50
+ searchPattern,
51
+ (_, matchedSchemaName) => {
52
+ let updatedSchemaName = matchedSchemaName;
53
+
54
+ if (updatedSchemaName.endsWith("Enum")) {
55
+ updatedSchemaName = updatedSchemaName.replace("Enum", "");
56
+ updatedSchemaName = `E${updatedSchemaName}`;
57
+ } else {
58
+ updatedSchemaName = `I${updatedSchemaName}`;
59
+ }
60
+
61
+ return `"${updatedSchemaName}.json"`;
62
+ },
63
+ );
64
+
65
+ const writeSchema = JSON.parse(convertedSchema);
66
+ writeFileSync(filePath, JSON.stringify(writeSchema, null, 2));
67
+ });
68
+ }
69
+
70
+ async function main() {
71
+ console.log("Fetching Schema");
72
+ const args: string[] = process.argv.slice(2);
73
+ const url = args[0];
74
+ const basePath = args[1];
75
+ const schemaPath = join(basePath, "schema.json");
76
+ const { components } = await fetchSchema(url, schemaPath);
77
+
78
+ await generateFiles(basePath, components);
79
+ await replaceReferences(basePath);
80
+ }
81
+
82
+ main();
@@ -0,0 +1,38 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ const $RefParser = require("@apidevtools/json-schema-ref-parser");
5
+
6
+ async function generateDereferencedSchema(schemaPath: string): Promise<string> {
7
+ try {
8
+ const schemaContent = readFileSync(schemaPath, "utf8");
9
+ const schemaObject = JSON.parse(schemaContent);
10
+
11
+ const dereferencedSchema = await $RefParser.dereference(schemaObject);
12
+ return JSON.stringify(dereferencedSchema, null, 2);
13
+ } catch (error) {
14
+ return "";
15
+ }
16
+ }
17
+
18
+ async function main() {
19
+ console.log("Resolving Schemas");
20
+ const args: string[] = process.argv.slice(2);
21
+ const basePath = args[0];
22
+ const schemaFolderPath = join(basePath, "api-schema");
23
+ const outputFolder = join(basePath, "form-schema");
24
+
25
+ const schemaFiles = readdirSync(schemaFolderPath);
26
+
27
+ let compilePromises = schemaFiles.map(async (schemaFile) => {
28
+ const schemaFilePath = join(schemaFolderPath, schemaFile);
29
+
30
+ const generatedCode = await generateDereferencedSchema(schemaFilePath);
31
+
32
+ const outputFilePath = join(outputFolder, schemaFile);
33
+ writeFileSync(outputFilePath, generatedCode);
34
+ });
35
+ let results = await Promise.all(compilePromises);
36
+ }
37
+
38
+ main();
@@ -0,0 +1,29 @@
1
+ import { join } from "path";
2
+ import { readdirSync, writeFileSync } from "fs";
3
+
4
+ async function main() {
5
+ console.log("Generating Schema Index");
6
+ const args: string[] = process.argv.slice(2);
7
+ const basePath = args[0];
8
+ const schemaFolderPath = join(basePath, "form-schema");
9
+
10
+ let indexFileData =
11
+ 'export * from "./interface";\nexport * from "./params";\n';
12
+
13
+ const schemaFiles = readdirSync(schemaFolderPath);
14
+
15
+ let compilePromises = schemaFiles.map((schemaFile) => {
16
+ const schemaFileName = schemaFile.replace(".json", "");
17
+ const formSchemaExport = `export { default as Deref${schemaFileName} } from "./form-schema/${schemaFile}";`;
18
+ const schemaExport = `export { default as Schema${schemaFileName} } from "./api-schema/${schemaFile}";`;
19
+ return `${formSchemaExport}\n${schemaExport}`;
20
+ });
21
+
22
+ let results = await Promise.all(compilePromises);
23
+ indexFileData = indexFileData + results.join("\n");
24
+
25
+ const outputFile = join(basePath, "index.ts");
26
+ writeFileSync(outputFile, indexFileData);
27
+ }
28
+
29
+ main();
@@ -0,0 +1,116 @@
1
+ import { writeFileSync } from "fs";
2
+ import {
3
+ ComponentsObject,
4
+ ParameterObject,
5
+ PathsObject,
6
+ } from "@ts-stack/openapi-spec";
7
+ import { OperationObject } from "@ts-stack/openapi-spec/src/origin/operation-object";
8
+ import { join } from "path";
9
+ import { fetchSchema } from "./schema/fetch-schema";
10
+
11
+ interface OpenAPISchema {
12
+ paths: PathsObject;
13
+ components: ComponentsObject;
14
+ }
15
+
16
+ interface PathParamItem {
17
+ name: string;
18
+ required: boolean;
19
+ schema: object | undefined;
20
+ }
21
+
22
+ interface PathParam {
23
+ operationName: string;
24
+ paramsArray: PathParamItem[];
25
+ }
26
+
27
+ function generateParams(paths: PathsObject): PathParam[] {
28
+ const pathParam: PathParam[] = [];
29
+ Object.keys(paths).map((path) => {
30
+ const pathItem = paths[path];
31
+ console.log(path);
32
+ Object.keys(pathItem).forEach((method) => {
33
+ // @ts-ignore
34
+ const pathOperation: OperationObject = pathItem[method];
35
+ const pathParamData: PathParamItem[] = [];
36
+ if (pathOperation.parameters) {
37
+ pathOperation.parameters.forEach((param) => {
38
+ // @ts-ignore
39
+ const paramObject: ParameterObject = param;
40
+ pathParamData.push({
41
+ name: paramObject.name,
42
+ required: paramObject.required || false,
43
+ schema: paramObject.schema,
44
+ });
45
+ });
46
+ }
47
+ if (pathOperation.operationId) {
48
+ // Operation id to snake case to camelcase and remove the text v1
49
+ const operationId = pathOperation.operationId;
50
+ const camelCaseOperationId = operationId
51
+ .replace(/_v1/g, "")
52
+ .replace(/_([a-z])/g, function (g) {
53
+ return g[1].toUpperCase();
54
+ });
55
+ const capitalizedOperationId =
56
+ camelCaseOperationId.charAt(0).toUpperCase() +
57
+ camelCaseOperationId.slice(1);
58
+
59
+ pathParam.push({
60
+ operationName: capitalizedOperationId,
61
+ paramsArray: pathParamData,
62
+ });
63
+ }
64
+ });
65
+ });
66
+ return pathParam;
67
+ }
68
+
69
+ function generateInterfacesFiles(pathParams: PathParam[], schemaPath: string) {
70
+ const interfaceFileValues = pathParams.map((pathParam) => {
71
+ const interfaceName = `I${pathParam.operationName}Params`;
72
+ const interfaceValues = pathParam.paramsArray.map((param) => {
73
+ let paramSchemaType = "string";
74
+ if (param.schema) {
75
+ // @ts-ignore
76
+ if (param.schema.type === "array") {
77
+ // @ts-ignore
78
+ if (param.schema.items.enum) {
79
+ // @ts-ignore
80
+ paramSchemaType = param.schema.items.enum
81
+ .map((value: string) => `"${value}"`)
82
+ .join(" | ");
83
+ paramSchemaType = `(${paramSchemaType})[]`;
84
+ }
85
+ } else {
86
+ // @ts-ignore
87
+ if (param.schema.type === "integer") {
88
+ paramSchemaType = "number";
89
+ }
90
+ }
91
+ }
92
+
93
+ return `${param.name}${param.required ? "" : "?"}: ${paramSchemaType};`;
94
+ });
95
+ return `export interface ${interfaceName} {
96
+ ${interfaceValues.join("\n")}
97
+ }`;
98
+ });
99
+ const interfaceFileValuesString = interfaceFileValues.join("\n");
100
+ const interfaceFile = join(schemaPath, `params.ts`);
101
+ writeFileSync(interfaceFile, interfaceFileValuesString, { flag: "w" });
102
+ }
103
+
104
+ async function main() {
105
+ console.log("Generating Schema Parameter Definitions");
106
+ const args: string[] = process.argv.slice(2);
107
+ const url = args[0];
108
+ const schemaPath = args[1];
109
+ const data = await fetchSchema(url);
110
+ const paths = data.paths;
111
+ const pathParams = generateParams(paths);
112
+
113
+ generateInterfacesFiles(pathParams, schemaPath);
114
+ }
115
+
116
+ main();