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,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * `rad compile | check | list | urls` — the TypeScript-first replacement for the
5
+ * old Django `manage.py rastack` command.
6
+ *
7
+ * rad compile [resourcesDir] [outDir] defs → schema.rad.json + openapi.json
8
+ * rad check [resourcesDir] validate the resource graph (exit != 0 on error)
9
+ * rad list [resourcesDir] print every resource + its fields/relations
10
+ * rad urls [resourcesDir] print the generated /api/{app}/v1/{model}/ routes
11
+ */
12
+
13
+ import { analyzeResources, collisions, compile, urlsFor } from "./compile";
14
+
15
+ const DEFAULT_RESOURCES = "resources";
16
+ const DEFAULT_OUT = ".rad";
17
+
18
+ function main(argv: string[]): void {
19
+ const [sub, ...rest] = argv;
20
+
21
+ switch (sub) {
22
+ case "compile": {
23
+ const resourcesDir = rest[0] || DEFAULT_RESOURCES;
24
+ const outDir = rest[1] || DEFAULT_OUT;
25
+ const result = compile(resourcesDir, outDir);
26
+ console.log(
27
+ `✓ compiled ${result.manifest.resources.length} resource(s) → ${result.files.join(", ")}`,
28
+ );
29
+ break;
30
+ }
31
+ case "check": {
32
+ const resources = analyzeResources(rest[0] || DEFAULT_RESOURCES);
33
+ const errors = collisions(resources);
34
+ if (errors.length) {
35
+ for (const e of errors) console.error(` ✗ ${e}`);
36
+ console.error(`${errors.length} error(s) found.`);
37
+ process.exit(1);
38
+ }
39
+ console.log(`✓ ${resources.length} resource(s): no collisions.`);
40
+ break;
41
+ }
42
+ case "list": {
43
+ const resources = analyzeResources(rest[0] || DEFAULT_RESOURCES);
44
+ if (!resources.length) {
45
+ console.log("No resources found.");
46
+ break;
47
+ }
48
+ for (const r of resources) {
49
+ console.log(`${r.app}.${r.model}`);
50
+ for (const f of r.fields) {
51
+ const rel = f.relation ? ` → ${f.relation.app}.${f.relation.model}` : "";
52
+ console.log(` ${f.name}: ${f.type}${rel}`);
53
+ }
54
+ if (r.sync) {
55
+ const flags = [
56
+ r.sync.mode,
57
+ r.sync.load === "lazy" ? "lazy" : "",
58
+ r.sync.clientIds ? "client-ids" : "",
59
+ r.sync.confirmed ? `confirmed: ${r.sync.confirmed.join(",")}` : "",
60
+ ]
61
+ .filter(Boolean)
62
+ .join(", ");
63
+ console.log(` sync: ${flags}`);
64
+ }
65
+ }
66
+ break;
67
+ }
68
+ case "urls": {
69
+ const resources = analyzeResources(rest[0] || DEFAULT_RESOURCES);
70
+ for (const url of urlsFor(resources)) console.log(url);
71
+ break;
72
+ }
73
+ default:
74
+ console.error(
75
+ `Unknown compile subcommand: "${sub}"\n\n` +
76
+ `Usage:\n` +
77
+ ` rad compile [resourcesDir] [outDir]\n` +
78
+ ` rad check [resourcesDir]\n` +
79
+ ` rad list [resourcesDir]\n` +
80
+ ` rad urls [resourcesDir]\n`,
81
+ );
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ if (require.main === module) {
87
+ main(process.argv.slice(2));
88
+ }
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * `rad wasm` — compile the Rust API to WebAssembly for in-browser local dev.
5
+ *
6
+ * Pipeline:
7
+ * 1. cargo build -p rad-wasm --release --target wasm32-unknown-unknown
8
+ * 2. wasm-bindgen --target web → stack/wasm/rad_wasm.js + rad_wasm_bg.wasm
9
+ * 3. wasm-opt -Oz (if available) to shrink the binary
10
+ *
11
+ * The generated bundle is what `<RAStackProvider mode="local">` loads. Prereqs:
12
+ * rustup target add wasm32-unknown-unknown
13
+ * cargo install wasm-bindgen-cli (version must match the wasm-bindgen crate)
14
+ */
15
+
16
+ import { spawnSync } from "child_process";
17
+ import fs from "fs";
18
+ import path from "path";
19
+
20
+ const repoRoot = path.resolve(__dirname, "..", "..");
21
+ const rustDir = path.join(repoRoot, "rust");
22
+ const outDir = path.join(repoRoot, "tools", "wasm");
23
+ const wasmPath = path.join(
24
+ rustDir,
25
+ "target",
26
+ "wasm32-unknown-unknown",
27
+ "release",
28
+ "rad_wasm.wasm",
29
+ );
30
+
31
+ function run(cmd: string, args: string[], cwd = repoRoot): void {
32
+ // Local build tool: `cmd`/`args` are the fixed toolchain invocations below
33
+ // (cargo, wasm-bindgen, wasm-opt), not untrusted input — no injection surface.
34
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
35
+ const result = spawnSync(cmd, args, {
36
+ stdio: "inherit",
37
+ cwd,
38
+ env: process.env,
39
+ });
40
+ if (result.error) {
41
+ console.error(`\nCould not run \`${cmd}\`. Is it installed and on PATH?`);
42
+ if (cmd === "wasm-bindgen") {
43
+ console.error(" cargo install wasm-bindgen-cli");
44
+ }
45
+ process.exit(1);
46
+ }
47
+ if (result.status !== 0) {
48
+ process.exit(result.status ?? 1);
49
+ }
50
+ }
51
+
52
+ function has(cmd: string): boolean {
53
+ // Probes a fixed toolchain name (e.g. "wasm-opt") for availability; not
54
+ // untrusted input.
55
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
56
+ return spawnSync(cmd, ["--version"], { stdio: "ignore" }).status === 0;
57
+ }
58
+
59
+ console.log("→ cargo build -p rad-wasm --release (wasm32-unknown-unknown)");
60
+ run(
61
+ "cargo",
62
+ [
63
+ "build",
64
+ "-p",
65
+ "rad-wasm",
66
+ "--release",
67
+ "--target",
68
+ "wasm32-unknown-unknown",
69
+ ],
70
+ rustDir,
71
+ );
72
+
73
+ console.log("→ wasm-bindgen → stack/wasm/");
74
+ run("wasm-bindgen", [
75
+ "--target",
76
+ "web",
77
+ "--out-dir",
78
+ outDir,
79
+ "--out-name",
80
+ "rad_wasm",
81
+ wasmPath,
82
+ ]);
83
+
84
+ // Mark the emitted bundle as ESM so Node and bundlers treat it correctly
85
+ // without forcing the parent rastack package to be a module.
86
+ fs.writeFileSync(
87
+ path.join(outDir, "package.json"),
88
+ JSON.stringify({ type: "module", sideEffects: false }, null, 2) + "\n",
89
+ );
90
+
91
+ if (has("wasm-opt")) {
92
+ console.log("→ wasm-opt -Oz");
93
+ const bg = path.join(outDir, "rad_wasm_bg.wasm");
94
+ run("wasm-opt", ["-Oz", "-o", bg, bg]);
95
+ } else {
96
+ console.log("· wasm-opt not found — skipping size optimisation (optional)");
97
+ }
98
+
99
+ const size = fs.statSync(path.join(outDir, "rad_wasm_bg.wasm")).size;
100
+ console.log(
101
+ `✓ wasm engine built: stack/wasm/ (${(size / 1024).toFixed(0)} KB)`,
102
+ );
package/src/rad.ts ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * rad — unified CLI for React API Stack
5
+ *
6
+ * Commands:
7
+ * rad compile [resourcesDir] [outDir] TypeScript resources → schema.rad.json + openapi.json
8
+ * rad check [resourcesDir] Validate the resource graph
9
+ * rad list [resourcesDir] Print resources, fields and relations
10
+ * rad urls [resourcesDir] Print the generated /api/{app}/v1/{model}/ routes
11
+ * rad generate [--schema-only] Generate typed hooks from OpenAPI
12
+ * rad serve [--warehouse dir] Run the Rust API over an Iceberg warehouse
13
+ * rad admin [--warehouse dir] Run the Rust admin (Django-admin-style DB browser)
14
+ * rad wasm Compile the API to WebAssembly (in-browser local dev)
15
+ * rad scan [files...] [--fail-on-pii] Scan CSVs for PII and schema info
16
+ * rad seed [--env dev] [--source gs://] Load CSVs into Iceberg namespace
17
+ */
18
+
19
+ import { execSync, spawnSync } from "child_process";
20
+ import path from "path";
21
+
22
+ const [command, ...rest] = process.argv.slice(2);
23
+
24
+ function run(script: string, args: string[] = rest) {
25
+ // `rad` is a local developer CLI: `script` is a fixed internal dist filename
26
+ // and `args` come from the developer's own shell (process.argv), not from an
27
+ // untrusted network request — there is no command-injection surface here.
28
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
29
+ execSync(`node ${path.join(__dirname, script)} ${args.join(" ")}`, {
30
+ stdio: "inherit",
31
+ env: process.env,
32
+ });
33
+ }
34
+
35
+ /** Invoke the compiled Rust server binary (built by `cargo build` in rust/). */
36
+ function runRust(subcommand: string) {
37
+ const repoRoot = path.resolve(__dirname, "..", "..");
38
+ const binary =
39
+ process.env.RAD_SERVER_BIN ||
40
+ path.join(repoRoot, "rust", "target", "release", "rad-server");
41
+ // Runs the locally-built Rust server binary with a fixed internal subcommand
42
+ // ("serve"/"admin"); `rest` is the developer's own CLI args, not untrusted
43
+ // input — no command-injection surface.
44
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
45
+ const result = spawnSync(binary, [subcommand, ...rest], {
46
+ stdio: "inherit",
47
+ env: process.env,
48
+ });
49
+ if (result.error) {
50
+ console.error(
51
+ `Could not run the Rust server (${binary}). Build it first:\n` +
52
+ ` cd rust && cargo build --release\n`,
53
+ );
54
+ process.exit(1);
55
+ }
56
+ process.exit(result.status ?? 0);
57
+ }
58
+
59
+ switch (command) {
60
+ case "compile":
61
+ case "check":
62
+ case "list":
63
+ case "urls":
64
+ // rad-compile dispatches on its own first argument, so pass the command.
65
+ run("rad-compile.js", [command, ...rest]);
66
+ break;
67
+ case "serve":
68
+ runRust("serve");
69
+ break;
70
+ case "admin":
71
+ runRust("admin");
72
+ break;
73
+ case "wasm":
74
+ // Compile the API to WebAssembly for in-browser local dev.
75
+ run("rad-wasm-build.js", rest);
76
+ break;
77
+ case "scan":
78
+ run("scan.js");
79
+ break;
80
+ case "seed":
81
+ run("seed.js");
82
+ break;
83
+ case "generate":
84
+ case "gen":
85
+ // Proxy to existing rastack codegen pipeline
86
+ run("index.js");
87
+ break;
88
+ default:
89
+ console.error(
90
+ `Unknown command: "${command}"\n\n` +
91
+ `Usage:\n` +
92
+ ` rad compile [resourcesDir] [outDir]\n` +
93
+ ` rad check | list | urls [resourcesDir]\n` +
94
+ ` rad generate [--schema-only]\n` +
95
+ ` rad serve | admin [--warehouse dir]\n` +
96
+ ` rad wasm Compile the API to WebAssembly (browser local dev)\n` +
97
+ ` rad scan [files...] [--fail-on-pii]\n` +
98
+ ` rad seed [--env dev|staging] [--source gs://bucket/ | ./myapp-dev/]\n`,
99
+ );
100
+ process.exit(1);
101
+ }
package/src/scan.ts ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * rad scan
5
+ *
6
+ * Scans CSV files in myapp-dev/ for PII column patterns and schema issues.
7
+ * Exits non-zero if violations are found and --fail-on-pii is passed.
8
+ *
9
+ * Usage:
10
+ * rad scan [files...] [--fail-on-pii]
11
+ * rad scan myapp-dev/passengers.csv myapp-dev/flights.csv --fail-on-pii
12
+ */
13
+
14
+ import path from "path";
15
+ import { discoverCsvFiles, inferSchema } from "./csv-schema";
16
+
17
+ const args = process.argv.slice(2);
18
+ const failOnPii = args.includes("--fail-on-pii");
19
+ const fileArgs = args.filter((a) => !a.startsWith("--") && a.endsWith(".csv"));
20
+
21
+ const repoRoot = process.cwd();
22
+ const devDir = path.join(repoRoot, "myapp-dev");
23
+
24
+ const files = fileArgs.length > 0 ? fileArgs : discoverCsvFiles(devDir);
25
+
26
+ if (files.length === 0) {
27
+ console.log("No CSV files found to scan.");
28
+ process.exit(0);
29
+ }
30
+
31
+ let totalViolations = 0;
32
+ let hasErrors = false;
33
+
34
+ for (const csvPath of files) {
35
+ const rel = path.relative(repoRoot, csvPath);
36
+
37
+ let result;
38
+ try {
39
+ result = inferSchema(csvPath, repoRoot);
40
+ } catch (err) {
41
+ console.error(`ERROR ${rel}: ${(err as Error).message}`);
42
+ hasErrors = true;
43
+ continue;
44
+ }
45
+
46
+ const { schema, piiViolations } = result;
47
+
48
+ const colSummary = schema.columns
49
+ .map((c) => {
50
+ const tags: string[] = [c.type];
51
+ if (c.primaryKey) tags.push("PK");
52
+ if (c.nullable) tags.push("nullable");
53
+ if (c.pii) tags.push("PII");
54
+ return ` ${c.name.padEnd(24)} ${tags.join(", ")}`;
55
+ })
56
+ .join("\n");
57
+
58
+ console.log(`\n${rel} [${schema.rowCount} rows, fingerprint: ${schema.fingerprint}]`);
59
+ console.log(colSummary);
60
+
61
+ if (piiViolations.length > 0) {
62
+ totalViolations += piiViolations.length;
63
+ console.log(`\n ⚠ PII violations (${piiViolations.length}):`);
64
+ for (const v of piiViolations) {
65
+ console.log(` ${v.column}: ${v.reason}`);
66
+ }
67
+ }
68
+ }
69
+
70
+ console.log("");
71
+
72
+ if (hasErrors) {
73
+ console.error("Scan completed with errors.");
74
+ process.exit(1);
75
+ }
76
+
77
+ if (totalViolations > 0) {
78
+ console.warn(
79
+ `Scan found ${totalViolations} PII violation(s) across ${files.length} file(s).`,
80
+ );
81
+ if (failOnPii) {
82
+ console.error(
83
+ "Failing because --fail-on-pii is set. Remove PII data or add access policies in .rad/policies/.",
84
+ );
85
+ process.exit(1);
86
+ }
87
+ } else {
88
+ console.log(`Scan passed. ${files.length} file(s) checked, no PII violations.`);
89
+ }
@@ -0,0 +1,3 @@
1
+ export function camelToPastel(str: string): string {
2
+ return str.charAt(0).toUpperCase() + str.slice(1);
3
+ }
@@ -0,0 +1,3 @@
1
+ export function capitaliseFirstLetter(str: string): string {
2
+ return str.charAt(0).toUpperCase() + str.slice(1);
3
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ MediaTypeObject,
3
+ OperationObject,
4
+ ReferenceObject,
5
+ } from "@ts-stack/openapi-spec";
6
+
7
+ export function extractResponseBodySchema(
8
+ methodInformation: OperationObject,
9
+ ): [string | undefined, boolean] {
10
+ let responseBodySchema: string | undefined;
11
+ let isList = false;
12
+
13
+ if (!methodInformation.responses) return [undefined, false];
14
+
15
+ Object.keys(methodInformation.responses).forEach((responseKey) => {
16
+ // @ts-ignore
17
+ const responseData = methodInformation.responses[responseKey];
18
+ if (responseData?.content) {
19
+ const mediaType = responseData.content[
20
+ "application/json"
21
+ ] as MediaTypeObject;
22
+ if (mediaType?.schema) {
23
+ const refSchema = (mediaType.schema as ReferenceObject).$ref;
24
+ if (refSchema) {
25
+ responseBodySchema = refSchema.replace("#/components/schemas/", "");
26
+ if (responseBodySchema.startsWith("Paginated")) {
27
+ isList = true;
28
+ responseBodySchema = responseBodySchema
29
+ .replace("Paginated", "")
30
+ .slice(0, -4);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ });
36
+
37
+ return [responseBodySchema, isList];
38
+ }
@@ -0,0 +1,12 @@
1
+ import { ComponentsObject, PathsObject } from "@ts-stack/openapi-spec";
2
+ import axios from "axios";
3
+
4
+ interface OpenAPISchema {
5
+ paths: PathsObject;
6
+ components: ComponentsObject;
7
+ }
8
+
9
+ export async function fetchSchema(url: string): Promise<OpenAPISchema> {
10
+ const response = await axios.get<OpenAPISchema>(url);
11
+ return response.data;
12
+ }
@@ -0,0 +1,13 @@
1
+ import { PathsObject } from "@ts-stack/openapi-spec";
2
+
3
+ export function removeNonModelPaths(paths: PathsObject): PathsObject {
4
+ const filteredPaths: PathsObject = {};
5
+ Object.keys(paths).forEach((key) => {
6
+ const appName = key.split("/")[2];
7
+ const modelName = key.split("/")[4];
8
+ if (appName && modelName) {
9
+ filteredPaths[key] = paths[key];
10
+ }
11
+ });
12
+ return filteredPaths;
13
+ }
@@ -0,0 +1,7 @@
1
+ export function toCamelCase(str: string): string {
2
+ const pastel = str
3
+ .split("-")
4
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
5
+ .join("");
6
+ return pastel.charAt(0).toLowerCase() + pastel.slice(1);
7
+ }
@@ -0,0 +1,6 @@
1
+ export function toPascalCase(str: string): string {
2
+ return str
3
+ .split("-")
4
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
5
+ .join("");
6
+ }
@@ -0,0 +1,61 @@
1
+ import { readdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { compileFromFile } from "json-schema-to-typescript";
4
+
5
+ function removeUnknownIndexSignature(code: string): string {
6
+ return code
7
+ .split("\n")
8
+ .filter((line) => !line.includes("[k: string]: unknown;"))
9
+ .join("\n");
10
+ }
11
+
12
+ function removeDuplicates(code: string): string {
13
+ const lines = code.split("\n");
14
+ const uniqueExports = new Set();
15
+ let filteredCode = "";
16
+
17
+ lines.forEach((line) => {
18
+ if (line.includes("export type ") && !uniqueExports.has(line)) {
19
+ uniqueExports.add(line);
20
+ filteredCode += line + "\n";
21
+ } else if (!line.includes("export type ")) {
22
+ filteredCode += line + "\n";
23
+ }
24
+ });
25
+
26
+ return filteredCode;
27
+ }
28
+
29
+ async function main() {
30
+ console.log("Converting Schema");
31
+ const args: string[] = process.argv.slice(2);
32
+ const basePath = args[0];
33
+ const schemaFolderPath = join(basePath, "api-schema");
34
+
35
+ let interfaceFileData = "";
36
+
37
+ const schemaFiles = readdirSync(schemaFolderPath);
38
+
39
+ let compilePromises = schemaFiles.map((schemaFile) => {
40
+ const schemaFilePath = join(schemaFolderPath, schemaFile);
41
+
42
+ return compileFromFile(schemaFilePath, {
43
+ bannerComment: "",
44
+ style: { useTabs: false, tabWidth: 2 },
45
+ ignoreMinAndMaxItems: true,
46
+ strictIndexSignatures: false,
47
+ }).then((result) => {
48
+ const removedSignature = removeUnknownIndexSignature(result);
49
+ return removedSignature + "\n";
50
+ });
51
+ });
52
+
53
+ let results = await Promise.all(compilePromises);
54
+ interfaceFileData = results.join("\n");
55
+ interfaceFileData = removeDuplicates(interfaceFileData);
56
+
57
+ const outputFile = join(basePath, "interface.ts");
58
+ writeFileSync(outputFile, interfaceFileData);
59
+ }
60
+
61
+ main().catch((error) => console.error(error));