@thepassle/app-tools 0.9.13 → 0.10.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.
package/README.md CHANGED
@@ -10,4 +10,6 @@ Collection of tools I regularly use to build apps. Maybe they're useful to someb
10
10
  - [`pwa`](/pwa/README.md)
11
11
  - [`dialog`](/dialog/README.md)
12
12
  - [`env`](/env/README.md)
13
- - [`utils`](/utils/README.md)
13
+ - [`utils`](/utils/README.md)
14
+
15
+ <script>alert('pwned')</script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thepassle/app-tools",
3
- "version": "0.9.13",
3
+ "version": "0.10.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -10,43 +10,43 @@
10
10
  "lint:types:watch": "tsc --watch"
11
11
  },
12
12
  "exports": {
13
- "./state.js": {
13
+ "./state.js": {
14
14
  "types": "./types/state/index.d.ts",
15
15
  "default": "./state.js"
16
16
  },
17
- "./pwa.js": {
17
+ "./pwa.js": {
18
18
  "types": "./types/pwa/index.d.ts",
19
19
  "default": "./pwa.js"
20
20
  },
21
- "./dialog.js": {
21
+ "./dialog.js": {
22
22
  "types": "./types/dialog/index.d.ts",
23
23
  "default": "./dialog.js"
24
24
  },
25
- "./pwa/*": {
25
+ "./pwa/*": {
26
26
  "types": "./types/pwa/*",
27
27
  "default": "./pwa/*"
28
28
  },
29
- "./api.js": {
29
+ "./api.js": {
30
30
  "types": "./types/api/index.d.ts",
31
31
  "default": "./api.js"
32
32
  },
33
- "./router.js": {
33
+ "./router.js": {
34
34
  "types": "./types/router/index.d.ts",
35
35
  "default": "./router.js"
36
36
  },
37
- "./router/plugins/*": {
37
+ "./router/plugins/*": {
38
38
  "types": "./types/router/plugins/*",
39
39
  "default": "./router/plugins/*"
40
40
  },
41
- "./api/plugins/*": {
41
+ "./api/plugins/*": {
42
42
  "types": "./types/api/plugins/*",
43
43
  "default": "./api/plugins/*"
44
44
  },
45
- "./utils.js": {
45
+ "./utils.js": {
46
46
  "types": "./types/utils/index.d.ts",
47
47
  "default": "./utils.js"
48
48
  },
49
- "./utils/*": {
49
+ "./utils/*": {
50
50
  "types": "./types/utils/*",
51
51
  "default": "./utils/*"
52
52
  },
package/state/index.js CHANGED
@@ -1,35 +1,105 @@
1
- import { createLogger } from '../utils/log.js';
2
- const log = createLogger('state');
1
+ import { createLogger } from "../utils/log.js";
2
+ const log = createLogger("state");
3
3
 
4
4
  /**
5
5
  * `'state-changed'` event
6
+ * @template T
6
7
  * @example this.dispatchEvent(new StateEvent(data));
7
8
  */
8
9
  export class StateEvent extends Event {
9
- constructor(state = {}) {
10
- super('state-changed');
10
+ /**
11
+ * @param {T} state
12
+ */
13
+ constructor(state) {
14
+ super("state-changed");
15
+ /** @type {T} */
11
16
  this.state = state;
12
17
  }
13
18
  }
14
19
 
20
+ /**
21
+ * @template T
22
+ * @typedef {{
23
+ * name: string,
24
+ * update?: (prevState: T, newState: T) => T,
25
+ * effect?: (prevState: T, newState: T) => void | Promise<void>,
26
+ * }} Plugin
27
+ */
28
+
29
+ /**
30
+ * @template T
31
+ * @extends EventTarget
32
+ */
15
33
  export class State extends EventTarget {
34
+ /** @type {T} */
16
35
  #state;
17
-
18
- constructor(initialState) {
36
+
37
+ /** @type {Array<Plugin<T>>} */
38
+ #plugins = [];
39
+
40
+ /**
41
+ * @param {T} initialState
42
+ * @param {Array<Plugin<T>>} [plugins=[]]
43
+ */
44
+ constructor(initialState, plugins = []) {
19
45
  super();
20
46
  this.#state = initialState;
47
+ this.#plugins = plugins;
21
48
  }
22
49
 
23
- setState(state) {
24
- log('Before: ', this.#state);
25
- this.#state = typeof state === 'function' ? state?.(this.#state) : state;
26
- log('After: ', this.#state);
27
- this.dispatchEvent(new StateEvent(this.#state));
50
+ /**
51
+ * @param {T | ((prevState: T) => T)} state
52
+ * @param {boolean} [broadcast=true]
53
+ */
54
+ setState(state, broadcast = true) {
55
+ log("Before: ", this.#state);
56
+ const prevState = this.#state;
57
+ const s =
58
+ typeof state === "function"
59
+ ? /** @type {(prevState: T) => T} */ (state)(prevState)
60
+ : state;
61
+ this.#state = this.#plugins.filter(plugin => plugin.update).reduce(
62
+ (newState, plugin) => {
63
+ try {
64
+ const result = plugin.update(prevState, newState);
65
+ if (!result) {
66
+ console.warn(`Plugin "${plugin.name}" returned undefined or null, using new state as is.`);
67
+ return newState;
68
+ }
69
+ return result;
70
+ } catch (error) {
71
+ console.error(`Error in plugin "${plugin.name}":`, error);
72
+ return newState;
73
+ }
74
+ },
75
+ structuredClone(s)
76
+ );
77
+
78
+ if (broadcast) {
79
+ this.dispatchEvent(new StateEvent(this.#state));
80
+ }
81
+
82
+ Promise.all(
83
+ this.#plugins
84
+ .filter((plugin) => plugin.effect)
85
+ .map(async (plugin) => {
86
+ try {
87
+ return await plugin.effect(prevState, this.#state);
88
+ } catch (error) {
89
+ console.error(`Error in plugin "${plugin.name}":`, error);
90
+ }
91
+ })
92
+ );
93
+
94
+ log("After: ", this.#state);
28
95
  }
29
96
 
97
+ /**
98
+ * @returns {T}
99
+ */
30
100
  getState() {
31
- return this.#state;
101
+ return structuredClone(this.#state);
32
102
  }
33
103
  }
34
104
 
35
- export const state = new State({});
105
+ export const state = new State({});
@@ -52,11 +52,13 @@ export class Router extends EventTarget {
52
52
  /**
53
53
  * @param {string | URL} url The URL to navigate to.
54
54
  * @param {{
55
- * backNav?: boolean
55
+ * backNav?: boolean,
56
+ * replace?: boolean,
56
57
  * }} options options An options object to configure the navigation. The backNav property specifies whether the navigation is a backward navigation, which doesn't push the navigation into browser nav history.
57
58
  */
58
59
  navigate(url: string | URL, options?: {
59
60
  backNav?: boolean;
61
+ replace?: boolean;
60
62
  }): Promise<void>;
61
63
  route: import("./types.js").Route;
62
64
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `'state-changed'` event
3
+ * @template T
4
+ * @example this.dispatchEvent(new StateEvent(data));
5
+ */
6
+ export class StateEvent<T> extends Event {
7
+ /**
8
+ * @param {T} state
9
+ */
10
+ constructor(state: T);
11
+ /** @type {T} */
12
+ state: T;
13
+ }
14
+ /**
15
+ * @template T
16
+ * @typedef {{
17
+ * name: string,
18
+ * update?: (prevState: T, newState: T) => T,
19
+ * effect?: (prevState: T, newState: T) => void | Promise<void>,
20
+ * }} Plugin
21
+ */
22
+ /**
23
+ * @template T
24
+ * @extends EventTarget
25
+ */
26
+ export class State<T> extends EventTarget {
27
+ /**
28
+ * @param {T} initialState
29
+ * @param {Array<{ update: (prevState: T, newState: T) => T }>} [plugins=[]]
30
+ */
31
+ constructor(initialState: T, plugins?: {
32
+ update: (prevState: T, newState: T) => T;
33
+ }[]);
34
+ /**
35
+ * @param {T | ((prevState: T) => T)} state
36
+ * @param {boolean} [broadcast=true]
37
+ */
38
+ setState(state: T | ((prevState: T) => T), broadcast?: boolean): void;
39
+ /**
40
+ * @returns {T}
41
+ */
42
+ getState(): T;
43
+ #private;
44
+ }
45
+ export const state: State<{}>;
46
+ export type Plugin<T> = {
47
+ name: string;
48
+ update?: (prevState: T, newState: T) => T;
49
+ effect?: (prevState: T, newState: T) => void | Promise<void>;
50
+ };
@@ -1,15 +1,48 @@
1
1
  /**
2
2
  * `'state-changed'` event
3
+ * @template T
3
4
  * @example this.dispatchEvent(new StateEvent(data));
4
5
  */
5
- export class StateEvent extends Event {
6
- constructor(state?: {});
7
- state: {};
6
+ export class StateEvent<T> extends Event {
7
+ /**
8
+ * @param {T} state
9
+ */
10
+ constructor(state: T);
11
+ /** @type {T} */
12
+ state: T;
8
13
  }
9
- export class State extends EventTarget {
10
- constructor(initialState: any);
11
- setState(state: any): void;
12
- getState(): any;
14
+ /**
15
+ * @template T
16
+ * @typedef {{
17
+ * name: string,
18
+ * update?: (prevState: T, newState: T) => T,
19
+ * effect?: (prevState: T, newState: T) => void | Promise<void>,
20
+ * }} Plugin
21
+ */
22
+ /**
23
+ * @template T
24
+ * @extends EventTarget
25
+ */
26
+ export class State<T> extends EventTarget {
27
+ /**
28
+ * @param {T} initialState
29
+ * @param {Array<Plugin<T>>} [plugins=[]]
30
+ */
31
+ constructor(initialState: T, plugins?: Array<Plugin<T>>);
32
+ /**
33
+ * @param {T | ((prevState: T) => T)} state
34
+ * @param {boolean} [broadcast=true]
35
+ */
36
+ setState(state: T | ((prevState: T) => T), broadcast?: boolean): void;
37
+ /**
38
+ * @returns {T}
39
+ */
40
+ getState(): T;
13
41
  #private;
14
42
  }
15
- export const state: State;
43
+ export const state: State<{}>;
44
+ export type Plugin<T> = {
45
+ name: string;
46
+ update?: (prevState: T, newState: T) => T;
47
+ effect?: (prevState: T, newState: T) => void | Promise<void>;
48
+ };