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.
Files changed (60) hide show
  1. package/bin/tova.js +261 -60
  2. package/package.json +1 -1
  3. package/src/analyzer/analyzer.js +351 -11
  4. package/src/analyzer/{client-analyzer.js → browser-analyzer.js} +20 -17
  5. package/src/analyzer/deploy-analyzer.js +44 -0
  6. package/src/analyzer/form-analyzer.js +113 -0
  7. package/src/analyzer/scope.js +2 -2
  8. package/src/codegen/base-codegen.js +1160 -10
  9. package/src/codegen/{client-codegen.js → browser-codegen.js} +444 -5
  10. package/src/codegen/codegen.js +119 -28
  11. package/src/codegen/deploy-codegen.js +49 -0
  12. package/src/codegen/edge-codegen.js +1351 -0
  13. package/src/codegen/form-codegen.js +553 -0
  14. package/src/codegen/security-codegen.js +5 -5
  15. package/src/codegen/server-codegen.js +88 -7
  16. package/src/codegen/shared-codegen.js +5 -0
  17. package/src/codegen/wasm-codegen.js +6 -0
  18. package/src/config/edit-toml.js +6 -2
  19. package/src/config/git-resolver.js +128 -0
  20. package/src/config/lock-file.js +57 -0
  21. package/src/config/module-cache.js +58 -0
  22. package/src/config/module-entry.js +37 -0
  23. package/src/config/module-path.js +31 -0
  24. package/src/config/pkg-errors.js +62 -0
  25. package/src/config/resolve.js +17 -0
  26. package/src/config/resolver.js +139 -0
  27. package/src/config/search.js +28 -0
  28. package/src/config/semver.js +72 -0
  29. package/src/config/toml.js +48 -5
  30. package/src/deploy/deploy.js +217 -0
  31. package/src/deploy/infer.js +218 -0
  32. package/src/deploy/provision.js +311 -0
  33. package/src/diagnostics/error-codes.js +1 -1
  34. package/src/docs/generator.js +1 -1
  35. package/src/formatter/formatter.js +4 -4
  36. package/src/lexer/tokens.js +12 -2
  37. package/src/lsp/server.js +483 -1
  38. package/src/parser/ast.js +60 -5
  39. package/src/parser/{client-ast.js → browser-ast.js} +3 -3
  40. package/src/parser/{client-parser.js → browser-parser.js} +42 -15
  41. package/src/parser/concurrency-ast.js +15 -0
  42. package/src/parser/concurrency-parser.js +236 -0
  43. package/src/parser/deploy-ast.js +37 -0
  44. package/src/parser/deploy-parser.js +132 -0
  45. package/src/parser/edge-ast.js +83 -0
  46. package/src/parser/edge-parser.js +262 -0
  47. package/src/parser/form-ast.js +80 -0
  48. package/src/parser/form-parser.js +206 -0
  49. package/src/parser/parser.js +82 -14
  50. package/src/parser/select-ast.js +39 -0
  51. package/src/registry/plugins/browser-plugin.js +30 -0
  52. package/src/registry/plugins/concurrency-plugin.js +32 -0
  53. package/src/registry/plugins/deploy-plugin.js +33 -0
  54. package/src/registry/plugins/edge-plugin.js +32 -0
  55. package/src/registry/register-all.js +8 -2
  56. package/src/runtime/ssr.js +2 -2
  57. package/src/stdlib/inline.js +38 -6
  58. package/src/stdlib/runtime-bridge.js +152 -0
  59. package/src/version.js +1 -1
  60. package/src/registry/plugins/client-plugin.js +0 -30
@@ -1,21 +1,45 @@
1
- // Main code generator — orchestrates shared/server/client codegen
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
- return ServerCodegen;
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
- function getClientCodegen() {
18
- return ClientCodegen;
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 clientBlocks = getBlocks('client');
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
- client: '',
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/client blocks for builtin usage so shared stdlib includes them
106
- this._scanBlocksForBuiltins([...serverBlocks, ...clientBlocks], sharedGen._usedBuiltins);
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 client blocks by name
142
+ // Group server and browser blocks by name
116
143
  const serverGroups = this._groupByName(serverBlocks);
117
- const clientGroups = this._groupByName(clientBlocks);
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
- ? SecurityCodegen.mergeSecurityBlocks(securityBlocks)
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
- servers[key] = gen.generate(blocks, combinedShared, name, peerBlocks, sharedBlocks, securityConfig);
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 client outputs (one per named group)
167
- const clients = {};
168
- for (const [name, blocks] of clientGroups) {
169
- const gen = new (getClientCodegen())();
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
- clients[key] = gen.generate(blocks, combinedShared, sharedGen._usedBuiltins, securityConfig);
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 hasNamedBlocks = [...serverGroups.keys(), ...clientGroups.keys()].some(k => k !== null);
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
- client: clients['default'] || '',
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
- client: clients['default'] || '',
302
+ browser: browserDefault,
303
+ client: browserDefault, // deprecated alias for backward compat
304
+ edge: edges['default'] || '',
219
305
  servers, // { "api": code, "ws": code, ... }
220
- clients, // { "admin": code, "dashboard": code, ... }
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 cliConfig = CliCodegen.mergeCliBlocks(cliBlocks);
254
- const cliGen = new CliCodegen();
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
- client: '',
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
+ }