@hypen-space/core 0.4.31 → 0.4.35

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 (71) hide show
  1. package/dist/app.d.ts +73 -2
  2. package/dist/app.js +112 -9
  3. package/dist/app.js.map +6 -5
  4. package/dist/components/builtin.js +112 -9
  5. package/dist/components/builtin.js.map +6 -5
  6. package/dist/context.d.ts +8 -0
  7. package/dist/context.js +6 -2
  8. package/dist/context.js.map +3 -3
  9. package/dist/datasource.d.ts +168 -0
  10. package/dist/discovery.js +114 -9
  11. package/dist/discovery.js.map +7 -6
  12. package/dist/disposable.js +6 -2
  13. package/dist/disposable.js.map +2 -2
  14. package/dist/engine.browser.d.ts +15 -0
  15. package/dist/engine.browser.js +19 -2
  16. package/dist/engine.browser.js.map +3 -3
  17. package/dist/engine.d.ts +22 -0
  18. package/dist/engine.js +37 -3
  19. package/dist/engine.js.map +3 -3
  20. package/dist/events.js +6 -2
  21. package/dist/events.js.map +2 -2
  22. package/dist/index.browser.js +295 -179
  23. package/dist/index.browser.js.map +9 -8
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.js +298 -179
  26. package/dist/index.js.map +10 -9
  27. package/dist/loader.js +6 -2
  28. package/dist/loader.js.map +2 -2
  29. package/dist/logger.js +6 -2
  30. package/dist/logger.js.map +2 -2
  31. package/dist/plugin.js +6 -2
  32. package/dist/plugin.js.map +2 -2
  33. package/dist/remote/client.js +6 -2
  34. package/dist/remote/client.js.map +2 -2
  35. package/dist/remote/index.js +152 -11
  36. package/dist/remote/index.js.map +9 -8
  37. package/dist/remote/server.js +152 -11
  38. package/dist/remote/server.js.map +9 -8
  39. package/dist/renderer.js +6 -2
  40. package/dist/renderer.js.map +2 -2
  41. package/dist/resolver.js +6 -2
  42. package/dist/resolver.js.map +2 -2
  43. package/dist/router.d.ts +9 -0
  44. package/dist/router.js +186 -18
  45. package/dist/router.js.map +6 -5
  46. package/dist/state.js +13 -6
  47. package/dist/state.js.map +3 -3
  48. package/package.json +1 -1
  49. package/src/app.ts +121 -4
  50. package/src/context.ts +10 -2
  51. package/src/datasource.ts +252 -0
  52. package/src/discovery.ts +2 -0
  53. package/src/engine.browser.ts +30 -0
  54. package/src/engine.ts +56 -1
  55. package/src/index.ts +16 -0
  56. package/src/managed-router.ts +3 -0
  57. package/src/remote/server.ts +11 -2
  58. package/src/router.ts +36 -10
  59. package/src/state.ts +10 -3
  60. package/wasm-browser/README.md +10 -10
  61. package/wasm-browser/hypen_engine.d.ts +368 -131
  62. package/wasm-browser/hypen_engine.js +896 -645
  63. package/wasm-browser/hypen_engine_bg.wasm +0 -0
  64. package/wasm-browser/hypen_engine_bg.wasm.d.ts +20 -13
  65. package/wasm-browser/package.json +1 -1
  66. package/wasm-node/README.md +10 -10
  67. package/wasm-node/hypen_engine.d.ts +318 -89
  68. package/wasm-node/hypen_engine.js +832 -613
  69. package/wasm-node/hypen_engine_bg.wasm +0 -0
  70. package/wasm-node/hypen_engine_bg.wasm.d.ts +20 -13
  71. package/wasm-node/package.json +1 -1
package/src/router.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { createObservableState, getStateSnapshot } from "./state.js";
7
+ import { disposableListener, type Disposable } from "./disposable.js";
7
8
  import { frameworkLoggers } from "./logger.js";
8
9
 
9
10
  const log = frameworkLoggers.router;
@@ -31,6 +32,7 @@ export class HypenRouter {
31
32
  private subscribers = new Set<RouteChangeCallback>();
32
33
  private isInitialized = false;
33
34
  private isUpdating = false;
35
+ private browserListeners: Disposable[] = [];
34
36
 
35
37
  constructor() {
36
38
  // Create observable state for reactivity
@@ -65,18 +67,22 @@ export class HypenRouter {
65
67
  this.state.query = this.parseQuery();
66
68
 
67
69
  // Listen for browser back/forward
68
- window.addEventListener("popstate", () => {
69
- const newPath = this.getPathFromBrowser();
70
- this.updatePath(newPath, false); // Don't push to history again
71
- });
70
+ this.browserListeners.push(
71
+ disposableListener(window, "popstate", () => {
72
+ const newPath = this.getPathFromBrowser();
73
+ this.updatePath(newPath, false); // Don't push to history again
74
+ })
75
+ );
72
76
 
73
77
  // Listen for hash changes
74
- window.addEventListener("hashchange", () => {
75
- // Don't respond to hashchange events we triggered ourselves
76
- if (this.isUpdating) return;
77
- const newPath = this.getPathFromBrowser();
78
- this.updatePath(newPath, false);
79
- });
78
+ this.browserListeners.push(
79
+ disposableListener(window, "hashchange", () => {
80
+ // Don't respond to hashchange events we triggered ourselves
81
+ if (this.isUpdating) return;
82
+ const newPath = this.getPathFromBrowser();
83
+ this.updatePath(newPath, false);
84
+ })
85
+ );
80
86
 
81
87
  this.isInitialized = true;
82
88
  log.debug("Router initialized at:", initialPath);
@@ -333,4 +339,24 @@ export class HypenRouter {
333
339
  const queryString = new URLSearchParams(query).toString();
334
340
  return `${path}?${queryString}`;
335
341
  }
342
+
343
+ /**
344
+ * Clean up browser event listeners and subscriptions
345
+ */
346
+ dispose(): void {
347
+ for (const listener of this.browserListeners) {
348
+ listener.dispose();
349
+ }
350
+ this.browserListeners = [];
351
+ this.subscribers.clear();
352
+ this.isInitialized = false;
353
+ log.debug("Router disposed");
354
+ }
355
+
356
+ /**
357
+ * Support `using` syntax for automatic cleanup
358
+ */
359
+ [Symbol.dispose](): void {
360
+ this.dispose();
361
+ }
336
362
  }
package/src/state.ts CHANGED
@@ -279,9 +279,15 @@ export function createObservableState<T extends object>(
279
279
  notificationPending = false;
280
280
  if (batchDepth === 0) {
281
281
  notifyChange();
282
+ } else {
283
+ // A batch started after we scheduled; the batch's __endBatch will
284
+ // call notifyChange() when it completes, so nothing to do here.
282
285
  }
283
286
  });
284
287
  }
288
+ } else {
289
+ // Inside a batch — mark that changes occurred so __endBatch knows to notify
290
+ pendingChange = pendingChange || { paths: [], newValues: {} };
285
291
  }
286
292
  }
287
293
 
@@ -377,11 +383,12 @@ export function createObservableState<T extends object>(
377
383
  },
378
384
 
379
385
  deleteProperty(obj, prop) {
380
- if (prop in obj) {
381
- delete obj[prop];
386
+ const existed = Object.prototype.hasOwnProperty.call(obj, prop);
387
+ const result = delete obj[prop];
388
+ if (existed) {
382
389
  scheduleBatch();
383
390
  }
384
- return true;
391
+ return result;
385
392
  },
386
393
  });
387
394
 
@@ -11,7 +11,7 @@ The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM
11
11
  ### Rust
12
12
 
13
13
  ```rust
14
- use hypen_engine::{Engine, ast_to_ir};
14
+ use hypen_engine::{Engine, ast_to_ir_node};
15
15
  use hypen_parser::parse_component;
16
16
  use serde_json::json;
17
17
 
@@ -21,7 +21,7 @@ engine.set_render_callback(|patches| {
21
21
  });
22
22
 
23
23
  let ast = parse_component(r#"Column { Text("Hello") }"#)?;
24
- engine.render(&ast_to_ir(&ast));
24
+ engine.render_ir_node(&ast_to_ir_node(&ast));
25
25
  engine.update_state(json!({ "count": 42 }));
26
26
  ```
27
27
 
@@ -120,7 +120,7 @@ use serde_json::json;
120
120
  let mut engine = Engine::new();
121
121
 
122
122
  // Register a custom component
123
- // Note: In practice, you'd typically parse Hypen DSL with ast_to_ir
123
+ // Note: In practice, you'd typically parse Hypen DSL with ast_to_ir_node
124
124
  engine.register_component(Component::new("Greeting", |props: IndexMap<String, serde_json::Value>| {
125
125
  Element::new("Text")
126
126
  .with_prop("text", Value::Binding(
@@ -400,7 +400,7 @@ The engine integrates with the Hypen parser from `../parser`:
400
400
 
401
401
  ```rust
402
402
  use hypen_parser::parse_component;
403
- use hypen_engine::ast_to_ir;
403
+ use hypen_engine::ast_to_ir_node;
404
404
 
405
405
  let source = r#"
406
406
  Column {
@@ -410,14 +410,14 @@ let source = r#"
410
410
  "#;
411
411
 
412
412
  let ast = parse_component(source)?;
413
- let element = ast_to_ir(&ast); // Convert AST → IR
414
- engine.render(&element);
413
+ let ir_node = ast_to_ir_node(&ast); // Convert AST → IR
414
+ engine.render_ir_node(&ir_node);
415
415
  ```
416
416
 
417
417
  ### Full Example with Parser
418
418
 
419
419
  ```rust
420
- use hypen_engine::{Engine, ast_to_ir};
420
+ use hypen_engine::{Engine, ast_to_ir_node};
421
421
  use hypen_parser::parse_component;
422
422
  use serde_json::json;
423
423
 
@@ -438,10 +438,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
438
438
  "#;
439
439
 
440
440
  let ast = parse_component(source)?;
441
- let element = ast_to_ir(&ast);
441
+ let ir_node = ast_to_ir_node(&ast);
442
442
 
443
443
  // Render
444
- engine.render(&element);
444
+ engine.render_ir_node(&ir_node);
445
445
 
446
446
  // Update state
447
447
  engine.update_state(json!({"count": 42}));
@@ -557,7 +557,7 @@ impl Engine {
557
557
 
558
558
  ```rust
559
559
  pub use engine::Engine;
560
- pub use ir::{ast_to_ir, Element, Value};
560
+ pub use ir::{ast_to_ir_node, Element, IRNode, Value};
561
561
  pub use lifecycle::{Module, ModuleInstance};
562
562
  pub use reconcile::Patch;
563
563
  pub use state::StateChange;