├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .mocharc.json ├── .prettierrc ├── .svetchrc ├── .svetchrc.backup ├── .swcrc ├── .vscode ├── launch.json └── settings.json ├── README.md ├── backup └── docs │ ├── +page.svelte │ └── components │ ├── BodyBlock.svelte │ └── Collapsible.svelte ├── bin.ts ├── biome.json ├── build.config.ts ├── build.js ├── bun.lockb ├── example.ts ├── isolate-0x6a571e0-1696734-v8.log ├── package.json ├── prisma └── schema.prisma ├── prompt.sh ├── src ├── assets │ ├── api.ts │ ├── client.ts │ ├── docs │ │ └── +page.svelte │ └── interfaces.ts ├── bin.ts ├── generator.ts ├── index.ts ├── init.ts ├── lib │ └── parsers │ │ ├── express │ │ └── responses.ts │ │ └── sveltekit │ │ └── responses.ts ├── routes │ ├── +page.server.ts │ ├── +page.svelte │ ├── api │ │ └── prisma │ │ │ └── [id] │ │ │ └── +server.ts │ └── docs │ │ └── +page.svelte ├── telemetry.json ├── types │ ├── core.ts │ └── telemetry.ts └── utils │ ├── check_package.ts │ ├── endpoint_extractors.ts │ ├── helpers │ ├── mock.ts │ └── type.ts │ ├── import-utils.ts │ ├── logger.ts │ ├── node_utils.ts │ ├── openapi.ts │ ├── svelte-codegen.ts │ ├── tsoa.ts │ ├── unroll-types.ts │ ├── util.ts │ ├── ux │ ├── progress_bar.ts │ └── spinner.ts │ └── writers.ts ├── static └── api │ └── schemas │ └── swagger.json ├── tests └── dependencies.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["oclif", "oclif-typescript", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-debug.log 2 | *-error.log 3 | **/.DS_Store 4 | /.idea 5 | /dist 6 | /tmp 7 | /node_modules 8 | oclif.manifest.json 9 | /src/lib/api 10 | /src/api/schemas 11 | yarn.lock 12 | package-lock.json 13 | 14 | 15 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "ts-node/register" 4 | ], 5 | "watch-extensions": [ 6 | "ts" 7 | ], 8 | "recursive": true, 9 | "reporter": "spec", 10 | "timeout": 60000, 11 | "node-option": [ 12 | "loader=ts-node/esm", 13 | "experimental-specifier-resolution=node" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "nonblock-statement-body-position": "error" 3 | } -------------------------------------------------------------------------------- /.svetchrc: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "sveltekit", 3 | "input": "src/routes/api", 4 | "out": "src/lib/api", 5 | "docs": "src/routes/docs", 6 | "staticFolder": "static", 7 | "tsconfig": "tsconfig.json", 8 | "logLevel": 5, 9 | "filter": null, 10 | "telemetry": true 11 | } -------------------------------------------------------------------------------- /.svetchrc.backup: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "sveltekit", 3 | "input": "src/routes/api", 4 | "out": "src/lib/api", 5 | "docs": "src/routes/docs", 6 | "staticFolder": "static", 7 | "tsconfig": "tsconfig.json", 8 | "logLevel": 5, 9 | "filter": null, 10 | "telemetry": true 11 | } -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/swcrc", 3 | "jsc": { 4 | "parser": { 5 | "syntax": "typescript", 6 | "dynamicImport": true, 7 | "decorators": false 8 | }, 9 | "target": "esnext", 10 | "loose": true, 11 | "externalHelpers": true, 12 | "keepClassNames": false 13 | }, 14 | 15 | "module": { 16 | "type": "es6", 17 | "strict": false, 18 | "strictMode": false, 19 | "lazy": false, 20 | "noInterop": false, 21 | "resolveFully": true 22 | }, 23 | "minify": false, 24 | "sourceMaps": true, 25 | "isModule": true, 26 | 27 | "exclude": [ 28 | "node_modules", 29 | "src/routes/**", 30 | "src/routes", 31 | "src/lib/api", 32 | "src/assets" 33 | ] 34 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "bun", 6 | "request": "launch", 7 | "name": "Debug Bun", 8 | 9 | // The path to a JavaScript or TypeScript file to run. 10 | "program": "${file}", 11 | 12 | // The arguments to pass to the program, if any. 13 | "args": [], 14 | 15 | // The working directory of the program. 16 | "cwd": "${workspaceFolder}", 17 | 18 | // The environment variables to pass to the program. 19 | "env": {}, 20 | 21 | // If the environment variables should not be inherited from the parent process. 22 | "strictEnv": false, 23 | 24 | // If the program should be run in watch mode. 25 | // This is equivalent to passing `--watch` to the `bun` executable. 26 | // You can also set this to "hot" to enable hot reloading using `--hot`. 27 | "watchMode": false, 28 | 29 | // If the debugger should stop on the first line of the program. 30 | "stopOnEntry": false, 31 | 32 | // If the debugger should be disabled. (for example, breakpoints will not be hit) 33 | "noDebug": false, 34 | 35 | // The path to the `bun` executable, defaults to your `PATH` environment variable. 36 | "runtime": "/root/.bun/bin/bun", 37 | 38 | // The arguments to pass to the `bun` executable, if any. 39 | // Unlike `args`, these are passed to the executable itself, not the program. 40 | "runtimeArgs": ["run"], 41 | }, 42 | { 43 | "type": "bun", 44 | "request": "attach", 45 | "name": "Attach to Bun", 46 | 47 | // The URL of the WebSocket inspector to attach to. 48 | // This value can be retrieved by using `bun --inspect`. 49 | "url": "ws://localhost:6499/", 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "bun.runtime": "/root/.bun/bin/bun", 4 | 5 | 6 | "bun.debugTerminal.enabled": true, 7 | "workbench.sideBar.location": "right", 8 | "workbench.panel.defaultLocation": "right", 9 | 10 | 11 | "bun.debugTerminal.stopOnEntry": false, 12 | "editor.formatOnSave": true, 13 | "editor.formatOnSaveMode": "file", 14 | "[typescript]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | "editor.fontFamily": "mononoki" 18 | 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/svetch.ts)](https://www.npmjs.com/package/svetch.ts) 2 | ![svetch-chan (1)](https://github.com/Bewinxed/svetch/assets/9145989/67c36f21-a21e-42f1-ba50-42f457948c46) 3 | 4 | # 🚀 Svetch.ts: Zero-Effort client/types/schema/OpenAPI Schema/Docs generator for your API endpoints 5 | 6 | _Typesafety, Minus the typing_ 7 | 8 | 👉 Check it out @ (https://svetch-dev.vercel.app/) 9 | Effortlessly generate a typesafe Fetch client for your Svelte/Sveltekit applications, complete with automatic input/output zod validation and autogenerated types & API Docs. 10 | 11 | # What is this? 12 | 13 | Svetch automatically scans your `+server.ts` files in /src/routes/api (or whatever directory you specify) and generates a typesafe Fetch client that has intellisense for path, query, body parameters & all the possible responses (and errors) 14 | 15 | # 🧙‍♂️ Automatic Detection 16 | 17 | - ❓ **Query Params** => Detected using any declarations of `url.searchParams.get` 18 | - 📂 **Path Params** => Detected from the file directory 19 | - 📦 **Payload Definition** => inferred from `const payload: X = await request.json` or `as X` 20 | - 💬 **Response Definition** => inferred from any return statement with `json(X) (sveltekit utility)` or `new Response(X)` 21 | - 📛 **Error Definitions** => inferred from any throw statement with `throw error()` or `throw new Error` or `return error(HTTP_CODE, error_message)` 22 | 23 | # ⏬ How to run 24 | 25 | `$ npx svetch.ts@latest` 26 | 27 | ### Make sure you have a path alias for src in your `svelte.config.js` 28 | 29 | ```diff 30 | import adapter from '@sveltejs/adapter-auto'; 31 | import { vitePreprocess } from '@sveltejs/kit/vite'; 32 | 33 | /** @type {import('@sveltejs/kit').Config} */ 34 | const config = { 35 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 36 | // for more information about preprocessors 37 | preprocess: vitePreprocess(), 38 | kit: { 39 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 40 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 41 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 42 | adapter: adapter(), 43 | + alias: { 44 | + "src": "./src", 45 | }, 46 | } 47 | }; 48 | 49 | export default config; 50 | 51 | ``` 52 | 53 | # 🌟 Advantages 54 | 55 | - 😍 **ALMOST** no code changes required to your existing API code 56 | - 🚀 Almost no learning curve, Augments a regular FETCH client with **data** and **error** along with the rest of the fetch client properties, you can use it just like a regular fetch client. 57 | - 🔎 **Intellisense** for your api paths. 58 | - ✅ Typesafety for api inputs & outputs. 59 | - 📚 Generates an OpenAPI Schema for your API, alongside with Swagger docs. 60 | - 📃 Can use **JSDocs** to add more info to the endpoint documentation (WIP) 61 | - 🤖 Handles multiple possible responses per http code. 62 | 63 | ## Autogenerated Docs 64 | 65 | The library will generate an OpenAPI compatible schema, alongside with swagger docs, by default in /docs/+page.svelte 66 | 67 | # ⚙ Config 68 | 69 | `.svetchrc` 70 | 71 | ```json 72 | { 73 | "framework": "sveltekit", // currently the only option 74 | "input": "src/routes/api", // the directory you want the generator to traverse 75 | "out": "src/lib/api", // the directory to output the types & the client to 76 | "docs": "src/routes/docs", // where to output docs 77 | "tsconfig": "tsconfig.json" // your tsconfig directory 78 | } 79 | ``` 80 | 81 | # 🔎 Detection 82 | 83 | 1. Svetch will traverse your input directory, will scan for any +server.ts with exported GET/POST/PUT/DELETE functions. 84 | 2. For each endpoint it will detect... 85 | 1. **path parameters**: based on the file name, e.g. `user/[user_id].ts` will have a path parameter of `user_id` 86 | 2. **query parameters**: based on any parameters instantiated like `url.searchParams.get('param')` 87 | 3. **body parameters**: based on the type of the payload `Must be assigned to a const called` **payload** **⚠ IMPORTANT** 88 | 4. **response types**: will pickup any top-level return statement that is instantiated like **json(xxx)** or **new Response(xxx)** along with status code 89 | 90 | ## In client code 91 | 92 | ```ts 93 | import { Svetch } from "src/lib/api/client"; // or wherever you chose to generate the client 94 | 95 | const svetch = new Svetch({ 96 | baseUrl: "/api", // default is '/api' 97 | fetch, // default is window.fetch, pass the global fetch to it in node, etc... 98 | validate: false, // default is false, uses Zod to validate payload + response (ON CLIENT THIS CAN MAKE THE IMPORT SIZE HUGE) 99 | }); 100 | 101 | await svetch 102 | .post("user/[user_id]", { 103 | path: { 104 | user_id: 1, 105 | }, 106 | body: { 107 | text: foodInput, 108 | journalId: $journal.id, 109 | today: new Date(), 110 | }, 111 | }) 112 | .then((response) => { 113 | if (response.error) throw new Error(response.error); 114 | food = response.data; 115 | loading = false; 116 | }) 117 | .catch((error) => { 118 | throw new Error(error); 119 | }); 120 | ``` 121 | 122 | ## In load functions 123 | 124 | ```ts 125 | import { Svetch } from "src/lib/api/client"; // or wherever you chose to generate the client 126 | 127 | const svetch = new Svetch({ 128 | baseUrl: "/api", // default is '/api' 129 | fetch, // pass the load function's fetch to it 130 | }); 131 | 132 | export async function load({ fetch, session }) { 133 | const user = await svetch.get("user").then((response) => { 134 | if (response.error) throw new Error(response.error); 135 | return response.data; 136 | }); 137 | return { 138 | props: { 139 | user, 140 | }, 141 | }; 142 | } 143 | ``` 144 | 145 | # License 146 | 147 | This library is **Free** for personal use, If it's useful to you, please consider purchasing a license @ https://petrasolutions.lemonsqueezy.com/checkout/buy/19210e05-ae3c-41a0-920c-324e3083618d 148 | Redistribution/Forking is **Not** Allowed. 149 | -------------------------------------------------------------------------------- /backup/docs/+page.svelte: -------------------------------------------------------------------------------- 1 | 136 | 137 | {#await loadSchema()} 138 | 139 |
143 |
144 |
145 |
146 |
147 | 148 |
149 |

Svetch API Docs

150 | 151 | Svetch Docs 156 |
157 |
158 |
159 |
160 | {:then} 161 | {#if schema} 162 | 180 | 214 | 215 | 216 |
217 | 218 |
219 | 220 | 251 | 252 |
253 |

254 |

Autogenerated from '/api/*'

255 | 256 | {#if schema.properties} 257 | {#each Object.entries(schema.properties) as [path, paths]} 258 |
259 |
260 | 293 |
294 |
295 |
299 | 300 | {#each path.split("/") as path_part} 301 | {"/"} 302 | {#if path_part.includes(":")} 303 |
306 | {path_part.replace(":", "")} 307 |
308 | {:else} 309 | {path_part} 310 | {/if} 311 | {/each} 312 |
313 | {#if paths && paths.properties} 314 | {#each Object.entries(paths.properties) as [method, method_data]} 315 |
316 | 317 | 318 |
321 |
327 | 328 | {method} 329 |
330 | {#if method_data.description} 331 |
332 | {method_data.description} 333 |
334 | {/if} 335 |
336 |
337 |
338 | {#if typeof method_data === "object" && method_data.properties && method_data.properties.parameters} 339 | {@const parameters = 340 | method_data.properties.parameters.properties} 341 | {@const responses = 342 | method_data.properties.responses} 343 | {@const errors = method_data.properties.errors} 344 | {@const testing = 345 | path_being_tested == path && 346 | method_being_tested == method} 347 | {#if parameters} 348 |
349 | 354 | Parameters 355 |
356 | {#if path_being_tested == path && method_being_tested == method && parameters.path && parameters.path.properties} 357 | 358 |
359 |

360 | Path 365 |

366 |
367 | {#each Object.entries(parameters.path.properties) as [param, type]} 368 |
369 | {param} 370 |
371 | 380 | {/each} 381 |
382 |
383 | {/if} 384 |
387 | {#if parameters.body && parameters.body.properties} 388 |

389 | Body 394 |

395 |
401 | 402 |
403 | {/if} 404 |
405 | 406 | {#if parameters.query && parameters.query.properties && Object.entries(parameters.query.properties).length > 0} 407 |

Query

408 | {JSON.stringify(parameters.query)} 409 | 410 | 411 | {/if} 412 | {/if} 413 | {#if testing && parameters.body && parameters.body.properties} 414 |
415 | 418 |