@praxisjs/decorators 1.7.0 → 2.0.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 (266) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/component/component.d.ts +1 -1
  3. package/dist/component/component.d.ts.map +1 -1
  4. package/dist/component/component.js +8 -15
  5. package/dist/component/component.js.map +1 -1
  6. package/dist/component/lazy.d.ts +1 -1
  7. package/dist/component/lazy.d.ts.map +1 -1
  8. package/dist/component/lazy.js +44 -36
  9. package/dist/component/lazy.js.map +1 -1
  10. package/dist/create-class-decorator.d.ts +27 -5
  11. package/dist/create-class-decorator.d.ts.map +1 -1
  12. package/dist/create-class-decorator.js +53 -18
  13. package/dist/create-class-decorator.js.map +1 -1
  14. package/dist/create-field-decorator.d.ts +42 -3
  15. package/dist/create-field-decorator.d.ts.map +1 -1
  16. package/dist/create-field-decorator.js +63 -27
  17. package/dist/create-field-decorator.js.map +1 -1
  18. package/dist/create-getter-decorator.d.ts +81 -16
  19. package/dist/create-getter-decorator.d.ts.map +1 -1
  20. package/dist/create-getter-decorator.js +127 -15
  21. package/dist/create-getter-decorator.js.map +1 -1
  22. package/dist/create-lifecycle-method-decorator.d.ts +21 -3
  23. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
  24. package/dist/create-lifecycle-method-decorator.js +25 -18
  25. package/dist/create-lifecycle-method-decorator.js.map +1 -1
  26. package/dist/create-method-decorator.d.ts +43 -3
  27. package/dist/create-method-decorator.d.ts.map +1 -1
  28. package/dist/create-method-decorator.js +49 -8
  29. package/dist/create-method-decorator.js.map +1 -1
  30. package/dist/events/command.d.ts +2 -1
  31. package/dist/events/command.d.ts.map +1 -1
  32. package/dist/events/command.js +10 -8
  33. package/dist/events/command.js.map +1 -1
  34. package/dist/events/emit.d.ts +2 -2
  35. package/dist/events/emit.d.ts.map +1 -1
  36. package/dist/events/emit.js +18 -24
  37. package/dist/events/emit.js.map +1 -1
  38. package/dist/events/on-command.d.ts +2 -1
  39. package/dist/events/on-command.d.ts.map +1 -1
  40. package/dist/events/on-command.js +12 -14
  41. package/dist/events/on-command.js.map +1 -1
  42. package/dist/field.d.ts +51 -0
  43. package/dist/field.d.ts.map +1 -0
  44. package/dist/field.js +82 -0
  45. package/dist/field.js.map +1 -0
  46. package/dist/functions/bind.d.ts +2 -1
  47. package/dist/functions/bind.d.ts.map +1 -1
  48. package/dist/functions/bind.js +4 -4
  49. package/dist/functions/bind.js.map +1 -1
  50. package/dist/functions/debounce.d.ts +2 -2
  51. package/dist/functions/debounce.d.ts.map +1 -1
  52. package/dist/functions/debounce.js +25 -23
  53. package/dist/functions/debounce.js.map +1 -1
  54. package/dist/functions/log.d.ts +2 -1
  55. package/dist/functions/log.d.ts.map +1 -1
  56. package/dist/functions/log.js +38 -40
  57. package/dist/functions/log.js.map +1 -1
  58. package/dist/functions/memo.d.ts +2 -1
  59. package/dist/functions/memo.d.ts.map +1 -1
  60. package/dist/functions/memo.js +41 -40
  61. package/dist/functions/memo.js.map +1 -1
  62. package/dist/functions/once.d.ts +2 -1
  63. package/dist/functions/once.d.ts.map +1 -1
  64. package/dist/functions/once.js +10 -14
  65. package/dist/functions/once.js.map +1 -1
  66. package/dist/functions/retry.d.ts +2 -1
  67. package/dist/functions/retry.d.ts.map +1 -1
  68. package/dist/functions/retry.js +21 -23
  69. package/dist/functions/retry.js.map +1 -1
  70. package/dist/functions/throttle.d.ts +2 -1
  71. package/dist/functions/throttle.d.ts.map +1 -1
  72. package/dist/functions/throttle.js +12 -14
  73. package/dist/functions/throttle.js.map +1 -1
  74. package/dist/functions/until.d.ts +2 -1
  75. package/dist/functions/until.d.ts.map +1 -1
  76. package/dist/functions/until.js +13 -14
  77. package/dist/functions/until.js.map +1 -1
  78. package/dist/functions/watch.d.ts +1 -1
  79. package/dist/functions/watch.d.ts.map +1 -1
  80. package/dist/functions/watch.js +36 -34
  81. package/dist/functions/watch.js.map +1 -1
  82. package/dist/functions/when.d.ts +1 -1
  83. package/dist/functions/when.d.ts.map +1 -1
  84. package/dist/functions/when.js +19 -21
  85. package/dist/functions/when.js.map +1 -1
  86. package/dist/index.d.ts +5 -5
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +2 -2
  89. package/dist/index.js.map +1 -1
  90. package/dist/lifecycle.d.ts +24 -0
  91. package/dist/lifecycle.d.ts.map +1 -0
  92. package/dist/lifecycle.js +55 -0
  93. package/dist/lifecycle.js.map +1 -0
  94. package/dist/properties/compose.d.ts +3 -2
  95. package/dist/properties/compose.d.ts.map +1 -1
  96. package/dist/properties/compose.js +33 -19
  97. package/dist/properties/compose.js.map +1 -1
  98. package/dist/properties/computed.d.ts +12 -10
  99. package/dist/properties/computed.d.ts.map +1 -1
  100. package/dist/properties/computed.js +26 -21
  101. package/dist/properties/computed.js.map +1 -1
  102. package/dist/properties/deep-state.d.ts +2 -1
  103. package/dist/properties/deep-state.d.ts.map +1 -1
  104. package/dist/properties/deep-state.js +66 -60
  105. package/dist/properties/deep-state.js.map +1 -1
  106. package/dist/properties/function-prop.d.ts +2 -1
  107. package/dist/properties/function-prop.d.ts.map +1 -1
  108. package/dist/properties/function-prop.js +20 -22
  109. package/dist/properties/function-prop.js.map +1 -1
  110. package/dist/properties/history.d.ts +2 -2
  111. package/dist/properties/history.d.ts.map +1 -1
  112. package/dist/properties/history.js +34 -33
  113. package/dist/properties/history.js.map +1 -1
  114. package/dist/properties/index.d.ts +1 -0
  115. package/dist/properties/index.d.ts.map +1 -1
  116. package/dist/properties/index.js +1 -0
  117. package/dist/properties/index.js.map +1 -1
  118. package/dist/properties/persisted.d.ts +2 -1
  119. package/dist/properties/persisted.d.ts.map +1 -1
  120. package/dist/properties/persisted.js +24 -18
  121. package/dist/properties/persisted.js.map +1 -1
  122. package/dist/properties/prop.d.ts +2 -1
  123. package/dist/properties/prop.d.ts.map +1 -1
  124. package/dist/properties/prop.js +26 -22
  125. package/dist/properties/prop.js.map +1 -1
  126. package/dist/properties/ref.d.ts +2 -1
  127. package/dist/properties/ref.d.ts.map +1 -1
  128. package/dist/properties/ref.js +11 -8
  129. package/dist/properties/ref.js.map +1 -1
  130. package/dist/properties/resource.d.ts.map +1 -1
  131. package/dist/properties/resource.js +21 -16
  132. package/dist/properties/resource.js.map +1 -1
  133. package/dist/properties/selector.d.ts +12 -0
  134. package/dist/properties/selector.d.ts.map +1 -0
  135. package/dist/properties/selector.js +25 -0
  136. package/dist/properties/selector.js.map +1 -0
  137. package/dist/properties/slot.d.ts +2 -1
  138. package/dist/properties/slot.d.ts.map +1 -1
  139. package/dist/properties/slot.js +22 -20
  140. package/dist/properties/slot.js.map +1 -1
  141. package/dist/properties/state.d.ts.map +1 -1
  142. package/dist/properties/state.js +27 -16
  143. package/dist/properties/state.js.map +1 -1
  144. package/dist/properties/synced.d.ts +2 -1
  145. package/dist/properties/synced.d.ts.map +1 -1
  146. package/dist/properties/synced.js +27 -19
  147. package/dist/properties/synced.js.map +1 -1
  148. package/dist/shared-method.d.ts +14 -0
  149. package/dist/shared-method.d.ts.map +1 -0
  150. package/dist/shared-method.js +87 -0
  151. package/dist/shared-method.js.map +1 -0
  152. package/package.json +14 -5
  153. package/src/component/component.ts +8 -27
  154. package/src/component/lazy.ts +51 -41
  155. package/src/create-class-decorator.ts +90 -31
  156. package/src/create-field-decorator.ts +120 -43
  157. package/src/create-getter-decorator.ts +263 -66
  158. package/src/create-lifecycle-method-decorator.ts +50 -28
  159. package/src/create-method-decorator.ts +119 -13
  160. package/src/events/command.ts +11 -9
  161. package/src/events/emit.ts +16 -22
  162. package/src/events/on-command.ts +17 -18
  163. package/src/field.ts +116 -0
  164. package/src/functions/bind.ts +4 -4
  165. package/src/functions/debounce.ts +25 -23
  166. package/src/functions/log.ts +44 -46
  167. package/src/functions/memo.ts +39 -37
  168. package/src/functions/once.ts +10 -13
  169. package/src/functions/retry.ts +19 -21
  170. package/src/functions/throttle.ts +12 -13
  171. package/src/functions/until.ts +14 -14
  172. package/src/functions/watch.ts +37 -36
  173. package/src/functions/when.ts +18 -20
  174. package/src/index.ts +12 -5
  175. package/src/lifecycle.ts +74 -0
  176. package/src/properties/compose.ts +33 -20
  177. package/src/properties/computed.ts +35 -45
  178. package/src/properties/deep-state.ts +66 -64
  179. package/src/properties/function-prop.ts +25 -27
  180. package/src/properties/history.ts +34 -35
  181. package/src/properties/index.ts +1 -0
  182. package/src/properties/persisted.ts +30 -20
  183. package/src/properties/prop.ts +30 -22
  184. package/src/properties/ref.ts +12 -10
  185. package/src/properties/resource.ts +22 -17
  186. package/src/properties/selector.ts +45 -0
  187. package/src/properties/slot.ts +28 -22
  188. package/src/properties/state.ts +32 -16
  189. package/src/properties/synced.ts +31 -19
  190. package/src/shared-method.ts +101 -0
  191. package/dist/__tests__/component.test.d.ts +0 -2
  192. package/dist/__tests__/component.test.d.ts.map +0 -1
  193. package/dist/__tests__/component.test.js +0 -147
  194. package/dist/__tests__/component.test.js.map +0 -1
  195. package/dist/__tests__/compose.test.d.ts +0 -2
  196. package/dist/__tests__/compose.test.d.ts.map +0 -1
  197. package/dist/__tests__/compose.test.js +0 -164
  198. package/dist/__tests__/compose.test.js.map +0 -1
  199. package/dist/__tests__/deep-state.test.d.ts +0 -2
  200. package/dist/__tests__/deep-state.test.d.ts.map +0 -1
  201. package/dist/__tests__/deep-state.test.js +0 -221
  202. package/dist/__tests__/deep-state.test.js.map +0 -1
  203. package/dist/__tests__/events.test.d.ts +0 -2
  204. package/dist/__tests__/events.test.d.ts.map +0 -1
  205. package/dist/__tests__/events.test.js +0 -295
  206. package/dist/__tests__/events.test.js.map +0 -1
  207. package/dist/__tests__/functions.test.d.ts +0 -2
  208. package/dist/__tests__/functions.test.d.ts.map +0 -1
  209. package/dist/__tests__/functions.test.js +0 -618
  210. package/dist/__tests__/functions.test.js.map +0 -1
  211. package/dist/__tests__/lazy.test.d.ts +0 -2
  212. package/dist/__tests__/lazy.test.d.ts.map +0 -1
  213. package/dist/__tests__/lazy.test.js +0 -227
  214. package/dist/__tests__/lazy.test.js.map +0 -1
  215. package/dist/__tests__/memo.test.d.ts +0 -2
  216. package/dist/__tests__/memo.test.d.ts.map +0 -1
  217. package/dist/__tests__/memo.test.js +0 -249
  218. package/dist/__tests__/memo.test.js.map +0 -1
  219. package/dist/__tests__/properties-browser.test.d.ts +0 -2
  220. package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
  221. package/dist/__tests__/properties-browser.test.js +0 -227
  222. package/dist/__tests__/properties-browser.test.js.map +0 -1
  223. package/dist/__tests__/properties.test.d.ts +0 -2
  224. package/dist/__tests__/properties.test.d.ts.map +0 -1
  225. package/dist/__tests__/properties.test.js +0 -1253
  226. package/dist/__tests__/properties.test.js.map +0 -1
  227. package/dist/__tests__/ref.test.d.ts +0 -2
  228. package/dist/__tests__/ref.test.d.ts.map +0 -1
  229. package/dist/__tests__/ref.test.js +0 -106
  230. package/dist/__tests__/ref.test.js.map +0 -1
  231. package/dist/__tests__/resource.test.d.ts +0 -2
  232. package/dist/__tests__/resource.test.d.ts.map +0 -1
  233. package/dist/__tests__/resource.test.js +0 -217
  234. package/dist/__tests__/resource.test.js.map +0 -1
  235. package/dist/__tests__/slot.test.d.ts +0 -2
  236. package/dist/__tests__/slot.test.d.ts.map +0 -1
  237. package/dist/__tests__/slot.test.js +0 -147
  238. package/dist/__tests__/slot.test.js.map +0 -1
  239. package/dist/__tests__/synced.test.d.ts +0 -2
  240. package/dist/__tests__/synced.test.d.ts.map +0 -1
  241. package/dist/__tests__/synced.test.js +0 -161
  242. package/dist/__tests__/synced.test.js.map +0 -1
  243. package/dist/__tests__/until.test.d.ts +0 -2
  244. package/dist/__tests__/until.test.d.ts.map +0 -1
  245. package/dist/__tests__/until.test.js +0 -88
  246. package/dist/__tests__/until.test.js.map +0 -1
  247. package/dist/create-accessor-decorator.d.ts +0 -14
  248. package/dist/create-accessor-decorator.d.ts.map +0 -1
  249. package/dist/create-accessor-decorator.js +0 -27
  250. package/dist/create-accessor-decorator.js.map +0 -1
  251. package/src/__tests__/component.test.ts +0 -167
  252. package/src/__tests__/compose.test.ts +0 -202
  253. package/src/__tests__/deep-state.test.ts +0 -265
  254. package/src/__tests__/events.test.ts +0 -366
  255. package/src/__tests__/functions.test.ts +0 -803
  256. package/src/__tests__/lazy.test.ts +0 -296
  257. package/src/__tests__/memo.test.ts +0 -293
  258. package/src/__tests__/properties-browser.test.ts +0 -288
  259. package/src/__tests__/properties.test.ts +0 -1556
  260. package/src/__tests__/ref.test.ts +0 -131
  261. package/src/__tests__/resource.test.ts +0 -246
  262. package/src/__tests__/slot.test.ts +0 -179
  263. package/src/__tests__/synced.test.ts +0 -193
  264. package/src/__tests__/until.test.ts +0 -113
  265. package/src/create-accessor-decorator.ts +0 -45
  266. package/tsconfig.json +0 -9
@@ -1,31 +1,25 @@
1
1
  import type { StatefulComponent } from "@praxisjs/core";
2
2
 
3
- import { createMethodDecorator } from "../create-method-decorator";
3
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
4
4
  import { readProp } from "./helper";
5
5
 
6
6
  export function Emit(propName: string) {
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
12
- const result = original.apply(instance, args);
7
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
8
+ return sharedMethod(context, value, function (this: StatefulComponent, ...args: unknown[]): unknown {
9
+ const result = value.apply(this, args) as unknown;
13
10
 
14
- const callback = readProp(comp, propName);
15
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
16
- if (typeof callback !== "function") return result;
11
+ const callback = readProp(this, propName);
12
+ if (typeof callback !== "function") return result;
17
13
 
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
- }
14
+ if (result !== undefined) {
15
+ (callback as (v: unknown) => void)(result);
16
+ } else if (args.length > 0) {
17
+ (callback as (...a: unknown[]) => void)(...args);
18
+ } else {
19
+ (callback as () => void)();
20
+ }
25
21
 
26
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
27
- return result;
28
- };
29
- },
30
- });
22
+ return result;
23
+ });
24
+ };
31
25
  }
@@ -1,29 +1,28 @@
1
+ import type { StatefulComponent } from "@praxisjs/core";
1
2
 
2
- import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
3
+ import { lifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
3
4
  import { readProp } from "./helper";
4
5
 
5
6
  import type { Command } from "./command";
6
7
 
7
8
  export function OnCommand(propName: string) {
8
- return createLifecycleMethodDecorator({
9
- register(callback, instance) {
10
- const command = readProp(instance, propName) as Command<unknown> | undefined;
9
+ return lifecycleMethodDecorator<StatefulComponent>((instance, method) => {
10
+ const command = readProp(instance, propName) as Command<unknown> | undefined;
11
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
- }
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
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
- }
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
25
 
26
- return command.subscribe((...args: unknown[]) => { callback(...args); });
27
- },
26
+ return command.subscribe((...args: unknown[]) => { method.apply(instance, args); });
28
27
  });
29
28
  }
package/src/field.ts ADDED
@@ -0,0 +1,116 @@
1
+ import type { StatefulComponent } from "@praxisjs/core";
2
+
3
+ /**
4
+ * The signature of a field decorator: usable on fields of classes extending `Base` whose type is
5
+ * assignable to `Value`, so TypeScript rejects `@Clamp() name = ''` for a number-only decorator.
6
+ */
7
+ export type FieldDecorator<Base = StatefulComponent, Value = unknown> = <This extends Base, V extends Value = Value>(
8
+ value: undefined,
9
+ context: ClassFieldDecoratorContext<This, V>,
10
+ ) => void;
11
+
12
+ export type FieldHost = Record<PropertyKey, unknown>;
13
+
14
+ const KIND_NAMES: Record<DecoratorContext["kind"], string> = {
15
+ class: "classes",
16
+ method: "methods",
17
+ getter: "getters",
18
+ setter: "setters",
19
+ field: "class fields",
20
+ accessor: "accessors",
21
+ };
22
+
23
+ export function describeValue(value: unknown): string {
24
+ if (typeof value === "function") return "a function";
25
+ if (typeof value === "object" && value !== null) return "an object";
26
+ return String(value as string | number | bigint | boolean | symbol | null | undefined);
27
+ }
28
+
29
+ /**
30
+ * Throws when a decorator built for one kind of member is applied to another, naming the member,
31
+ * e.g. `@Confirm() decorates methods only, not the field "title".`
32
+ */
33
+ export function assertMemberKind(
34
+ context: { readonly kind: DecoratorContext["kind"]; readonly name: string | symbol | undefined },
35
+ kind: DecoratorContext["kind"],
36
+ decorator: string,
37
+ ): void {
38
+ // A context without a kind was built by hand (decorators called directly in tests), not by a
39
+ // decorator application, which always has one.
40
+ if (context.kind === kind || (context.kind as string | undefined) === undefined) return;
41
+ throw new TypeError(
42
+ `${decorator} decorates ${KIND_NAMES[kind]} only, not the ${context.kind} "${String(context.name)}".`,
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Throws when a field decorator is applied to anything but a plain class field. Above all an
48
+ * `accessor`: the decorator would install its property on the instance, shadowing the accessor,
49
+ * and decorators stacked on the same member would wrap the wrong thing.
50
+ */
51
+ export function assertFieldContext(context: DecoratorContext, decorator = "This field decorator"): void {
52
+ if (context.kind === "field") return;
53
+ const member = String(context.name);
54
+ throw new TypeError(
55
+ context.kind === "accessor"
56
+ ? `${decorator} decorates plain class fields only: remove \`accessor\` from "${member}".`
57
+ : `${decorator} decorates class fields only, not the ${context.kind} "${member}".`,
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Lets the decorator lowering of `@praxisjs/vite-plugin` define a decorated field through its
63
+ * innermost decorator: an initializer added with `addClaimableInitializer` carries a `claim`
64
+ * function under this key, which installs the decorator's property directly with the field's
65
+ * initial value. The field is then never defined as a data property first, which in V8 would turn
66
+ * every instance into a slow dictionary-mode object when the accessor replaces it. Without the
67
+ * plugin (or under another decorator in the innermost position), the field is defined as usual and
68
+ * the initializer runs as an ordinary one.
69
+ */
70
+ export const CLAIM = Symbol.for("praxisjs.field.claim");
71
+
72
+ /**
73
+ * Registers `claim` as the field's initializer: run as an ordinary extra initializer, it reads the
74
+ * value from the data property the field was defined with; the lowering can call it with the
75
+ * value instead (see CLAIM).
76
+ */
77
+ export function addClaimableInitializer<This, Value>(
78
+ context: ClassFieldDecoratorContext<This, Value>,
79
+ claim: (this: This, value: Value) => void,
80
+ ): void {
81
+ const name = context.name;
82
+ const init = function (this: This) {
83
+ claim.call(this, (this as FieldHost)[name] as Value);
84
+ } as ((this: This) => void) & { [CLAIM]?: typeof claim };
85
+ init[CLAIM] = claim;
86
+ context.addInitializer(init);
87
+ }
88
+
89
+ export interface SharedField {
90
+ /** Installed on every instance in place of the field. */
91
+ descriptor: PropertyDescriptor;
92
+ /** Runs per instance before the descriptor is installed, with the field's initial value. */
93
+ init?: (instance: FieldHost, initialValue: unknown) => void;
94
+ }
95
+
96
+ /**
97
+ * Builds a field decorator whose accessors are created once per decorated field (by `setup`, at
98
+ * class definition) and shared by every instance, which keeps its own state in symbol slots: all
99
+ * instances share one hidden class and building one allocates no closures.
100
+ */
101
+ export function sharedField<Base = StatefulComponent>(
102
+ setup: (name: string) => SharedField,
103
+ ): FieldDecorator<Base> {
104
+ return function (_value, context) {
105
+ assertFieldContext(context);
106
+ const name = context.name as string;
107
+ const { descriptor, init } = setup(name);
108
+ descriptor.enumerable ??= true;
109
+ descriptor.configurable ??= true;
110
+ addClaimableInitializer(context, function (value) {
111
+ const instance = this as unknown as FieldHost;
112
+ init?.(instance, value);
113
+ Object.defineProperty(instance, name, descriptor);
114
+ });
115
+ };
116
+ }
@@ -1,7 +1,7 @@
1
- import { createMethodDecorator } from "../create-method-decorator";
1
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
2
 
3
3
  export function Bind() {
4
- return createMethodDecorator({
5
- wrap: (original, instance) => original.bind(instance),
6
- });
4
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
5
+ return sharedMethod(context, value);
6
+ };
7
7
  }
@@ -1,29 +1,31 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { addLifecycleHooks } from "../lifecycle";
2
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
3
 
3
- import { createMethodDecorator } from "../create-method-decorator";
4
+ type Timer = ReturnType<typeof setTimeout>;
4
5
 
5
6
  export function Debounce(ms: number) {
6
- return createMethodDecorator({
7
- wrap(original, instance, _name) {
8
- let timer: ReturnType<typeof setTimeout> | undefined;
7
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
8
+ // undefined: never called. null: called, nothing pending.
9
+ const timer = Symbol(`praxis.debounce.${String(context.name)}`);
10
+ type Host = Record<symbol, Timer | null | undefined>;
9
11
 
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
- };
12
+ function cancel(instance: object): void {
13
+ const pending = (instance as Host)[timer];
14
+ if (pending != null) {
15
+ clearTimeout(pending);
16
+ (instance as Host)[timer] = null;
17
+ }
18
+ }
19
19
 
20
- return (...args: unknown[]) => {
21
- if (timer !== undefined) clearTimeout(timer);
22
- timer = setTimeout(() => {
23
- timer = undefined;
24
- original.apply(instance, args);
25
- }, ms);
26
- };
27
- },
28
- });
20
+ return sharedMethod(context, value, function (this: Host, ...args: unknown[]): void {
21
+ const pending = this[timer];
22
+ // Only an instance that has called the method can have a timer to cancel on unmount.
23
+ if (pending === undefined) addLifecycleHooks(this, undefined, cancel);
24
+ else if (pending !== null) clearTimeout(pending);
25
+ this[timer] = setTimeout(() => {
26
+ this[timer] = null;
27
+ value.apply(this, args);
28
+ }, ms);
29
+ });
30
+ };
29
31
  }
@@ -1,4 +1,4 @@
1
- import { createMethodDecorator } from "../create-method-decorator";
1
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
2
 
3
3
  export interface LogOptions {
4
4
  level?: "log" | "warn" | "error" | "debug";
@@ -17,49 +17,47 @@ export function Log(options: LogOptions = {}) {
17
17
  devOnly = true,
18
18
  } = options;
19
19
 
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) as unknown;
29
- }
30
-
31
- const className =
32
- (instance.constructor as { name?: string }).name ?? "Unknown";
33
- const label = `[${className}.${name}]`;
34
- const logger = console[level].bind(console);
35
-
36
- if (logArgs) logger(`${label} args:`, args);
37
-
38
- const start = time ? performance.now() : 0;
39
- const output: unknown = original.apply(instance, args) as unknown;
40
-
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
- }
55
-
56
- const elapsed = time
57
- ? ` (${(performance.now() - start).toFixed(2)}ms)`
58
- : "";
59
- if (result) logger(`${label} returned:`, output, elapsed);
60
-
61
- return output;
62
- };
63
- },
64
- });
20
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
21
+ const name = String(context.name);
22
+ return sharedMethod(context, value, function (this: object, ...args: unknown[]): unknown {
23
+ if (
24
+ devOnly &&
25
+ typeof process !== "undefined" &&
26
+ process.env.NODE_ENV === "production"
27
+ ) {
28
+ return value.apply(this, args) as unknown;
29
+ }
30
+
31
+ const className =
32
+ (this.constructor as { name?: string }).name ?? "Unknown";
33
+ const label = `[${className}.${name}]`;
34
+
35
+ if (logArgs) console[level](`${label} args:`, args);
36
+
37
+ const start = time ? performance.now() : 0;
38
+ const output: unknown = value.apply(this, args) as unknown;
39
+
40
+ if (output instanceof Promise) {
41
+ return (output as Promise<unknown>)
42
+ .then((resolved) => {
43
+ const elapsed = time
44
+ ? ` (${(performance.now() - start).toFixed(2)}ms)`
45
+ : "";
46
+ if (result) console[level](`${label} resolved:`, resolved, elapsed);
47
+ return resolved;
48
+ })
49
+ .catch((e: unknown) => {
50
+ console[level](`${label} rejected:`, e);
51
+ throw e;
52
+ });
53
+ }
54
+
55
+ const elapsed = time
56
+ ? ` (${(performance.now() - start).toFixed(2)}ms)`
57
+ : "";
58
+ if (result) console[level](`${label} returned:`, output, elapsed);
59
+
60
+ return output;
61
+ });
62
+ };
65
63
  }
@@ -1,7 +1,7 @@
1
1
  import { computed } from "@praxisjs/core/internal";
2
2
  import type { Computed } from "@praxisjs/shared";
3
3
 
4
- import { createMethodDecorator } from "../create-method-decorator";
4
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
5
5
 
6
6
  const objectIdMap = new WeakMap<object, number>();
7
7
  let objectIdCounter = 0;
@@ -13,21 +13,23 @@ function objectIdentityKey(obj: object): string {
13
13
  return `__obj_${String(objectIdMap.get(obj))}__`;
14
14
  }
15
15
 
16
+ function serializeArg(a: unknown): string {
17
+ if (a === null || typeof a === "object") {
18
+ try {
19
+ return JSON.stringify(a);
20
+ } catch {
21
+ return objectIdentityKey(a as object);
22
+ }
23
+ }
24
+ if (typeof a === "symbol") return a.toString();
25
+ return String(a as string | number | boolean | bigint | undefined);
26
+ }
27
+
16
28
  function serializeArgs(args: unknown[]) {
17
29
  if (args.length === 0) return "__no_args__";
18
- return args
19
- .map((a) => {
20
- if (a === null || typeof a === "object") {
21
- try {
22
- return JSON.stringify(a);
23
- } catch {
24
- return objectIdentityKey(a as object);
25
- }
26
- }
27
- if (typeof a === "symbol") return a.toString();
28
- return String(a as string | number | boolean | bigint | undefined);
29
- })
30
- .join("|");
30
+ // The common single-argument call skips the intermediate array and join.
31
+ if (args.length === 1) return serializeArg(args[0]);
32
+ return args.map(serializeArg).join("|");
31
33
  }
32
34
 
33
35
  export interface MemoOptions {
@@ -45,28 +47,28 @@ type Disposable = Computed<unknown> & { dispose(): void };
45
47
  export function Memo(options: MemoOptions = {}) {
46
48
  const { max = DEFAULT_MAX } = options;
47
49
 
48
- return createMethodDecorator({
49
- wrap(original, instance, _name) {
50
- const cache = new Map<string, Computed<unknown>>();
51
- return (...args: unknown[]) => {
52
- const key = serializeArgs(args);
53
- let memoized = cache.get(key);
54
- if (memoized) {
55
- // Re-insert so Map order tracks recency.
56
- cache.delete(key);
57
- cache.set(key, memoized);
58
- } else {
59
- memoized = computed(() => original.apply(instance, args) as unknown);
60
- cache.set(key, memoized);
61
- if (cache.size > max) {
62
- const [oldestKey, oldest] = cache.entries().next().value as [string, Computed<unknown>];
63
- cache.delete(oldestKey);
64
- // Releases the entry's hold on the signals it read; a no-op if a binding still uses it.
65
- (oldest as Disposable).dispose();
66
- }
50
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
51
+ const caches = Symbol(`praxis.memo.${String(context.name)}`);
52
+ type Host = Record<symbol, Map<string, Computed<unknown>> | undefined>;
53
+ return sharedMethod(context, value, function (this: Host, ...args: unknown[]): unknown {
54
+ const cache = (this[caches] ??= new Map<string, Computed<unknown>>());
55
+ const key = serializeArgs(args);
56
+ let memoized = cache.get(key);
57
+ if (memoized) {
58
+ // Re-insert so Map order tracks recency.
59
+ cache.delete(key);
60
+ cache.set(key, memoized);
61
+ } else {
62
+ memoized = computed(() => value.apply(this, args) as unknown);
63
+ cache.set(key, memoized);
64
+ if (cache.size > max) {
65
+ const [oldestKey, oldest] = cache.entries().next().value as [string, Computed<unknown>];
66
+ cache.delete(oldestKey);
67
+ // Releases the entry's hold on the signals it read; a no-op if a binding still uses it.
68
+ (oldest as Disposable).dispose();
67
69
  }
68
- return memoized();
69
- };
70
- },
71
- });
70
+ }
71
+ return memoized();
72
+ });
73
+ };
72
74
  }
@@ -1,16 +1,13 @@
1
- import { createMethodDecorator } from "../create-method-decorator";
1
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
2
 
3
3
  export function Once() {
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
- });
4
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
5
+ const result = Symbol(`praxis.once.${String(context.name)}`);
6
+ type Host = Record<symbol, unknown>;
7
+ return sharedMethod(context, value, function (this: Host, ...args: unknown[]): unknown {
8
+ if (result in this) return this[result];
9
+ // Stored only once the call returns, so a call that throws can be retried.
10
+ return (this[result] = value.apply(this, args));
11
+ });
12
+ };
16
13
  }
@@ -1,4 +1,4 @@
1
- import { createMethodDecorator } from "../create-method-decorator";
1
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
2
 
3
3
  export interface RetryOptions {
4
4
  delay?: number;
@@ -9,29 +9,27 @@ 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 createMethodDecorator({
13
- wrap(original, instance) {
14
- return async (...args: unknown[]) => {
15
- let lastError: Error = new Error("Unknown error");
16
- let currentDelay = delay;
12
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
13
+ return sharedMethod(context, value, async function (this: unknown, ...args: unknown[]): Promise<unknown> {
14
+ let lastError: Error = new Error("Unknown error");
15
+ let currentDelay = delay;
17
16
 
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);
17
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
18
+ try {
19
+ return await (value.apply(this, args) as Promise<unknown>);
20
+ } catch (e: unknown) {
21
+ lastError = e instanceof Error ? e : new Error(String(e));
22
+ if (attempt === maxAttempts) break;
23
+ onRetry?.(lastError, attempt);
25
24
 
26
- if (currentDelay > 0) {
27
- await new Promise((res) => setTimeout(res, currentDelay));
28
- currentDelay = Math.round(currentDelay * backoff);
29
- }
25
+ if (currentDelay > 0) {
26
+ await new Promise((res) => setTimeout(res, currentDelay));
27
+ currentDelay = Math.round(currentDelay * backoff);
30
28
  }
31
29
  }
30
+ }
32
31
 
33
- throw lastError;
34
- };
35
- },
36
- });
32
+ throw lastError;
33
+ });
34
+ };
37
35
  }
@@ -1,16 +1,15 @@
1
- import { createMethodDecorator } from "../create-method-decorator";
1
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
2
2
 
3
3
  export function Throttle(ms: number) {
4
- return createMethodDecorator({
5
- wrap(original, instance) {
6
- const clampedMs = Math.max(0, ms);
7
- let last = 0;
8
- return (...args: unknown[]) => {
9
- const now = Date.now();
10
- if (now - last < clampedMs) return;
11
- last = now;
12
- return original.apply(instance, args) as unknown;
13
- };
14
- },
15
- });
4
+ const clampedMs = Math.max(0, ms);
5
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
6
+ const last = Symbol(`praxis.throttle.${String(context.name)}`);
7
+ type Host = Record<symbol, number | undefined>;
8
+ return sharedMethod(context, value, function (this: Host, ...args: unknown[]): unknown {
9
+ const now = Date.now();
10
+ if (now - (this[last] ?? 0) < clampedMs) return;
11
+ this[last] = now;
12
+ return value.apply(this, args);
13
+ });
14
+ };
16
15
  }
@@ -1,19 +1,19 @@
1
- import { until } from "@praxisjs/core/internal";
1
+ import { computed, until } from "@praxisjs/core/internal";
2
2
  import type { Computed, Signal } from "@praxisjs/shared";
3
- import { isComputed } from "@praxisjs/shared/internal";
3
+ import { isReactive } from "@praxisjs/shared/internal";
4
4
 
5
- import { createMethodDecorator } from "../create-method-decorator";
5
+ import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
6
6
 
7
7
  export function Until(propName: string) {
8
- return createMethodDecorator({
9
- wrap(_original, instance) {
10
- return () => {
11
- const raw = (instance as Record<string, unknown>)[propName];
12
- const source = isComputed(raw)
13
- ? (raw as Computed<unknown>)
14
- : (raw as Signal<unknown>);
15
- return until(source);
16
- };
17
- },
18
- });
8
+ return function (value: AnyMethod, context: MethodContext): AnyMethod {
9
+ return sharedMethod(context, value, function (this: Record<string, unknown>) {
10
+ const raw = this[propName];
11
+ // A @State/@Prop field reads as its plain value; reading it inside a computed tracks its signal.
12
+ const source =
13
+ isReactive(raw)
14
+ ? (raw as Signal<unknown> | Computed<unknown>)
15
+ : computed(() => this[propName]);
16
+ return until(source);
17
+ });
18
+ };
19
19
  }