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.
- package/Readme.md +233 -0
- package/bin/veko.js +79 -0
- package/index.js +94 -0
- package/package.json +71 -0
- package/src/cli/build.js +16 -0
- package/src/cli/check.ts +203 -0
- package/src/cli/dev.ts +189 -0
- package/src/cli/migrate.js +80 -0
- package/src/compiler/aot-validation.js +67 -0
- package/src/compiler/codegen.js +203 -0
- package/src/compiler/compile.d.ts +16 -0
- package/src/compiler/compile.js +39 -0
- package/src/compiler/index.js +5 -0
- package/src/compiler/lexer.js +114 -0
- package/src/compiler/parser.d.ts +7 -0
- package/src/compiler/parser.js +185 -0
- package/src/compiler/resolver.d.ts +10 -0
- package/src/compiler/resolver.js +88 -0
- package/src/compiler/source-utils.js +46 -0
- package/src/compiler/types.d.ts +50 -0
- package/src/compiler/types.js +15 -0
- package/src/config/config-types.js +35 -0
- package/src/config/defaults.d.ts +16 -0
- package/src/config/defaults.js +36 -0
- package/src/config/index.js +6 -0
- package/src/config/load-config.js +59 -0
- package/src/runtime/adapters/base.js +96 -0
- package/src/runtime/adapters/postgres.js +98 -0
- package/src/runtime/adapters/sqlite.js +87 -0
- package/src/runtime/auth.js +64 -0
- package/src/runtime/errors.js +63 -0
- package/src/runtime/index.js +36 -0
- package/src/runtime/queryBuilder.js +86 -0
- package/src/runtime/sugar.js +76 -0
- package/src/runtime/validator.js +71 -0
- package/src/runtime/wrapAction.js +24 -0
- package/tsconfig.json +17 -0
|
@@ -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,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,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,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;
|