veko-framework 1.0.0

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.
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Veko v1 – Lexer (structure only; do-body is extracted from source)
3
+ */
4
+
5
+ export const TT = {
6
+ KEYWORD: 'KEYWORD',
7
+ IDENT: 'IDENT',
8
+ NUMBER: 'NUMBER',
9
+ LPAREN: 'LPAREN',
10
+ RPAREN: 'RPAREN',
11
+ LBRACE: 'LBRACE',
12
+ RBRACE: 'RBRACE',
13
+ LBRACKET: 'LBRACKET',
14
+ RBRACKET: 'RBRACKET',
15
+ STRING: 'STRING',
16
+ FAT_ARROW: 'FAT_ARROW',
17
+ COMMA: 'COMMA',
18
+ COLON: 'COLON',
19
+ PIPE: 'PIPE',
20
+ QUESTION: 'QUESTION',
21
+ HTTP_METHOD: 'HTTP_METHOD',
22
+ EOF: 'EOF',
23
+ };
24
+
25
+ const KEYWORDS = new Set(['data', 'do', 'route', 'migration', 'import']);
26
+ const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']);
27
+
28
+ export function lex(source) {
29
+ const tokens = [];
30
+ let i = 0;
31
+ let line = 1;
32
+ let column = 1;
33
+
34
+ function peek(offset = 0) {
35
+ return source[i + offset];
36
+ }
37
+ function advance() {
38
+ const c = source[i++];
39
+ if (c === '\n') {
40
+ line++;
41
+ column = 1;
42
+ } else {
43
+ column++;
44
+ }
45
+ return c;
46
+ }
47
+ function add(type, value) {
48
+ tokens.push({ type, value, line, column });
49
+ }
50
+
51
+ while (i < source.length) {
52
+ if (/\s/.test(peek())) {
53
+ advance();
54
+ continue;
55
+ }
56
+ if (peek() === '/' && peek(1) === '/') {
57
+ while (i < source.length && peek() !== '\n') advance();
58
+ continue;
59
+ }
60
+ if (peek() === '/' && peek(1) === '*') {
61
+ advance();
62
+ advance();
63
+ while (i < source.length && !(peek() === '*' && peek(1) === '/')) advance();
64
+ advance();
65
+ advance();
66
+ continue;
67
+ }
68
+ if (peek() === '=' && peek(1) === '>') {
69
+ advance();
70
+ advance();
71
+ add(TT.FAT_ARROW, '=>');
72
+ continue;
73
+ }
74
+ const singles = {
75
+ '{': TT.LBRACE, '}': TT.RBRACE, '(': TT.LPAREN, ')': TT.RPAREN,
76
+ '[': TT.LBRACKET, ']': TT.RBRACKET, ',': TT.COMMA, ':': TT.COLON,
77
+ '|': TT.PIPE, '?': TT.QUESTION,
78
+ };
79
+ if (singles[peek()]) {
80
+ add(singles[peek()], advance());
81
+ continue;
82
+ }
83
+ if (peek() === '"' || peek() === "'") {
84
+ const q = advance();
85
+ const start = i;
86
+ while (i < source.length && peek() !== q) {
87
+ if (peek() === '\\') advance();
88
+ advance();
89
+ }
90
+ if (i < source.length) advance();
91
+ add(TT.STRING, source.slice(start, i - 1));
92
+ continue;
93
+ }
94
+ if (/[0-9]/.test(peek())) {
95
+ const start = i;
96
+ while (i < source.length && /[0-9]/.test(peek())) advance();
97
+ add(TT.NUMBER, source.slice(start, i));
98
+ continue;
99
+ }
100
+ if (/[a-zA-Z_]/.test(peek())) {
101
+ const start = i;
102
+ while (i < source.length && /[a-zA-Z0-9_]/.test(peek())) advance();
103
+ const value = source.slice(start, i);
104
+ if (KEYWORDS.has(value)) add(TT.KEYWORD, value);
105
+ else if (HTTP_METHODS.has(value)) add(TT.HTTP_METHOD, value);
106
+ else add(TT.IDENT, value);
107
+ continue;
108
+ }
109
+ advance();
110
+ }
111
+
112
+ add(TT.EOF, '');
113
+ return tokens;
114
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type declarations for parser.js
3
+ */
4
+
5
+ import type { AST } from './types';
6
+
7
+ export function parse(source: string): AST;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Veko v1 – Parser (AST for data, do, route, migration)
3
+ */
4
+
5
+ import { lex, TT } from './lexer.js';
6
+ import { locateBraceInSource, extractRawBlock } from './source-utils.js';
7
+
8
+ /**
9
+ * @param {string} source
10
+ * @returns {import('./types.js').AST}
11
+ */
12
+ export function parse(source) {
13
+ const tokens = lex(source);
14
+ let pos = 0;
15
+
16
+ function peek(o = 0) {
17
+ return tokens[pos + o];
18
+ }
19
+ function consume(type, value) {
20
+ const t = tokens[pos];
21
+ if (!t) throw new Error('Unexpected end of file');
22
+ if (type && t.type !== type) throw new Error(`Line ${t.line}: expected ${type}, got ${t.type}`);
23
+ if (value !== undefined && t.value !== value) throw new Error(`Line ${t.line}: expected "${value}"`);
24
+ pos++;
25
+ return t;
26
+ }
27
+ function check(type, value) {
28
+ const t = tokens[pos];
29
+ return t && t.type === type && (value === undefined || t.value === value);
30
+ }
31
+
32
+ const ast = { dataBlocks: [], doBlocks: [], routeLines: [], migrations: [], imports: [] };
33
+
34
+ while (!check(TT.EOF)) {
35
+ if (check(TT.KEYWORD, 'import')) {
36
+ consume(TT.KEYWORD);
37
+ const path = consume(TT.STRING).value;
38
+ ast.imports.push({ path });
39
+ continue;
40
+ }
41
+
42
+ if (check(TT.KEYWORD, 'data')) {
43
+ consume(TT.KEYWORD);
44
+ const name = consume(TT.IDENT).value;
45
+ consume(TT.LBRACE);
46
+ const fields = [];
47
+ while (!check(TT.RBRACE) && !check(TT.EOF)) {
48
+ if (check(TT.COMMA)) {
49
+ consume(TT.COMMA);
50
+ continue;
51
+ }
52
+ const fieldName = consume(TT.IDENT).value;
53
+ consume(TT.COLON);
54
+ const typeTok = consume(TT.IDENT);
55
+ let typeName = typeTok.value;
56
+ let typeArgs = {};
57
+ if (check(TT.LPAREN)) {
58
+ consume(TT.LPAREN);
59
+ while (!check(TT.RPAREN) && !check(TT.EOF)) {
60
+ if (check(TT.PIPE) || check(TT.COMMA)) {
61
+ consume(tokens[pos].type);
62
+ continue;
63
+ }
64
+ const k = consume(TT.IDENT).value;
65
+ if (check(TT.COLON)) {
66
+ consume(TT.COLON);
67
+ const vTok = check(TT.NUMBER) ? consume(TT.NUMBER) : consume(TT.IDENT);
68
+ const v = vTok.value;
69
+ typeArgs[k] = isNaN(Number(v)) ? v : Number(v);
70
+ } else {
71
+ if (!typeArgs.enum) typeArgs.enum = [];
72
+ typeArgs.enum.push(k);
73
+ }
74
+ }
75
+ consume(TT.RPAREN);
76
+ }
77
+ const optional = check(TT.QUESTION) ? (consume(TT.QUESTION), true) : false;
78
+ fields.push({ name: fieldName, type: typeName, optional, args: typeArgs });
79
+ }
80
+ consume(TT.RBRACE);
81
+ ast.dataBlocks.push({ name, fields });
82
+ continue;
83
+ }
84
+
85
+ if (check(TT.KEYWORD, 'do')) {
86
+ const kw = consume(TT.KEYWORD);
87
+ const name = consume(TT.IDENT).value;
88
+ consume(TT.LPAREN);
89
+ while (!check(TT.RPAREN) && !check(TT.EOF)) {
90
+ if (check(TT.COMMA)) consume(TT.COMMA);
91
+ else pos++;
92
+ }
93
+ consume(TT.RPAREN);
94
+ const lbrace = tokens[pos];
95
+ const openIdx = locateBraceInSource(source, lbrace.line);
96
+ consume(TT.LBRACE);
97
+ const raw = extractRawBlock(source, openIdx);
98
+ let depth = 1;
99
+ while (pos < tokens.length && depth > 0) {
100
+ if (check(TT.LBRACE)) depth++;
101
+ else if (check(TT.RBRACE)) {
102
+ depth--;
103
+ if (depth === 0) {
104
+ consume(TT.RBRACE);
105
+ break;
106
+ }
107
+ }
108
+ pos++;
109
+ }
110
+ ast.doBlocks.push({ name, body: raw.body, line: kw.line });
111
+ continue;
112
+ }
113
+
114
+ if (check(TT.KEYWORD, 'route')) {
115
+ consume(TT.KEYWORD);
116
+ consume(TT.LBRACE);
117
+ while (!check(TT.RBRACE) && !check(TT.EOF)) {
118
+ if (!check(TT.HTTP_METHOD)) {
119
+ pos++;
120
+ continue;
121
+ }
122
+ const method = consume(TT.HTTP_METHOD).value;
123
+ const path = consume(TT.STRING).value;
124
+ consume(TT.FAT_ARROW);
125
+ const pipeline = [];
126
+ if (check(TT.LBRACKET)) consume(TT.LBRACKET);
127
+ const inBracket = tokens[pos - 1]?.value === '[';
128
+ while (
129
+ !check(TT.EOF) &&
130
+ !check(TT.HTTP_METHOD) &&
131
+ !check(TT.RBRACE) &&
132
+ !(inBracket && check(TT.RBRACKET))
133
+ ) {
134
+ if (check(TT.COMMA)) {
135
+ consume(TT.COMMA);
136
+ continue;
137
+ }
138
+ const ident = consume(TT.IDENT).value;
139
+ if (ident === 'validate' && check(TT.LPAREN)) {
140
+ consume(TT.LPAREN);
141
+ const schema = consume(TT.IDENT).value;
142
+ consume(TT.RPAREN);
143
+ pipeline.push({ kind: 'validate', schema });
144
+ } else if (ident === 'auth') {
145
+ pipeline.push({ kind: 'auth' });
146
+ } else {
147
+ pipeline.push({ kind: 'action', name: ident });
148
+ }
149
+ }
150
+ if (inBracket && check(TT.RBRACKET)) consume(TT.RBRACKET);
151
+ ast.routeLines.push({ method, path, pipeline });
152
+ }
153
+ consume(TT.RBRACE);
154
+ continue;
155
+ }
156
+
157
+ if (check(TT.KEYWORD, 'migration')) {
158
+ consume(TT.KEYWORD);
159
+ const version = consume(TT.STRING).value;
160
+ consume(TT.LBRACE);
161
+ const operations = [];
162
+ while (!check(TT.RBRACE) && !check(TT.EOF)) {
163
+ const op = consume(TT.IDENT).value;
164
+ const table = consume(TT.STRING).value;
165
+ if (op === 'addColumn') {
166
+ const column = consume(TT.IDENT).value;
167
+ const type = consume(TT.IDENT).value;
168
+ operations.push({ op, table, column, type });
169
+ } else if (op === 'dropColumn') {
170
+ const column = consume(TT.IDENT).value;
171
+ operations.push({ op, table, column });
172
+ } else {
173
+ operations.push({ op, table });
174
+ }
175
+ }
176
+ consume(TT.RBRACE);
177
+ ast.migrations.push({ version, operations });
178
+ continue;
179
+ }
180
+
181
+ pos++;
182
+ }
183
+
184
+ return ast;
185
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type declarations for resolver.js
3
+ */
4
+
5
+ import type { AST } from './types';
6
+
7
+ export function resolveModules(
8
+ entryFile: string,
9
+ rootDir?: string
10
+ ): AST;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Veko v1 – Module Resolver
3
+ * Handles import statements, resolves .vk files, and merges ASTs
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { parse } from './parser.js';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ /**
14
+ * Resolves and loads all imported modules, merging their ASTs
15
+ * @param {string} entryFile - Path to the main .vk file
16
+ * @param {string} [rootDir] - Project root directory
17
+ * @returns {import('./types.js').AST} Merged AST from all files
18
+ */
19
+ export function resolveModules(entryFile, rootDir) {
20
+ const root = rootDir ? path.resolve(rootDir) : path.dirname(entryFile);
21
+ const visited = new Set();
22
+ const resolved = new Map();
23
+ const loading = new Set();
24
+
25
+ function resolvePath(importPath, fromFile) {
26
+ let normalized = importPath.replace(/\.vk$/, '');
27
+ if (!normalized.endsWith('.vk')) {
28
+ normalized += '.vk';
29
+ }
30
+ if (normalized.startsWith('./') || normalized.startsWith('../')) {
31
+ return path.resolve(path.dirname(fromFile), normalized);
32
+ }
33
+ if (path.isAbsolute(normalized)) {
34
+ return normalized;
35
+ }
36
+ return path.resolve(root, normalized);
37
+ }
38
+
39
+ function loadModule(filePath) {
40
+ const normalizedPath = path.normalize(filePath);
41
+
42
+ if (loading.has(normalizedPath)) {
43
+ throw new Error(`Circular dependency detected: ${normalizedPath} is already being loaded`);
44
+ }
45
+ if (resolved.has(normalizedPath)) {
46
+ return resolved.get(normalizedPath);
47
+ }
48
+ if (!fs.existsSync(normalizedPath)) {
49
+ throw new Error(`Import not found: ${normalizedPath} (resolved from ${filePath})`);
50
+ }
51
+
52
+ loading.add(normalizedPath);
53
+ visited.add(normalizedPath);
54
+
55
+ try {
56
+ const source = fs.readFileSync(normalizedPath, 'utf-8');
57
+ const ast = parse(source);
58
+
59
+ for (const imp of ast.imports) {
60
+ const importedPath = resolvePath(imp.path, normalizedPath);
61
+ const importedAST = loadModule(importedPath);
62
+ ast.dataBlocks.push(...importedAST.dataBlocks);
63
+ ast.doBlocks.push(...importedAST.doBlocks);
64
+ ast.routeLines.push(...importedAST.routeLines);
65
+ ast.migrations.push(...importedAST.migrations);
66
+ }
67
+
68
+ resolved.set(normalizedPath, ast);
69
+ return ast;
70
+ } finally {
71
+ loading.delete(normalizedPath);
72
+ }
73
+ }
74
+
75
+ const entryPath = path.isAbsolute(entryFile)
76
+ ? entryFile
77
+ : path.resolve(root, entryFile);
78
+
79
+ const mainAST = loadModule(entryPath);
80
+
81
+ return {
82
+ dataBlocks: mainAST.dataBlocks,
83
+ doBlocks: mainAST.doBlocks,
84
+ routeLines: mainAST.routeLines,
85
+ migrations: mainAST.migrations,
86
+ imports: [],
87
+ };
88
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Locate opening brace at token's line in source; extract content to matching closing brace.
3
+ */
4
+
5
+ export function locateBraceInSource(source, line) {
6
+ let currentLine = 1;
7
+ let i = 0;
8
+ while (i < source.length) {
9
+ if (currentLine === line) {
10
+ while (i < source.length && source[i] !== '{') i++;
11
+ return i;
12
+ }
13
+ if (source[i] === '\n') currentLine++;
14
+ i++;
15
+ }
16
+ throw new Error(`Line ${line} not found in source`);
17
+ }
18
+
19
+ export function extractRawBlock(source, openBraceIndex) {
20
+ let depth = 1;
21
+ let i = openBraceIndex + 1;
22
+ while (i < source.length && depth > 0) {
23
+ const c = source[i];
24
+ if (c === '"' || c === "'" || c === '`') {
25
+ const q = c;
26
+ i++;
27
+ while (i < source.length) {
28
+ if (source[i] === '\\') {
29
+ i += 2;
30
+ continue;
31
+ }
32
+ if (source[i] === q) {
33
+ i++;
34
+ break;
35
+ }
36
+ i++;
37
+ }
38
+ continue;
39
+ }
40
+ if (c === '{') depth++;
41
+ else if (c === '}') depth--;
42
+ i++;
43
+ }
44
+ if (depth !== 0) throw new Error('Unbalanced braces');
45
+ return { body: source.slice(openBraceIndex + 1, i - 1).trim(), endIndex: i };
46
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Type definitions for Veko AST
3
+ */
4
+
5
+ export interface FieldDef {
6
+ name: string;
7
+ type: string;
8
+ optional: boolean;
9
+ args: Record<string, any>;
10
+ }
11
+
12
+ export interface DataBlock {
13
+ name: string;
14
+ fields: FieldDef[];
15
+ }
16
+
17
+ export interface DoBlock {
18
+ name: string;
19
+ body: string;
20
+ line: number;
21
+ }
22
+
23
+ export type PipelineStep =
24
+ | { kind: 'validate'; schema: string }
25
+ | { kind: 'auth' }
26
+ | { kind: 'action'; name: string };
27
+
28
+ export interface RouteLine {
29
+ method: string;
30
+ path: string;
31
+ pipeline: PipelineStep[];
32
+ }
33
+
34
+ export interface Migration {
35
+ version: string;
36
+ operations: Array<{
37
+ op: string;
38
+ table: string;
39
+ column?: string;
40
+ type?: string;
41
+ }>;
42
+ }
43
+
44
+ export interface AST {
45
+ dataBlocks: DataBlock[];
46
+ doBlocks: DoBlock[];
47
+ routeLines: RouteLine[];
48
+ migrations: Migration[];
49
+ imports?: Array<{ path: string }>;
50
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Veko v1 – Type definitions for AST (JSDoc for IDE/type gen)
3
+ */
4
+
5
+ /**
6
+ * @typedef {{ name: string, type: string, optional: boolean, args: Record<string,any> }} FieldDef
7
+ * @typedef {{ name: string, fields: FieldDef[] }} DataBlock
8
+ * @typedef {{ name: string, body: string, line: number }} DoBlock
9
+ * @typedef {{ kind: 'validate', schema: string }|{ kind: 'auth' }|{ kind: 'action', name: string }} PipelineStep
10
+ * @typedef {{ method: string, path: string, pipeline: PipelineStep[] }} RouteLine
11
+ * @typedef {{ version: string, operations: { op: string, table: string, column?: string, type?: string }[] }} Migration
12
+ * @typedef {{ dataBlocks: DataBlock[], doBlocks: DoBlock[], routeLines: RouteLine[], migrations: Migration[] }} AST
13
+ */
14
+
15
+ export default {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Veko v1 – Config and resolved config types
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} VekoPlugin
7
+ * @property {function(object): void} [onRouteRegister]
8
+ * @property {function(object): Promise<object>|object} [beforeAction]
9
+ * @property {function(any): Promise<any>|any} [afterAction]
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} VekoConfig
14
+ * @property {string} [entry='./api.vk']
15
+ * @property {{ server?: string, types?: string }} [output]
16
+ * @property {'sqlite'|'postgres'} [adapter='sqlite']
17
+ * @property {{ sqlite?: { path?: string }, postgres?: { connectionString?: string, ssl?: object } }} [database]
18
+ * @property {{ port?: number }} [server]
19
+ * @property {{ table?: string, dir?: string }} [migrations]
20
+ * @property {VekoPlugin[]} [plugins]
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} ResolvedConfig
25
+ * @property {string} root
26
+ * @property {string} entry
27
+ * @property {{ server: string, types: string }} output
28
+ * @property {'sqlite'|'postgres'} adapter
29
+ * @property {object} database
30
+ * @property {{ port?: number }} server
31
+ * @property {{ table: string, dir: string }} migrations
32
+ * @property {VekoPlugin[]} plugins
33
+ */
34
+
35
+ export default {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Type declarations for defaults.js
3
+ */
4
+
5
+ export const DEFAULTS: {
6
+ readonly PORT: number;
7
+ readonly HOST: string;
8
+ readonly DB_FILE: string;
9
+ readonly DB_FILE_TEST: string;
10
+ readonly JWT_SECRET: string;
11
+ readonly LOG_LEVEL: string;
12
+ };
13
+
14
+ export function env(key: string): string | undefined;
15
+
16
+ export default typeof DEFAULTS;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Veko v1 – Default configuration values
3
+ * Single source of truth for compiler, CLI, and runtime.
4
+ */
5
+
6
+ export const DEFAULTS = Object.freeze({
7
+ PORT: 3000,
8
+ HOST: '0.0.0.0',
9
+ DB_FILE: 'veko.db',
10
+ DB_FILE_TEST: 'veko.test.db',
11
+ /** Dev-only default; production must set JWT_SECRET in the environment. */
12
+ JWT_SECRET: 'changeme-secret',
13
+ LOG_LEVEL: 'info',
14
+ });
15
+
16
+ /**
17
+ * @param {string} [key] - Env key (e.g. 'PORT'). Returns process.env[key] or default.
18
+ */
19
+ export function env(key) {
20
+ switch (key) {
21
+ case 'PORT':
22
+ return process.env.PORT != null ? process.env.PORT : String(DEFAULTS.PORT);
23
+ case 'HOST':
24
+ return process.env.HOST || DEFAULTS.HOST;
25
+ case 'DB_FILE':
26
+ return process.env.DB_FILE || (process.env.NODE_ENV === 'test' ? DEFAULTS.DB_FILE_TEST : DEFAULTS.DB_FILE);
27
+ case 'JWT_SECRET':
28
+ return process.env.JWT_SECRET || DEFAULTS.JWT_SECRET;
29
+ case 'LOG_LEVEL':
30
+ return process.env.LOG_LEVEL || DEFAULTS.LOG_LEVEL;
31
+ default:
32
+ return process.env?.[key];
33
+ }
34
+ }
35
+
36
+ export default DEFAULTS;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Veko v1 – Config module (defaults, loadConfig, types)
3
+ */
4
+
5
+ export { DEFAULTS, env } from './defaults.js';
6
+ export { loadConfig } from './load-config.js';
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Veko v1 – Load veko.config.js and return resolved config
3
+ */
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath, pathToFileURL } from 'url';
8
+ import { DEFAULTS } from './defaults.js';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ /**
13
+ * Load veko.config.js from root. Returns resolved paths and options.
14
+ * When rootDir is omitted, uses process.cwd() so that `npx veko build` from a consumer app uses that app's directory.
15
+ * @param {string} [rootDir] - Project root (default: process.cwd())
16
+ * @returns {Promise<import('./config-types.js').ResolvedConfig>}
17
+ */
18
+ export async function loadConfig(rootDir) {
19
+ const root = rootDir ? path.resolve(rootDir) : process.cwd();
20
+ const configPath = path.join(root, 'veko.config.js');
21
+
22
+ if (!fs.existsSync(configPath)) {
23
+ return {
24
+ root,
25
+ entry: path.join(root, 'api.vk'),
26
+ output: {
27
+ server: path.join(root, 'generated', 'server.js'),
28
+ types: path.join(root, 'generated', 'types.d.ts'),
29
+ },
30
+ adapter: 'sqlite',
31
+ database: {},
32
+ server: { port: DEFAULTS.PORT },
33
+ migrations: {
34
+ table: '_veko_migrations',
35
+ dir: path.join(root, 'migrations'),
36
+ },
37
+ plugins: [],
38
+ };
39
+ }
40
+
41
+ const mod = await import(pathToFileURL(configPath).href);
42
+ const c = mod.default || mod;
43
+
44
+ return {
45
+ root,
46
+ entry: path.resolve(root, c.entry || 'api.vk'),
47
+ output: {
48
+ server: path.resolve(root, c.output?.server || 'generated/server.js'),
49
+ types: path.resolve(root, c.output?.types || 'generated/types.d.ts'),
50
+ },
51
+ adapter: c.adapter || 'sqlite',
52
+ database: c.database || {},
53
+ server: c.server || {},
54
+ migrations: c.migrations || {},
55
+ plugins: c.plugins || [],
56
+ };
57
+ }
58
+
59
+ export default loadConfig;