vovk 0.2.3-beta.6 → 0.2.3-beta.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Segment.js +3 -0
- package/cli/.eslintrc.js +1 -1
- package/cli/generateClient.js +72 -29
- package/cli/getVars.js +29 -0
- package/cli/index.js +48 -43
- package/cli/lib/concurrent.js +42 -0
- package/cli/lib/getAvailablePort.js +31 -0
- package/cli/postinstall.js +21 -0
- package/cli/server.js +57 -10
- package/client/clientizeController.d.ts +20 -2
- package/client/clientizeController.js +5 -5
- package/client/defaultFetcher.d.ts +1 -0
- package/client/defaultFetcher.js +11 -9
- package/client/defaultStreamFetcher.js +1 -1
- package/client/types.d.ts +8 -3
- package/config.d.ts +1 -0
- package/config.js +27 -0
- package/createSegment.d.ts +3 -2
- package/createSegment.js +23 -18
- package/index.d.ts +3 -3
- package/index.js +1 -2
- package/package.json +4 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/types.d.ts +28 -0
- package/worker/promisifyWorker.d.ts +1 -1
- package/worker/promisifyWorker.js +26 -8
- package/worker/types.d.ts +8 -2
- package/cli/getVovkrc.js +0 -28
- package/cli/watchMetadata.js +0 -31
package/Segment.js
CHANGED
|
@@ -39,6 +39,9 @@ class _Segment {
|
|
|
39
39
|
#callMethod = async (httpMethod, req, params) => {
|
|
40
40
|
const controllers = this._routes[httpMethod];
|
|
41
41
|
const methodParams = {};
|
|
42
|
+
if (params[Object.keys(params)[0]]?.[0] === '__ping') {
|
|
43
|
+
return this.#respond(200, { message: 'pong' });
|
|
44
|
+
}
|
|
42
45
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
43
46
|
const handlers = Object.fromEntries([...controllers.entries()]
|
|
44
47
|
.map(([controller, staticMethods]) => {
|
package/cli/.eslintrc.js
CHANGED
|
@@ -7,6 +7,6 @@ module.exports = {
|
|
|
7
7
|
'@typescript-eslint/no-unsafe-call': 'off',
|
|
8
8
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
9
9
|
'@typescript-eslint/no-unsafe-return': 'off',
|
|
10
|
-
'no-console': ['error', { allow: ['info', 'error'] }]
|
|
10
|
+
'no-console': ['error', { allow: ['info', 'error', 'warn'] }]
|
|
11
11
|
}
|
|
12
12
|
}
|
package/cli/generateClient.js
CHANGED
|
@@ -1,54 +1,97 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
const fs = require('fs/promises');
|
|
2
3
|
const path = require('path');
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
function canRequire(moduleName) {
|
|
6
|
+
try {
|
|
7
|
+
require.resolve(moduleName);
|
|
8
|
+
return true; // The module exists and can be required
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return false; // The module does not exist
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
|
-
* Generates client code with string concatenation so it should be
|
|
6
|
-
*
|
|
15
|
+
* Generates client code with string concatenation so it should be much faster than using AST
|
|
16
|
+
* TODO: Check fetcher and streamFetcher for existence
|
|
17
|
+
* @type {(rcPath: import('../src').VovkEnv) => Promise<boolean>}
|
|
7
18
|
*/
|
|
8
|
-
async function generateClient(
|
|
9
|
-
const
|
|
19
|
+
async function generateClient({ ...env }) {
|
|
20
|
+
const jsonPath = '../../.vovk.json';
|
|
21
|
+
const localJsonPath = path.join('..', jsonPath);
|
|
22
|
+
const fetcherPath = env.VOVK_FETCHER.startsWith('.') ? path.join('../..', env.VOVK_FETCHER) : env.VOVK_FETCHER;
|
|
23
|
+
|
|
24
|
+
const streamFetcherPath = env.VOVK_STREAM_FETCHER.startsWith('.')
|
|
25
|
+
? path.join('../..', env.VOVK_STREAM_FETCHER)
|
|
26
|
+
: env.VOVK_STREAM_FETCHER;
|
|
27
|
+
const validatePath = env.VOVK_VALIDATE_ON_CLIENT.startsWith('.')
|
|
28
|
+
? path.join(__dirname, '../..', env.VOVK_VALIDATE_ON_CLIENT)
|
|
29
|
+
: env.VOVK_VALIDATE_ON_CLIENT;
|
|
30
|
+
const localValidatePath = env.VOVK_VALIDATE_ON_CLIENT.startsWith('.') ? path.join('..', validatePath) : validatePath;
|
|
31
|
+
|
|
32
|
+
if (!env.VOVK_VALIDATE_ON_CLIENT) {
|
|
33
|
+
env.VOVK_VALIDATE_ON_CLIENT = canRequire('vovk-zod/zodValidateOnClient') ? 'vovk-zod/zodValidateOnClient' : '';
|
|
34
|
+
} else if (env.VOVK_VALIDATE_ON_CLIENT && !canRequire(localValidatePath)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Unble to generate Vovk Client: cannot find "validateOnClient" module '${env.VOVK_VALIDATE_ON_CLIENT}'. Check your .vovkrc.js file`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
10
39
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
: vovkrc.streamFetcher;
|
|
40
|
+
if (!canRequire(localJsonPath)) {
|
|
41
|
+
throw new Error(`Unble to generate Vovk Client: cannot find ".vovk.json" file '${jsonPath}'.`);
|
|
42
|
+
}
|
|
15
43
|
|
|
16
|
-
const controllersPath = path.join('
|
|
17
|
-
let ts =
|
|
44
|
+
const controllersPath = path.join('../..', env.VOVK_ROUTE).replace(/\.ts$/, '');
|
|
45
|
+
let ts = `/* auto-generated */
|
|
46
|
+
import type { Controllers, Workers } from "${controllersPath}";
|
|
18
47
|
import type { clientizeController } from 'vovk/client';
|
|
19
48
|
import type { promisifyWorker } from 'vovk/worker';
|
|
20
|
-
import type {
|
|
49
|
+
import type { VovkClientFetcher } from 'vovk/client';
|
|
21
50
|
import type fetcher from '${fetcherPath}';
|
|
22
51
|
|
|
23
|
-
type Options = typeof fetcher extends
|
|
52
|
+
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
24
53
|
`;
|
|
25
|
-
let js =
|
|
54
|
+
let js = `/* auto-generated */
|
|
55
|
+
const { clientizeController } = require('vovk/client');
|
|
26
56
|
const { promisifyWorker } = require('vovk/worker');
|
|
27
|
-
const metadata = require('
|
|
28
|
-
const fetcher = require('${fetcherPath}');
|
|
29
|
-
const streamFetcher = require('${streamFetcherPath}');
|
|
30
|
-
const prefix = '${
|
|
31
|
-
const validateOnClient =
|
|
57
|
+
const metadata = require('${jsonPath}');
|
|
58
|
+
const { default: fetcher } = require('${fetcherPath}');
|
|
59
|
+
const { default: streamFetcher } = require('${streamFetcherPath}');
|
|
60
|
+
const prefix = '${env.VOVK_PREFIX ?? '/api'}';
|
|
61
|
+
const { default: validateOnClient = null } = ${
|
|
62
|
+
env.VOVK_VALIDATE_ON_CLIENT ? `require('${env.VOVK_VALIDATE_ON_CLIENT}')` : '{}'
|
|
63
|
+
};
|
|
32
64
|
|
|
33
65
|
`;
|
|
34
|
-
const metadataJson = await fs
|
|
35
|
-
.readFile(path.join(__dirname, '../../.vovk/vovk-metadata.json'), 'utf-8')
|
|
36
|
-
.catch(() => null);
|
|
66
|
+
const metadataJson = await fs.readFile(path.join(__dirname, localJsonPath), 'utf-8').catch(() => null);
|
|
37
67
|
const metadata = JSON.parse(metadataJson || '{}');
|
|
38
68
|
|
|
39
69
|
for (const key of Object.keys(metadata)) {
|
|
40
70
|
if (key !== 'workers') {
|
|
41
|
-
ts += `export
|
|
71
|
+
ts += `export const ${key}: ReturnType<typeof clientizeController<Controllers["${key}"], Options>>;\n`;
|
|
42
72
|
js += `exports.${key} = clientizeController(metadata.${key}, { fetcher, streamFetcher, validateOnClient, defaultOptions: { prefix } });\n`;
|
|
43
73
|
}
|
|
44
74
|
}
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
for (const key of Object.keys(metadata.workers ?? {})) {
|
|
77
|
+
ts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers["${key}"]>>;\n`;
|
|
78
|
+
js += `exports.${key} = promisifyWorker(null, metadata.workers.${key});\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
js += `
|
|
82
|
+
if(typeof window !== 'undefined') fetch(prefix + '/__ping', { method: 'POST' });
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const jsPath = path.join(__dirname, '../../.vovk/index.js');
|
|
86
|
+
const tsPath = path.join(__dirname, '../../.vovk/index.d.ts');
|
|
87
|
+
await fs.mkdir('../../.vovk', { recursive: true });
|
|
88
|
+
const existingJs = await fs.readFile(jsPath, 'utf-8').catch(() => '');
|
|
89
|
+
const existingTs = await fs.readFile(tsPath, 'utf-8').catch(() => '');
|
|
90
|
+
if (existingJs === js && existingTs === ts) return false;
|
|
91
|
+
await fs.writeFile(tsPath, ts);
|
|
92
|
+
await fs.writeFile(jsPath, js);
|
|
93
|
+
|
|
94
|
+
return true;
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
module.exports = generateClient;
|
package/cli/getVars.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @type {(rcPath: string) => import('../src').VovkEnv} */
|
|
3
|
+
function getVars(rcPath) {
|
|
4
|
+
/** @type {Required<import('../src').VovkRc>} */
|
|
5
|
+
const vovkRc = {
|
|
6
|
+
route: 'src/app/api/[[...]]/route.ts',
|
|
7
|
+
fetcher: 'vovk/client/defaultFetcher',
|
|
8
|
+
streamFetcher: 'vovk/client/defaultStreamFetcher',
|
|
9
|
+
prefix: '/api',
|
|
10
|
+
validateOnClient: '',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
Object.assign(vovkRc, require(rcPath));
|
|
15
|
+
} catch {
|
|
16
|
+
console.info(` 🐺 No .vovkrc.js file found in the root directory`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
VOVK_PORT: process.env.VOVK_PORT || '3420',
|
|
21
|
+
VOVK_ROUTE: process.env.VOVK_ROUTE || vovkRc.route,
|
|
22
|
+
VOVK_FETCHER: process.env.VOVK_FETCHER || vovkRc.fetcher,
|
|
23
|
+
VOVK_STREAM_FETCHER: process.env.VOVK_STREAM_FETCHER || vovkRc.streamFetcher,
|
|
24
|
+
VOVK_PREFIX: process.env.VOVK_PREFIX || vovkRc.prefix,
|
|
25
|
+
VOVK_VALIDATE_ON_CLIENT: process.env.VOVK_VALIDATE_ON_CLIENT || vovkRc.validateOnClient,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = getVars;
|
package/cli/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
2
3
|
const yargs = require('yargs/yargs');
|
|
3
4
|
const { hideBin } = require('yargs/helpers');
|
|
4
|
-
const { concurrently } = require('concurrently');
|
|
5
5
|
const generateClient = require('./generateClient');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const concurrent = require('./lib/concurrent');
|
|
8
|
+
const getAvailablePort = require('./lib/getAvailablePort');
|
|
9
|
+
const getVars = require('./getVars');
|
|
7
10
|
|
|
8
11
|
const builder = {
|
|
9
12
|
rc: {
|
|
@@ -17,61 +20,63 @@ const builder = {
|
|
|
17
20
|
default: process.cwd(),
|
|
18
21
|
describe: 'Path to Next.js project',
|
|
19
22
|
},
|
|
20
|
-
|
|
21
|
-
output: {
|
|
22
|
-
type: 'string',
|
|
23
|
-
default: path.join(__dirname, '../.vovk/index.d.ts'),
|
|
24
|
-
describe: 'Path to the wildcard route file',
|
|
25
|
-
},
|
|
26
23
|
};
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/** @type {{ rc: string, project: string, output: string }} */
|
|
34
|
-
const argv = yargs(hideBin(process.argv))
|
|
35
|
-
.command('dev', 'Run development server', builder)
|
|
36
|
-
.command('build', 'Build the app', builder)
|
|
25
|
+
/** @type {{ vovkrc: string, project: string }} */
|
|
26
|
+
// @ts-expect-error yargs
|
|
27
|
+
const argv = yargs(hideBin(process.argv)) // @ts-expect-error yargs
|
|
28
|
+
.command('dev', 'Run development server', builder) // @ts-expect-error yargs
|
|
29
|
+
.command('build', 'Build the app', builder) // @ts-expect-error yargs
|
|
37
30
|
.command('generate', 'Generate client', builder).argv;
|
|
38
31
|
|
|
39
32
|
const nextArgs = process.argv.join(' ').split(' -- ')[1] ?? '';
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
const { result } = concurrently(
|
|
43
|
-
[
|
|
44
|
-
{ command: `node ${__dirname}/server.js`, name: 'Vovk' },
|
|
45
|
-
{ command: `node ${__dirname}/watchMetadata.js --rc ${argv.rc} --output ${argv.output}`, name: 'Watch metadata' },
|
|
46
|
-
{ command: `cd ${argv.project} && npx next dev ${nextArgs}`, name: 'Next' },
|
|
47
|
-
],
|
|
48
|
-
options
|
|
49
|
-
);
|
|
34
|
+
const env = getVars(argv.vovkrc);
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
let VOVK_PORT = parseInt(env.VOVK_PORT);
|
|
37
|
+
|
|
38
|
+
// @ts-expect-error yargs
|
|
39
|
+
if (argv._.includes('dev')) {
|
|
40
|
+
void (async () => {
|
|
41
|
+
env.VOVK_PORT = await getAvailablePort(VOVK_PORT, 20).catch(() => {
|
|
42
|
+
throw new Error(' 🐺 Failed to find available port');
|
|
43
|
+
});
|
|
44
|
+
await concurrent(
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
command: `node ${__dirname}/server.js`,
|
|
48
|
+
name: 'Vovk',
|
|
49
|
+
},
|
|
50
|
+
{ command: `cd ${argv.project} && npx next dev ${nextArgs}`, name: 'Next' },
|
|
51
|
+
],
|
|
52
|
+
env
|
|
53
|
+
).catch((e) => console.error(e));
|
|
54
|
+
console.info(' 🐺 All processes have completed');
|
|
55
|
+
})();
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
// @ts-expect-error yargs
|
|
56
59
|
if (argv._.includes('build')) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{ command: `cd ${argv.project} && npx next build ${nextArgs}`, name: 'Next' },
|
|
61
|
-
],
|
|
62
|
-
options
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
void result
|
|
66
|
-
.catch((e) => console.error(e))
|
|
67
|
-
.then(async () => {
|
|
68
|
-
await generateClient(argv.rc, argv.output);
|
|
69
|
-
console.info(' 🐺 Both processes have completed and the client is generated.');
|
|
60
|
+
void (async () => {
|
|
61
|
+
env.VOVK_PORT = await getAvailablePort(VOVK_PORT, 20).catch(() => {
|
|
62
|
+
throw new Error(' 🐺 Failed to find available port');
|
|
70
63
|
});
|
|
64
|
+
await concurrent(
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
command: `node ${__dirname}/server.js --once`,
|
|
68
|
+
name: 'Vovk',
|
|
69
|
+
},
|
|
70
|
+
{ command: `cd ${argv.project} && npx next build ${nextArgs}`, name: 'Next' },
|
|
71
|
+
],
|
|
72
|
+
env
|
|
73
|
+
).catch((e) => console.error(e));
|
|
74
|
+
})();
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
// @ts-expect-error yargs
|
|
73
78
|
if (argv._.includes('generate')) {
|
|
74
|
-
void generateClient(
|
|
75
|
-
console.info(' 🐺 Client generated
|
|
79
|
+
void generateClient(env).then(() => {
|
|
80
|
+
console.info(' 🐺 Client generated');
|
|
76
81
|
});
|
|
77
82
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
|
|
4
|
+
/** @type {(commands: { command: string; name: string; }[], env: import('../../src').VovkEnv) => Promise<void>} */
|
|
5
|
+
function concurrent(commands, env) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
/** @type {{ name: string; process: import('child_process').ChildProcess; }[]} */
|
|
8
|
+
let processes = [];
|
|
9
|
+
|
|
10
|
+
commands.forEach((cmd) => {
|
|
11
|
+
const processObj = {
|
|
12
|
+
name: cmd.name,
|
|
13
|
+
process: runCommand(cmd.command, cmd.name, (code) => handleProcessExit(code, cmd.name)),
|
|
14
|
+
};
|
|
15
|
+
processes.push(processObj);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function runCommand(command, name, onExit) {
|
|
19
|
+
const proc = spawn(command, { shell: true, env: { ...env, ...process.env }, stdio: 'inherit' });
|
|
20
|
+
|
|
21
|
+
proc.on('exit', onExit);
|
|
22
|
+
|
|
23
|
+
return proc;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleProcessExit(code, name) {
|
|
27
|
+
processes = processes.filter((p) => p.name !== name);
|
|
28
|
+
|
|
29
|
+
if (code !== 0) {
|
|
30
|
+
processes.forEach((p) => p.name !== name && p.process.kill('SIGINT'));
|
|
31
|
+
processes = [];
|
|
32
|
+
return reject(new Error(`Process ${name} exited with code ${code}`));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!processes.length) {
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = concurrent;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
|
|
3
|
+
function checkPort(port, callback) {
|
|
4
|
+
const server = net.createServer();
|
|
5
|
+
|
|
6
|
+
server.listen(port, () => {
|
|
7
|
+
server.close(() => {
|
|
8
|
+
callback(true); // Port is available
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.on('error', () => {
|
|
13
|
+
callback(false);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getAvailablePort(startPort, maxAttempts, attempt = 1) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
checkPort(startPort, (isAvailable) => {
|
|
20
|
+
if (isAvailable) {
|
|
21
|
+
resolve(startPort); // Found an available port
|
|
22
|
+
} else if (attempt < maxAttempts) {
|
|
23
|
+
getAvailablePort(startPort + 1, maxAttempts, attempt + 1).then(resolve, reject);
|
|
24
|
+
} else {
|
|
25
|
+
reject(null);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = getAvailablePort;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const fileExists = async (path) => !!(await fs.stat(path).catch(() => false));
|
|
6
|
+
|
|
7
|
+
async function postinstall() {
|
|
8
|
+
const vovk = path.join(__dirname, '../../.vovk');
|
|
9
|
+
const js = path.join(vovk, 'index.js');
|
|
10
|
+
const ts = path.join(vovk, 'index.d.ts');
|
|
11
|
+
|
|
12
|
+
if ((await fileExists(js)) || (await fileExists(ts))) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await fs.mkdir(vovk, { recursive: true });
|
|
17
|
+
await fs.writeFile(js, '/* postinstall */');
|
|
18
|
+
await fs.writeFile(ts, '/* postinstall */');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void postinstall();
|
package/cli/server.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
const http = require('http');
|
|
2
3
|
const fs = require('fs/promises');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const yargs = require('yargs/yargs');
|
|
5
6
|
const { hideBin } = require('yargs/helpers');
|
|
7
|
+
const generateClient = require('./generateClient');
|
|
8
|
+
const getVars = require('./getVars');
|
|
6
9
|
|
|
10
|
+
/** @type {{ once?: boolean; vovkrc: string }} */
|
|
11
|
+
// @ts-expect-error yargs
|
|
7
12
|
const argv = yargs(hideBin(process.argv)).argv;
|
|
8
13
|
|
|
9
14
|
const once = argv.once ?? false;
|
|
@@ -36,9 +41,33 @@ const isEqual = (obj1, obj2) => {
|
|
|
36
41
|
const writeMetadata = async (metadataPath, metadata) => {
|
|
37
42
|
await fs.mkdir(path.dirname(metadataPath), { recursive: true });
|
|
38
43
|
const existingMetadata = await fs.readFile(metadataPath, 'utf-8').catch(() => '{}');
|
|
39
|
-
if (isEqual(JSON.parse(existingMetadata), metadata)) return;
|
|
44
|
+
if (isEqual(JSON.parse(existingMetadata), metadata)) return false;
|
|
40
45
|
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
41
|
-
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let pingInterval;
|
|
50
|
+
|
|
51
|
+
const vars = getVars(argv.vovkrc);
|
|
52
|
+
|
|
53
|
+
const startPinging = (port) => {
|
|
54
|
+
clearInterval(pingInterval);
|
|
55
|
+
pingInterval = setInterval(() => {
|
|
56
|
+
let prefix = vars.VOVK_PREFIX;
|
|
57
|
+
prefix = prefix.startsWith('http://')
|
|
58
|
+
? prefix
|
|
59
|
+
: `http://localhost:${port}/${prefix.startsWith('/') ? prefix.slice(1) : prefix}`;
|
|
60
|
+
const endpoint = `${prefix.endsWith('/') ? prefix.slice(0, -1) : prefix}/__ping`;
|
|
61
|
+
// Create the HTTP GET request
|
|
62
|
+
const req = http.get(endpoint, () => {
|
|
63
|
+
// noop
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Error handling for the request
|
|
67
|
+
req.on('error', (err) => {
|
|
68
|
+
console.error(`🐺 Error during HTTP request made to ${endpoint}:`, err.message);
|
|
69
|
+
});
|
|
70
|
+
}, 1000 * 3);
|
|
42
71
|
};
|
|
43
72
|
|
|
44
73
|
const server = http.createServer((req, res) => {
|
|
@@ -46,23 +75,37 @@ const server = http.createServer((req, res) => {
|
|
|
46
75
|
let body = '';
|
|
47
76
|
|
|
48
77
|
req.on('data', (chunk) => {
|
|
49
|
-
body += chunk.toString();
|
|
78
|
+
body += chunk.toString();
|
|
50
79
|
});
|
|
51
80
|
|
|
52
81
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
53
82
|
req.on('end', async () => {
|
|
54
83
|
try {
|
|
55
|
-
const metadata = JSON.parse(body); // Parse the JSON data
|
|
56
|
-
const filePath = path.join(__dirname, '
|
|
57
|
-
await writeMetadata(filePath, metadata);
|
|
84
|
+
const { metadata, PORT } = JSON.parse(body); // Parse the JSON data
|
|
85
|
+
const filePath = path.join(__dirname, '../../../.vovk.json');
|
|
86
|
+
const metadataWritten = await writeMetadata(filePath, metadata);
|
|
87
|
+
|
|
88
|
+
const codeWritten = await generateClient(vars);
|
|
58
89
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
59
90
|
res.end('JSON data received and file created');
|
|
91
|
+
if (metadataWritten || codeWritten) {
|
|
92
|
+
console.info(' 🐺 JSON metadata received and the client is generated');
|
|
93
|
+
} else if (once) {
|
|
94
|
+
console.info(' 🐺 JSON metadata received and the client is not changed');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (PORT && !once) {
|
|
98
|
+
startPinging(PORT);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (once && metadata) server.close();
|
|
60
102
|
} catch (err) {
|
|
61
103
|
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
62
104
|
res.end('Invalid JSON');
|
|
63
105
|
console.error(' ❌ ' + err.message);
|
|
106
|
+
|
|
107
|
+
if (once) server.close();
|
|
64
108
|
}
|
|
65
|
-
if (once) server.close();
|
|
66
109
|
});
|
|
67
110
|
} else {
|
|
68
111
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
@@ -70,7 +113,11 @@ const server = http.createServer((req, res) => {
|
|
|
70
113
|
}
|
|
71
114
|
});
|
|
72
115
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
console.
|
|
116
|
+
const VOVK_PORT = process.env.VOVK_PORT;
|
|
117
|
+
if (!VOVK_PORT) {
|
|
118
|
+
console.error(' 🐺 Unable to run Vovk Metadata Server: no port specified');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
server.listen(VOVK_PORT, () => {
|
|
122
|
+
console.info(` 🐺 Vovk Metadata Server is running on port ${VOVK_PORT}`);
|
|
76
123
|
});
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { type _VovkControllerMetadataJson as VovkControllerMetadataJson } from '../types';
|
|
2
|
-
import { type _VovkClientOptions as VovkClientOptions, type _VovkClient as VovkClient } from './types';
|
|
2
|
+
import { type _VovkClientOptions as VovkClientOptions, type _VovkClient as VovkClient, type _StreamAsyncIterator as StreamAsyncIterator } from './types';
|
|
3
3
|
import { type _DefaultFetcherOptions as DefaultFetcherOptions } from './defaultFetcher';
|
|
4
|
-
export declare const _clientizeController: <T, OPTS extends Record<string, any> = DefaultFetcherOptions>(givenController: VovkControllerMetadataJson, options?: VovkClientOptions<OPTS> | undefined) =>
|
|
4
|
+
export declare const _clientizeController: <T, OPTS extends Record<string, any> = DefaultFetcherOptions>(givenController: VovkControllerMetadataJson, options?: VovkClientOptions<OPTS> | undefined) => { [K_1 in keyof { [K in keyof T]: T[K] extends (...args: any) => any ? <R>(options: (import("./types")._StaticMethodInput<T[K]> extends {
|
|
5
|
+
body?: null | undefined;
|
|
6
|
+
query?: undefined;
|
|
7
|
+
params?: undefined;
|
|
8
|
+
} ? unknown : Parameters<T[K]>[0] extends void ? import("./types")._StaticMethodInput<T[K]>["params"] extends object ? {
|
|
9
|
+
params: import("./types")._StaticMethodInput<T[K]>["params"];
|
|
10
|
+
} : unknown : import("./types")._StaticMethodInput<T[K]>) & (void | Partial<OPTS>)) => ReturnType<T[K]> extends import("../StreamResponse")._StreamResponse<infer U> | Iterator<infer U, any, undefined> | AsyncIterator<infer U, any, undefined> | Promise<import("../StreamResponse")._StreamResponse<infer U>> ? Promise<StreamAsyncIterator<U>> : R extends object ? Promise<R> : ReturnType<T[K]> extends infer T_1 ? T_1 extends ReturnType<T[K]> ? T_1 extends PromiseLike<unknown> ? T_1 : Promise<T_1> : never : never : never; } as { [K in keyof T]: T[K] extends (...args: any) => any ? <R>(options: (import("./types")._StaticMethodInput<T[K]> extends {
|
|
11
|
+
body?: null | undefined;
|
|
12
|
+
query?: undefined;
|
|
13
|
+
params?: undefined;
|
|
14
|
+
} ? unknown : Parameters<T[K]>[0] extends void ? import("./types")._StaticMethodInput<T[K]>["params"] extends object ? {
|
|
15
|
+
params: import("./types")._StaticMethodInput<T[K]>["params"];
|
|
16
|
+
} : unknown : import("./types")._StaticMethodInput<T[K]>) & (void | Partial<OPTS>)) => ReturnType<T[K]> extends import("../StreamResponse")._StreamResponse<infer U> | Iterator<infer U, any, undefined> | AsyncIterator<infer U, any, undefined> | Promise<import("../StreamResponse")._StreamResponse<infer U>> ? Promise<StreamAsyncIterator<U>> : R extends object ? Promise<R> : ReturnType<T[K]> extends infer T_2 ? T_2 extends ReturnType<T[K]> ? T_2 extends PromiseLike<unknown> ? T_2 : Promise<T_2> : never : never : never; }[K_1] extends never ? never : K_1]: { [K in keyof T]: T[K] extends (...args: any) => any ? <R>(options: (import("./types")._StaticMethodInput<T[K]> extends {
|
|
17
|
+
body?: null | undefined;
|
|
18
|
+
query?: undefined;
|
|
19
|
+
params?: undefined;
|
|
20
|
+
} ? unknown : Parameters<T[K]>[0] extends void ? import("./types")._StaticMethodInput<T[K]>["params"] extends object ? {
|
|
21
|
+
params: import("./types")._StaticMethodInput<T[K]>["params"];
|
|
22
|
+
} : unknown : import("./types")._StaticMethodInput<T[K]>) & (void | Partial<OPTS>)) => ReturnType<T[K]> extends import("../StreamResponse")._StreamResponse<infer U> | Iterator<infer U, any, undefined> | AsyncIterator<infer U, any, undefined> | Promise<import("../StreamResponse")._StreamResponse<infer U>> ? Promise<StreamAsyncIterator<U>> : R extends object ? Promise<R> : ReturnType<T[K]> extends infer T_3 ? T_3 extends ReturnType<T[K]> ? T_3 extends PromiseLike<unknown> ? T_3 : Promise<T_3> : never : never : never; }[K_1]; };
|
|
@@ -32,10 +32,8 @@ const _clientizeController = (givenController, options) => {
|
|
|
32
32
|
const { fetcher = defaultFetcher_1.default, streamFetcher = defaultStreamFetcher_1.default } = options ?? {};
|
|
33
33
|
for (const [staticMethodName, { path, httpMethod, clientValidators }] of Object.entries(metadata)) {
|
|
34
34
|
const getPath = (params, query) => getHandlerPath([prefix, path].filter(Boolean).join('/'), params, query);
|
|
35
|
-
const validate = ({ body, query }) => {
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
return options?.validateOnClient?.({ body, query }, clientValidators ?? {});
|
|
35
|
+
const validate = async ({ body, query }) => {
|
|
36
|
+
await options?.validateOnClient?.({ body, query }, clientValidators ?? {});
|
|
39
37
|
};
|
|
40
38
|
const handler = (input = {}) => {
|
|
41
39
|
const internalOptions = {
|
|
@@ -57,9 +55,11 @@ const _clientizeController = (givenController, options) => {
|
|
|
57
55
|
const fetcherPromise = streamFetcher(internalOptions, internalInput);
|
|
58
56
|
return fetcherPromise;
|
|
59
57
|
}
|
|
58
|
+
if (!fetcher)
|
|
59
|
+
throw new Error('Fetcher is not provided');
|
|
60
60
|
const fetcherPromise = fetcher(internalOptions, internalInput);
|
|
61
61
|
if (!(fetcherPromise instanceof Promise))
|
|
62
|
-
|
|
62
|
+
return Promise.resolve(fetcherPromise);
|
|
63
63
|
return fetcherPromise;
|
|
64
64
|
};
|
|
65
65
|
// @ts-expect-error TODO: Fix this
|
|
@@ -2,6 +2,7 @@ import { type _VovkClientFetcher as VovkClientFetcher } from './types';
|
|
|
2
2
|
export interface _DefaultFetcherOptions extends Omit<RequestInit, 'body' | 'method'> {
|
|
3
3
|
prefix?: string;
|
|
4
4
|
isStream?: boolean;
|
|
5
|
+
disableClientValidation?: true;
|
|
5
6
|
}
|
|
6
7
|
export declare const DEFAULT_ERROR_MESSAGE = "Unknown error at defaultFetcher";
|
|
7
8
|
declare const defaultFetcher: VovkClientFetcher<_DefaultFetcherOptions>;
|
package/client/defaultFetcher.js
CHANGED
|
@@ -10,15 +10,17 @@ const defaultFetcher = async ({ httpMethod, getPath, validate }, { params, query
|
|
|
10
10
|
const endpoint = (prefix.startsWith('http://') || prefix.startsWith('https://') || prefix.startsWith('/') ? '' : '/') +
|
|
11
11
|
(prefix.endsWith('/') ? prefix : `${prefix}/`) +
|
|
12
12
|
getPath(params, query);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
if (!options.disableClientValidation) {
|
|
14
|
+
try {
|
|
15
|
+
await validate({ body, query });
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
// if HttpException is thrown, rethrow it
|
|
19
|
+
if (e instanceof HttpException_1._HttpException)
|
|
20
|
+
throw e;
|
|
21
|
+
// otherwise, throw HttpException with status 0
|
|
22
|
+
throw new HttpException_1._HttpException(types_1._HttpStatus.NULL, e.message ?? exports.DEFAULT_ERROR_MESSAGE);
|
|
23
|
+
}
|
|
22
24
|
}
|
|
23
25
|
const init = {
|
|
24
26
|
method: httpMethod,
|
|
@@ -10,7 +10,7 @@ const defaultStreamFetcher = async ({ httpMethod, getPath, validate }, { params,
|
|
|
10
10
|
(prefix.endsWith('/') ? prefix : `${prefix}/`) +
|
|
11
11
|
getPath(params, query);
|
|
12
12
|
try {
|
|
13
|
-
validate({ body, query });
|
|
13
|
+
await validate({ body, query });
|
|
14
14
|
}
|
|
15
15
|
catch (e) {
|
|
16
16
|
// if HttpException is thrown, rethrow it
|
package/client/types.d.ts
CHANGED
|
@@ -27,11 +27,17 @@ type ClientMethod<T extends (...args: KnownAny[]) => void | object | StreamRespo
|
|
|
27
27
|
} ? unknown : Parameters<T>[0] extends void ? _StaticMethodInput<T>['params'] extends object ? {
|
|
28
28
|
params: _StaticMethodInput<T>['params'];
|
|
29
29
|
} : unknown : _StaticMethodInput<T>) & (Partial<OPTS> | void)) => ReturnType<T> extends Promise<StreamResponse<infer U>> | StreamResponse<infer U> | Iterator<infer U> | AsyncIterator<infer U> ? Promise<_StreamAsyncIterator<U>> : R extends object ? Promise<R> : ToPromise<ReturnType<T>>;
|
|
30
|
-
|
|
30
|
+
type OmitNever<T> = {
|
|
31
|
+
[K in keyof T as T[K] extends never ? never : K]: T[K];
|
|
32
|
+
};
|
|
33
|
+
type _VovkClientWithNever<T, OPTS extends {
|
|
31
34
|
[key: string]: KnownAny;
|
|
32
35
|
}> = {
|
|
33
36
|
[K in keyof T]: T[K] extends (...args: KnownAny) => KnownAny ? ClientMethod<T[K], OPTS> : never;
|
|
34
37
|
};
|
|
38
|
+
export type _VovkClient<T, OPTS extends {
|
|
39
|
+
[key: string]: KnownAny;
|
|
40
|
+
}> = OmitNever<_VovkClientWithNever<T, OPTS>>;
|
|
35
41
|
export type _VovkClientFetcher<OPTS extends Record<string, KnownAny> = Record<string, never>, T = KnownAny> = (options: {
|
|
36
42
|
name: keyof T;
|
|
37
43
|
httpMethod: HttpMethod;
|
|
@@ -43,7 +49,7 @@ export type _VovkClientFetcher<OPTS extends Record<string, KnownAny> = Record<st
|
|
|
43
49
|
validate: (input: {
|
|
44
50
|
body?: unknown;
|
|
45
51
|
query?: unknown;
|
|
46
|
-
}) => void
|
|
52
|
+
}) => void | Promise<void>;
|
|
47
53
|
}, input: {
|
|
48
54
|
body: unknown;
|
|
49
55
|
query: {
|
|
@@ -54,7 +60,6 @@ export type _VovkClientFetcher<OPTS extends Record<string, KnownAny> = Record<st
|
|
|
54
60
|
};
|
|
55
61
|
} & OPTS) => KnownAny;
|
|
56
62
|
export type _VovkClientOptions<OPTS extends Record<string, KnownAny> = Record<string, never>> = {
|
|
57
|
-
disableClientValidation?: boolean;
|
|
58
63
|
fetcher?: _VovkClientFetcher<OPTS>;
|
|
59
64
|
streamFetcher?: _VovkClientFetcher<OPTS>;
|
|
60
65
|
validateOnClient?: (input: {
|
package/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare function config(): void;
|