apprun 3.35.0 → 3.36.1

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 (75) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +134 -16
  3. package/WHATSNEW.md +28 -12
  4. package/apprun-book.jpg +0 -0
  5. package/apprun.d.ts +9 -6
  6. package/cli/app.js +29 -0
  7. package/cli/index.html +13 -0
  8. package/{apprun-cli.js → cli/index.js} +8 -14
  9. package/dist/apprun-dev-tools.js +1 -2
  10. package/dist/apprun-dev-tools.js.map +1 -1
  11. package/dist/apprun-html.esm.js +7 -7
  12. package/dist/apprun-html.esm.js.map +1 -1
  13. package/dist/apprun-html.js +1 -1
  14. package/dist/apprun-html.js.map +1 -1
  15. package/dist/apprun-play-html.esm.js +7 -7
  16. package/dist/apprun-play-html.esm.js.map +1 -1
  17. package/dist/apprun-play.js +1 -1
  18. package/dist/apprun-play.js.map +1 -1
  19. package/dist/apprun.esm.js +1 -1
  20. package/dist/apprun.esm.js.map +1 -1
  21. package/dist/apprun.js +1 -1
  22. package/dist/apprun.js.map +1 -1
  23. package/esm/app.js +60 -12
  24. package/esm/app.js.map +1 -1
  25. package/esm/apprun-dev-tools.js +1 -7
  26. package/esm/apprun-dev-tools.js.map +1 -1
  27. package/esm/apprun-html.js +8 -4
  28. package/esm/apprun-html.js.map +1 -1
  29. package/esm/apprun.js +81 -17
  30. package/esm/apprun.js.map +1 -1
  31. package/esm/component.js +60 -19
  32. package/esm/component.js.map +1 -1
  33. package/esm/decorator.js +22 -5
  34. package/esm/decorator.js.map +1 -1
  35. package/esm/directive.js +64 -13
  36. package/esm/directive.js.map +1 -1
  37. package/esm/router.js +263 -22
  38. package/esm/router.js.map +1 -1
  39. package/esm/type-utils.js +91 -0
  40. package/esm/type-utils.js.map +1 -0
  41. package/esm/types.js +32 -11
  42. package/esm/types.js.map +1 -1
  43. package/esm/vdom-my-new.js +329 -0
  44. package/esm/vdom-my-new.js.map +1 -0
  45. package/esm/vdom-my-prop-attr.js +227 -0
  46. package/esm/vdom-my-prop-attr.js.map +1 -0
  47. package/esm/vdom-my.js +77 -88
  48. package/esm/vdom-my.js.map +1 -1
  49. package/esm/version.js +15 -0
  50. package/esm/version.js.map +1 -0
  51. package/esm/web-component.js +30 -10
  52. package/esm/web-component.js.map +1 -1
  53. package/index.html +1 -1
  54. package/jest.config.js +3 -8
  55. package/jest.setup.js +29 -3
  56. package/jsx-runtime.js +1 -1
  57. package/jsx-runtime.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/app.ts +58 -12
  60. package/src/apprun-dev-tools.tsx +1 -7
  61. package/src/apprun-html.ts +8 -4
  62. package/src/apprun.ts +97 -20
  63. package/src/component.ts +62 -20
  64. package/src/decorator.ts +23 -6
  65. package/src/directive.ts +64 -13
  66. package/src/router.ts +282 -20
  67. package/src/type-utils.ts +130 -0
  68. package/src/types.ts +33 -12
  69. package/src/vdom-my-new.ts +311 -0
  70. package/src/vdom-my-prop-attr.ts +241 -0
  71. package/src/vdom-my.ts +82 -71
  72. package/src/version.ts +16 -0
  73. package/src/web-component.ts +31 -11
  74. package/cli/export.js +0 -92
  75. package/cli/import.js +0 -68
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "apprun",
3
- "version": "3.35.0",
3
+ "version": "3.36.1",
4
4
  "description": "JavaScript library that has Elm inspired architecture, event pub-sub and components",
5
5
  "main": "dist/apprun.js",
6
6
  "module": "esm/apprun.js",
7
7
  "types": "apprun.d.ts",
8
8
  "bin": {
9
- "apprun": "apprun-cli.js"
9
+ "apprun": "cli/index.js"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p src && rollup -c && webpack --mode production",
13
- "test": "jest --watch",
14
- "jest": "jest",
13
+ "test": "jest",
14
+ "jest": "jest --watch",
15
15
  "jest:debug": "node --inspect node_modules/.bin/jest --runInBand",
16
16
  "start": "webpack serve --mode development",
17
17
  "build:docs": "marked -i README.md -o demo/home.html & marked -i WHATSNEW.md -o demo/new.html",
@@ -53,10 +53,10 @@
53
53
  "typescript": "^5.7.2",
54
54
  "webpack": "^5.97.1",
55
55
  "webpack-cli": "^6.0.1",
56
- "webpack-dev-server": "^5.2.0"
56
+ "webpack-dev-server": "^5.2.0",
57
+ "property-information": "^7.1.0"
57
58
  },
58
59
  "dependencies": {
59
- "commander": "^14.0.0",
60
- "js-yaml": "^4.1.0"
60
+ "commander": "^14.0.0"
61
61
  }
62
62
  }
package/src/app.ts CHANGED
@@ -3,16 +3,30 @@
3
3
  *
4
4
  * This file provides:
5
5
  * 1. App class - The core event system implementation with pub/sub capabilities
6
- * - on(): Subscribe to events
7
- * - off(): Unsubscribe from events
8
- * - run(): Publish events synchronously
9
- * - runAsync(): Publish events asynchronously
10
- * - query(): Alias for runAsync
6
+ * - on(): Subscribe to events with options (once, delay, global)
7
+ * - off(): Unsubscribe from events with proper cleanup
8
+ * - run(): Publish events synchronously with error handling
9
+ * - runAsync(): Publish events asynchronously with Promise support, returns handler values
10
+ * - query(): Deprecated alias for runAsync() - use runAsync() instead
11
11
  *
12
12
  * 2. Default app singleton - Global event bus instance
13
13
  * - Created once and reused across the application
14
- * - Stored in global scope (window/global)
15
- * - Version tracked to prevent duplicate instances
14
+ * - Stored in global scope (window/global) with version tracking
15
+ * - Prevents duplicate instances across different versions
16
+ *
17
+ * Features:
18
+ * - Event wildcards support (events ending with '*')
19
+ * - Delayed event execution with timeout management
20
+ * - Once-only event subscriptions
21
+ * - Async event handling with Promise.all
22
+ * - Global event bus shared across components
23
+ * - Memory leak prevention with proper cleanup
24
+ *
25
+ * Type Safety Improvements (v3.35.1):
26
+ * - Added validation for event handler functions
27
+ * - Enhanced error handling in event execution
28
+ * - Improved null checks in delayed event handling
29
+ * - Better error reporting for invalid handlers
16
30
  *
17
31
  * Usage:
18
32
  * ```ts
@@ -21,12 +35,18 @@
21
35
  * // Handle event
22
36
  * });
23
37
  *
24
- * // Publish events
38
+ * // Publish events (fire-and-forget)
25
39
  * app.run('event-name', ...args);
40
+ *
41
+ * // Get return values from event handlers
42
+ * app.runAsync('event-name', data).then(results => {
43
+ * // Handle results array
44
+ * });
26
45
  * ```
27
46
  */
28
47
 
29
48
  import { EventOptions } from './types'
49
+ import { APPRUN_VERSION_GLOBAL } from './version'
30
50
 
31
51
  export class App {
32
52
 
@@ -42,6 +62,7 @@ export class App {
42
62
  public use_render: any;
43
63
  public use_react: any;
44
64
  public route: any;
65
+ public basePath: string; // Base path for sub-directory deployments
45
66
 
46
67
  public version: string;
47
68
 
@@ -69,10 +90,18 @@ export class App {
69
90
  console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
70
91
  subscribers.forEach((sub) => {
71
92
  const { fn, options } = sub;
93
+ if (!fn || typeof fn !== 'function') {
94
+ console.error(`AppRun event handler for '${name}' is not a function:`, fn);
95
+ return false;
96
+ }
72
97
  if (options.delay) {
73
98
  this.delay(name, fn, args, options);
74
99
  } else {
75
- Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
100
+ try {
101
+ Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
102
+ } catch (error) {
103
+ console.error(`Error in event handler for '${name}':`, error);
104
+ }
76
105
  }
77
106
  return !sub.options.once;
78
107
  });
@@ -88,7 +117,11 @@ export class App {
88
117
  if (options._t) clearTimeout(options._t);
89
118
  options._t = setTimeout(() => {
90
119
  clearTimeout(options._t);
91
- Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
120
+ try {
121
+ Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
122
+ } catch (error) {
123
+ console.error(`Error in delayed event handler for '${name}':`, error);
124
+ }
92
125
  }, options.delay);
93
126
  }
94
127
 
@@ -97,12 +130,25 @@ export class App {
97
130
  console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
98
131
  const promises = subscribers.map(sub => {
99
132
  const { fn, options } = sub;
100
- return Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
133
+ if (!fn || typeof fn !== 'function') {
134
+ console.error(`AppRun async event handler for '${name}' is not a function:`, fn);
135
+ return Promise.resolve(null);
136
+ }
137
+ try {
138
+ return Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
139
+ } catch (error) {
140
+ console.error(`Error in async event handler for '${name}':`, error);
141
+ return Promise.reject(error);
142
+ }
101
143
  });
102
144
  return Promise.all(promises);
103
145
  }
104
146
 
147
+ /**
148
+ * @deprecated Use runAsync() instead. app.query() will be removed in a future version.
149
+ */
105
150
  query(name: string, ...args: any[]): Promise<any[]> {
151
+ console.warn('app.query() is deprecated. Use app.runAsync() instead.');
106
152
  return this.runAsync(name, ...args);
107
153
  }
108
154
 
@@ -125,7 +171,7 @@ export class App {
125
171
  }
126
172
  }
127
173
 
128
- const AppRunVersions = 'AppRun-3.3.11';
174
+ const AppRunVersions = APPRUN_VERSION_GLOBAL;
129
175
  let _app: App;
130
176
  const root = (typeof window !== 'undefined' ? window :
131
177
  typeof global !== 'undefined' ? global :
@@ -1,12 +1,6 @@
1
1
  import app from './app';
2
2
  import toHTML from './vdom-to-html';
3
3
  import { _createEventTests, _createStateTests } from './apprun-dev-tools-tests';
4
- import yaml from 'js-yaml';
5
-
6
- function replacer(key, value) {
7
- if (typeof value === 'function') return value.toString(); // value.toString();
8
- return ['', null].includes(value) || (typeof value === 'object' && (value.length === 0 || Object.keys(value).length === 0)) ? undefined : value;
9
- }
10
4
 
11
5
  function createProxy(obj) {
12
6
  const handler = {
@@ -109,7 +103,7 @@ const viewComponents = state => {
109
103
 
110
104
  return <li>
111
105
  <div>{component.constructor.name}</div>
112
- <div><pre>{htmlEncode(yaml.dump(component_def, { replacer }))}</pre></div>
106
+ <div><pre>{htmlEncode(JSON.stringify(component_def, null, 2))}</pre></div>
113
107
  <br />
114
108
  </li>;
115
109
 
@@ -1,4 +1,5 @@
1
1
  import app from './apprun'
2
+ import { safeGlobalAssign, SafeGlobalContext } from './type-utils';
2
3
  export {
3
4
  app, Component, View, Action, Update, on, update, event, EventOptions,
4
5
  customElement, CustomElementOptions,
@@ -14,8 +15,11 @@ app.Fragment = Fragment;
14
15
  export default app;
15
16
 
16
17
  if (typeof window === 'object') {
17
- window['React'] = window['_React'] || app;
18
- window['html'] = html;
19
- window['svg'] = svg;
20
- window['run'] = run;
18
+ const globalWindow = window as SafeGlobalContext;
19
+ safeGlobalAssign(globalWindow, {
20
+ 'React': globalWindow['_React'] || app,
21
+ 'html': html,
22
+ 'svg': svg,
23
+ 'run': run
24
+ });
21
25
  }
package/src/apprun.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  * 3. Initializes global app instance with:
8
8
  * - Virtual DOM rendering
9
9
  * - Component system
10
- * - Router
10
+ * - Router with improved null safety
11
11
  * - Web component support
12
+ * - Type-safe React integration
12
13
  *
13
14
  * Key exports:
14
15
  * - app: Global event system instance
@@ -17,6 +18,22 @@
17
18
  * - Router events and configuration
18
19
  * - Web component registration
19
20
  *
21
+ * Features:
22
+ * - Event-driven architecture with pub/sub pattern
23
+ * - Virtual DOM rendering with multiple renderer support
24
+ * - Component lifecycle management
25
+ * - Client-side routing with hash/path support
26
+ * - Web Components integration
27
+ * - React compatibility layer
28
+ * - TypeScript support with strong typing
29
+ *
30
+ * Type Safety Improvements (v3.35.1):
31
+ * - Added null checks for DOM event targets
32
+ * - Improved global window object assignments with proper typing
33
+ * - Enhanced React integration parameter validation
34
+ * - Better error handling for invalid event handlers
35
+ * - Safer element access with proper type assertions
36
+ *
20
37
  * Usage:
21
38
  * ```ts
22
39
  * import { app, Component } from 'apprun';
@@ -39,6 +56,7 @@ import { VNode, View, Action, Update, EventOptions, ActionOptions, MountOptions,
39
56
  import { on, update, customElement } from './decorator';
40
57
  import webComponent, { CustomElementOptions } from './web-component';
41
58
  import { Route, route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
59
+ import { APPRUN_VERSION } from './version';
42
60
 
43
61
  export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
44
62
  type OnDecorator = {
@@ -70,9 +88,13 @@ export interface IApp {
70
88
  start<T, E = any>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
71
89
  options?: AppStartOptions<T>): Component<T, E>;
72
90
  on(name: string, fn: (...args: any[]) => void, options?: any): void;
91
+ once(name: string, fn: (...args: any[]) => void, options?: any): void;
73
92
  off(name: string, fn: (...args: any[]) => void): void;
74
93
  run(name: string, ...args: any[]): number;
75
94
  find(name: string): any | any[];
95
+ /** @deprecated Use runAsync() instead. query() will be removed in a future version. */
96
+ query(name: string, ...args: any[]): Promise<any[]>;
97
+ runAsync(name: string, ...args: any[]): Promise<any[]>;
76
98
  h(tag: string | Function, props, ...children): VNode | VNode[];
77
99
  createElement(tag: string | Function, props, ...children): VNode | VNode[];
78
100
  render(element: Element | string, node: VNode): void;
@@ -81,12 +103,13 @@ export interface IApp {
81
103
  webComponent(name: string, componentClass, options?: CustomElementOptions): void;
82
104
  safeHTML(html: string): any[];
83
105
  use_render(render, mode?: 0 | 1);
84
- use_react(createRoot);
106
+ use_react(React, ReactDOM);
107
+ version: string;
85
108
  }
86
109
 
87
110
  if (!app.start) {
88
111
 
89
- app.version = '3.35.0';
112
+ app.version = APPRUN_VERSION;
90
113
 
91
114
  app.h = app.createElement = createElement;
92
115
  app.render = render;
@@ -104,8 +127,15 @@ if (!app.start) {
104
127
  return component;
105
128
  };
106
129
 
130
+ app.once = app.once || ((name: string, fn: (...args: any[]) => void, options: any = {}) => {
131
+ app.on(name, fn, { ...options, once: true });
132
+ });
133
+
134
+ // Deprecated: app.query is deprecated in favor of app.runAsync
135
+ app.query = app.query || app.runAsync;
136
+
107
137
  const NOOP = _ => {/* Intentionally empty */ }
108
- // app.on('$', NOOP);
138
+ app.on('/', NOOP);
109
139
  app.on('debug', _ => NOOP);
110
140
  app.on(ROUTER_EVENT, NOOP);
111
141
  app.on(ROUTER_404_EVENT, NOOP);
@@ -114,7 +144,7 @@ if (!app.start) {
114
144
 
115
145
  if (typeof document === 'object') {
116
146
  document.addEventListener("DOMContentLoaded", () => {
117
- const init_load = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
147
+ const no_init_route = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
118
148
  const use_hash = app.find('#') || app.find('#/') || false;
119
149
 
120
150
  // console.log(`AppRun ${app.version} started with ${use_hash ? 'hash' : 'path'} routing. Initial load: ${init_load ? 'disabled' : 'enabled'}.`);
@@ -122,17 +152,36 @@ if (!app.start) {
122
152
  window.addEventListener('popstate', () => route(location.pathname));
123
153
 
124
154
  if (use_hash) {
125
- init_load && route(location.hash);
155
+ !no_init_route && route(location.hash);
126
156
  } else {
127
- init_load && route(location.pathname);
157
+ !no_init_route && (() => {
158
+ const basePath = app.basePath || '';
159
+ let initialPath = location.pathname;
160
+
161
+ // Strip base path if present
162
+ if (basePath && initialPath.startsWith(basePath)) {
163
+ initialPath = initialPath.substring(basePath.length);
164
+ if (!initialPath.startsWith('/')) initialPath = '/' + initialPath;
165
+ }
166
+
167
+ route(initialPath);
168
+ })();
128
169
  document.body.addEventListener('click', e => {
129
170
  const element = e.target as HTMLElement;
171
+ if (!element) return;
172
+
130
173
  const menu = (element.tagName === 'A' ? element : element.closest('a')) as HTMLAnchorElement;
131
174
  if (menu &&
132
- menu.origin === location.origin) {
175
+ menu.origin === location.origin &&
176
+ menu.pathname) {
133
177
  e.preventDefault();
134
- history.pushState(null, '', menu.pathname);
135
- route(menu.pathname);
178
+
179
+ // Handle base path for navigation
180
+ const basePath = app.basePath || '';
181
+ const fullPath = basePath + menu.pathname;
182
+
183
+ history.pushState(null, '', fullPath);
184
+ route(menu.pathname); // Route with relative path (without base path)
136
185
  }
137
186
  });
138
187
  }
@@ -144,12 +193,13 @@ if (!app.start) {
144
193
  };
145
194
 
146
195
  if (typeof window === 'object') {
147
- window['Component'] = Component as ComponentType;
148
- window['_React'] = window['React'];
149
- window['React'] = app;
150
- window['on'] = on as OnDecorator;
151
- window['customElement'] = customElement;
152
- window['safeHTML'] = safeHTML;
196
+ const globalWindow = window as any;
197
+ globalWindow['Component'] = Component as ComponentType;
198
+ globalWindow['_React'] = globalWindow['React'];
199
+ globalWindow['React'] = app;
200
+ globalWindow['on'] = on as OnDecorator;
201
+ globalWindow['customElement'] = customElement;
202
+ globalWindow['safeHTML'] = safeHTML;
153
203
  }
154
204
 
155
205
  app.use_render = (render, mode = 0) => {
@@ -161,15 +211,42 @@ if (!app.start) {
161
211
  };
162
212
 
163
213
  app.use_react = (React, ReactDOM) => {
214
+ if (!React || !ReactDOM) {
215
+ console.error('AppRun use_react: React and ReactDOM parameters are required');
216
+ return;
217
+ }
218
+
219
+ if (typeof React.createElement !== 'function') {
220
+ console.error('AppRun use_react: Invalid React object - createElement method not found');
221
+ return;
222
+ }
223
+
224
+ if (!React.Fragment) {
225
+ console.error('AppRun use_react: Invalid React object - Fragment not found');
226
+ return;
227
+ }
228
+
164
229
  app.h = app.createElement = React.createElement;
165
230
  app.Fragment = React.Fragment;
166
- app.render = (el, vdom) => ReactDOM.render(vdom, el);
231
+
232
+ // React 18+ uses createRoot API
167
233
  if (React.version && React.version.startsWith('18')) {
234
+ if (!ReactDOM.createRoot || typeof ReactDOM.createRoot !== 'function') {
235
+ console.error('AppRun use_react: ReactDOM.createRoot not found in React 18+');
236
+ return;
237
+ }
168
238
  app.render = (el, vdom) => {
169
- if (!el || !vdom) return;
170
- if (!el._root) el._root = ReactDOM.createRoot(el);
171
- el._root.render(vdom);
239
+ if (!el || vdom === undefined) return;
240
+ if (!(el as any)._root) (el as any)._root = ReactDOM.createRoot(el);
241
+ (el as any)._root.render(vdom);
242
+ }
243
+ } else {
244
+ // Legacy React versions
245
+ if (!ReactDOM.render || typeof ReactDOM.render !== 'function') {
246
+ console.error('AppRun use_react: ReactDOM.render not found in legacy React');
247
+ return;
172
248
  }
249
+ app.render = (el, vdom) => ReactDOM.render(vdom, el);
173
250
  }
174
251
  }
175
252
  }
package/src/component.ts CHANGED
@@ -3,19 +3,38 @@
3
3
  *
4
4
  * This file provides the Component class which is the foundation for:
5
5
  * 1. State Management
6
- * - Maintains component state
7
- * - Handles state updates
8
- * - Supports state history
6
+ * - Maintains component state with history support
7
+ * - Handles state updates with async/iterator support
8
+ * - Supports state history navigation (prev/next)
9
+ * - Promise and async iterator state handling
9
10
  *
10
11
  * 2. View Rendering
11
- * - Renders virtual DOM to real DOM
12
+ * - Renders virtual DOM to real DOM with directives
12
13
  * - Handles component lifecycle (mounted, rendered, unload)
13
14
  * - Supports shadow DOM and web components
15
+ * - DOM change tracking with MutationObserver
16
+ * - View transition API support
14
17
  *
15
18
  * 3. Event Handling
16
- * - Local and global event subscription
19
+ * - Local and global event subscription management
17
20
  * - Event handler registration via decorators
18
- * - Action to state updates
21
+ * - Action to state updates with error handling
22
+ * - Support for event options (delay, once, global)
23
+ *
24
+ * Features:
25
+ * - Component caching for debugging
26
+ * - Element tracking for cleanup
27
+ * - History navigation support
28
+ * - Global vs local event routing
29
+ * - Async state handling
30
+ * - Memory leak prevention
31
+ * - Component unmounting with cleanup
32
+ *
33
+ * Type Safety Improvements (v3.35.1):
34
+ * - Enhanced element access with null checks and warnings
35
+ * - Improved action validation and error handling
36
+ * - Better error reporting in component actions
37
+ * - Safer DOM element queries with fallback warnings
19
38
  *
20
39
  * Usage:
21
40
  * ```ts
@@ -36,6 +55,7 @@ import app, { App } from './app';
36
55
  import { Reflect } from './decorator'
37
56
  import { View, Update, ActionDef, ActionOptions, MountOptions, EventOptions } from './types';
38
57
  import directive from './directive';
58
+ import { safeQuerySelector, safeGetElementById } from './type-utils';
39
59
 
40
60
  const componentCache = new Map();
41
61
  if (!app.find('get-components')) app.on('get-components', o => o.components = componentCache);
@@ -74,9 +94,12 @@ export class Component<T = any, E = any> {
74
94
  if (typeof document !== 'object') return;
75
95
 
76
96
  const el = (typeof this.element === 'string' && this.element) ?
77
- document.getElementById(this.element) || document.querySelector(this.element) : this.element;
97
+ safeGetElementById(this.element) || safeQuerySelector(this.element) : this.element;
78
98
 
79
- if (!el) return;
99
+ if (!el) {
100
+ console.warn(`Component element not found: ${this.element}`);
101
+ return;
102
+ }
80
103
  const tracking_attr = '_c';
81
104
  if (!this.unload) {
82
105
  el.removeAttribute && el.removeAttribute(tracking_attr);
@@ -236,7 +259,10 @@ export class Component<T = any, E = any> {
236
259
  }
237
260
 
238
261
  add_action(name: string, action, options: ActionOptions = {}) {
239
- if (!action || typeof action !== 'function') return;
262
+ if (!action || typeof action !== 'function') {
263
+ console.warn(`Component action for '${name}' is not a valid function:`, action);
264
+ return;
265
+ }
240
266
  if (options.global) this._global_events.push(name);
241
267
  this.on(name as any, (...p) => {
242
268
 
@@ -248,18 +274,30 @@ export class Component<T = any, E = any> {
248
274
  options
249
275
  });
250
276
 
251
- const newState = action(this.state, ...p);
252
-
253
- app['debug'] && app.run('debug', {
254
- component: this,
255
- _: '<',
256
- event: name, p,
257
- newState,
258
- state: this.state,
259
- options
260
- });
277
+ try {
278
+ const newState = action(this.state, ...p);
279
+
280
+ app['debug'] && app.run('debug', {
281
+ component: this,
282
+ _: '<',
283
+ event: name, p,
284
+ newState,
285
+ state: this.state,
286
+ options
287
+ });
261
288
 
262
- this.setState(newState, options)
289
+ this.setState(newState, options);
290
+ } catch (error) {
291
+ console.error(`Error in component action '${name}':`, error);
292
+ app['debug'] && app.run('debug', {
293
+ component: this,
294
+ _: '!',
295
+ event: name, p,
296
+ error,
297
+ state: this.state,
298
+ options
299
+ });
300
+ }
263
301
  }, options);
264
302
  }
265
303
 
@@ -329,7 +367,11 @@ export class Component<T = any, E = any> {
329
367
  }
330
368
 
331
369
  // obsolete
370
+ /**
371
+ * @deprecated Use runAsync() instead. query() will be removed in a future version.
372
+ */
332
373
  public query(event: E, ...args) {
374
+ console.warn('component.query() is deprecated. Use component.runAsync() instead.');
333
375
  return this.runAsync(event, ...args);
334
376
  }
335
377
 
package/src/decorator.ts CHANGED
@@ -5,30 +5,47 @@ import webComponent, { CustomElementOptions } from './web-component';
5
5
  *
6
6
  * This file provides decorators that enable:
7
7
  * 1. Event Handler Registration
8
- * - @on(): Subscribe to events with options
9
- * - @update(): Define state updates with metadata
8
+ * - @on(): Subscribe to events with options (global, once, delay)
9
+ * - @update(): Define state updates with metadata and options
10
+ * - Supports method and class decoration patterns
10
11
  *
11
12
  * 2. Web Component Integration
12
13
  * - @customElement(): Register as custom element with options
13
14
  * - Handles shadow DOM and attribute observation
15
+ * - Automatic lifecycle management for web components
14
16
  *
15
17
  * 3. Metadata Management
16
18
  * - Custom Reflect implementation for decorator metadata
17
- * - Stores event handler and update metadata
19
+ * - Stores event handler and update metadata for runtime use
18
20
  * - Supports runtime reflection for dynamic behavior
21
+ * - Metadata keys for event bindings and updates
19
22
  *
23
+ * Features:
24
+ * - Event handler decoration with options
25
+ * - State update method decoration
26
+ * - Web component registration
27
+ * - Metadata-driven event binding
28
+ * - Flexible decorator patterns
29
+ * - Runtime metadata access
30
+ *
31
+ * Type Safety Improvements (v3.35.1):
32
+ * - Enhanced decorator typing for better IDE support
33
+ * - Improved metadata key management
34
+ * - Better type inference for decorated methods
35
+ *
20
36
  * Usage:
21
37
  * ```ts
22
38
  * @customElement('my-element')
23
39
  * class MyComponent extends Component {
24
- * @on('event')
40
+ * @on('event', { global: true })
25
41
  * handler(state, ...args) {
26
42
  * // Handle event
27
43
  * }
28
44
  *
29
- * @update('event')
45
+ * @update('event', { render: true })
30
46
  * updater(state, ...args) {
31
47
  * // Update state
48
+ * return newState;
32
49
  * }
33
50
  * }
34
51
  * ```
@@ -55,7 +72,7 @@ export const Reflect = {
55
72
  }
56
73
  }
57
74
 
58
- export function update<E=string>(events?: E, options: any = {}) {
75
+ export function update<E = string>(events?: E, options: any = {}) {
59
76
  return (target: any, key: string, descriptor: any) => {
60
77
  const name = events ? events.toString() : key;
61
78
  Reflect.defineMetadata(`apprun-update:${name}`,