tova 0.4.5 → 0.4.7

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 CHANGED
@@ -1319,13 +1319,19 @@ async function generateDevHTML(clientCode, srcDir, reloadPort = 0) {
1319
1319
  const liveReloadScript = reloadPort ? `
1320
1320
  <script>
1321
1321
  (function() {
1322
- var es = new EventSource("http://localhost:${reloadPort}/__tova_reload");
1322
+ var reloadUrl = "http://localhost:${reloadPort}/__tova_reload";
1323
+ var es = new EventSource(reloadUrl);
1324
+ var errorCount = 0;
1325
+ es.onopen = function() { errorCount = 0; };
1323
1326
  es.onmessage = function(e) { if (e.data === "reload") window.location.reload(); };
1324
1327
  es.onerror = function() {
1328
+ errorCount++;
1329
+ // EventSource auto-reconnects — only intervene after repeated failures
1330
+ if (errorCount < 3) return;
1325
1331
  es.close();
1326
- // Server is rebuilding — poll until it's back, then reload
1332
+ // SSE server is likely gone (dev server restarting) — poll until back
1327
1333
  var check = setInterval(function() {
1328
- fetch(window.location.href, { mode: "no-cors" }).then(function() {
1334
+ fetch(reloadUrl, { mode: "no-cors" }).then(function(r) {
1329
1335
  clearInterval(check);
1330
1336
  window.location.reload();
1331
1337
  }).catch(function() {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Tova — a modern programming language that transpiles to JavaScript, unifying frontend and backend",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -37,5 +37,17 @@
37
37
  },
38
38
  "author": "Enoch Kujem Abassey",
39
39
  "keywords": ["language", "transpiler", "fullstack", "javascript"],
40
- "license": "MIT"
40
+ "license": "MIT",
41
+ "devDependencies": {
42
+ "@codemirror/autocomplete": "^6.20.0",
43
+ "@codemirror/commands": "^6.10.2",
44
+ "@codemirror/language": "^6.12.1",
45
+ "@codemirror/lint": "^6.9.4",
46
+ "@codemirror/search": "^6.6.0",
47
+ "@codemirror/state": "^6.5.4",
48
+ "@codemirror/theme-one-dark": "^6.1.3",
49
+ "@codemirror/view": "^6.39.15",
50
+ "@lezer/highlight": "^1.2.3",
51
+ "codemirror": "^6.0.2"
52
+ }
41
53
  }
@@ -2547,11 +2547,13 @@ export class Analyzer {
2547
2547
  if (typeName) {
2548
2548
  const existingImpls = this.typeRegistry.impls.get(typeName) || [];
2549
2549
  for (const method of node.methods) {
2550
+ const hasSelf = (method.params || []).some(p => p.name === 'self');
2550
2551
  existingImpls.push({
2551
2552
  name: method.name,
2552
2553
  params: (method.params || []).map(p => p.name),
2553
2554
  paramTypes: (method.params || []).map(p => typeAnnotationToType(p.typeAnnotation)),
2554
2555
  returnType: typeAnnotationToType(method.returnType),
2556
+ isAssociated: !hasSelf,
2555
2557
  });
2556
2558
  }
2557
2559
  this.typeRegistry.impls.set(typeName, existingImpls);
@@ -2583,13 +2585,16 @@ export class Analyzer {
2583
2585
 
2584
2586
  // Validate that methods reference the type
2585
2587
  for (const method of node.methods) {
2588
+ const hasSelf = (method.params || []).some(p => p.name === 'self');
2586
2589
  this.pushScope('function');
2587
2590
  try {
2588
- // self is implicitly available
2589
- try {
2590
- this.currentScope.define('self',
2591
- new Symbol('self', 'variable', null, true, method.loc));
2592
- } catch (e) { /* ignore */ }
2591
+ // self is only available for instance methods (not associated functions)
2592
+ if (hasSelf) {
2593
+ try {
2594
+ this.currentScope.define('self',
2595
+ new Symbol('self', 'variable', null, true, method.loc));
2596
+ } catch (e) { /* ignore */ }
2597
+ }
2593
2598
  for (const p of method.params) {
2594
2599
  if (p.name && p.name !== 'self') {
2595
2600
  try {
@@ -25,7 +25,7 @@ export class TypeRegistry {
25
25
 
26
26
  /**
27
27
  * Get all members (fields + impl methods) for a type name.
28
- * Used for dot-completion.
28
+ * Used for dot-completion on instances.
29
29
  */
30
30
  getMembers(typeName) {
31
31
  const fields = new Map();
@@ -48,17 +48,36 @@ export class TypeRegistry {
48
48
  }
49
49
  }
50
50
 
51
- // Get impl methods
51
+ // Get instance methods (methods with self)
52
52
  const implMethods = this.impls.get(typeName);
53
53
  if (implMethods) {
54
54
  for (const method of implMethods) {
55
- methods.push(method);
55
+ if (!method.isAssociated) {
56
+ methods.push(method);
57
+ }
56
58
  }
57
59
  }
58
60
 
59
61
  return { fields, methods };
60
62
  }
61
63
 
64
+ /**
65
+ * Get associated functions for a type name (functions without self).
66
+ * Used for dot-completion on the type itself (e.g., Point.origin()).
67
+ */
68
+ getAssociatedFunctions(typeName) {
69
+ const functions = [];
70
+ const implMethods = this.impls.get(typeName);
71
+ if (implMethods) {
72
+ for (const method of implMethods) {
73
+ if (method.isAssociated) {
74
+ functions.push(method);
75
+ }
76
+ }
77
+ }
78
+ return functions;
79
+ }
80
+
62
81
  /**
63
82
  * Get variant names for a type (for match completion).
64
83
  */
@@ -2898,11 +2898,22 @@ export class BaseCodegen {
2898
2898
  }
2899
2899
  const body = this.genBlockBody(method.body);
2900
2900
  this.popScope();
2901
- const selfBinding = hasSelf ? `\n${this.i()} const self = this;` : '';
2902
- if (hasPropagate) {
2903
- lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}};`);
2901
+
2902
+ if (hasSelf) {
2903
+ // Instance method prototype
2904
+ const selfBinding = `\n${this.i()} const self = this;`;
2905
+ if (hasPropagate) {
2906
+ lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}};`);
2907
+ } else {
2908
+ lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${body}\n${this.i()}};`);
2909
+ }
2904
2910
  } else {
2905
- lines.push(`${this.i()}${node.typeName}.prototype.${method.name} = ${asyncPrefix}function(${paramStr}) {${selfBinding}\n${body}\n${this.i()}};`);
2911
+ // Associated function → constructor namespace (callable as Type.method())
2912
+ if (hasPropagate) {
2913
+ lines.push(`${this.i()}${node.typeName}.${method.name} = ${asyncPrefix}function(${paramStr}) {\n${this.i()} try {\n${body}\n${this.i()} } catch (__e) {\n${this.i()} if (__e && __e.__tova_propagate) return __e.value;\n${this.i()} throw __e;\n${this.i()} }\n${this.i()}};`);
2914
+ } else {
2915
+ lines.push(`${this.i()}${node.typeName}.${method.name} = ${asyncPrefix}function(${paramStr}) {\n${body}\n${this.i()}};`);
2916
+ }
2906
2917
  }
2907
2918
  }
2908
2919
  return lines.join('\n');
@@ -189,8 +189,9 @@ export class ClientCodegen extends BaseCodegen {
189
189
  const lines = [];
190
190
 
191
191
  // Runtime imports
192
- lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_transition, tova_inject_css, batch, onMount, onUnmount, onCleanup, onBeforeUpdate, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, ErrorInfo, createRoot, watch, untrack, Dynamic, Portal, lazy, Suspense, __tova_action } from './runtime/reactivity.js';`);
193
- lines.push(`import { rpc } from './runtime/rpc.js';`);
192
+ lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_transition, tova_inject_css, batch, onMount, onUnmount, onCleanup, onBeforeUpdate, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, ErrorInfo, createRoot, watch, untrack, Dynamic, Portal, lazy, Suspense, Head, createResource, __tova_action, TransitionGroup, createForm, configureCSP } from './runtime/reactivity.js';`);
193
+ lines.push(`import { rpc, configureRPC, addRPCInterceptor, setCSRFToken } from './runtime/rpc.js';`);
194
+ lines.push(`import { navigate, getCurrentRoute, getParams, getPath, getQuery, defineRoutes, onRouteChange, beforeNavigate, afterNavigate, Router, Outlet, Link, Redirect } from './runtime/router.js';`);
194
195
 
195
196
  // Hoist import lines from shared code to the top of the module
196
197
  let sharedRest = sharedCode;
@@ -394,14 +395,16 @@ export class ClientCodegen extends BaseCodegen {
394
395
  return p.join('');
395
396
  }
396
397
 
397
- // Generate a short hash from component name + CSS content (for CSS scoping)
398
+ // Generate a scope hash from component name + CSS content (for CSS scoping)
399
+ // Uses FNV-1a for better distribution and 8-char output to reduce collision risk.
398
400
  _genScopeId(name, css) {
399
401
  const str = name + ':' + (css || '');
400
- let h = 0;
402
+ let h = 0x811c9dc5; // FNV offset basis
401
403
  for (let i = 0; i < str.length; i++) {
402
- h = ((h << 5) - h + str.charCodeAt(i)) | 0;
404
+ h ^= str.charCodeAt(i);
405
+ h = Math.imul(h, 0x01000193); // FNV prime
403
406
  }
404
- return Math.abs(h).toString(36).slice(0, 6);
407
+ return (h >>> 0).toString(36).padStart(8, '0').slice(0, 8);
405
408
  }
406
409
 
407
410
  // Scope CSS selectors by appending [data-tova-HASH] to each selector
@@ -279,12 +279,13 @@ export class CodeGenerator {
279
279
  break;
280
280
  }
281
281
  case 'ValidateBlock': {
282
- // Validate: validator function
282
+ // Validate: validator function — include rule expression in error for debuggability
283
283
  const rules = stmt.rules.map(r => gen.genExpression(r));
284
284
  lines.push(`function __validate_${stmt.typeName}(it) {`);
285
285
  lines.push(` const errors = [];`);
286
286
  for (let i = 0; i < rules.length; i++) {
287
- lines.push(` if (!(${rules[i]})) errors.push("Validation rule ${i + 1} failed for ${stmt.typeName}");`);
287
+ const escapedRule = rules[i].replace(/\\/g, '\\\\').replace(/"/g, '\\"');
288
+ lines.push(` if (!(${rules[i]})) errors.push("Validation failed for ${stmt.typeName}: expected \`${escapedRule}\`");`);
288
289
  }
289
290
  lines.push(` return errors.length === 0 ? { valid: true, errors: [] } : { valid: false, errors };`);
290
291
  lines.push(`}`);