├── .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 | [](https://www.npmjs.com/package/svetch.ts)
2 | 
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 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
237 |
Svetch Docs
238 |
239 |
249 |
250 |
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 |
440 | {/if}
441 |
442 | {#if responses && responses.properties}
443 |
448 | {#each Object.entries(responses.properties) as [code, response]}
449 |
450 |
451 |
456 |
467 | {parseInt(code)}
468 |
469 |
470 |
475 |
476 |
477 |
478 | {/each}
479 |
480 | {/if}
481 | {#if errors && errors.properties}
482 |
487 | {#each Object.entries(errors.properties) as [code, response]}
488 |
489 |
490 |
495 |
506 | {parseInt(code)}
507 |
508 |
509 |
515 |
516 |
517 |
518 | {/each}
519 |
520 | {/if}
521 |
522 | {#if path_being_tested === path && method_being_tested === method}
523 |
524 | {#if error_response}
525 |
534 |
538 |
542 |
{JSON.stringify(
544 | error_response,
545 | null,
546 | 2
547 | )}
549 |
550 |
551 | {#if error_cause}
552 |
556 |
{JSON.stringify(
558 | error_cause,
559 | null,
560 | 2
561 | )}
563 |
564 | {/if}
565 | {:else if testing_response}
566 |
575 |
579 | Success!
580 |
581 |
585 |
{JSON.stringify(
588 | testing_response,
589 | null,
590 | "\t"
591 | )}
593 |
594 | {:else if working}
595 |
604 |
608 | Testing...
609 |
610 | {/if}
611 |
612 | {/if}
613 |
614 |
615 |
681 |
686 |
687 | {/if}
688 |
689 |
690 |
691 | {/each}
692 | {/if}
693 |
694 |
695 | {/each}
696 | {/if}
697 |
698 |
699 |
700 |
701 |
702 |
728 |
729 |
730 |
740 | {/if}
741 | {:catch error}
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
Error
750 |
{error.message}
751 |
752 |
753 |
754 |
755 | {/await}
756 |
757 |
762 |
--------------------------------------------------------------------------------
/backup/docs/components/BodyBlock.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 | {#if data}
19 | {#if data.type === "object" && data.properties}
20 | {#each Object.entries(data.properties) as [param, schema] (param)}
21 |
27 | {/each}
28 | {:else if data.type === "array" && data.items}
29 |
30 | {name}: {data.type} [
35 |
36 |
37 |
38 |
39 |
42 |
43 | ]
44 |
45 | {:else}
46 |
47 |
48 |
49 | {name}: {data.const ? data.const : data.type}
54 |
55 | {/if}
56 | {/if}
57 |
58 |
65 |
--------------------------------------------------------------------------------
/backup/docs/components/Collapsible.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 | {#if title}
12 |
13 | {#if icon}
14 |
15 | {/if}
16 | {title}
17 |
18 | {:else}
19 |
20 |
21 |
22 | {/if}
23 |
28 |
29 |
--------------------------------------------------------------------------------
/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { runAll } from './src/index.js';
3 | console.log(import.meta.dirname);
4 | runAll();
5 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "formatter": {
7 | "indentStyle": "tab",
8 | "lineWidth": 80,
9 | "enabled": true
10 | },
11 | "linter": {
12 | "enabled": true,
13 | "rules": {
14 | "recommended": true
15 | }
16 | },
17 | "javascript": {
18 | "parser": {
19 | "unsafeParameterDecoratorsEnabled": true
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build.config.ts:
--------------------------------------------------------------------------------
1 | import { defineBuildConfig } from 'unbuild'
2 | import fs from 'fs'
3 | import path from 'path'
4 |
5 |
6 | const copyFolderRecursive = (src: string, dest: string) => {
7 | if (!fs.existsSync(dest)) {
8 | fs.mkdirSync(dest, { recursive: true });
9 | }
10 |
11 | const entries = fs.readdirSync(src, { withFileTypes: true });
12 |
13 | for (const entry of entries) {
14 | const srcPath = path.join(src, entry.name);
15 | const destPath = path.join(dest, entry.name);
16 |
17 | if (entry.isDirectory()) {
18 | copyFolderRecursive(srcPath, destPath);
19 | } else {
20 | fs.copyFileSync(srcPath, destPath);
21 | }
22 | }
23 | };
24 |
25 | export default defineBuildConfig({
26 | entries: [
27 | "src/bin.ts"
28 | ],
29 | clean: true,
30 | declaration: true, // Generate .d.ts files
31 | outDir: 'dist',
32 | rollup: {
33 | emitCJS: true,
34 | inlineDependencies: false,
35 |
36 | },
37 | hooks: {
38 | 'build:done': () => {
39 | const srcDir = path.join(__dirname, 'src/assets');
40 | const destDir = path.join(__dirname, 'dist/assets');
41 |
42 | if (fs.existsSync(srcDir)) {
43 | copyFolderRecursive(srcDir, destDir);
44 | console.log('Copied src/assets to dist/assets');
45 | } else {
46 | console.warn('Source directory not found: src/assets');
47 | }
48 | },
49 | },
50 | })
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require('child_process');
2 | const fs = require('fs-extra');
3 | const path = require('path');
4 |
5 | // Run SWC
6 | console.log('Running SWC...');
7 | execSync('swc src -d dist', { stdio: 'inherit' });
8 |
9 | // Copy assets
10 | console.log('Copying assets...');
11 | const srcDir = path.join(__dirname, 'src/assets');
12 | const destDir = path.join(__dirname, 'dist/assets');
13 |
14 | if (fs.existsSync(srcDir)) {
15 | fs.copySync(srcDir, destDir, { overwrite: true });
16 | console.log('Assets copied successfully.');
17 | } else {
18 | console.warn('Source assets directory not found.');
19 | }
20 |
21 | console.log('Build completed.');
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bewinxed/svetch/8031f56ac2e947f9e09dc2671312c7d794e7baa9/bun.lockb
--------------------------------------------------------------------------------
/example.ts:
--------------------------------------------------------------------------------
1 | {"query":"string","number":"number"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Omar Al Matar",
3 | "version": "2.1.43",
4 | "funding": [
5 | "https://petrasolutions.lemonsqueezy.com/checkout/buy/19210e05-ae3c-41a0-920c-324e3083618d"
6 | ],
7 | "bin": {
8 | "svetch.ts": "./dist/src/bin.js"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/Bewinxed/svetch/issues"
12 | },
13 | "dependencies": {
14 | "ansi-colors": "^4.1.3",
15 | "comment-parser": "^1.4.1",
16 | "ora": "^8.1.0",
17 | "prompts": "^2.4.2",
18 | "ts-morph": "^23.0.0",
19 | "tsoa": "^6.4.0"
20 | },
21 | "description": "End-to-End typesafe fetch client for your API",
22 | "devDependencies": {
23 | "@biomejs/biome": "^1.8.3",
24 | "@prisma/client": "5.17.0",
25 | "@swc/cli": "^0.4.0",
26 | "@swc/core": "^1.7.23",
27 | "@total-typescript/tsconfig": "^1.0.4",
28 | "@types/detective": "^5.1.5",
29 | "@types/inquirer": "^9.0.7",
30 | "@types/node": "^20.16.3",
31 | "@types/prompts": "^2.4.9",
32 | "@types/readline-sync": "^1.4.8",
33 | "@types/uuid": "^9.0.8",
34 | "detective-es6": "^4.0.1",
35 | "lucia": "^3.2.0",
36 | "openapi-types": "^12.1.3",
37 | "prisma": "^5.19.1",
38 | "svetch.ts": "^2.1.41",
39 | "unbuild": "^2.0.0"
40 | },
41 | "exports": {
42 | ".": {
43 | "import": "./dist/index.mjs",
44 | "require": "./dist/index.cjs"
45 | }
46 | },
47 | "files": [
48 | "dist"
49 | ],
50 | "homepage": "https://github.com/Bewinxed/svetch#readme",
51 | "keywords": [
52 | "svelte",
53 | "sveltekit",
54 | "typescript",
55 | "fetch",
56 | "typesafe",
57 | "svetch.ts",
58 | "openapi",
59 | "swagger"
60 | ],
61 | "license": "MIT",
62 | "main": "./dist/src/index.js",
63 | "module": "./dist/src/index.js",
64 | "name": "svetch.ts",
65 | "repository": {
66 | "type": "git",
67 | "url": "git+https://github.com/Bewinxed/svetch"
68 | },
69 | "scripts": {
70 | "build": "rm -rf dist && npx swc src -d dist && cp -r src/assets dist/src/assets",
71 | "bump": "npm version patch",
72 | "yolo": "npm run bump && bun run build && npm publish"
73 | },
74 | "type": "module",
75 | "types": "./dist/src/index.d.ts"
76 | }
77 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6 |
7 | generator client {
8 | provider = "prisma-client-js"
9 | }
10 |
11 | datasource db {
12 | provider = "postgresql"
13 | url = env("DATABASE_URL")
14 | }
15 |
16 | model User {
17 | id String @id @default(cuid())
18 | email String @unique
19 | name String?
20 | password String
21 | createdAt DateTime @default(now())
22 | updatedAt DateTime @updatedAt
23 | posts Post[]
24 | }
25 |
26 | model Post {
27 | id String @id @default(cuid())
28 | title String
29 | content String
30 | createdAt DateTime @default(now())
31 | updatedAt DateTime @updatedAt
32 | author User @relation(fields: [authorId], references: [id])
33 | authorId String
34 | }
35 |
--------------------------------------------------------------------------------
/prompt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Output file
4 | output_file="combined_code.txt"
5 |
6 | # Empty the output file if it already exists
7 | > "$output_file"
8 |
9 | # Function to add a file's content to the output
10 | add_file_content() {
11 | local file=$1
12 | echo "==== File: $file ====" >> "$output_file"
13 | cat "$file" >> "$output_file"
14 | echo -e "\n\n" >> "$output_file"
15 | }
16 |
17 | # Add src/generator.ts
18 | if [ -f "src/generator.ts" ]; then
19 | add_file_content "src/generator.ts"
20 | else
21 | echo "src/generator.ts not found" >> "$output_file"
22 | fi
23 |
24 | # Add files from src/utils directory
25 | if [ -d "src/utils" ]; then
26 | for file in src/utils/*; do
27 | if [ -f "$file" ]; then
28 | add_file_content "$file"
29 | fi
30 | done
31 | else
32 | echo "src/utils directory not found" >> "$output_file"
33 | fi
34 |
35 | # Add files from src/types directory
36 | if [ -d "src/types" ]; then
37 | for file in src/types/*; do
38 | if [ -f "$file" ]; then
39 | add_file_content "$file"
40 | fi
41 | done
42 | else
43 | echo "src/types directory not found" >> "$output_file"
44 | fi
45 |
46 | # add bin and index
47 | add_file_content "bin.ts"
48 | add_file_content "index.ts"
49 |
50 | echo "All files have been combined into $output_file"
--------------------------------------------------------------------------------
/src/assets/api.ts:
--------------------------------------------------------------------------------
1 | export type GET = object;
2 | export type POST = object;
3 | export type PUT = object;
4 | export type PATCH = object;
5 | export type DELETE = object;
6 |
--------------------------------------------------------------------------------
/src/assets/client.ts:
--------------------------------------------------------------------------------
1 | import type { GET, POST, PUT, PATCH, DELETE } from "./api.js";
2 |
3 | const APIPaths = {
4 | GET: {} as GET,
5 | POST: {} as POST,
6 | PUT: {} as PUT,
7 | PATCH: {} as PATCH,
8 | DELETE: {} as DELETE,
9 | } as const;
10 |
11 | type APIPaths = typeof APIPaths;
12 |
13 | type EndpointMethod<
14 | EP extends keyof APIPaths,
15 | M extends keyof APIPaths[EP],
16 | > = APIPaths[EP][M];
17 |
18 | // biome-ignore lint/suspicious/noExplicitAny:
19 | type Params> = M extends {
20 | parameters: infer P;
21 | }
22 | ? { [K in keyof P]: undefined extends P[K] ? P[K] | undefined : P[K] }
23 | : never;
24 |
25 | // biome-ignore lint/suspicious/noExplicitAny:
26 | type SuccessResponse> = M extends {
27 | responses: { 200: infer S };
28 | }
29 | ? S
30 | : never;
31 |
32 | // biome-ignore lint/suspicious/noExplicitAny:
33 | type ErrorResponse> = M extends {
34 | errors: infer E;
35 | }
36 | ? E
37 | : never;
38 |
39 | type ExtendedResponse<
40 | EP extends keyof APIPaths,
41 | K extends keyof APIPaths[EP],
42 | > = Response & {
43 | data?: SuccessResponse;
44 | error?: ErrorResponse;
45 | isOk: () => this is ExtendedResponse & {
46 | data: SuccessResponse;
47 | error: undefined;
48 | };
49 | okOrThrow: () => SuccessResponse;
50 | };
51 |
52 | export class Svetch {
53 | private baseURL: string;
54 | private fetchFn: typeof fetch;
55 |
56 | constructor(baseURL = "", fetchInstance?: typeof fetch) {
57 | this.baseURL = baseURL;
58 | this.fetchFn = fetchInstance || fetch;
59 | }
60 |
61 | error(status: number, message: string) {
62 | const err = new Error(message);
63 | // @ts-ignore
64 | err.status = status;
65 | throw err;
66 | }
67 |
68 | async request(
69 | endpoint: EP,
70 | method: M,
71 | options: Params>,
72 | ): Promise> {
73 | const { path, query, body } = options as unknown as {
74 | path?: { [key: string]: string };
75 | query?: { [key: string]: unknown };
76 | body?: {
77 | [key: string]: unknown;
78 | };
79 | };
80 | let updatedEndpoint = endpoint as string;
81 |
82 | if (path) {
83 | for (const [key, value] of Object.entries(path)) {
84 | if (value === undefined) {
85 | console.warn(`Path parameter ${key} is undefined, skipping`);
86 | continue;
87 | }
88 | updatedEndpoint = updatedEndpoint.replace(`:${key}`, value as string);
89 | }
90 | }
91 |
92 | const query_params = new URLSearchParams();
93 | if (query) {
94 | for (const [key, value] of Object.entries(query)) {
95 | if (value === undefined) {
96 | console.warn(`Query parameter ${key} is undefined, skipping`);
97 | continue;
98 | }
99 | if (value) {
100 | query_params.append(key, value.toString());
101 | }
102 | }
103 | }
104 |
105 | let baseUrl: string | undefined;
106 |
107 | if (typeof window === "undefined") {
108 | baseUrl = undefined;
109 | } else {
110 | baseUrl = window.location.origin;
111 | }
112 |
113 | if (typeof window !== "undefined" && !baseUrl) {
114 | throw new Error(
115 | "Unable to determine base URL, Please provide it in the constructor",
116 | );
117 | }
118 |
119 | const url = `${baseUrl ?? ""}${updatedEndpoint}${query_params.size > 0 ? `?${query_params.toString()}` : ""}`;
120 |
121 | const response = (await this.fetchFn(url, {
122 | body: JSON.stringify(body),
123 | method: method as string,
124 | })) as ExtendedResponse;
125 |
126 | if (!response.ok) {
127 | const errorResponse = await response.json();
128 | response.error = { [response.status]: errorResponse } as ErrorResponse<
129 | APIPaths[M][EP]
130 | >;
131 | return response;
132 | }
133 |
134 | const responseData = (await response.json()) as SuccessResponse<
135 | APIPaths[M][EP]
136 | >;
137 |
138 | response.data = responseData;
139 | response.isOk = function (
140 | this: ExtendedResponse,
141 | ): this is ExtendedResponse & {
142 | data: SuccessResponse;
143 | error: undefined;
144 | } {
145 | return this.ok && !!this.data && !this.error;
146 | };
147 |
148 | response.okOrThrow = () => {
149 | if (!response.ok || !response.data || response.error) {
150 | throw new Error(
151 | response.error &&
152 | typeof response.error === "object" &&
153 | "message" in response.error &&
154 | typeof response.error.message === "string"
155 | ? response.error.message
156 | : JSON.stringify(response.error),
157 | );
158 | }
159 | return response.data;
160 | };
161 | return response;
162 | }
163 |
164 | get(endpoint: EP, parameters: Params) {
165 | return this.request(endpoint, "GET", parameters);
166 | }
167 |
168 | post(endpoint: EP, parameters: Params) {
169 | return this.request(endpoint, "POST", parameters);
170 | }
171 |
172 | put(endpoint: EP, parameters: Params) {
173 | return this.request(endpoint, "PUT", parameters);
174 | }
175 |
176 | patch(endpoint: EP, parameters: Params) {
177 | return this.request(endpoint, "PATCH", parameters);
178 | }
179 |
180 | delete(
181 | endpoint: EP,
182 | parameters: Params,
183 | ) {
184 | return this.request(endpoint, "DELETE", parameters);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/assets/docs/+page.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | SwaggerUI
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/assets/interfaces.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Get<
3 | PathParams = { [key: string]: unknown },
4 | QueryParams = { [key: string]: unknown },
5 | Output = { [key: string]: unknown }
6 | > {
7 | parameters: {
8 | path?: PathParams;
9 | query: QueryParams;
10 | };
11 | responses: {
12 | 200: Output;
13 | };
14 | }
15 |
16 | export interface Post<
17 | PathParams = { [key: string]: unknown },
18 | RequestBody = { [key: string]: unknown },
19 | QueryParams = { [key: string]: unknown },
20 | Output = { [key: string]: unknown }
21 | > {
22 | parameters: {
23 | path?: PathParams;
24 | body: RequestBody;
25 | query?: QueryParams;
26 | };
27 | responses: {
28 | 200: Output;
29 | };
30 | }
31 |
32 | export interface Put<
33 | PathParams = { [key: string]: unknown },
34 | RequestBody = { [key: string]: unknown },
35 | QueryParams = { [key: string]: unknown },
36 | Output = { [key: string]: unknown }
37 | > {
38 | parameters: {
39 | path?: PathParams;
40 | body: RequestBody;
41 | query?: QueryParams;
42 | };
43 | responses: {
44 | 200: Output;
45 | };
46 | }
47 |
48 | export interface Delete<
49 | PathParams = { [key: string]: unknown },
50 | QueryParams = { [key: string]: unknown },
51 | Output = { [key: string]: unknown }
52 | > {
53 | parameters: {
54 | path?: PathParams;
55 | query: QueryParams;
56 | };
57 | responses: {
58 | 200: Output;
59 | };
60 | }
61 |
62 | export interface Patch<
63 | PathParams = { [key: string]: unknown },
64 | RequestBody = { [key: string]: unknown },
65 | QueryParams = { [key: string]: unknown },
66 | Output = { [key: string]: unknown }
67 | > {
68 | parameters: {
69 | path?: PathParams;
70 | body: RequestBody;
71 | query?: QueryParams;
72 | };
73 | responses: {
74 | 200: Output;
75 | };
76 | }
77 |
78 | export type RecursiveJSONSchema = {
79 | description?: string
80 | type: string
81 | const?: string
82 | format?: string
83 | properties?: {
84 | [key: string]: RecursiveJSONSchema
85 | }
86 | items?: RecursiveJSONSchema
87 | required?: string[]
88 | }
--------------------------------------------------------------------------------
/src/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { runAll } from './index.js';
3 | import * as path from 'node:path';
4 |
5 | console.log(import.meta.dirname);
6 | runAll();
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from "node:path";
2 | import * as fs from "node:fs";
3 | import prompts, { type PromptObject } from "prompts";
4 | import { log } from "./utils/logger.js";
5 | import { main } from "./generator.js";
6 | import { performance } from "node:perf_hooks";
7 | import ora, { oraPromise } from "ora";
8 | import { spinner } from "./utils/ux/spinner.js";
9 | import { ChildProcess, execSync } from "node:child_process";
10 | import { checkAndInstallPackage } from "./utils/check_package.js";
11 |
12 | const separator = "--------------------------------------";
13 |
14 | interface ScriptArgs {
15 | framework: string;
16 | input: string;
17 | staticFolder: string;
18 | out: string;
19 | docs: string;
20 | tsconfig: string;
21 | logLevel?: number;
22 | filter?: string | null;
23 | telemetry: boolean;
24 | }
25 |
26 | const defaultArgs: ScriptArgs = {
27 | framework: "sveltekit",
28 | input: "src/routes/api",
29 | out: "src/lib/api",
30 | docs: "src/routes/docs",
31 | staticFolder: "static",
32 | tsconfig: "tsconfig.json",
33 | logLevel: 5,
34 | filter: null,
35 | telemetry: true,
36 | };
37 |
38 | const isInit = process.argv[2] === "init";
39 |
40 | // Get the command-line arguments
41 | const args: string[] = process.argv.slice(3);
42 |
43 | function parseArgs(rawArgs: string[]): ScriptArgs {
44 | const svetchrcExists = fs.existsSync(".svetchrc");
45 | const parsedArgs = svetchrcExists ? readSvetchrc() : {};
46 |
47 | for (let i = 0; i < rawArgs.length; i += 2) {
48 | const argName = rawArgs[i]?.replace("--", "");
49 | const argValue = rawArgs[i + 1];
50 | if (argName && argValue) {
51 | parsedArgs[argName] = argValue;
52 | }
53 | }
54 |
55 | return { ...defaultArgs, ...parsedArgs };
56 | }
57 |
58 | const workingDir = process.cwd();
59 | export async function initSvetchrc() {
60 | console.log(`
61 | # ███████╗██╗ ██╗███████╗████████╗ ██████╗██╗ ██╗
62 | # ██╔════╝██║ ██║██╔════╝╚══██╔══╝██╔════╝██║ ██║
63 | # ███████╗██║ ██║█████╗ ██║ ██║ ███████║
64 | # ╚════██║╚██╗ ██╔╝██╔══╝ ██║ ██║ ██╔══██║
65 | # ███████║ ╚████╔╝ ███████╗ ██║ ╚██████╗██║ ██║
66 | # ╚══════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
67 | # NEVER TYPE YOUR TYPES TWICE
68 | Send any feedback or issues here 👉 https://github.com/Bewinxed/svetch/
69 | `);
70 |
71 | if (fs.existsSync(".svetchrc")) {
72 | fs.renameSync(".svetchrc", ".svetchrc.backup");
73 | spinner.warn(
74 | "Existing .svetchrc file has been renamed to .svetchrc.backup",
75 | );
76 | }
77 |
78 | const questions: PromptObject[] = [
79 | {
80 | type: "text",
81 | name: "input",
82 | message: "Which folder would you like svetch to scan for API routes?",
83 | initial: defaultArgs.input,
84 | },
85 | {
86 | type: "text",
87 | name: "out",
88 | message:
89 | "Where would you like svetch to output the generated API files? (The Client/Types/Zod Schemas will be written here)",
90 | initial: defaultArgs.out,
91 | },
92 | {
93 | type: "text",
94 | name: "docs",
95 | message:
96 | "Where would you like svetch to output the generated API documentation?",
97 | initial: defaultArgs.docs,
98 | },
99 | {
100 | type: "text",
101 | name: "staticFolder",
102 | message: "Where is your static folder located?",
103 | initial: defaultArgs.staticFolder,
104 | },
105 | ];
106 |
107 | const response = await prompts(questions);
108 |
109 | fs.writeFileSync(
110 | ".svetchrc",
111 | JSON.stringify({ ...defaultArgs, ...response }, null, 2),
112 | );
113 |
114 | spinner.start(
115 | `Svetch configuration written to ${path.resolve(workingDir, ".svetchrc")}`,
116 | );
117 | }
118 |
119 | function readSvetchrc() {
120 | try {
121 | const config_file = fs.readFileSync(".svetchrc", "utf8");
122 | return JSON.parse(config_file) as ScriptArgs;
123 | } catch (error) {
124 | console.error("Error reading .svetchrc file:", error);
125 | process.exit(1);
126 | }
127 | }
128 |
129 | export async function runAll() {
130 | const start = performance.now();
131 |
132 | // check if swagger-ui-dist is installed
133 | checkAndInstallPackage("swagger-ui-dist", true);
134 |
135 | try {
136 | if (isInit || !fs.existsSync(path.resolve(workingDir, ".svetchrc"))) {
137 | await initSvetchrc();
138 | }
139 | const spinner = ora({ color: "yellow" }).start("Starting task");
140 | await main(parseArgs(args));
141 | const end = performance.now();
142 | spinner.succeed(`Task completed in ${(end - start).toFixed(2)}ms`);
143 | } catch (error) {
144 | spinner.fail(`Task failed: ${(error as Error).message}`);
145 | console.error(error);
146 | throw error;
147 | } finally {
148 | process.exit(0);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/init.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env tsx
2 |
3 | import { initSvetchrc } from './index.js';
4 |
5 | initSvetchrc();
6 |
--------------------------------------------------------------------------------
/src/lib/parsers/express/responses.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bewinxed/svetch/8031f56ac2e947f9e09dc2671312c7d794e7baa9/src/lib/parsers/express/responses.ts
--------------------------------------------------------------------------------
/src/lib/parsers/sveltekit/responses.ts:
--------------------------------------------------------------------------------
1 | import type { ErrorDetails } from "../../../types/core.js";
2 | import { mock_node } from "../../../utils/helpers/mock.js";
3 | import { Node, SyntaxKind, type CallExpression } from "ts-morph";
4 |
5 | export function extract_kit_error(
6 | node: CallExpression,
7 | // biome-ignore lint/suspicious/noExplicitAny: This is a generic function
8 | ): ErrorDetails | null {
9 | const expression = node.getExpression();
10 | if (Node.isIdentifier(expression) && expression.getText() === "error") {
11 | const [status, initializer] = node.getArguments();
12 | if (Node.isNumericLiteral(status) && Node.isStringLiteral(initializer)) {
13 | // biome-ignore lint/style/noNonNullAssertion: This is a mock node
14 | const updated_node = mock_node(
15 | node,
16 | `
17 | const error = {
18 | message: "${initializer.getLiteralValue()}"
19 | }
20 | `.replace("'", "\\'"),
21 | ).getFirstDescendantByKind(SyntaxKind.ObjectLiteralExpression)!;
22 | return {
23 | status: status.getLiteralValue(),
24 | type_string: `
25 | {
26 | message: "${initializer.getLiteralValue()}"
27 | }
28 | `,
29 | node: updated_node,
30 | };
31 | }
32 | }
33 | return null;
34 | }
35 |
--------------------------------------------------------------------------------
/src/routes/+page.server.ts:
--------------------------------------------------------------------------------
1 | export const actions: Actions = {
2 | edit_profile: async ({ request, locals, fetch }) => {
3 | const session = await locals.auth.validate()
4 |
5 | if (!session) {
6 | return fail(401, { message: "You're not logged in" })
7 | }
8 |
9 | const data = await request.formData()
10 |
11 | const payload = {
12 | age: parseInt(data.get('age') as string) || undefined,
13 | gender: (data.get('gender') as string) || undefined,
14 | height: parseInt(data.get('height') as string) || undefined,
15 | weight: parseInt(data.get('weight') as string) || undefined,
16 | activityLevel: (data.get('activity_level') as string) || undefined,
17 | goal: (data.get('goal') as string) || undefined,
18 | goalWeight: parseInt(data.get('goal_weight') as string) || undefined,
19 | allergies: {
20 | createMany: {
21 | data: ((data.get('allergies') as string) ?? '').split(',').map((allergy) => {
22 | return {
23 | name: allergy
24 | }
25 | })
26 | }
27 | },
28 | comorbidities: {
29 | createMany: {
30 | data: ((data.get('comorbidities') as string) ?? '').split(',').map((comorbidity) => {
31 | return {
32 | name: comorbidity
33 | }
34 | })
35 | }
36 | }
37 | }
38 |
39 | return {
40 | status: 200,
41 | body: {
42 | message: 'Profile updated successfully',
43 | }
44 | }
45 | }
46 | };
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/routes/api/prisma/[id]/+server.ts:
--------------------------------------------------------------------------------
1 | import type { Prisma } from "@prisma/client";
2 | import type { Session } from "lucia";
3 |
4 | export type SolanaSignInInput = {
5 | account: {
6 | address: string;
7 | icon: string;
8 | label: string;
9 | };
10 | proof: string;
11 | };
12 |
13 | export type SerializedSolanaSignInOutput = {
14 | account: {
15 | address: string;
16 | icon: string;
17 | label: string;
18 | };
19 | signature: string;
20 | };
21 | /**
22 | * @tsoaModel
23 | */
24 | type Document = {};
25 | export const POST = async ({
26 | getClientAddress,
27 | locals,
28 | params,
29 | request,
30 | url
31 | }) => {
32 | let session = await locals.auth.validate();
33 |
34 | const application = await prisma.application
35 | .findFirstOrThrow({
36 | include: {
37 | app_role: {
38 | orderBy: {
39 | created_at: 'asc'
40 | }
41 | },
42 | authentication_rule: {
43 | where: {
44 | provider: 'solana'
45 | }
46 | }
47 | },
48 | where: {
49 | id: params.app_id
50 | }
51 | })
52 | .catch(() => {
53 | throw error(
54 | 404,
55 | 'Application not found, Or Solana OAuth not enabled for this application'
56 | );
57 | });
58 |
59 | const state = url.searchParams.get('state');
60 |
61 | if (!state) {
62 | throw error(400, 'Missing state');
63 | }
64 |
65 | const payload = (await request.json()) as {
66 | input: SolanaSignInInput;
67 | output: SerializedSolanaSignInOutput;
68 | };
69 |
70 | const {
71 | output: {
72 | account: { publicKey: public_key },
73 | signature
74 | }
75 | } = payload;
76 |
77 | if (!public_key) {
78 | throw new Error('Invalid public key');
79 | }
80 |
81 | const authRequest = await verifyMessage(url, state, payload);
82 |
83 | const { access_token, access_token_expires_in, refresh_token } =
84 | await create_session_tokens({
85 | account_id: public_key,
86 | application,
87 | date: authRequest.created_at,
88 | proof: signature
89 | });
90 |
91 | let user: Prisma.UserGetPayload<{
92 | include: {
93 | key: true;
94 | };
95 | }> | null = null;
96 |
97 | try {
98 | if (session) {
99 | user = await handle_existing_session({
100 | application,
101 | provider: 'solana',
102 | provider_account_id: public_key,
103 | provider_user: {
104 | email: payload.output.account.address,
105 | id: public_key,
106 | image: payload.output.account.icon ?? '',
107 | name: payload.output.account.label ?? 'Unnamed'
108 | },
109 | session
110 | });
111 | } else {
112 | user = await handle_no_session({
113 | application,
114 | provider: 'solana',
115 | provider_account_id: public_key,
116 | provider_user: {
117 | email: payload.output.account.address,
118 | id: public_key,
119 | image: payload.output.account.icon ?? '',
120 | name: payload.output.account.label ?? 'Unnamed'
121 | }
122 | });
123 | }
124 | } catch (e) {
125 | if (e instanceof Error) {
126 | console.log(e);
127 | throw error(500, e.message);
128 | }
129 | }
130 |
131 | if (!user) {
132 | throw error(500, 'User not found nor created');
133 | }
134 |
135 | await prisma.authRequest.update({
136 | data: {
137 | access_token,
138 | access_token_expires_in,
139 | ip_address: getClientAddress(),
140 | provider_access_token: access_token,
141 | provider_refresh_token: refresh_token,
142 | refresh_token,
143 | signature,
144 | user_id: user.id
145 | },
146 | where: {
147 | id: authRequest.id
148 | }
149 | });
150 |
151 | session = await auth.createSession({
152 | attributes: {
153 | access_token,
154 | access_token_expires_in,
155 | application_id: authRequest?.application_id,
156 | auth_request_id: authRequest?.id,
157 | provider_account_id: public_key,
158 | refresh_token
159 | },
160 | userId: user.id
161 | });
162 |
163 | locals.auth.setSession(session);
164 |
165 | const results = session as Session
166 |
167 | return json(results);
168 | };
169 |
170 | /**
171 | * @tsoaModel
172 | */
173 | type UnExportedType = {
174 | id: string;
175 | create: Prisma.UserGetPayload