@hypen-space/core 0.4.32 → 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/dist/app.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import type { Action } from "./types.js";
6
6
  import type { Session } from "./remote/types.js";
7
7
  import { HypenError } from "./result.js";
8
+ import { type DataSourcePlugin } from "./datasource.js";
8
9
  export interface IEngine {
9
10
  setModule(name: string, actions: string[], stateKeys: string[], initialState: unknown): void;
10
11
  onAction(actionName: string, handler: (action: Action) => void | Promise<void>): void;
@@ -34,6 +35,19 @@ export interface ActionHandlerContext<T, P = unknown> {
34
35
  action: ActionContext<P>;
35
36
  state: T;
36
37
  context: GlobalContext;
38
+ /** Access to registered data source plugins for mutations */
39
+ dataSources: DataSourceAccessor;
40
+ }
41
+ /**
42
+ * Provides typed access to registered data source plugins.
43
+ * Unknown method calls are forwarded to `plugin.call()`:
44
+ * `dataSources.spacetime.sendMessage(text)` → `plugin.call("sendMessage", text)`
45
+ *
46
+ * You can also call `.call()` directly if you prefer:
47
+ * `dataSources.spacetime.call("sendMessage", text)`
48
+ */
49
+ export interface DataSourceAccessor {
50
+ [providerName: string]: DataSourcePlugin & Record<string, (...args: unknown[]) => Promise<unknown>>;
37
51
  }
38
52
  /**
39
53
  * Action handler - receives all context in a single object
@@ -115,6 +129,14 @@ export interface HypenModuleDefinition<T = unknown> {
115
129
  * Set via the `.ui(hypen`...`)` method.
116
130
  */
117
131
  template?: string;
132
+ /**
133
+ * Data source plugins registered via `.useDataSource()`.
134
+ * Each entry contains the plugin and its configuration.
135
+ */
136
+ dataSources?: Array<{
137
+ plugin: DataSourcePlugin;
138
+ config: unknown;
139
+ }>;
118
140
  handlers: {
119
141
  onCreated?: LifecycleHandler<T>;
120
142
  onAction: Map<string, ActionHandler<T, any>>;
@@ -148,6 +170,7 @@ export declare class HypenAppBuilder<T> {
148
170
  private errorHandler?;
149
171
  private template?;
150
172
  private _registry?;
173
+ private dataSourceEntries;
151
174
  constructor(initialState: T, options?: {
152
175
  persist?: boolean;
153
176
  version?: number;
@@ -225,6 +248,35 @@ export declare class HypenAppBuilder<T> {
225
248
  * @returns Builder for chaining
226
249
  */
227
250
  onError(fn: ErrorHandler<T>): this;
251
+ /**
252
+ * Register a data source plugin for live database subscriptions.
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * import { SpacetimeDBPlugin } from "@hypen-space/plugin-spacetimedb";
257
+ *
258
+ * export default app
259
+ * .defineState({ messageText: "" })
260
+ * .useDataSource(new SpacetimeDBPlugin(), {
261
+ * uri: "ws://localhost:3000",
262
+ * moduleName: "chat",
263
+ * tables: ["user", "message"],
264
+ * })
265
+ * .onAction("sendMessage", async ({ state, dataSources }) => {
266
+ * await dataSources.spacetime.sendMessage(state.messageText);
267
+ * state.messageText = "";
268
+ * })
269
+ * .build();
270
+ * ```
271
+ *
272
+ * In Hypen DSL, bind to data source tables with `$provider.table`:
273
+ * ```hypen
274
+ * ForEach(items: $spacetime.message, key: "id") {
275
+ * Text("${item.text}")
276
+ * }
277
+ * ```
278
+ */
279
+ useDataSource<C>(plugin: DataSourcePlugin<C>, config: C): this;
228
280
  /**
229
281
  * Define the component's UI template inline (single-file component).
230
282
  *
@@ -252,6 +304,23 @@ export declare class HypenAppBuilder<T> {
252
304
  * @returns The built module definition (calls build() internally)
253
305
  */
254
306
  ui(template: string): HypenModuleDefinition<T>;
307
+ /**
308
+ * Load a UI template from a .hypen file on disk.
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * import { app } from "@hypen-space/core";
313
+ *
314
+ * export default app
315
+ * .defineState({ count: 0 })
316
+ * .onAction("increment", ({ state }) => { state.count += 1; })
317
+ * .uiFile("./counter.hypen");
318
+ * ```
319
+ *
320
+ * @param path - Path to a .hypen template file
321
+ * @returns The built module definition (calls build() internally)
322
+ */
323
+ uiFile(path: string): HypenModuleDefinition<T>;
255
324
  /**
256
325
  * Build the module definition
257
326
  */
@@ -336,6 +405,8 @@ export declare class HypenModuleInstance<T extends object = any> {
336
405
  private router;
337
406
  private globalContext?;
338
407
  private stateChangeCallbacks;
408
+ private dataSourceManager?;
409
+ private dataSourceAccessor;
339
410
  constructor(engine: IEngine, definition: HypenModuleDefinition<T>, router?: HypenRouter | null, globalContext?: HypenGlobalContext);
340
411
  /**
341
412
  * Create the global context API for this module
@@ -353,7 +424,7 @@ export declare class HypenModuleInstance<T extends object = any> {
353
424
  */
354
425
  private handleError;
355
426
  /**
356
- * Call the onCreated handler
427
+ * Call the onCreated handler and connect data source plugins
357
428
  */
358
429
  private callCreatedHandler;
359
430
  /**
@@ -361,7 +432,7 @@ export declare class HypenModuleInstance<T extends object = any> {
361
432
  */
362
433
  onStateChange(callback: () => void): void;
363
434
  /**
364
- * Destroy the module instance
435
+ * Destroy the module instance and disconnect all data source plugins
365
436
  */
366
437
  destroy(): Promise<void>;
367
438
  /**
package/dist/app.js CHANGED
@@ -1,11 +1,15 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
2
6
  var __export = (target, all) => {
3
7
  for (var name in all)
4
8
  __defProp(target, name, {
5
9
  get: all[name],
6
10
  enumerable: true,
7
11
  configurable: true,
8
- set: (newValue) => all[name] = () => newValue
12
+ set: __exportSetter.bind(all, name)
9
13
  });
10
14
  };
11
15
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -156,9 +160,11 @@ function createObservableState(initialState, options) {
156
160
  notificationPending = false;
157
161
  if (batchDepth === 0) {
158
162
  notifyChange();
159
- }
163
+ } else {}
160
164
  });
161
165
  }
166
+ } else {
167
+ pendingChange = pendingChange || { paths: [], newValues: {} };
162
168
  }
163
169
  }
164
170
  const proxyCache = new WeakMap;
@@ -217,11 +223,12 @@ function createObservableState(initialState, options) {
217
223
  return true;
218
224
  },
219
225
  deleteProperty(obj, prop) {
220
- if (prop in obj) {
221
- delete obj[prop];
226
+ const existed = Object.prototype.hasOwnProperty.call(obj, prop);
227
+ const result = delete obj[prop];
228
+ if (existed) {
222
229
  scheduleBatch();
223
230
  }
224
- return true;
231
+ return result;
225
232
  }
226
233
  });
227
234
  proxyCache.set(target, proxy);
@@ -555,6 +562,59 @@ var init_result = __esm(() => {
555
562
  };
556
563
  });
557
564
 
565
+ // src/datasource.ts
566
+ class DataSourceManager {
567
+ plugins = new Map;
568
+ configs = new Map;
569
+ state = new Map;
570
+ engine;
571
+ constructor(engine) {
572
+ this.engine = engine;
573
+ }
574
+ async use(plugin, config2) {
575
+ const { name } = plugin;
576
+ this.state.set(name, {});
577
+ this.plugins.set(name, plugin);
578
+ this.configs.set(name, config2);
579
+ await plugin.connect(config2, (change) => {
580
+ const current = this.state.get(name) ?? {};
581
+ for (const path of change.paths) {
582
+ if (path in change.values) {
583
+ current[path] = change.values[path];
584
+ }
585
+ }
586
+ this.state.set(name, current);
587
+ this.engine.setContext(name, current);
588
+ });
589
+ }
590
+ get(name) {
591
+ return this.plugins.get(name);
592
+ }
593
+ has(name) {
594
+ return this.plugins.has(name);
595
+ }
596
+ getNames() {
597
+ return Array.from(this.plugins.keys());
598
+ }
599
+ getStatus(name) {
600
+ return this.plugins.get(name)?.status;
601
+ }
602
+ async remove(name) {
603
+ const plugin = this.plugins.get(name);
604
+ if (plugin) {
605
+ await plugin.disconnect();
606
+ this.plugins.delete(name);
607
+ this.configs.delete(name);
608
+ this.state.delete(name);
609
+ this.engine.removeContext(name);
610
+ }
611
+ }
612
+ async disconnectAll() {
613
+ const names = Array.from(this.plugins.keys());
614
+ await Promise.all(names.map((name) => this.remove(name)));
615
+ }
616
+ }
617
+
558
618
  // src/app.ts
559
619
  var exports_app = {};
560
620
  __export(exports_app, {
@@ -576,6 +636,7 @@ class HypenAppBuilder {
576
636
  errorHandler;
577
637
  template;
578
638
  _registry;
639
+ dataSourceEntries = [];
579
640
  constructor(initialState, options, registry) {
580
641
  this.initialState = initialState;
581
642
  this.options = options || {};
@@ -609,10 +670,19 @@ class HypenAppBuilder {
609
670
  this.errorHandler = fn;
610
671
  return this;
611
672
  }
673
+ useDataSource(plugin, config2) {
674
+ this.dataSourceEntries.push({ plugin, config: config2 });
675
+ return this;
676
+ }
612
677
  ui(template) {
613
678
  this.template = template;
614
679
  return this.build();
615
680
  }
681
+ uiFile(path) {
682
+ const fs = (() => ({}));
683
+ this.template = fs.readFileSync(path, "utf-8").trim();
684
+ return this.build();
685
+ }
616
686
  build() {
617
687
  const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
618
688
  const definition = {
@@ -623,6 +693,7 @@ class HypenAppBuilder {
623
693
  version: this.options.version,
624
694
  initialState: this.initialState,
625
695
  template: this.template,
696
+ dataSources: this.dataSourceEntries.length > 0 ? this.dataSourceEntries : undefined,
626
697
  handlers: {
627
698
  onCreated: this.createdHandler,
628
699
  onAction: this.actionHandlers,
@@ -684,6 +755,8 @@ class HypenModuleInstance {
684
755
  router;
685
756
  globalContext;
686
757
  stateChangeCallbacks = [];
758
+ dataSourceManager;
759
+ dataSourceAccessor = {};
687
760
  constructor(engine, definition, router, globalContext) {
688
761
  this.engine = engine;
689
762
  this.definition = definition;
@@ -713,7 +786,8 @@ class HypenModuleInstance {
713
786
  const result = await this.executeAction(actionName, handler, {
714
787
  action: actionCtx,
715
788
  state: this.state,
716
- context
789
+ context,
790
+ dataSources: this.dataSourceAccessor
717
791
  });
718
792
  if (!result.ok) {
719
793
  const shouldRethrow = await this.handleError(result.error, { actionName });
@@ -756,8 +830,9 @@ class HypenModuleInstance {
756
830
  on: (event, handler) => ctx.on(event, handler),
757
831
  router: this.router
758
832
  };
759
- if (ctx.__hypenEngine) {
760
- api.__hypenEngine = ctx.__hypenEngine;
833
+ const ctxRecord = ctx;
834
+ if (ctxRecord.__hypenEngine) {
835
+ api.__hypenEngine = ctxRecord.__hypenEngine;
761
836
  }
762
837
  return api;
763
838
  }
@@ -804,6 +879,25 @@ class HypenModuleInstance {
804
879
  return false;
805
880
  }
806
881
  async callCreatedHandler() {
882
+ if (this.definition.dataSources?.length) {
883
+ const dsEngine = this.engine;
884
+ this.dataSourceManager = new DataSourceManager(dsEngine);
885
+ for (const { plugin, config: config2 } of this.definition.dataSources) {
886
+ try {
887
+ await this.dataSourceManager.use(plugin, config2);
888
+ this.dataSourceAccessor[plugin.name] = new Proxy(plugin, {
889
+ get(target, prop) {
890
+ if (typeof prop === "string" && !(prop in target)) {
891
+ return (...args) => target.call(prop, ...args);
892
+ }
893
+ return target[prop];
894
+ }
895
+ });
896
+ } catch (e) {
897
+ log2.error(`Failed to connect data source "${plugin.name}":`, e);
898
+ }
899
+ }
900
+ }
807
901
  if (this.definition.handlers.onCreated) {
808
902
  const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
809
903
  try {
@@ -823,6 +917,15 @@ class HypenModuleInstance {
823
917
  async destroy() {
824
918
  if (this.isDestroyed)
825
919
  return;
920
+ if (this.dataSourceManager) {
921
+ try {
922
+ await this.dataSourceManager.disconnectAll();
923
+ } catch (e) {
924
+ log2.error("Error disconnecting data sources:", e);
925
+ }
926
+ this.dataSourceManager = undefined;
927
+ this.dataSourceAccessor = {};
928
+ }
826
929
  if (this.definition.handlers.onDestroyed) {
827
930
  try {
828
931
  await this.definition.handlers.onDestroyed(this.state);
@@ -863,4 +966,4 @@ export {
863
966
  HypenApp
864
967
  };
865
968
 
866
- //# debugId=CCD1FE55458A9C0764756E2164756E21
969
+ //# debugId=DF0AC5B74AEC489464756E2164756E21