@praxisjs/decorators 0.4.3 → 0.6.0

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 (192) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/__tests__/component.test.js +10 -1
  3. package/dist/__tests__/component.test.js.map +1 -1
  4. package/dist/__tests__/compose.test.d.ts +2 -0
  5. package/dist/__tests__/compose.test.d.ts.map +1 -0
  6. package/dist/__tests__/compose.test.js +171 -0
  7. package/dist/__tests__/compose.test.js.map +1 -0
  8. package/dist/__tests__/events.test.js +26 -0
  9. package/dist/__tests__/events.test.js.map +1 -1
  10. package/dist/__tests__/functions.test.js +205 -47
  11. package/dist/__tests__/functions.test.js.map +1 -1
  12. package/dist/__tests__/lazy.test.js +5 -7
  13. package/dist/__tests__/lazy.test.js.map +1 -1
  14. package/dist/__tests__/memo.test.js +114 -25
  15. package/dist/__tests__/memo.test.js.map +1 -1
  16. package/dist/__tests__/properties.test.js +193 -0
  17. package/dist/__tests__/properties.test.js.map +1 -1
  18. package/dist/__tests__/resource.test.d.ts +2 -0
  19. package/dist/__tests__/resource.test.d.ts.map +1 -0
  20. package/dist/__tests__/resource.test.js +127 -0
  21. package/dist/__tests__/resource.test.js.map +1 -0
  22. package/dist/__tests__/slot.test.js +32 -0
  23. package/dist/__tests__/slot.test.js.map +1 -1
  24. package/dist/__tests__/until.test.d.ts +2 -0
  25. package/dist/__tests__/until.test.d.ts.map +1 -0
  26. package/dist/__tests__/until.test.js +88 -0
  27. package/dist/__tests__/until.test.js.map +1 -0
  28. package/dist/__tests__/virtual.test.js +164 -15
  29. package/dist/__tests__/virtual.test.js.map +1 -1
  30. package/dist/component/component.d.ts +1 -1
  31. package/dist/component/component.d.ts.map +1 -1
  32. package/dist/component/component.js +15 -10
  33. package/dist/component/component.js.map +1 -1
  34. package/dist/component/lazy.d.ts.map +1 -1
  35. package/dist/component/lazy.js +29 -30
  36. package/dist/component/lazy.js.map +1 -1
  37. package/dist/component/virtual.d.ts.map +1 -1
  38. package/dist/component/virtual.js +49 -50
  39. package/dist/component/virtual.js.map +1 -1
  40. package/dist/create-class-decorator.d.ts +16 -0
  41. package/dist/create-class-decorator.d.ts.map +1 -0
  42. package/dist/create-class-decorator.js +35 -0
  43. package/dist/create-class-decorator.js.map +1 -0
  44. package/dist/create-field-decorator.d.ts +14 -0
  45. package/dist/create-field-decorator.d.ts.map +1 -0
  46. package/dist/create-field-decorator.js +37 -0
  47. package/dist/create-field-decorator.js.map +1 -0
  48. package/dist/create-getter-decorator.d.ts +18 -0
  49. package/dist/create-getter-decorator.d.ts.map +1 -0
  50. package/dist/create-getter-decorator.js +15 -0
  51. package/dist/create-getter-decorator.js.map +1 -0
  52. package/dist/create-lifecycle-method-decorator.d.ts +6 -0
  53. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
  54. package/dist/create-lifecycle-method-decorator.js +22 -0
  55. package/dist/create-lifecycle-method-decorator.js.map +1 -0
  56. package/dist/create-method-decorator.d.ts +6 -0
  57. package/dist/create-method-decorator.d.ts.map +1 -0
  58. package/dist/create-method-decorator.js +14 -0
  59. package/dist/create-method-decorator.js.map +1 -0
  60. package/dist/events/emit.d.ts +1 -1
  61. package/dist/events/emit.d.ts.map +1 -1
  62. package/dist/events/emit.js +24 -29
  63. package/dist/events/emit.js.map +1 -1
  64. package/dist/events/on-command.d.ts +1 -2
  65. package/dist/events/on-command.d.ts.map +1 -1
  66. package/dist/events/on-command.js +15 -32
  67. package/dist/events/on-command.js.map +1 -1
  68. package/dist/functions/bind.d.ts +1 -2
  69. package/dist/functions/bind.d.ts.map +1 -1
  70. package/dist/functions/bind.js +4 -11
  71. package/dist/functions/bind.js.map +1 -1
  72. package/dist/functions/debounce.d.ts.map +1 -1
  73. package/dist/functions/debounce.js +21 -20
  74. package/dist/functions/debounce.js.map +1 -1
  75. package/dist/functions/index.d.ts +1 -0
  76. package/dist/functions/index.d.ts.map +1 -1
  77. package/dist/functions/index.js +1 -0
  78. package/dist/functions/index.js.map +1 -1
  79. package/dist/functions/log.d.ts +1 -2
  80. package/dist/functions/log.d.ts.map +1 -1
  81. package/dist/functions/log.js +40 -38
  82. package/dist/functions/log.js.map +1 -1
  83. package/dist/functions/memo.d.ts +1 -2
  84. package/dist/functions/memo.d.ts.map +1 -1
  85. package/dist/functions/memo.js +29 -27
  86. package/dist/functions/memo.js.map +1 -1
  87. package/dist/functions/once.d.ts +1 -2
  88. package/dist/functions/once.d.ts.map +1 -1
  89. package/dist/functions/once.js +14 -12
  90. package/dist/functions/once.js.map +1 -1
  91. package/dist/functions/retry.d.ts +1 -2
  92. package/dist/functions/retry.d.ts.map +1 -1
  93. package/dist/functions/retry.js +23 -20
  94. package/dist/functions/retry.js.map +1 -1
  95. package/dist/functions/throttle.d.ts +1 -2
  96. package/dist/functions/throttle.d.ts.map +1 -1
  97. package/dist/functions/throttle.js +11 -17
  98. package/dist/functions/throttle.js.map +1 -1
  99. package/dist/functions/until.d.ts +2 -0
  100. package/dist/functions/until.d.ts.map +1 -0
  101. package/dist/functions/until.js +17 -0
  102. package/dist/functions/until.js.map +1 -0
  103. package/dist/functions/watch.d.ts +1 -1
  104. package/dist/functions/watch.d.ts.map +1 -1
  105. package/dist/functions/watch.js +25 -39
  106. package/dist/functions/watch.js.map +1 -1
  107. package/dist/functions/when.d.ts +1 -2
  108. package/dist/functions/when.d.ts.map +1 -1
  109. package/dist/functions/when.js +10 -26
  110. package/dist/functions/when.js.map +1 -1
  111. package/dist/index.d.ts +7 -2
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +7 -2
  114. package/dist/index.js.map +1 -1
  115. package/dist/properties/compose.d.ts +3 -0
  116. package/dist/properties/compose.d.ts.map +1 -0
  117. package/dist/properties/compose.js +40 -0
  118. package/dist/properties/compose.js.map +1 -0
  119. package/dist/properties/computed.d.ts +1 -2
  120. package/dist/properties/computed.d.ts.map +1 -1
  121. package/dist/properties/computed.js +10 -9
  122. package/dist/properties/computed.js.map +1 -1
  123. package/dist/properties/history.d.ts.map +1 -1
  124. package/dist/properties/history.js +37 -32
  125. package/dist/properties/history.js.map +1 -1
  126. package/dist/properties/index.d.ts +2 -0
  127. package/dist/properties/index.d.ts.map +1 -1
  128. package/dist/properties/index.js +2 -0
  129. package/dist/properties/index.js.map +1 -1
  130. package/dist/properties/persisted.d.ts +1 -2
  131. package/dist/properties/persisted.d.ts.map +1 -1
  132. package/dist/properties/persisted.js +13 -29
  133. package/dist/properties/persisted.js.map +1 -1
  134. package/dist/properties/prop.d.ts +1 -2
  135. package/dist/properties/prop.d.ts.map +1 -1
  136. package/dist/properties/prop.js +21 -25
  137. package/dist/properties/prop.js.map +1 -1
  138. package/dist/properties/resource.d.ts +14 -0
  139. package/dist/properties/resource.d.ts.map +1 -0
  140. package/dist/properties/resource.js +27 -0
  141. package/dist/properties/resource.js.map +1 -0
  142. package/dist/properties/slot.d.ts +1 -2
  143. package/dist/properties/slot.d.ts.map +1 -1
  144. package/dist/properties/slot.js +15 -20
  145. package/dist/properties/slot.js.map +1 -1
  146. package/dist/properties/state.d.ts +1 -2
  147. package/dist/properties/state.d.ts.map +1 -1
  148. package/dist/properties/state.js +13 -18
  149. package/dist/properties/state.js.map +1 -1
  150. package/package.json +2 -2
  151. package/src/__tests__/component.test.ts +10 -0
  152. package/src/__tests__/compose.test.ts +202 -0
  153. package/src/__tests__/events.test.ts +32 -0
  154. package/src/__tests__/functions.test.ts +257 -46
  155. package/src/__tests__/lazy.test.ts +3 -5
  156. package/src/__tests__/memo.test.ts +143 -25
  157. package/src/__tests__/properties.test.ts +233 -0
  158. package/src/__tests__/resource.test.ts +141 -0
  159. package/src/__tests__/slot.test.ts +38 -0
  160. package/src/__tests__/until.test.ts +113 -0
  161. package/src/__tests__/virtual.test.ts +135 -7
  162. package/src/component/component.ts +27 -12
  163. package/src/component/lazy.ts +33 -37
  164. package/src/component/virtual.tsx +67 -75
  165. package/src/create-class-decorator.ts +61 -0
  166. package/src/create-field-decorator.ts +66 -0
  167. package/src/create-getter-decorator.ts +52 -0
  168. package/src/create-lifecycle-method-decorator.ts +38 -0
  169. package/src/create-method-decorator.ts +26 -0
  170. package/src/events/emit.ts +22 -33
  171. package/src/events/on-command.ts +22 -47
  172. package/src/functions/bind.ts +4 -15
  173. package/src/functions/debounce.ts +21 -23
  174. package/src/functions/index.ts +1 -0
  175. package/src/functions/log.ts +40 -43
  176. package/src/functions/memo.ts +28 -42
  177. package/src/functions/once.ts +13 -16
  178. package/src/functions/retry.ts +21 -22
  179. package/src/functions/throttle.ts +11 -24
  180. package/src/functions/until.ts +19 -0
  181. package/src/functions/watch.ts +27 -43
  182. package/src/functions/when.ts +11 -36
  183. package/src/index.ts +29 -0
  184. package/src/properties/compose.ts +53 -0
  185. package/src/properties/computed.ts +11 -13
  186. package/src/properties/history.ts +42 -41
  187. package/src/properties/index.ts +2 -0
  188. package/src/properties/persisted.ts +17 -58
  189. package/src/properties/prop.ts +21 -31
  190. package/src/properties/resource.ts +34 -0
  191. package/src/properties/slot.ts +17 -30
  192. package/src/properties/state.ts +14 -24
@@ -0,0 +1,61 @@
1
+ import type { RootComponent } from "@praxisjs/core/internal";
2
+
3
+ export interface ClassEnhancement {
4
+ onMount?(): void;
5
+ onUnmount?(): void;
6
+ render?(
7
+ originalRender: () => Node | Node[] | null,
8
+ ): Node | Node[] | null;
9
+ }
10
+
11
+ export abstract class ClassBehavior {
12
+ abstract create(instance: RootComponent): ClassEnhancement;
13
+
14
+ /**
15
+ * Called once after the enhanced class is created.
16
+ * Use this to set static properties on the class constructor itself.
17
+ */
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ initialize?(_Enhanced: new (...args: any[]) => unknown, _original: new (...args: any[]) => unknown): void;
20
+ }
21
+
22
+ export function createClassDecorator(behavior: ClassBehavior) {
23
+ return function <
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ T extends new (...args: any[]) => RootComponent<Record<string, any>>,
26
+ >(constructor: T, _context: ClassDecoratorContext): T {
27
+ const Enhanced = class extends constructor {
28
+ private readonly _enh: ClassEnhancement = behavior.create(
29
+ this as unknown as RootComponent,
30
+ );
31
+
32
+ onMount() {
33
+ super.onMount?.();
34
+ this._enh.onMount?.();
35
+ }
36
+
37
+ onUnmount() {
38
+ super.onUnmount?.();
39
+ this._enh.onUnmount?.();
40
+ }
41
+
42
+ render(...args: unknown[]): Node | Node[] | null {
43
+ const originalRender = () =>
44
+ (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
45
+ if (this._enh.render) {
46
+ return this._enh.render(originalRender);
47
+ }
48
+ return originalRender();
49
+ }
50
+ };
51
+
52
+ // If the original class has no render, remove the wrapper so instances see `undefined`.
53
+ if (typeof (constructor.prototype as { render?: unknown }).render !== "function") {
54
+ delete (Enhanced.prototype as { render?: unknown }).render;
55
+ }
56
+
57
+ Object.defineProperty(Enhanced, "name", { value: constructor.name });
58
+ behavior.initialize?.(Enhanced, constructor);
59
+ return Enhanced as unknown as T;
60
+ };
61
+ }
@@ -0,0 +1,66 @@
1
+ import type { StatefulComponent } from "@praxisjs/core";
2
+
3
+ export interface FieldBinding {
4
+ /** Replaces the decorated property. If absent, the original property is left untouched. */
5
+ descriptor?: PropertyDescriptor;
6
+ /** Extra properties to define on the instance (e.g. companion properties like `nameHistory`). */
7
+ additional?: Record<string, PropertyDescriptor>;
8
+ onMount?: () => void;
9
+ onUnmount?: () => void;
10
+ }
11
+
12
+ export interface FieldBehavior {
13
+ bind(
14
+ instance: StatefulComponent,
15
+ name: string,
16
+ initialValue: unknown,
17
+ ): FieldBinding;
18
+ }
19
+
20
+ export function createFieldDecorator(behavior: FieldBehavior) {
21
+ return function (
22
+ _value: undefined,
23
+ context: ClassFieldDecoratorContext<StatefulComponent>,
24
+ ): void {
25
+ context.addInitializer(function (this: unknown) {
26
+ const instance = this as StatefulComponent & Record<string, unknown>;
27
+ const name = context.name as string;
28
+ const initialValue = instance[name];
29
+
30
+ const { descriptor, additional, onMount, onUnmount } = behavior.bind(
31
+ instance,
32
+ name,
33
+ initialValue,
34
+ );
35
+
36
+ if (descriptor) {
37
+ Reflect.deleteProperty(instance, name);
38
+ Object.defineProperty(instance, name, {
39
+ enumerable: true,
40
+ configurable: true,
41
+ ...descriptor,
42
+ });
43
+ }
44
+
45
+ for (const [key, desc] of Object.entries(additional ?? {})) {
46
+ Object.defineProperty(instance, key, {
47
+ configurable: true,
48
+ ...desc,
49
+ });
50
+ }
51
+
52
+ if (onMount ?? onUnmount) {
53
+ const prevMount = instance.onMount?.bind(instance);
54
+ const prevUnmount = instance.onUnmount?.bind(instance);
55
+ instance.onMount = () => {
56
+ prevMount?.();
57
+ onMount?.();
58
+ };
59
+ instance.onUnmount = () => {
60
+ prevUnmount?.();
61
+ onUnmount?.();
62
+ };
63
+ }
64
+ });
65
+ };
66
+ }
@@ -0,0 +1,52 @@
1
+ export interface GetterBehavior {
2
+ /**
3
+ * Given the original getter and the current instance, returns a function
4
+ * that produces the getter value. Called on every property access.
5
+ */
6
+ wrap(
7
+ original: (this: object) => unknown,
8
+ instance: object,
9
+ ): () => unknown;
10
+ }
11
+
12
+ export function createGetterDecorator(behavior: GetterBehavior) {
13
+ return function <This, T>(
14
+ target: (this: This) => T,
15
+ _context: ClassGetterDecoratorContext<This, T>,
16
+ ): (this: This) => T {
17
+ return function (this: This): T {
18
+ return behavior.wrap(
19
+ target as (this: object) => unknown,
20
+ this as object,
21
+ )() as T;
22
+ };
23
+ };
24
+ }
25
+
26
+ export interface GetterObserverBehavior {
27
+ /**
28
+ * Called once per instance when the class is initialized.
29
+ * Use this to set up observation side-effects on the getter without
30
+ * changing its return value (e.g. tracking changes for devtools).
31
+ */
32
+ observe(
33
+ getter: (this: object) => unknown,
34
+ instance: object,
35
+ name: string,
36
+ ): void;
37
+ }
38
+
39
+ export function createGetterObserverDecorator(behavior: GetterObserverBehavior) {
40
+ return function <This, T>(
41
+ target: (this: This) => T,
42
+ context: ClassGetterDecoratorContext<This, T>,
43
+ ): void {
44
+ context.addInitializer(function (this: unknown) {
45
+ behavior.observe(
46
+ target as (this: object) => unknown,
47
+ this as object,
48
+ context.name as string,
49
+ );
50
+ });
51
+ };
52
+ }
@@ -0,0 +1,38 @@
1
+ import type { StatefulComponent } from "@praxisjs/core";
2
+
3
+ export interface LifecycleMethodBehavior<
4
+ T extends StatefulComponent = StatefulComponent,
5
+ > {
6
+ register(
7
+ callback: (...args: unknown[]) => void,
8
+ instance: T,
9
+ ): (() => void) | undefined;
10
+ }
11
+
12
+ export function createLifecycleMethodDecorator<
13
+ T extends StatefulComponent = StatefulComponent,
14
+ >(behavior: LifecycleMethodBehavior<T>) {
15
+ return function (
16
+ value: (this: T, ...args: unknown[]) => void,
17
+ _context: ClassMethodDecoratorContext<T>,
18
+ ): void {
19
+ _context.addInitializer(function (this: unknown) {
20
+ const instance = this as T;
21
+ const prevMount = instance.onMount?.bind(instance);
22
+ const prevUnmount = instance.onUnmount?.bind(instance);
23
+ let cleanup: (() => void) | undefined;
24
+
25
+ instance.onMount = function (this: T) {
26
+ prevMount?.call(this);
27
+ const result = behavior.register(value.bind(this), this);
28
+ if (result) cleanup = result;
29
+ };
30
+
31
+ instance.onUnmount = function (this: T) {
32
+ prevUnmount?.call(this);
33
+ cleanup?.();
34
+ cleanup = undefined;
35
+ };
36
+ });
37
+ };
38
+ }
@@ -0,0 +1,26 @@
1
+ import type { StatefulComponent } from "@praxisjs/core";
2
+
3
+ export interface MethodBehavior {
4
+ wrap(
5
+ original: (...args: unknown[]) => unknown,
6
+ instance: object,
7
+ name: string,
8
+ ): (...args: unknown[]) => unknown;
9
+ }
10
+
11
+ export function createMethodDecorator(behavior: MethodBehavior) {
12
+ return function (
13
+ value: (this: object, ...args: unknown[]) => unknown,
14
+ context: ClassMethodDecoratorContext<StatefulComponent>,
15
+ ): void {
16
+ context.addInitializer(function (this: unknown) {
17
+ const instance = this as object;
18
+ const name = context.name as string;
19
+ Object.defineProperty(instance, name, {
20
+ value: behavior.wrap(value, instance, name),
21
+ configurable: true,
22
+ writable: true,
23
+ });
24
+ });
25
+ };
26
+ }
@@ -1,42 +1,31 @@
1
1
  import type { StatefulComponent } from "@praxisjs/core";
2
2
 
3
+ import { createMethodDecorator } from "../create-method-decorator";
3
4
  import { readProp } from "./helper";
4
5
 
5
6
  export function Emit(propName: string) {
6
- return function (
7
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
- value: (this: StatefulComponent, ...args: any[]) => any,
9
- context: ClassMethodDecoratorContext<StatefulComponent>,
10
- ): void {
11
- const wrapped = function (
12
- this: StatefulComponent,
13
- ...args: unknown[]
14
- ): unknown {
15
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
16
- const result = value.apply(this, args);
7
+ return createMethodDecorator({
8
+ wrap(original, instance) {
9
+ const comp = instance as StatefulComponent;
10
+ return (...args: unknown[]) => {
11
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
12
+ const result = original.apply(instance, args) as any;
17
13
 
18
- const callback = readProp(this, propName);
19
- if (typeof callback !== "function") return result;
14
+ const callback = readProp(comp, propName);
15
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
16
+ if (typeof callback !== "function") return result;
20
17
 
21
- if (result !== undefined) {
22
- (callback as (v: unknown) => void)(result);
23
- } else if (args.length > 0) {
24
- (callback as (...a: unknown[]) => void)(...args);
25
- } else {
26
- (callback as () => void)();
27
- }
18
+ if (result !== undefined) {
19
+ (callback as (v: unknown) => void)(result);
20
+ } else if (args.length > 0) {
21
+ (callback as (...a: unknown[]) => void)(...args);
22
+ } else {
23
+ (callback as () => void)();
24
+ }
28
25
 
29
- return result;
30
- };
31
-
32
- context.addInitializer(function (this: unknown) {
33
- const name = context.name as string;
34
- const bound = wrapped.bind(this as StatefulComponent);
35
- Object.defineProperty(this as object, name, {
36
- value: bound,
37
- configurable: true,
38
- writable: true,
39
- });
40
- });
41
- };
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
27
+ return result;
28
+ };
29
+ },
30
+ });
42
31
  }
@@ -1,54 +1,29 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
1
 
2
+ import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
3
3
  import { readProp } from "./helper";
4
4
 
5
5
  import type { Command } from "./command";
6
6
 
7
7
  export function OnCommand(propName: string) {
8
- return function (
9
- value: (this: StatefulComponent, ...args: unknown[]) => void,
10
- context: ClassMethodDecoratorContext<StatefulComponent>,
11
- ): void {
12
- const cleanups = new WeakMap<object, () => void>();
13
-
14
- context.addInitializer(function (this: unknown) {
15
- const instance = this as StatefulComponent;
16
- // eslint-disable-next-line @typescript-eslint/unbound-method
17
- const originalOnMount = instance.onMount;
18
- // eslint-disable-next-line @typescript-eslint/unbound-method
19
- const originalOnUnmount = instance.onUnmount;
20
-
21
- instance.onMount = function (this: StatefulComponent) {
22
- originalOnMount?.call(this);
23
-
24
- const command = readProp(this, propName) as Command<unknown> | undefined;
25
-
26
- if (!command) {
27
- console.warn(
28
- `[OnCommand] prop "${propName}" was not provided to ${this.constructor.name}`,
29
- );
30
- return;
31
- }
32
-
33
- if (typeof command.subscribe !== "function") {
34
- console.warn(
35
- `[OnCommand] prop "${propName}" is not a valid Command in ${this.constructor.name}`,
36
- );
37
- return;
38
- }
39
-
40
- const bound = value.bind(this);
41
- const unsub = command.subscribe((...args: unknown[]) => {
42
- bound(...args);
43
- });
44
- cleanups.set(this, unsub);
45
- };
46
-
47
- instance.onUnmount = function (this: StatefulComponent) {
48
- originalOnUnmount?.call(this);
49
- cleanups.get(this)?.();
50
- cleanups.delete(this);
51
- };
52
- });
53
- };
8
+ return createLifecycleMethodDecorator({
9
+ register(callback, instance) {
10
+ const command = readProp(instance, propName) as Command<unknown> | undefined;
11
+
12
+ if (!command) {
13
+ console.warn(
14
+ `[OnCommand] prop "${propName}" was not provided to ${(instance.constructor as { name?: string }).name ?? "unknown"}`,
15
+ );
16
+ return;
17
+ }
18
+
19
+ if (typeof command.subscribe !== "function") {
20
+ console.warn(
21
+ `[OnCommand] prop "${propName}" is not a valid Command in ${(instance.constructor as { name?: string }).name ?? "unknown"}`,
22
+ );
23
+ return;
24
+ }
25
+
26
+ return command.subscribe((...args: unknown[]) => { callback(...args); });
27
+ },
28
+ });
54
29
  }
@@ -1,18 +1,7 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export function Bind() {
4
- return function (
5
- value: (this: object, ...args: unknown[]) => unknown,
6
- context: ClassMethodDecoratorContext<StatefulComponent>,
7
- ): void {
8
- context.addInitializer(function (this: unknown) {
9
- const name = context.name as string;
10
- const bound = value.bind(this as object);
11
- Object.defineProperty(this as object, name, {
12
- value: bound,
13
- configurable: true,
14
- writable: true,
15
- });
16
- });
17
- };
4
+ return createMethodDecorator({
5
+ wrap: (original, instance) => original.bind(instance),
6
+ });
18
7
  }
@@ -1,31 +1,29 @@
1
1
  import type { StatefulComponent } from "@praxisjs/core";
2
2
 
3
+ import { createMethodDecorator } from "../create-method-decorator";
4
+
3
5
  export function Debounce(ms: number) {
4
- const timers = new WeakMap<object, ReturnType<typeof setTimeout>>();
6
+ return createMethodDecorator({
7
+ wrap(original, instance, _name) {
8
+ let timer: ReturnType<typeof setTimeout> | undefined;
5
9
 
6
- return function (
7
- value: (this: object, ...args: unknown[]) => unknown,
8
- context: ClassMethodDecoratorContext<StatefulComponent>,
9
- ): void {
10
- context.addInitializer(function (this: unknown) {
11
- const instance = this as object;
12
- const name = context.name as string;
13
- const bound = (...args: unknown[]) => {
14
- const existing = timers.get(instance);
15
- if (existing !== undefined) clearTimeout(existing);
10
+ const comp = instance as StatefulComponent;
11
+ const prevUnmount = comp.onUnmount?.bind(comp);
12
+ comp.onUnmount = function (this: StatefulComponent) {
13
+ prevUnmount?.call(this);
14
+ if (timer !== undefined) {
15
+ clearTimeout(timer);
16
+ timer = undefined;
17
+ }
18
+ };
16
19
 
17
- const timer = setTimeout(() => {
18
- timers.delete(instance);
19
- value.apply(instance, args);
20
+ return (...args: unknown[]) => {
21
+ if (timer !== undefined) clearTimeout(timer);
22
+ timer = setTimeout(() => {
23
+ timer = undefined;
24
+ original.apply(instance, args);
20
25
  }, ms);
21
-
22
- timers.set(instance, timer);
23
26
  };
24
- Object.defineProperty(instance, name, {
25
- value: bound,
26
- configurable: true,
27
- writable: true,
28
- });
29
- });
30
- };
27
+ },
28
+ });
31
29
  }
@@ -6,4 +6,5 @@ export { Retry } from "./retry";
6
6
  export { Debounce } from "./debounce";
7
7
  export { Throttle } from "./throttle";
8
8
  export { When } from "./when";
9
+ export { Until } from "./until";
9
10
  export { Watch, type WatchVal, type WatchVals } from "./watch";
@@ -1,4 +1,4 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export interface LogOptions {
4
4
  level?: "log" | "warn" | "error" | "debug";
@@ -17,52 +17,49 @@ export function Log(options: LogOptions = {}) {
17
17
  devOnly = true,
18
18
  } = options;
19
19
 
20
- return function (
21
- value: (this: object, ...args: unknown[]) => unknown,
22
- context: ClassMethodDecoratorContext<StatefulComponent>,
23
- ): (this: object, ...args: unknown[]) => unknown {
24
- const methodKey = context.name as string;
20
+ return createMethodDecorator({
21
+ wrap(original, instance, name) {
22
+ return (...args: unknown[]) => {
23
+ if (
24
+ devOnly &&
25
+ typeof process !== "undefined" &&
26
+ process.env.NODE_ENV === "production"
27
+ ) {
28
+ return original.apply(instance, args);
29
+ }
25
30
 
26
- return function (this: object, ...args: unknown[]) {
27
- if (
28
- devOnly &&
29
- typeof process !== "undefined" &&
30
- process.env.NODE_ENV === "production"
31
- ) {
32
- return value.apply(this, args);
33
- }
31
+ const className =
32
+ (instance.constructor as { name?: string }).name ?? "Unknown";
33
+ const label = `[${className}.${name}]`;
34
+ const logger = console[level].bind(console);
34
35
 
35
- const className =
36
- (this.constructor as { name?: string }).name ?? "Unknown";
37
- const label = `[${className}.${methodKey}]`;
38
- const logger = console[level].bind(console);
36
+ if (logArgs) logger(`${label} args:`, args);
39
37
 
40
- if (logArgs) logger(`${label} args:`, args);
38
+ const start = time ? performance.now() : 0;
39
+ const output = original.apply(instance, args);
41
40
 
42
- const start = time ? performance.now() : 0;
43
- const output = value.apply(this, args);
41
+ if (output instanceof Promise) {
42
+ return (output as Promise<unknown>)
43
+ .then((resolved) => {
44
+ const elapsed = time
45
+ ? ` (${(performance.now() - start).toFixed(2)}ms)`
46
+ : "";
47
+ if (result) logger(`${label} resolved:`, resolved, elapsed);
48
+ return resolved;
49
+ })
50
+ .catch((e: unknown) => {
51
+ logger(`${label} rejected:`, e);
52
+ throw e;
53
+ });
54
+ }
44
55
 
45
- if (output instanceof Promise) {
46
- return (output as Promise<unknown>)
47
- .then((resolved) => {
48
- const elapsed = time
49
- ? ` (${(performance.now() - start).toFixed(2)}ms)`
50
- : "";
51
- if (result) logger(`${label} resolved:`, resolved, elapsed);
52
- return resolved;
53
- })
54
- .catch((e: unknown) => {
55
- logger(`${label} rejected:`, e);
56
- throw e;
57
- });
58
- }
56
+ const elapsed = time
57
+ ? ` (${(performance.now() - start).toFixed(2)}ms)`
58
+ : "";
59
+ if (result) logger(`${label} returned:`, output, elapsed);
59
60
 
60
- const elapsed = time
61
- ? ` (${(performance.now() - start).toFixed(2)}ms)`
62
- : "";
63
- if (result) logger(`${label} returned:`, output, elapsed);
64
-
65
- return output;
66
- };
67
- };
61
+ return output;
62
+ };
63
+ },
64
+ });
68
65
  }
@@ -1,44 +1,29 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
1
  import { computed } from "@praxisjs/core/internal";
3
2
  import type { Computed } from "@praxisjs/shared";
4
3
 
5
- const instanceCache = new WeakMap<
6
- object,
7
- Map<string, Map<string, Computed<unknown>>>
8
- >();
4
+ import { createMethodDecorator } from "../create-method-decorator";
9
5
 
10
- function getCache(
11
- instance: object,
12
- methodName: string,
13
- argKey: string,
14
- factory: () => unknown,
15
- ): Computed<unknown> {
16
- let methodMap = instanceCache.get(instance);
17
- if (!methodMap) {
18
- methodMap = new Map();
19
- instanceCache.set(instance, methodMap);
20
- }
21
-
22
- let argMap = methodMap.get(methodName);
23
- if (!argMap) {
24
- argMap = new Map();
25
- methodMap.set(methodName, argMap);
26
- }
6
+ const objectIdMap = new WeakMap<object, number>();
7
+ let objectIdCounter = 0;
27
8
 
28
- let cached = argMap.get(argKey);
29
- if (!cached) {
30
- cached = computed(factory);
31
- argMap.set(argKey, cached);
9
+ function objectIdentityKey(obj: object): string {
10
+ if (!objectIdMap.has(obj)) {
11
+ objectIdMap.set(obj, ++objectIdCounter);
32
12
  }
33
-
34
- return cached;
13
+ return `__obj_${String(objectIdMap.get(obj))}__`;
35
14
  }
36
15
 
37
16
  function serializeArgs(args: unknown[]) {
38
17
  if (args.length === 0) return "__no_args__";
39
18
  return args
40
19
  .map((a) => {
41
- if (a === null || typeof a === "object") return JSON.stringify(a);
20
+ if (a === null || typeof a === "object") {
21
+ try {
22
+ return JSON.stringify(a);
23
+ } catch {
24
+ return a === null ? "null" : objectIdentityKey(a);
25
+ }
26
+ }
42
27
  if (typeof a === "symbol") return a.toString();
43
28
  return String(a as string | number | boolean | bigint | undefined);
44
29
  })
@@ -46,17 +31,18 @@ function serializeArgs(args: unknown[]) {
46
31
  }
47
32
 
48
33
  export function Memo() {
49
- return function (
50
- value: (this: object, ...args: unknown[]) => unknown,
51
- context: ClassMethodDecoratorContext<StatefulComponent>,
52
- ): (this: object, ...args: unknown[]) => unknown {
53
- const methodName = context.name as string;
54
- return function (this: object, ...args: unknown[]) {
55
- const argKey = serializeArgs(args);
56
- const memoized = getCache(this, methodName, argKey, () =>
57
- value.apply(this, args),
58
- );
59
- return memoized();
60
- };
61
- };
34
+ return createMethodDecorator({
35
+ wrap(original, instance, _name) {
36
+ const cache = new Map<string, Computed<unknown>>();
37
+ return (...args: unknown[]) => {
38
+ const key = serializeArgs(args);
39
+ let memoized = cache.get(key);
40
+ if (!memoized) {
41
+ memoized = computed(() => original.apply(instance, args));
42
+ cache.set(key, memoized);
43
+ }
44
+ return memoized();
45
+ };
46
+ },
47
+ });
62
48
  }