@praxisjs/decorators 0.4.2 → 0.5.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 (190) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/__tests__/component.test.d.ts +2 -0
  3. package/dist/__tests__/component.test.d.ts.map +1 -0
  4. package/dist/__tests__/component.test.js +146 -0
  5. package/dist/__tests__/component.test.js.map +1 -0
  6. package/dist/__tests__/compose.test.d.ts +2 -0
  7. package/dist/__tests__/compose.test.d.ts.map +1 -0
  8. package/dist/__tests__/compose.test.js +171 -0
  9. package/dist/__tests__/compose.test.js.map +1 -0
  10. package/dist/__tests__/events.test.d.ts +2 -0
  11. package/dist/__tests__/events.test.d.ts.map +1 -0
  12. package/dist/__tests__/events.test.js +216 -0
  13. package/dist/__tests__/events.test.js.map +1 -0
  14. package/dist/__tests__/functions.test.js +283 -31
  15. package/dist/__tests__/functions.test.js.map +1 -1
  16. package/dist/__tests__/lazy.test.d.ts +2 -0
  17. package/dist/__tests__/lazy.test.d.ts.map +1 -0
  18. package/dist/__tests__/lazy.test.js +163 -0
  19. package/dist/__tests__/lazy.test.js.map +1 -0
  20. package/dist/__tests__/memo.test.js +71 -22
  21. package/dist/__tests__/memo.test.js.map +1 -1
  22. package/dist/__tests__/properties.test.js +191 -1
  23. package/dist/__tests__/properties.test.js.map +1 -1
  24. package/dist/__tests__/resource.test.d.ts +2 -0
  25. package/dist/__tests__/resource.test.d.ts.map +1 -0
  26. package/dist/__tests__/resource.test.js +103 -0
  27. package/dist/__tests__/resource.test.js.map +1 -0
  28. package/dist/__tests__/slot.test.d.ts +2 -0
  29. package/dist/__tests__/slot.test.d.ts.map +1 -0
  30. package/dist/__tests__/slot.test.js +147 -0
  31. package/dist/__tests__/slot.test.js.map +1 -0
  32. package/dist/__tests__/virtual.test.d.ts +2 -0
  33. package/dist/__tests__/virtual.test.d.ts.map +1 -0
  34. package/dist/__tests__/virtual.test.js +242 -0
  35. package/dist/__tests__/virtual.test.js.map +1 -0
  36. package/dist/component/component.d.ts +1 -1
  37. package/dist/component/component.d.ts.map +1 -1
  38. package/dist/component/component.js +15 -10
  39. package/dist/component/component.js.map +1 -1
  40. package/dist/component/lazy.d.ts +1 -1
  41. package/dist/component/lazy.d.ts.map +1 -1
  42. package/dist/component/lazy.js +29 -27
  43. package/dist/component/lazy.js.map +1 -1
  44. package/dist/component/virtual.d.ts +1 -1
  45. package/dist/component/virtual.d.ts.map +1 -1
  46. package/dist/component/virtual.js +46 -50
  47. package/dist/component/virtual.js.map +1 -1
  48. package/dist/create-class-decorator.d.ts +16 -0
  49. package/dist/create-class-decorator.d.ts.map +1 -0
  50. package/dist/create-class-decorator.js +35 -0
  51. package/dist/create-class-decorator.js.map +1 -0
  52. package/dist/create-field-decorator.d.ts +14 -0
  53. package/dist/create-field-decorator.d.ts.map +1 -0
  54. package/dist/create-field-decorator.js +37 -0
  55. package/dist/create-field-decorator.js.map +1 -0
  56. package/dist/create-getter-decorator.d.ts +18 -0
  57. package/dist/create-getter-decorator.d.ts.map +1 -0
  58. package/dist/create-getter-decorator.js +15 -0
  59. package/dist/create-getter-decorator.js.map +1 -0
  60. package/dist/create-lifecycle-method-decorator.d.ts +6 -0
  61. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
  62. package/dist/create-lifecycle-method-decorator.js +22 -0
  63. package/dist/create-lifecycle-method-decorator.js.map +1 -0
  64. package/dist/create-method-decorator.d.ts +6 -0
  65. package/dist/create-method-decorator.d.ts.map +1 -0
  66. package/dist/create-method-decorator.js +14 -0
  67. package/dist/create-method-decorator.js.map +1 -0
  68. package/dist/events/emit.d.ts +1 -1
  69. package/dist/events/emit.d.ts.map +1 -1
  70. package/dist/events/emit.js +24 -29
  71. package/dist/events/emit.js.map +1 -1
  72. package/dist/events/on-command.d.ts +1 -2
  73. package/dist/events/on-command.d.ts.map +1 -1
  74. package/dist/events/on-command.js +15 -32
  75. package/dist/events/on-command.js.map +1 -1
  76. package/dist/functions/bind.d.ts +1 -2
  77. package/dist/functions/bind.d.ts.map +1 -1
  78. package/dist/functions/bind.js +4 -11
  79. package/dist/functions/bind.js.map +1 -1
  80. package/dist/functions/debounce.d.ts +1 -2
  81. package/dist/functions/debounce.d.ts.map +1 -1
  82. package/dist/functions/debounce.js +12 -20
  83. package/dist/functions/debounce.js.map +1 -1
  84. package/dist/functions/log.d.ts +1 -2
  85. package/dist/functions/log.d.ts.map +1 -1
  86. package/dist/functions/log.js +40 -38
  87. package/dist/functions/log.js.map +1 -1
  88. package/dist/functions/memo.d.ts +1 -2
  89. package/dist/functions/memo.d.ts.map +1 -1
  90. package/dist/functions/memo.js +15 -27
  91. package/dist/functions/memo.js.map +1 -1
  92. package/dist/functions/once.d.ts +1 -2
  93. package/dist/functions/once.d.ts.map +1 -1
  94. package/dist/functions/once.js +14 -12
  95. package/dist/functions/once.js.map +1 -1
  96. package/dist/functions/retry.d.ts +1 -2
  97. package/dist/functions/retry.d.ts.map +1 -1
  98. package/dist/functions/retry.js +23 -20
  99. package/dist/functions/retry.js.map +1 -1
  100. package/dist/functions/throttle.d.ts +1 -2
  101. package/dist/functions/throttle.d.ts.map +1 -1
  102. package/dist/functions/throttle.js +9 -16
  103. package/dist/functions/throttle.js.map +1 -1
  104. package/dist/functions/watch.d.ts +1 -1
  105. package/dist/functions/watch.d.ts.map +1 -1
  106. package/dist/functions/watch.js +25 -31
  107. package/dist/functions/watch.js.map +1 -1
  108. package/dist/functions/when.d.ts +1 -2
  109. package/dist/functions/when.d.ts.map +1 -1
  110. package/dist/functions/when.js +10 -26
  111. package/dist/functions/when.js.map +1 -1
  112. package/dist/index.d.ts +6 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +6 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/properties/compose.d.ts +3 -0
  117. package/dist/properties/compose.d.ts.map +1 -0
  118. package/dist/properties/compose.js +40 -0
  119. package/dist/properties/compose.js.map +1 -0
  120. package/dist/properties/computed.d.ts +1 -2
  121. package/dist/properties/computed.d.ts.map +1 -1
  122. package/dist/properties/computed.js +10 -9
  123. package/dist/properties/computed.js.map +1 -1
  124. package/dist/properties/history.d.ts.map +1 -1
  125. package/dist/properties/history.js +37 -32
  126. package/dist/properties/history.js.map +1 -1
  127. package/dist/properties/index.d.ts +2 -0
  128. package/dist/properties/index.d.ts.map +1 -1
  129. package/dist/properties/index.js +2 -0
  130. package/dist/properties/index.js.map +1 -1
  131. package/dist/properties/persisted.d.ts +1 -2
  132. package/dist/properties/persisted.d.ts.map +1 -1
  133. package/dist/properties/persisted.js +13 -29
  134. package/dist/properties/persisted.js.map +1 -1
  135. package/dist/properties/prop.d.ts +1 -2
  136. package/dist/properties/prop.d.ts.map +1 -1
  137. package/dist/properties/prop.js +21 -25
  138. package/dist/properties/prop.js.map +1 -1
  139. package/dist/properties/resource.d.ts +14 -0
  140. package/dist/properties/resource.d.ts.map +1 -0
  141. package/dist/properties/resource.js +27 -0
  142. package/dist/properties/resource.js.map +1 -0
  143. package/dist/properties/slot.d.ts +1 -2
  144. package/dist/properties/slot.d.ts.map +1 -1
  145. package/dist/properties/slot.js +15 -20
  146. package/dist/properties/slot.js.map +1 -1
  147. package/dist/properties/state.d.ts +1 -2
  148. package/dist/properties/state.d.ts.map +1 -1
  149. package/dist/properties/state.js +13 -18
  150. package/dist/properties/state.js.map +1 -1
  151. package/package.json +2 -2
  152. package/src/__tests__/component.test.ts +166 -0
  153. package/src/__tests__/compose.test.ts +202 -0
  154. package/src/__tests__/events.test.ts +268 -0
  155. package/src/__tests__/functions.test.ts +368 -30
  156. package/src/__tests__/lazy.test.ts +218 -0
  157. package/src/__tests__/memo.test.ts +89 -22
  158. package/src/__tests__/properties.test.ts +249 -1
  159. package/src/__tests__/resource.test.ts +113 -0
  160. package/src/__tests__/slot.test.ts +179 -0
  161. package/src/__tests__/virtual.test.ts +260 -0
  162. package/src/component/component.ts +27 -12
  163. package/src/component/lazy.ts +33 -35
  164. package/src/component/virtual.tsx +62 -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 +11 -26
  174. package/src/functions/log.ts +40 -43
  175. package/src/functions/memo.ts +15 -45
  176. package/src/functions/once.ts +13 -16
  177. package/src/functions/retry.ts +21 -22
  178. package/src/functions/throttle.ts +9 -23
  179. package/src/functions/watch.ts +27 -34
  180. package/src/functions/when.ts +11 -36
  181. package/src/index.ts +28 -0
  182. package/src/properties/compose.ts +53 -0
  183. package/src/properties/computed.ts +11 -13
  184. package/src/properties/history.ts +42 -41
  185. package/src/properties/index.ts +2 -0
  186. package/src/properties/persisted.ts +17 -58
  187. package/src/properties/prop.ts +21 -31
  188. package/src/properties/resource.ts +34 -0
  189. package/src/properties/slot.ts +17 -30
  190. package/src/properties/state.ts +14 -24
@@ -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,16 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export function Debounce(ms: number) {
4
- const timers = new WeakMap<object, ReturnType<typeof setTimeout>>();
5
-
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);
16
-
17
- const timer = setTimeout(() => {
18
- timers.delete(instance);
19
- value.apply(instance, args);
4
+ return createMethodDecorator({
5
+ wrap(original, instance) {
6
+ let timer: ReturnType<typeof setTimeout> | undefined;
7
+ return (...args: unknown[]) => {
8
+ if (timer !== undefined) clearTimeout(timer);
9
+ timer = setTimeout(() => {
10
+ timer = undefined;
11
+ original.apply(instance, args);
20
12
  }, ms);
21
-
22
- timers.set(instance, timer);
23
13
  };
24
- Object.defineProperty(instance, name, {
25
- value: bound,
26
- configurable: true,
27
- writable: true,
28
- });
29
- });
30
- };
14
+ },
15
+ });
31
16
  }
@@ -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,38 +1,7 @@
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
- >();
9
-
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
- }
27
-
28
- let cached = argMap.get(argKey);
29
- if (!cached) {
30
- cached = computed(factory);
31
- argMap.set(argKey, cached);
32
- }
33
-
34
- return cached;
35
- }
4
+ import { createMethodDecorator } from "../create-method-decorator";
36
5
 
37
6
  function serializeArgs(args: unknown[]) {
38
7
  if (args.length === 0) return "__no_args__";
@@ -46,17 +15,18 @@ function serializeArgs(args: unknown[]) {
46
15
  }
47
16
 
48
17
  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
- };
18
+ return createMethodDecorator({
19
+ wrap(original, instance, _name) {
20
+ const cache = new Map<string, Computed<unknown>>();
21
+ return (...args: unknown[]) => {
22
+ const key = serializeArgs(args);
23
+ let memoized = cache.get(key);
24
+ if (!memoized) {
25
+ memoized = computed(() => original.apply(instance, args));
26
+ cache.set(key, memoized);
27
+ }
28
+ return memoized();
29
+ };
30
+ },
31
+ });
62
32
  }
@@ -1,19 +1,16 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export function Once() {
4
- const called = new WeakMap<object, boolean>();
5
- const results = new WeakMap<object, unknown>();
6
-
7
- return function (
8
- value: (this: object, ...args: unknown[]) => unknown,
9
- _context: ClassMethodDecoratorContext<StatefulComponent>,
10
- ): (this: object, ...args: unknown[]) => unknown {
11
- return function (this: object, ...args: unknown[]) {
12
- if (called.get(this)) return results.get(this);
13
- const result = value.apply(this, args);
14
- called.set(this, true);
15
- results.set(this, result);
16
- return result;
17
- };
18
- };
4
+ return createMethodDecorator({
5
+ wrap(original, instance) {
6
+ let called = false;
7
+ let result: unknown;
8
+ return (...args: unknown[]) => {
9
+ if (called) return result;
10
+ result = original.apply(instance, args);
11
+ called = true;
12
+ return result;
13
+ };
14
+ },
15
+ });
19
16
  }
@@ -1,4 +1,4 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export interface RetryOptions {
4
4
  delay?: number;
@@ -9,30 +9,29 @@ export interface RetryOptions {
9
9
  export function Retry(maxAttempts: number, options: RetryOptions = {}) {
10
10
  const { delay = 0, backoff = 1, onRetry } = options;
11
11
 
12
- return function (
13
- value: (this: object, ...args: unknown[]) => Promise<unknown>,
14
- _context: ClassMethodDecoratorContext<StatefulComponent>,
15
- ): (this: object, ...args: unknown[]) => Promise<unknown> {
16
- return async function (this: object, ...args: unknown[]) {
17
- let lastError: Error = new Error("Unknown error");
18
- let currentDelay = delay;
12
+ return createMethodDecorator({
13
+ wrap(original, instance) {
14
+ return async (...args: unknown[]) => {
15
+ let lastError: Error = new Error("Unknown error");
16
+ let currentDelay = delay;
19
17
 
20
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
21
- try {
22
- return await value.apply(this, args);
23
- } catch (e: unknown) {
24
- lastError = e instanceof Error ? e : new Error(String(e));
25
- if (attempt === maxAttempts) break;
26
- onRetry?.(lastError, attempt);
18
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
19
+ try {
20
+ return await (original.apply(instance, args) as Promise<unknown>);
21
+ } catch (e: unknown) {
22
+ lastError = e instanceof Error ? e : new Error(String(e));
23
+ if (attempt === maxAttempts) break;
24
+ onRetry?.(lastError, attempt);
27
25
 
28
- if (currentDelay > 0) {
29
- await new Promise((res) => setTimeout(res, currentDelay));
30
- currentDelay = Math.round(currentDelay * backoff);
26
+ if (currentDelay > 0) {
27
+ await new Promise((res) => setTimeout(res, currentDelay));
28
+ currentDelay = Math.round(currentDelay * backoff);
29
+ }
31
30
  }
32
31
  }
33
- }
34
32
 
35
- throw lastError;
36
- };
37
- };
33
+ throw lastError;
34
+ };
35
+ },
36
+ });
38
37
  }
@@ -1,29 +1,15 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export function Throttle(ms: number) {
4
- const lastRun = new WeakMap<object, number>();
5
-
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[]) => {
4
+ return createMethodDecorator({
5
+ wrap(original, instance) {
6
+ let last = 0;
7
+ return (...args: unknown[]) => {
14
8
  const now = Date.now();
15
- const last = lastRun.get(instance) ?? 0;
16
-
17
9
  if (now - last < ms) return;
18
-
19
- lastRun.set(instance, now);
20
- return value.apply(instance, args);
10
+ last = now;
11
+ return original.apply(instance, args);
21
12
  };
22
- Object.defineProperty(instance, name, {
23
- value: bound,
24
- configurable: true,
25
- writable: true,
26
- });
27
- });
28
- };
13
+ },
14
+ });
29
15
  }
@@ -3,6 +3,8 @@ import { effect } from "@praxisjs/core/internal";
3
3
  import type { Computed } from "@praxisjs/shared";
4
4
  import { isComputed } from "@praxisjs/shared/internal";
5
5
 
6
+ import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
7
+
6
8
  type BaseComponentKeys = keyof StatefulComponent;
7
9
 
8
10
  type WatchableKeys<T> = {
@@ -54,42 +56,33 @@ export function Watch<
54
56
  T extends StatefulComponent,
55
57
  const Keys extends ReadonlyArray<WatchableKeys<T>>,
56
58
  >(...propNames: ValidateKeys<T, Keys>) {
57
- return function (
58
- value: (this: T, ...args: unknown[]) => void,
59
- context: ClassMethodDecoratorContext<T>,
60
- ): void {
61
- const props = propNames as unknown as string[];
59
+ const props = propNames as unknown as string[];
62
60
 
63
- context.addInitializer(function (this: unknown) {
64
- const instance = this as T & Record<string, unknown>;
65
- // eslint-disable-next-line @typescript-eslint/unbound-method
66
- const originalOnMount = instance.onMount;
61
+ return createLifecycleMethodDecorator<T>({
62
+ register(callback, instance) {
63
+ const inst = instance as T & Record<string, unknown>;
67
64
 
68
- instance.onMount = function (this: T & Record<string, unknown>) {
69
- originalOnMount?.call(this);
65
+ if (props.length === 1) {
66
+ let oldVal = readValue(inst, props[0]);
67
+ return effect(() => {
68
+ const newVal = readValue(inst, props[0]);
69
+ if (!Object.is(newVal, oldVal)) {
70
+ callback(newVal, oldVal);
71
+ oldVal = newVal;
72
+ }
73
+ });
74
+ }
70
75
 
71
- if (props.length === 1) {
72
- let oldVal = readValue(this, props[0]);
73
- effect(() => {
74
- const newVal = readValue(this, props[0]);
75
- if (!Object.is(newVal, oldVal)) {
76
- value.call(this as T, newVal, oldVal);
77
- oldVal = newVal;
78
- }
79
- });
80
- } else {
81
- const read = () =>
82
- Object.fromEntries(props.map((p) => [p, readValue(this, p)]));
83
- let oldVals = read();
84
- effect(() => {
85
- const newVals = read();
86
- if (props.some((p) => !Object.is(newVals[p], oldVals[p]))) {
87
- value.call(this as T, newVals, oldVals);
88
- oldVals = newVals;
89
- }
90
- });
76
+ const read = () =>
77
+ Object.fromEntries(props.map((p) => [p, readValue(inst, p)]));
78
+ let oldVals = read();
79
+ return effect(() => {
80
+ const newVals = read();
81
+ if (props.some((p) => !Object.is(newVals[p], oldVals[p]))) {
82
+ callback(newVals, oldVals);
83
+ oldVals = newVals;
91
84
  }
92
- };
93
- });
94
- };
85
+ });
86
+ },
87
+ });
95
88
  }
@@ -1,42 +1,17 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
1
  import { when } from "@praxisjs/core/internal";
3
2
  import type { Computed, Signal } from "@praxisjs/shared";
4
3
  import { isComputed } from "@praxisjs/shared/internal";
5
4
 
6
- export function When(propName: string) {
7
- return function (
8
- value: (this: StatefulComponent, val: unknown) => void,
9
- context: ClassMethodDecoratorContext<StatefulComponent>,
10
- ): void {
11
- const cancels = new WeakMap<object, () => void>();
12
-
13
- context.addInitializer(function (this: unknown) {
14
- const instance = this as StatefulComponent;
15
- // eslint-disable-next-line @typescript-eslint/unbound-method
16
- const originalOnMount = instance.onMount;
17
- // eslint-disable-next-line @typescript-eslint/unbound-method
18
- const originalOnUnmount = instance.onUnmount;
19
-
20
- instance.onMount = function (this: StatefulComponent) {
21
- originalOnMount?.call(this);
22
-
23
- const raw = (this as unknown as Record<string, unknown>)[propName];
24
- const source = isComputed(raw)
25
- ? (raw as Computed<unknown>)
26
- : (raw as Signal<unknown>);
5
+ import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
27
6
 
28
- const cancel = when(source, (val) => {
29
- value.call(this, val);
30
- });
31
-
32
- cancels.set(this, cancel);
33
- };
34
-
35
- instance.onUnmount = function (this: StatefulComponent) {
36
- originalOnUnmount?.call(this);
37
- cancels.get(this)?.();
38
- cancels.delete(this);
39
- };
40
- });
41
- };
7
+ export function When(propName: string) {
8
+ return createLifecycleMethodDecorator({
9
+ register(callback, instance) {
10
+ const raw = (instance as unknown as Record<string, unknown>)[propName];
11
+ const source = isComputed(raw)
12
+ ? (raw as Computed<unknown>)
13
+ : (raw as Signal<unknown>);
14
+ return when(source, (val) => { callback(val); });
15
+ },
16
+ });
42
17
  }
package/src/index.ts CHANGED
@@ -21,5 +21,33 @@ export {
21
21
  initSlots,
22
22
  History,
23
23
  type WithHistory,
24
+ Compose,
25
+ Resource,
26
+ type ResourceInstance,
27
+ type ResourceOptions,
24
28
  } from "./properties";
25
29
  export { type Command, createCommand, Emit, OnCommand } from "./events";
30
+ export {
31
+ createFieldDecorator,
32
+ type FieldBehavior,
33
+ type FieldBinding,
34
+ } from "./create-field-decorator";
35
+ export {
36
+ createClassDecorator,
37
+ ClassBehavior,
38
+ type ClassEnhancement,
39
+ } from "./create-class-decorator";
40
+ export {
41
+ createMethodDecorator,
42
+ type MethodBehavior,
43
+ } from "./create-method-decorator";
44
+ export {
45
+ createLifecycleMethodDecorator,
46
+ type LifecycleMethodBehavior,
47
+ } from "./create-lifecycle-method-decorator";
48
+ export {
49
+ createGetterDecorator,
50
+ type GetterBehavior,
51
+ createGetterObserverDecorator,
52
+ type GetterObserverBehavior,
53
+ } from "./create-getter-decorator";
@@ -0,0 +1,53 @@
1
+ import type { StatefulComponent, Composable } from "@praxisjs/core";
2
+
3
+ import { createFieldDecorator } from "../create-field-decorator";
4
+
5
+ function isReactive(
6
+ value: unknown,
7
+ ): value is () => unknown {
8
+ return (
9
+ typeof value === "function" &&
10
+ ("__isSignal" in (value as object) || "__isComputed" in (value as object))
11
+ );
12
+ }
13
+
14
+ function buildView(bindings: Record<string, unknown>): object {
15
+ const view: Record<string, unknown> = {};
16
+ for (const [key, value] of Object.entries(bindings)) {
17
+ if (isReactive(value)) {
18
+ Object.defineProperty(view, key, {
19
+ get: () => (value as () => unknown)(),
20
+ enumerable: true,
21
+ configurable: true,
22
+ });
23
+ } else {
24
+ view[key] = value;
25
+ }
26
+ }
27
+ return view;
28
+ }
29
+
30
+ export function Compose(
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ ComposableClass: new (...args: any[]) => Composable,
33
+ ...ctorArgs: unknown[]
34
+ ) {
35
+ return createFieldDecorator({
36
+ bind(instance: StatefulComponent, _name: string, _initialValue: unknown) {
37
+ const resolvedArgs = ctorArgs.map((arg) =>
38
+ typeof arg === "string"
39
+ ? (instance as unknown as Record<string, unknown>)[arg]
40
+ : arg,
41
+ );
42
+
43
+ const composable = new ComposableClass(...resolvedArgs);
44
+ const view = buildView(composable.setup());
45
+
46
+ return {
47
+ descriptor: { get: () => view },
48
+ onMount: composable.onMount?.bind(composable),
49
+ onUnmount: composable.onUnmount?.bind(composable),
50
+ };
51
+ },
52
+ });
53
+ }