tova 0.5.1 → 0.8.2
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/bin/tova.js +261 -60
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +351 -11
- package/src/analyzer/{client-analyzer.js → browser-analyzer.js} +20 -17
- package/src/analyzer/deploy-analyzer.js +44 -0
- package/src/analyzer/form-analyzer.js +113 -0
- package/src/analyzer/scope.js +2 -2
- package/src/codegen/base-codegen.js +1160 -10
- package/src/codegen/{client-codegen.js → browser-codegen.js} +444 -5
- package/src/codegen/codegen.js +119 -28
- package/src/codegen/deploy-codegen.js +49 -0
- package/src/codegen/edge-codegen.js +1351 -0
- package/src/codegen/form-codegen.js +553 -0
- package/src/codegen/security-codegen.js +5 -5
- package/src/codegen/server-codegen.js +88 -7
- package/src/codegen/shared-codegen.js +5 -0
- package/src/codegen/wasm-codegen.js +6 -0
- package/src/config/edit-toml.js +6 -2
- package/src/config/git-resolver.js +128 -0
- package/src/config/lock-file.js +57 -0
- package/src/config/module-cache.js +58 -0
- package/src/config/module-entry.js +37 -0
- package/src/config/module-path.js +31 -0
- package/src/config/pkg-errors.js +62 -0
- package/src/config/resolve.js +17 -0
- package/src/config/resolver.js +139 -0
- package/src/config/search.js +28 -0
- package/src/config/semver.js +72 -0
- package/src/config/toml.js +48 -5
- package/src/deploy/deploy.js +217 -0
- package/src/deploy/infer.js +218 -0
- package/src/deploy/provision.js +311 -0
- package/src/diagnostics/error-codes.js +1 -1
- package/src/docs/generator.js +1 -1
- package/src/formatter/formatter.js +4 -4
- package/src/lexer/tokens.js +12 -2
- package/src/lsp/server.js +483 -1
- package/src/parser/ast.js +60 -5
- package/src/parser/{client-ast.js → browser-ast.js} +3 -3
- package/src/parser/{client-parser.js → browser-parser.js} +42 -15
- package/src/parser/concurrency-ast.js +15 -0
- package/src/parser/concurrency-parser.js +236 -0
- package/src/parser/deploy-ast.js +37 -0
- package/src/parser/deploy-parser.js +132 -0
- package/src/parser/edge-ast.js +83 -0
- package/src/parser/edge-parser.js +262 -0
- package/src/parser/form-ast.js +80 -0
- package/src/parser/form-parser.js +206 -0
- package/src/parser/parser.js +82 -14
- package/src/parser/select-ast.js +39 -0
- package/src/registry/plugins/browser-plugin.js +30 -0
- package/src/registry/plugins/concurrency-plugin.js +32 -0
- package/src/registry/plugins/deploy-plugin.js +33 -0
- package/src/registry/plugins/edge-plugin.js +32 -0
- package/src/registry/register-all.js +8 -2
- package/src/runtime/ssr.js +2 -2
- package/src/stdlib/inline.js +38 -6
- package/src/stdlib/runtime-bridge.js +152 -0
- package/src/version.js +1 -1
- package/src/registry/plugins/client-plugin.js +0 -30
package/src/codegen/codegen.js
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
|
-
// Main code generator — orchestrates shared/server/
|
|
1
|
+
// Main code generator — orchestrates shared/server/browser codegen
|
|
2
2
|
// Supports named multi-blocks: server "api" { }, server "ws" { }
|
|
3
3
|
// Blocks with the same name are merged; different names produce separate output files.
|
|
4
4
|
|
|
5
|
+
import { createRequire } from 'module';
|
|
5
6
|
import { SharedCodegen } from './shared-codegen.js';
|
|
6
7
|
import { BUILTIN_NAMES } from '../stdlib/inline.js';
|
|
7
|
-
import { ServerCodegen } from './server-codegen.js';
|
|
8
|
-
import { ClientCodegen } from './client-codegen.js';
|
|
9
|
-
import { SecurityCodegen } from './security-codegen.js';
|
|
10
|
-
import { CliCodegen } from './cli-codegen.js';
|
|
11
8
|
import { BlockRegistry } from '../registry/register-all.js';
|
|
12
9
|
|
|
10
|
+
// Lazy-load domain-specific codegens so pure scripts don't pay for them
|
|
11
|
+
const _require = createRequire(import.meta.url);
|
|
12
|
+
let _ServerCodegen, _BrowserCodegen, _SecurityCodegen, _CliCodegen, _EdgeCodegen;
|
|
13
|
+
|
|
13
14
|
function getServerCodegen() {
|
|
14
|
-
|
|
15
|
+
if (!_ServerCodegen) _ServerCodegen = _require('./server-codegen.js').ServerCodegen;
|
|
16
|
+
return _ServerCodegen;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getBrowserCodegen() {
|
|
20
|
+
if (!_BrowserCodegen) _BrowserCodegen = _require('./browser-codegen.js').BrowserCodegen;
|
|
21
|
+
return _BrowserCodegen;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getSecurityCodegen() {
|
|
25
|
+
if (!_SecurityCodegen) _SecurityCodegen = _require('./security-codegen.js').SecurityCodegen;
|
|
26
|
+
return _SecurityCodegen;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getCliCodegen() {
|
|
30
|
+
if (!_CliCodegen) _CliCodegen = _require('./cli-codegen.js').CliCodegen;
|
|
31
|
+
return _CliCodegen;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getEdgeCodegen() {
|
|
35
|
+
if (!_EdgeCodegen) _EdgeCodegen = _require('./edge-codegen.js').EdgeCodegen;
|
|
36
|
+
return _EdgeCodegen;
|
|
15
37
|
}
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
39
|
+
let _DeployCodegen;
|
|
40
|
+
function getDeployCodegen() {
|
|
41
|
+
if (!_DeployCodegen) _DeployCodegen = _require('./deploy-codegen.js').DeployCodegen;
|
|
42
|
+
return _DeployCodegen;
|
|
19
43
|
}
|
|
20
44
|
|
|
21
45
|
export class CodeGenerator {
|
|
@@ -63,11 +87,13 @@ export class CodeGenerator {
|
|
|
63
87
|
// Convenience aliases
|
|
64
88
|
const sharedBlocks = getBlocks('shared');
|
|
65
89
|
const serverBlocks = getBlocks('server');
|
|
66
|
-
const
|
|
90
|
+
const browserBlocks = getBlocks('browser');
|
|
67
91
|
const testBlocks = getBlocks('test');
|
|
68
92
|
const benchBlocks = getBlocks('bench');
|
|
69
93
|
const dataBlocks = getBlocks('data');
|
|
70
94
|
const securityBlocks = getBlocks('security');
|
|
95
|
+
const edgeBlocks = getBlocks('edge');
|
|
96
|
+
const deployBlocks = getBlocks('deploy');
|
|
71
97
|
|
|
72
98
|
// Detect module mode: no blocks, only top-level statements
|
|
73
99
|
const hasAnyBlocks = BlockRegistry.all().some(p => getBlocks(p.name).length > 0);
|
|
@@ -85,7 +111,8 @@ export class CodeGenerator {
|
|
|
85
111
|
return {
|
|
86
112
|
shared: combined,
|
|
87
113
|
server: '',
|
|
88
|
-
|
|
114
|
+
browser: '',
|
|
115
|
+
client: '', // deprecated alias
|
|
89
116
|
isModule: true,
|
|
90
117
|
sourceMappings: moduleGen.getSourceMappings(),
|
|
91
118
|
_sourceFile: this.filename,
|
|
@@ -102,8 +129,8 @@ export class CodeGenerator {
|
|
|
102
129
|
? sharedGen.genBlockStatements({ type: 'BlockStatement', body: topLevel })
|
|
103
130
|
: '';
|
|
104
131
|
|
|
105
|
-
// Pre-scan server/
|
|
106
|
-
this._scanBlocksForBuiltins([...serverBlocks, ...
|
|
132
|
+
// Pre-scan server/browser blocks for builtin usage so shared stdlib includes them
|
|
133
|
+
this._scanBlocksForBuiltins([...serverBlocks, ...browserBlocks, ...edgeBlocks], sharedGen._usedBuiltins);
|
|
107
134
|
|
|
108
135
|
const helpers = sharedGen.generateHelpers();
|
|
109
136
|
|
|
@@ -112,9 +139,9 @@ export class CodeGenerator {
|
|
|
112
139
|
|
|
113
140
|
const combinedShared = [helpers, sharedCode, topLevelCode, dataCode].filter(s => s.trim()).join('\n').trim();
|
|
114
141
|
|
|
115
|
-
// Group server and
|
|
142
|
+
// Group server and browser blocks by name
|
|
116
143
|
const serverGroups = this._groupByName(serverBlocks);
|
|
117
|
-
const
|
|
144
|
+
const browserGroups = this._groupByName(browserBlocks);
|
|
118
145
|
|
|
119
146
|
// Collect function names per named server block for inter-server RPC
|
|
120
147
|
const serverFunctionMap = new Map(); // blockName -> [fnName, ...]
|
|
@@ -141,7 +168,7 @@ export class CodeGenerator {
|
|
|
141
168
|
|
|
142
169
|
// Merge security blocks into a single config
|
|
143
170
|
const securityConfig = securityBlocks.length > 0
|
|
144
|
-
?
|
|
171
|
+
? getSecurityCodegen().mergeSecurityBlocks(securityBlocks)
|
|
145
172
|
: null;
|
|
146
173
|
|
|
147
174
|
// Generate server outputs (one per named group)
|
|
@@ -160,16 +187,67 @@ export class CodeGenerator {
|
|
|
160
187
|
}
|
|
161
188
|
}
|
|
162
189
|
}
|
|
163
|
-
|
|
190
|
+
// Include top-level statements so _collectTypes finds top-level type declarations
|
|
191
|
+
const allSharedBlocks = topLevel.length > 0
|
|
192
|
+
? [...sharedBlocks, { type: 'BlockStatement', body: topLevel }]
|
|
193
|
+
: sharedBlocks;
|
|
194
|
+
servers[key] = gen.generate(blocks, combinedShared, name, peerBlocks, allSharedBlocks, securityConfig);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Collect type validators from shared blocks and top-level for form type inheritance
|
|
198
|
+
const typeValidatorsMap = {};
|
|
199
|
+
const _collectTypeValidators = (stmts) => {
|
|
200
|
+
for (const stmt of stmts) {
|
|
201
|
+
if (stmt.type === 'TypeDeclaration' && stmt.variants) {
|
|
202
|
+
const fields = [];
|
|
203
|
+
for (const v of stmt.variants) {
|
|
204
|
+
if (v.type === 'TypeField' && v.validators && v.validators.length > 0) {
|
|
205
|
+
fields.push({ name: v.name, validators: v.validators });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (fields.length > 0) {
|
|
209
|
+
typeValidatorsMap[stmt.name] = { fields };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
for (const sb of sharedBlocks) {
|
|
215
|
+
_collectTypeValidators(sb.body);
|
|
164
216
|
}
|
|
217
|
+
_collectTypeValidators(topLevel);
|
|
165
218
|
|
|
166
|
-
// Generate
|
|
167
|
-
const
|
|
168
|
-
for (const [name, blocks] of
|
|
169
|
-
const gen = new (
|
|
219
|
+
// Generate browser outputs (one per named group)
|
|
220
|
+
const browsers = {};
|
|
221
|
+
for (const [name, blocks] of browserGroups) {
|
|
222
|
+
const gen = new (getBrowserCodegen())();
|
|
170
223
|
gen._sourceMapsEnabled = this._sourceMaps;
|
|
171
224
|
const key = name || 'default';
|
|
172
|
-
|
|
225
|
+
browsers[key] = gen.generate(blocks, combinedShared, sharedGen._usedBuiltins, securityConfig, typeValidatorsMap);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Generate edge outputs (one per named group)
|
|
229
|
+
const edges = {};
|
|
230
|
+
if (edgeBlocks.length > 0) {
|
|
231
|
+
const edgeGroups = this._groupByName(edgeBlocks);
|
|
232
|
+
for (const [name, blocks] of edgeGroups) {
|
|
233
|
+
const Edge = getEdgeCodegen();
|
|
234
|
+
const gen = new Edge();
|
|
235
|
+
gen._sourceMapsEnabled = this._sourceMaps;
|
|
236
|
+
const key = name || 'default';
|
|
237
|
+
const edgeConfig = Edge.mergeEdgeBlocks(blocks);
|
|
238
|
+
edges[key] = gen.generate(edgeConfig, combinedShared, securityConfig);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Generate deploy configs (one per named block)
|
|
243
|
+
const deploys = {};
|
|
244
|
+
if (deployBlocks.length > 0) {
|
|
245
|
+
const Deploy = getDeployCodegen();
|
|
246
|
+
const deployGroups = this._groupByName(deployBlocks);
|
|
247
|
+
for (const [name, blocks] of deployGroups) {
|
|
248
|
+
const key = name || 'default';
|
|
249
|
+
deploys[key] = Deploy.mergeDeployBlocks(blocks);
|
|
250
|
+
}
|
|
173
251
|
}
|
|
174
252
|
|
|
175
253
|
// Generate tests if test blocks exist
|
|
@@ -193,16 +271,21 @@ export class CodeGenerator {
|
|
|
193
271
|
}
|
|
194
272
|
|
|
195
273
|
// Backward-compatible: if only unnamed blocks, return flat structure
|
|
196
|
-
const
|
|
274
|
+
const edgeGroupKeys = edgeBlocks.length > 0 ? [...this._groupByName(edgeBlocks).keys()] : [];
|
|
275
|
+
const hasNamedBlocks = [...serverGroups.keys(), ...browserGroups.keys(), ...edgeGroupKeys].some(k => k !== null);
|
|
197
276
|
|
|
198
277
|
// Collect source mappings from all codegens
|
|
199
278
|
const sourceMappings = sharedGen.getSourceMappings();
|
|
200
279
|
|
|
201
280
|
if (!hasNamedBlocks) {
|
|
281
|
+
const browserCode = browsers['default'] || '';
|
|
202
282
|
const result = {
|
|
203
283
|
shared: combinedShared,
|
|
204
284
|
server: servers['default'] || '',
|
|
205
|
-
|
|
285
|
+
browser: browserCode,
|
|
286
|
+
client: browserCode, // deprecated alias for backward compat
|
|
287
|
+
edge: edges['default'] || '',
|
|
288
|
+
deploy: Object.keys(deploys).length > 0 ? deploys : undefined,
|
|
206
289
|
sourceMappings,
|
|
207
290
|
_sourceFile: this.filename,
|
|
208
291
|
};
|
|
@@ -212,12 +295,18 @@ export class CodeGenerator {
|
|
|
212
295
|
}
|
|
213
296
|
|
|
214
297
|
// Multi-block output: separate files per named block
|
|
298
|
+
const browserDefault = browsers['default'] || '';
|
|
215
299
|
const result = {
|
|
216
300
|
shared: combinedShared,
|
|
217
301
|
server: servers['default'] || '',
|
|
218
|
-
|
|
302
|
+
browser: browserDefault,
|
|
303
|
+
client: browserDefault, // deprecated alias for backward compat
|
|
304
|
+
edge: edges['default'] || '',
|
|
219
305
|
servers, // { "api": code, "ws": code, ... }
|
|
220
|
-
|
|
306
|
+
browsers, // { "admin": code, "dashboard": code, ... }
|
|
307
|
+
clients: browsers, // deprecated alias for backward compat
|
|
308
|
+
edges, // { "api": code, "assets": code, ... }
|
|
309
|
+
deploy: Object.keys(deploys).length > 0 ? deploys : undefined,
|
|
221
310
|
multiBlock: true,
|
|
222
311
|
sourceMappings,
|
|
223
312
|
_sourceFile: this.filename,
|
|
@@ -250,8 +339,9 @@ export class CodeGenerator {
|
|
|
250
339
|
const helpers = sharedGen.generateHelpers();
|
|
251
340
|
const combinedShared = [helpers, topLevelCode].filter(s => s.trim()).join('\n').trim();
|
|
252
341
|
|
|
253
|
-
const
|
|
254
|
-
const
|
|
342
|
+
const Cli = getCliCodegen();
|
|
343
|
+
const cliConfig = Cli.mergeCliBlocks(cliBlocks);
|
|
344
|
+
const cliGen = new Cli();
|
|
255
345
|
cliGen._sourceMapsEnabled = this._sourceMaps;
|
|
256
346
|
const cliCode = cliGen.generate(cliConfig, combinedShared);
|
|
257
347
|
|
|
@@ -260,7 +350,8 @@ export class CodeGenerator {
|
|
|
260
350
|
isCli: true,
|
|
261
351
|
shared: '',
|
|
262
352
|
server: '',
|
|
263
|
-
|
|
353
|
+
browser: '',
|
|
354
|
+
client: '', // deprecated alias
|
|
264
355
|
sourceMappings: sharedGen.getSourceMappings(),
|
|
265
356
|
_sourceFile: this.filename,
|
|
266
357
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Deploy-specific codegen for the Tova language
|
|
2
|
+
// Produces a configuration manifest (plain JS object) from deploy block AST nodes.
|
|
3
|
+
|
|
4
|
+
const DEFAULTS = {
|
|
5
|
+
instances: 1,
|
|
6
|
+
memory: '512mb',
|
|
7
|
+
branch: 'main',
|
|
8
|
+
health: '/healthz',
|
|
9
|
+
health_interval: 30,
|
|
10
|
+
health_timeout: 5,
|
|
11
|
+
restart_on_failure: true,
|
|
12
|
+
keep_releases: 5,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class DeployCodegen {
|
|
16
|
+
static mergeDeployBlocks(blocks) {
|
|
17
|
+
const config = { ...DEFAULTS, env: {}, databases: [] };
|
|
18
|
+
for (const block of blocks) {
|
|
19
|
+
config.name = block.name;
|
|
20
|
+
for (const stmt of block.body) {
|
|
21
|
+
switch (stmt.type) {
|
|
22
|
+
case 'DeployConfigField': {
|
|
23
|
+
// Extract literal value from AST expression
|
|
24
|
+
const val = stmt.value;
|
|
25
|
+
config[stmt.key] = val.value !== undefined ? val.value : val;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'DeployEnvBlock': {
|
|
29
|
+
for (const entry of stmt.entries) {
|
|
30
|
+
config.env[entry.key] = entry.value.value !== undefined ? entry.value.value : entry.value;
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case 'DeployDbBlock': {
|
|
35
|
+
const dbConfig = {};
|
|
36
|
+
if (stmt.config && typeof stmt.config === 'object') {
|
|
37
|
+
for (const [k, v] of Object.entries(stmt.config)) {
|
|
38
|
+
dbConfig[k] = v.value !== undefined ? v.value : v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
config.databases.push({ engine: stmt.engine, config: dbConfig });
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
}
|