@praxisjs/decorators 1.7.0 → 2.0.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.
- package/CHANGELOG.md +33 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +8 -15
- package/dist/component/component.js.map +1 -1
- package/dist/component/lazy.d.ts +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +44 -36
- package/dist/component/lazy.js.map +1 -1
- package/dist/create-class-decorator.d.ts +27 -5
- package/dist/create-class-decorator.d.ts.map +1 -1
- package/dist/create-class-decorator.js +53 -18
- package/dist/create-class-decorator.js.map +1 -1
- package/dist/create-field-decorator.d.ts +42 -3
- package/dist/create-field-decorator.d.ts.map +1 -1
- package/dist/create-field-decorator.js +63 -27
- package/dist/create-field-decorator.js.map +1 -1
- package/dist/create-getter-decorator.d.ts +81 -16
- package/dist/create-getter-decorator.d.ts.map +1 -1
- package/dist/create-getter-decorator.js +127 -15
- package/dist/create-getter-decorator.js.map +1 -1
- package/dist/create-lifecycle-method-decorator.d.ts +21 -3
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
- package/dist/create-lifecycle-method-decorator.js +25 -18
- package/dist/create-lifecycle-method-decorator.js.map +1 -1
- package/dist/create-method-decorator.d.ts +43 -3
- package/dist/create-method-decorator.d.ts.map +1 -1
- package/dist/create-method-decorator.js +49 -8
- package/dist/create-method-decorator.js.map +1 -1
- package/dist/events/command.d.ts +2 -1
- package/dist/events/command.d.ts.map +1 -1
- package/dist/events/command.js +10 -8
- package/dist/events/command.js.map +1 -1
- package/dist/events/emit.d.ts +2 -2
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +18 -24
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +2 -1
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +12 -14
- package/dist/events/on-command.js.map +1 -1
- package/dist/field.d.ts +51 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +82 -0
- package/dist/field.js.map +1 -0
- package/dist/functions/bind.d.ts +2 -1
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -4
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +2 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +25 -23
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/log.d.ts +2 -1
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +38 -40
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +2 -1
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +41 -40
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +2 -1
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +10 -14
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +2 -1
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +21 -23
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +2 -1
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +12 -14
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/until.d.ts +2 -1
- package/dist/functions/until.d.ts.map +1 -1
- package/dist/functions/until.js +13 -14
- package/dist/functions/until.js.map +1 -1
- package/dist/functions/watch.d.ts +1 -1
- package/dist/functions/watch.d.ts.map +1 -1
- package/dist/functions/watch.js +36 -34
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -1
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +19 -21
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +24 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +55 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/properties/compose.d.ts +3 -2
- package/dist/properties/compose.d.ts.map +1 -1
- package/dist/properties/compose.js +33 -19
- package/dist/properties/compose.js.map +1 -1
- package/dist/properties/computed.d.ts +12 -10
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +26 -21
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/deep-state.d.ts +2 -1
- package/dist/properties/deep-state.d.ts.map +1 -1
- package/dist/properties/deep-state.js +66 -60
- package/dist/properties/deep-state.js.map +1 -1
- package/dist/properties/function-prop.d.ts +2 -1
- package/dist/properties/function-prop.d.ts.map +1 -1
- package/dist/properties/function-prop.js +20 -22
- package/dist/properties/function-prop.js.map +1 -1
- package/dist/properties/history.d.ts +2 -2
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +34 -33
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +1 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +1 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +2 -1
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +24 -18
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +2 -1
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +26 -22
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/ref.d.ts +2 -1
- package/dist/properties/ref.d.ts.map +1 -1
- package/dist/properties/ref.js +11 -8
- package/dist/properties/ref.js.map +1 -1
- package/dist/properties/resource.d.ts.map +1 -1
- package/dist/properties/resource.js +21 -16
- package/dist/properties/resource.js.map +1 -1
- package/dist/properties/selector.d.ts +12 -0
- package/dist/properties/selector.d.ts.map +1 -0
- package/dist/properties/selector.js +25 -0
- package/dist/properties/selector.js.map +1 -0
- package/dist/properties/slot.d.ts +2 -1
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +22 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +27 -16
- package/dist/properties/state.js.map +1 -1
- package/dist/properties/synced.d.ts +2 -1
- package/dist/properties/synced.d.ts.map +1 -1
- package/dist/properties/synced.js +27 -19
- package/dist/properties/synced.js.map +1 -1
- package/dist/shared-method.d.ts +14 -0
- package/dist/shared-method.d.ts.map +1 -0
- package/dist/shared-method.js +87 -0
- package/dist/shared-method.js.map +1 -0
- package/package.json +14 -5
- package/src/component/component.ts +8 -27
- package/src/component/lazy.ts +51 -41
- package/src/create-class-decorator.ts +90 -31
- package/src/create-field-decorator.ts +120 -43
- package/src/create-getter-decorator.ts +263 -66
- package/src/create-lifecycle-method-decorator.ts +50 -28
- package/src/create-method-decorator.ts +119 -13
- package/src/events/command.ts +11 -9
- package/src/events/emit.ts +16 -22
- package/src/events/on-command.ts +17 -18
- package/src/field.ts +116 -0
- package/src/functions/bind.ts +4 -4
- package/src/functions/debounce.ts +25 -23
- package/src/functions/log.ts +44 -46
- package/src/functions/memo.ts +39 -37
- package/src/functions/once.ts +10 -13
- package/src/functions/retry.ts +19 -21
- package/src/functions/throttle.ts +12 -13
- package/src/functions/until.ts +14 -14
- package/src/functions/watch.ts +37 -36
- package/src/functions/when.ts +18 -20
- package/src/index.ts +12 -5
- package/src/lifecycle.ts +74 -0
- package/src/properties/compose.ts +33 -20
- package/src/properties/computed.ts +35 -45
- package/src/properties/deep-state.ts +66 -64
- package/src/properties/function-prop.ts +25 -27
- package/src/properties/history.ts +34 -35
- package/src/properties/index.ts +1 -0
- package/src/properties/persisted.ts +30 -20
- package/src/properties/prop.ts +30 -22
- package/src/properties/ref.ts +12 -10
- package/src/properties/resource.ts +22 -17
- package/src/properties/selector.ts +45 -0
- package/src/properties/slot.ts +28 -22
- package/src/properties/state.ts +32 -16
- package/src/properties/synced.ts +31 -19
- package/src/shared-method.ts +101 -0
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -147
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/compose.test.d.ts +0 -2
- package/dist/__tests__/compose.test.d.ts.map +0 -1
- package/dist/__tests__/compose.test.js +0 -164
- package/dist/__tests__/compose.test.js.map +0 -1
- package/dist/__tests__/deep-state.test.d.ts +0 -2
- package/dist/__tests__/deep-state.test.d.ts.map +0 -1
- package/dist/__tests__/deep-state.test.js +0 -221
- package/dist/__tests__/deep-state.test.js.map +0 -1
- package/dist/__tests__/events.test.d.ts +0 -2
- package/dist/__tests__/events.test.d.ts.map +0 -1
- package/dist/__tests__/events.test.js +0 -295
- package/dist/__tests__/events.test.js.map +0 -1
- package/dist/__tests__/functions.test.d.ts +0 -2
- package/dist/__tests__/functions.test.d.ts.map +0 -1
- package/dist/__tests__/functions.test.js +0 -618
- package/dist/__tests__/functions.test.js.map +0 -1
- package/dist/__tests__/lazy.test.d.ts +0 -2
- package/dist/__tests__/lazy.test.d.ts.map +0 -1
- package/dist/__tests__/lazy.test.js +0 -227
- package/dist/__tests__/lazy.test.js.map +0 -1
- package/dist/__tests__/memo.test.d.ts +0 -2
- package/dist/__tests__/memo.test.d.ts.map +0 -1
- package/dist/__tests__/memo.test.js +0 -249
- package/dist/__tests__/memo.test.js.map +0 -1
- package/dist/__tests__/properties-browser.test.d.ts +0 -2
- package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
- package/dist/__tests__/properties-browser.test.js +0 -227
- package/dist/__tests__/properties-browser.test.js.map +0 -1
- package/dist/__tests__/properties.test.d.ts +0 -2
- package/dist/__tests__/properties.test.d.ts.map +0 -1
- package/dist/__tests__/properties.test.js +0 -1253
- package/dist/__tests__/properties.test.js.map +0 -1
- package/dist/__tests__/ref.test.d.ts +0 -2
- package/dist/__tests__/ref.test.d.ts.map +0 -1
- package/dist/__tests__/ref.test.js +0 -106
- package/dist/__tests__/ref.test.js.map +0 -1
- package/dist/__tests__/resource.test.d.ts +0 -2
- package/dist/__tests__/resource.test.d.ts.map +0 -1
- package/dist/__tests__/resource.test.js +0 -217
- package/dist/__tests__/resource.test.js.map +0 -1
- package/dist/__tests__/slot.test.d.ts +0 -2
- package/dist/__tests__/slot.test.d.ts.map +0 -1
- package/dist/__tests__/slot.test.js +0 -147
- package/dist/__tests__/slot.test.js.map +0 -1
- package/dist/__tests__/synced.test.d.ts +0 -2
- package/dist/__tests__/synced.test.d.ts.map +0 -1
- package/dist/__tests__/synced.test.js +0 -161
- package/dist/__tests__/synced.test.js.map +0 -1
- package/dist/__tests__/until.test.d.ts +0 -2
- package/dist/__tests__/until.test.d.ts.map +0 -1
- package/dist/__tests__/until.test.js +0 -88
- package/dist/__tests__/until.test.js.map +0 -1
- package/dist/create-accessor-decorator.d.ts +0 -14
- package/dist/create-accessor-decorator.d.ts.map +0 -1
- package/dist/create-accessor-decorator.js +0 -27
- package/dist/create-accessor-decorator.js.map +0 -1
- package/src/__tests__/component.test.ts +0 -167
- package/src/__tests__/compose.test.ts +0 -202
- package/src/__tests__/deep-state.test.ts +0 -265
- package/src/__tests__/events.test.ts +0 -366
- package/src/__tests__/functions.test.ts +0 -803
- package/src/__tests__/lazy.test.ts +0 -296
- package/src/__tests__/memo.test.ts +0 -293
- package/src/__tests__/properties-browser.test.ts +0 -288
- package/src/__tests__/properties.test.ts +0 -1556
- package/src/__tests__/ref.test.ts +0 -131
- package/src/__tests__/resource.test.ts +0 -246
- package/src/__tests__/slot.test.ts +0 -179
- package/src/__tests__/synced.test.ts +0 -193
- package/src/__tests__/until.test.ts +0 -113
- package/src/create-accessor-decorator.ts +0 -45
- package/tsconfig.json +0 -9
|
@@ -1,26 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
import { assertMemberKind, describeValue } from "./field";
|
|
2
|
+
import { addLifecycleHooks, addMountRegistration, type LifecycleCleanup } from "./lifecycle";
|
|
3
|
+
|
|
4
|
+
type Getter<This = object, Value = unknown> = (this: This) => Value;
|
|
5
|
+
|
|
6
|
+
/** A getter decorator usable on getters of classes extending `Host` whose type is assignable to `Value`. */
|
|
7
|
+
export type GetterDecorator<Host = object, Value = unknown> = <This extends Host, V extends Value>(
|
|
8
|
+
target: (this: This) => V,
|
|
9
|
+
context: ClassGetterDecoratorContext<This, V>,
|
|
10
|
+
) => (this: This) => V;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What a shared getter decorator installs: `get` (the original getter when absent) and `set` are
|
|
14
|
+
* shared by every instance (`this` is the instance), so per-instance state belongs in symbol slots
|
|
15
|
+
* created in `init`.
|
|
16
|
+
*/
|
|
17
|
+
export interface GetterSetup<This = object, Value = unknown> {
|
|
18
|
+
get?(this: This): Value;
|
|
19
|
+
set?(this: This, value: Value): void;
|
|
20
|
+
/** Runs once per instance, before the class's fields are initialized. */
|
|
21
|
+
init?: (instance: This) => void;
|
|
22
|
+
onMount?: (instance: This) => void;
|
|
23
|
+
onUnmount?: (instance: This) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SharedGetterBehavior<Host = object, Value = unknown> {
|
|
27
|
+
/** Names the decorator in error messages, e.g. `"@Clamp()"`. */
|
|
28
|
+
label?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Runs once per decorated getter, when the class is defined: returns the replacement getter, or
|
|
31
|
+
* a `GetterSetup`.
|
|
32
|
+
*/
|
|
33
|
+
setup(
|
|
34
|
+
original: Getter<Host, Value>,
|
|
35
|
+
context: ClassGetterDecoratorContext<Host, Value>,
|
|
36
|
+
): Getter<Host, Value> | GetterSetup<Host, Value>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SharedWritableGetterBehavior<Host = object, Value = unknown> {
|
|
40
|
+
/** Names the decorator in error messages, e.g. `"@Overridable()"`. */
|
|
41
|
+
label?: string;
|
|
42
|
+
/** Runs once per decorated getter, when the class is defined. */
|
|
43
|
+
setup(original: Getter<Host, Value>, context: ClassGetterDecoratorContext<Host, Value>): GetterSetup<Host, Value>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GetterBehavior<T = object> {
|
|
47
|
+
/** Names the decorator in error messages. */
|
|
48
|
+
label?: string;
|
|
2
49
|
/**
|
|
3
50
|
* Given the original getter and the current instance, returns a function
|
|
4
51
|
* that produces the getter value. Called on every property access.
|
|
5
52
|
*/
|
|
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
|
-
};
|
|
53
|
+
wrap(original: (this: object) => unknown, instance: T): () => unknown;
|
|
24
54
|
}
|
|
25
55
|
|
|
26
56
|
export interface WritableGetterBinding {
|
|
@@ -28,64 +58,231 @@ export interface WritableGetterBinding {
|
|
|
28
58
|
set?: (value: unknown) => void;
|
|
29
59
|
}
|
|
30
60
|
|
|
31
|
-
export interface WritableGetterBehavior {
|
|
61
|
+
export interface WritableGetterBehavior<T = object> {
|
|
62
|
+
/** Names the decorator in error messages. */
|
|
63
|
+
label?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Called once per instance during construction. Returns the get/set pair the property reads and
|
|
66
|
+
* writes through for that instance. If `set` is omitted, assigning the property throws a
|
|
67
|
+
* `TypeError`, as for a getter without a setter.
|
|
68
|
+
*/
|
|
69
|
+
bind(instance: T, name: string | symbol, original: (this: object) => unknown): WritableGetterBinding;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface GetterObserverBehavior<T = object, Value = unknown> {
|
|
73
|
+
/** Names the decorator in error messages. */
|
|
74
|
+
label?: string;
|
|
32
75
|
/**
|
|
33
|
-
* Called once per instance
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* may still see a setter if one is declared in the class body).
|
|
76
|
+
* Called once per instance, while it is built and before its fields are initialized. Use it to
|
|
77
|
+
* set up observation without changing the getter's value. A function it returns runs when the
|
|
78
|
+
* instance unmounts.
|
|
37
79
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
81
|
+
observe?(getter: (this: object) => Value, instance: T, name: string): (() => void) | undefined | void;
|
|
82
|
+
/**
|
|
83
|
+
* Called on every mount of each instance, once its fields are set. What it returns (a stop
|
|
84
|
+
* function or an effect) is undone on unmount.
|
|
85
|
+
*/
|
|
86
|
+
register?(getter: (this: object) => Value, instance: T, name: string): LifecycleCleanup;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Replacement getter -> the getter it wraps. Lets a setter added by an inner decorator find the
|
|
90
|
+
// prototype entry after outer getter decorators replaced its getter again.
|
|
91
|
+
const wraps = new WeakMap<Getter, Getter>();
|
|
92
|
+
|
|
93
|
+
function isFrom(get: Getter | undefined, target: Getter): boolean {
|
|
94
|
+
for (let g = get; g !== undefined; g = wraps.get(g)) if (g === target) return true;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Adds `set` to the class entry of the getter `get`: on the prototype (the class itself for a
|
|
100
|
+
* static getter) where it lives, found from the first instance. Returns false when the getter was
|
|
101
|
+
* not found, i.e. a decorator applied by hand, and defines the pair on `target` alone instead.
|
|
102
|
+
*/
|
|
103
|
+
// A property descriptor, read as data: its functions are only passed along, never called here.
|
|
104
|
+
interface FoundDescriptor {
|
|
105
|
+
get?: Getter;
|
|
106
|
+
enumerable?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function addSetter(
|
|
110
|
+
target: object,
|
|
111
|
+
context: ClassGetterDecoratorContext,
|
|
112
|
+
get: Getter,
|
|
113
|
+
set: NonNullable<PropertyDescriptor["set"]>,
|
|
114
|
+
): boolean {
|
|
115
|
+
const name = context.name;
|
|
116
|
+
let fallback: FoundDescriptor | undefined;
|
|
117
|
+
for (
|
|
118
|
+
let owner = (context.static ? target : Object.getPrototypeOf(target)) as object | null;
|
|
119
|
+
owner !== null;
|
|
120
|
+
owner = Object.getPrototypeOf(owner) as object | null
|
|
121
|
+
) {
|
|
122
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, name) as FoundDescriptor | undefined;
|
|
123
|
+
if (descriptor === undefined) continue;
|
|
124
|
+
if (isFrom(descriptor.get, get)) {
|
|
125
|
+
Object.defineProperty(owner, name, { get: descriptor.get, set, enumerable: descriptor.enumerable, configurable: true });
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
fallback ??= descriptor;
|
|
129
|
+
}
|
|
130
|
+
Object.defineProperty(target, name, { get: fallback?.get ?? get, set, enumerable: false, configurable: true });
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface SharedGetterLike {
|
|
135
|
+
label?: string;
|
|
136
|
+
setup(original: Getter, context: ClassGetterDecoratorContext): unknown;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sharedGetter(behavior: SharedGetterLike) {
|
|
140
|
+
const label = behavior.label ?? "This getter decorator";
|
|
141
|
+
return function (target: Getter, context: ClassGetterDecoratorContext): Getter {
|
|
142
|
+
assertMemberKind(context, "getter", label);
|
|
143
|
+
const result = behavior.setup(target, context);
|
|
144
|
+
if (typeof result !== "object" && typeof result !== "function") {
|
|
145
|
+
throw new TypeError(`${label}: setup() must return a getter or a GetterSetup object, got ${describeValue(result)}.`);
|
|
146
|
+
}
|
|
147
|
+
if (result === null) {
|
|
148
|
+
throw new TypeError(`${label}: setup() must return a getter or a GetterSetup object, got null.`);
|
|
149
|
+
}
|
|
150
|
+
const spec = (typeof result === "function" ? { get: result } : result) as GetterSetup;
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- installed on the class, which calls them with the instance as `this`
|
|
152
|
+
const { get = target, set, init, onMount, onUnmount } = spec;
|
|
153
|
+
if (typeof get !== "function") {
|
|
154
|
+
throw new TypeError(`${label}: setup() returned a "get" that is not a function.`);
|
|
155
|
+
}
|
|
156
|
+
if (get !== target) wraps.set(get, target);
|
|
157
|
+
const hooks = onMount !== undefined || onUnmount !== undefined;
|
|
158
|
+
if (hooks && context.static) {
|
|
159
|
+
throw new TypeError(`${label}: onMount/onUnmount need an instance getter, not the static "${String(context.name)}".`);
|
|
160
|
+
}
|
|
161
|
+
if (set !== undefined || init !== undefined || hooks) {
|
|
162
|
+
let installed = set === undefined;
|
|
163
|
+
context.addInitializer(function (this: unknown) {
|
|
164
|
+
const instance = this as object;
|
|
165
|
+
if (!installed) installed = addSetter(instance, context, get, set as NonNullable<PropertyDescriptor["set"]>);
|
|
166
|
+
init?.(instance);
|
|
167
|
+
if (hooks) addLifecycleHooks(instance, onMount, onUnmount);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return get;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function wrapGetterDecorator(behavior: GetterBehavior) {
|
|
175
|
+
const label = behavior.label ?? "This getter decorator";
|
|
176
|
+
return function <This, T>(target: (this: This) => T, context: ClassGetterDecoratorContext<This, T>): (this: This) => T {
|
|
177
|
+
assertMemberKind(context, "getter", label);
|
|
178
|
+
const getter = function (this: This): T {
|
|
179
|
+
return behavior.wrap(target as (this: object) => unknown, this as object)() as T;
|
|
180
|
+
};
|
|
181
|
+
wraps.set(getter as Getter, target as Getter);
|
|
182
|
+
return getter;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Builds a getter decorator that changes what a getter returns.
|
|
188
|
+
*
|
|
189
|
+
* With `setup`, the recommended form, the replacement getter is created once per decorated getter
|
|
190
|
+
* and shared by every instance: neither building an instance nor reading the getter allocates.
|
|
191
|
+
*
|
|
192
|
+
* With `wrap`, a function is created on every read.
|
|
193
|
+
*/
|
|
194
|
+
export function createGetterDecorator<Host = object, Value = unknown>(
|
|
195
|
+
behavior: SharedGetterBehavior<Host, Value>,
|
|
196
|
+
): GetterDecorator<Host, Value>;
|
|
197
|
+
export function createGetterDecorator(
|
|
198
|
+
behavior: GetterBehavior,
|
|
199
|
+
): <This, T>(target: (this: This) => T, context: ClassGetterDecoratorContext<This, T>) => (this: This) => T;
|
|
200
|
+
export function createGetterDecorator(behavior: SharedGetterBehavior | GetterBehavior): unknown {
|
|
201
|
+
if (!("setup" in behavior)) return wrapGetterDecorator(behavior);
|
|
202
|
+
return sharedGetter(behavior as SharedGetterLike);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function bindWritableGetter(behavior: WritableGetterBehavior) {
|
|
206
|
+
const label = behavior.label ?? "This getter decorator";
|
|
207
|
+
return function <This, T>(target: (this: This) => T, context: ClassGetterDecoratorContext<This, T>): (this: This) => T {
|
|
208
|
+
assertMemberKind(context, "getter", label);
|
|
209
|
+
const name = context.name;
|
|
210
|
+
// Each instance's binding lives in this slot, read through one accessor pair on the class.
|
|
211
|
+
const slot = Symbol(`praxis.getter.${String(name)}`);
|
|
212
|
+
type Host = Record<symbol, WritableGetterBinding | undefined>;
|
|
213
|
+
const original = target as (this: object) => unknown;
|
|
214
|
+
const bindingOf = (instance: Host): WritableGetterBinding =>
|
|
215
|
+
(instance[slot] ??= behavior.bind(instance, name, original));
|
|
216
|
+
const get = function (this: Host): T {
|
|
217
|
+
return bindingOf(this).get() as T;
|
|
218
|
+
};
|
|
219
|
+
const set = function (this: Host, value: unknown): void {
|
|
220
|
+
const write = bindingOf(this).set;
|
|
221
|
+
if (write === undefined) throw new TypeError(`Cannot set property ${String(name)}, which has only a getter.`);
|
|
222
|
+
write(value);
|
|
223
|
+
};
|
|
224
|
+
wraps.set(get as Getter, original);
|
|
225
|
+
let installed = false;
|
|
50
226
|
context.addInitializer(function (this: unknown) {
|
|
51
|
-
const instance = this as
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
get: binding.get,
|
|
55
|
-
configurable: true,
|
|
56
|
-
enumerable: false,
|
|
57
|
-
};
|
|
58
|
-
if (binding.set !== undefined) descriptor.set = binding.set;
|
|
59
|
-
Object.defineProperty(instance, context.name, descriptor);
|
|
227
|
+
const instance = this as Host;
|
|
228
|
+
instance[slot] = behavior.bind(instance, name, original);
|
|
229
|
+
if (!installed) installed = addSetter(instance, context as ClassGetterDecoratorContext, get as Getter, set);
|
|
60
230
|
});
|
|
61
|
-
return
|
|
231
|
+
return get as (this: This) => T;
|
|
62
232
|
};
|
|
63
233
|
}
|
|
64
234
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Builds a getter decorator that owns writes to the property as well as reads.
|
|
237
|
+
*
|
|
238
|
+
* With `setup`, the recommended form, `get` and `set` are created once per decorated getter and
|
|
239
|
+
* shared by every instance.
|
|
240
|
+
*
|
|
241
|
+
* With `bind`, a get/set pair is created for each instance while it is built.
|
|
242
|
+
*/
|
|
243
|
+
export function createWritableGetterDecorator<Host = object, Value = unknown>(
|
|
244
|
+
behavior: SharedWritableGetterBehavior<Host, Value>,
|
|
245
|
+
): GetterDecorator<Host, Value>;
|
|
246
|
+
export function createWritableGetterDecorator(
|
|
247
|
+
behavior: WritableGetterBehavior,
|
|
248
|
+
): <This, T>(target: (this: This) => T, context: ClassGetterDecoratorContext<This, T>) => (this: This) => T;
|
|
249
|
+
export function createWritableGetterDecorator(
|
|
250
|
+
behavior: SharedWritableGetterBehavior | WritableGetterBehavior,
|
|
251
|
+
): unknown {
|
|
252
|
+
if (!("setup" in behavior)) return bindWritableGetter(behavior);
|
|
253
|
+
return sharedGetter(behavior as SharedGetterLike);
|
|
76
254
|
}
|
|
77
255
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Builds a getter decorator that observes the getter (for logging, devtools, syncing) without
|
|
258
|
+
* changing its value: `observe` runs once per instance while it is built, `register` on every mount.
|
|
259
|
+
*/
|
|
260
|
+
export function createGetterObserverDecorator<T = object, Value = unknown>(
|
|
261
|
+
behavior: GetterObserverBehavior<T, Value>,
|
|
262
|
+
) {
|
|
263
|
+
const label = behavior.label ?? "This getter decorator";
|
|
264
|
+
if (behavior.observe === undefined && behavior.register === undefined) {
|
|
265
|
+
throw new TypeError(`${label}: createGetterObserverDecorator() needs observe() or register().`);
|
|
266
|
+
}
|
|
267
|
+
return function <This extends T, V extends Value>(
|
|
268
|
+
target: (this: This) => V,
|
|
269
|
+
context: ClassGetterDecoratorContext<This, V>,
|
|
82
270
|
): void {
|
|
271
|
+
assertMemberKind(context, "getter", label);
|
|
272
|
+
const name = context.name as string;
|
|
273
|
+
const getter = target as unknown as (this: object) => Value;
|
|
274
|
+
const observes = behavior.observe !== undefined;
|
|
275
|
+
const mount =
|
|
276
|
+
behavior.register === undefined
|
|
277
|
+
? undefined
|
|
278
|
+
: (instance: T, read: (this: object) => Value) => behavior.register?.(read, instance, name);
|
|
83
279
|
context.addInitializer(function (this: unknown) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
280
|
+
const instance = this as T & object;
|
|
281
|
+
if (observes) {
|
|
282
|
+
const cleanup = behavior.observe?.(getter, instance, name);
|
|
283
|
+
if (typeof cleanup === "function") addLifecycleHooks(instance, undefined, cleanup);
|
|
284
|
+
}
|
|
285
|
+
if (mount !== undefined) addMountRegistration(instance, mount, getter);
|
|
89
286
|
});
|
|
90
287
|
};
|
|
91
288
|
}
|
|
@@ -1,39 +1,61 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { assertMemberKind } from "./field";
|
|
4
|
+
import { addMountRegistration, type LifecycleCleanup } from "./lifecycle";
|
|
5
|
+
|
|
6
|
+
export type { LifecycleCleanup };
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export interface LifecycleMethodBehavior<T extends object = StatefulComponent, Args extends unknown[] = any[]> {
|
|
10
|
+
/** Names the decorator in error messages, e.g. `"@OnResize()"`. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Runs on every mount of each instance with the method bound to it. Returns what undoes the
|
|
14
|
+
* registration on unmount, if anything.
|
|
15
|
+
*/
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
17
|
+
register(callback: (...args: Args) => void, instance: T, name: string): (() => void) | undefined | void;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
type AnyMethod = (this: any, ...args: any[]) => void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Builds a method decorator that calls `start` on every mount and undoes what it returned on
|
|
25
|
+
* unmount.
|
|
26
|
+
*/
|
|
27
|
+
export function lifecycleMethodDecorator<T extends object>(
|
|
28
|
+
start: (instance: T, method: AnyMethod, name: string) => LifecycleCleanup,
|
|
29
|
+
label = "This lifecycle method decorator",
|
|
30
|
+
) {
|
|
15
31
|
return function (
|
|
16
32
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
33
|
value: (this: T, ...args: any[]) => void,
|
|
18
|
-
|
|
34
|
+
context: ClassMethodDecoratorContext<T>,
|
|
19
35
|
): void {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const result = behavior.register(value.bind(this), this);
|
|
29
|
-
if (result) cleanup = result;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
instance.onUnmount = function (this: T) {
|
|
33
|
-
prevUnmount?.call(this);
|
|
34
|
-
cleanup?.();
|
|
35
|
-
cleanup = undefined;
|
|
36
|
-
};
|
|
36
|
+
assertMemberKind(context, "method", label);
|
|
37
|
+
if (context.static) {
|
|
38
|
+
throw new TypeError(`${label} decorates instance methods only, not the static method "${String(context.name)}".`);
|
|
39
|
+
}
|
|
40
|
+
const name = String(context.name);
|
|
41
|
+
const run = (instance: T, method: AnyMethod) => start(instance, method, name);
|
|
42
|
+
context.addInitializer(function (this: T) {
|
|
43
|
+
addMountRegistration(this, run, value);
|
|
37
44
|
});
|
|
38
45
|
};
|
|
39
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds a method decorator that registers the method somewhere on every mount (an event listener,
|
|
50
|
+
* a subscription) and undoes it on unmount.
|
|
51
|
+
*/
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
export function createLifecycleMethodDecorator<T extends object = StatefulComponent, Args extends unknown[] = any[]>(
|
|
54
|
+
behavior: LifecycleMethodBehavior<T, Args>,
|
|
55
|
+
): <This extends T>(value: (this: This, ...args: Args) => void, context: ClassMethodDecoratorContext<This>) => void {
|
|
56
|
+
const label = behavior.label ?? "This lifecycle method decorator";
|
|
57
|
+
return lifecycleMethodDecorator<T>(
|
|
58
|
+
(instance, method, name) => behavior.register(method.bind(instance) as (...args: Args) => void, instance, name),
|
|
59
|
+
label,
|
|
60
|
+
) as never;
|
|
61
|
+
}
|
|
@@ -1,29 +1,135 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { assertMemberKind, describeValue } from "./field";
|
|
4
|
+
import { addLifecycleHooks } from "./lifecycle";
|
|
5
|
+
import { type AnyMethod, sharedMethod } from "./shared-method";
|
|
6
|
+
|
|
7
|
+
export type { AnyMethod };
|
|
8
|
+
|
|
9
|
+
export interface MethodBehavior<T = object> {
|
|
10
|
+
/** Names the decorator in error messages, e.g. `"@Confirm()"`. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Runs once per instance, while it is built: returns the function that runs in place of the
|
|
14
|
+
* method for that instance.
|
|
15
|
+
*/
|
|
4
16
|
wrap(
|
|
5
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
18
|
original: (...args: any[]) => any,
|
|
7
|
-
instance:
|
|
19
|
+
instance: T,
|
|
8
20
|
name: string,
|
|
9
21
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
22
|
): (...args: any[]) => any;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
/** A replacement method, with hooks that run on every mount and unmount of each instance. */
|
|
26
|
+
export interface MethodSetup<This = object, M extends AnyMethod = AnyMethod> {
|
|
27
|
+
method: M;
|
|
28
|
+
onMount?: (instance: This) => void;
|
|
29
|
+
onUnmount?: (instance: This) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SharedMethodBehavior<Host = object, M extends AnyMethod = AnyMethod> {
|
|
33
|
+
/** Names the decorator in error messages, e.g. `"@Confirm()"`. */
|
|
34
|
+
label?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Runs once per decorated method, when the class is defined: returns the replacement, shared by
|
|
37
|
+
* every instance (`this` is the instance), or a `MethodSetup` that also has lifecycle hooks.
|
|
38
|
+
*
|
|
39
|
+
* `M` is never inferred from it, so a decorator fits every method unless `M` is given.
|
|
40
|
+
*/
|
|
41
|
+
setup(
|
|
42
|
+
original: NoInfer<M>,
|
|
43
|
+
context: ClassMethodDecoratorContext<Host, NoInfer<M>>,
|
|
44
|
+
): NoInfer<M> | MethodSetup<Host, NoInfer<M>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A method decorator usable on methods of classes extending `Host` whose type is assignable to `M`. */
|
|
48
|
+
export type MethodDecorator<Host = object, M extends AnyMethod = AnyMethod> = <This extends Host, V extends M>(
|
|
49
|
+
value: V,
|
|
50
|
+
context: ClassMethodDecoratorContext<This, V>,
|
|
51
|
+
) => V;
|
|
52
|
+
|
|
53
|
+
const DEFAULT_LABEL = "This method decorator";
|
|
54
|
+
|
|
55
|
+
function sharedMethodDecorator<Host, M extends AnyMethod>(
|
|
56
|
+
behavior: SharedMethodBehavior<Host, M>,
|
|
57
|
+
): MethodDecorator<Host, M> {
|
|
58
|
+
const label = behavior.label ?? DEFAULT_LABEL;
|
|
59
|
+
return function (value, context) {
|
|
60
|
+
assertMemberKind(context, "method", label);
|
|
61
|
+
const result: unknown = behavior.setup(value, context as unknown as ClassMethodDecoratorContext<Host, M>);
|
|
62
|
+
const setup = typeof result === "object" && result !== null ? (result as MethodSetup) : undefined;
|
|
63
|
+
const method: unknown = setup === undefined ? result : setup.method;
|
|
64
|
+
if (typeof method !== "function") {
|
|
65
|
+
throw new TypeError(`${label}: setup() must return a function or { method }, got ${describeValue(method)}.`);
|
|
66
|
+
}
|
|
67
|
+
if (setup !== undefined && (setup.onMount !== undefined || setup.onUnmount !== undefined)) {
|
|
68
|
+
if (context.static) {
|
|
69
|
+
throw new TypeError(`${label}: onMount/onUnmount need an instance method, not the static "${String(context.name)}".`);
|
|
70
|
+
}
|
|
71
|
+
const { onMount, onUnmount } = setup;
|
|
72
|
+
context.addInitializer(function () {
|
|
73
|
+
addLifecycleHooks(this as object, onMount, onUnmount);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return sharedMethod(context, value, method as AnyMethod) as typeof value;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function wrapMethodDecorator<T>(behavior: MethodBehavior<T>) {
|
|
81
|
+
const label = behavior.label ?? DEFAULT_LABEL;
|
|
14
82
|
return function (
|
|
15
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
84
|
value: (this: object, ...args: any[]) => any,
|
|
17
|
-
context: ClassMethodDecoratorContext
|
|
18
|
-
)
|
|
19
|
-
context
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
85
|
+
context: ClassMethodDecoratorContext,
|
|
86
|
+
) {
|
|
87
|
+
assertMemberKind(context, "method", label);
|
|
88
|
+
const name = context.name as string;
|
|
89
|
+
// Each instance's wrapper lives in this slot, called through one replacement on the prototype:
|
|
90
|
+
// no property definition per instance, and decorators stacked around this one keep working.
|
|
91
|
+
const slot = Symbol(`praxis.method.${name}`);
|
|
92
|
+
type Host = Record<symbol, AnyMethod | undefined>;
|
|
93
|
+
const wrapFor = (instance: object): AnyMethod => {
|
|
94
|
+
const wrapped: unknown = behavior.wrap(value, instance as T, name);
|
|
95
|
+
if (typeof wrapped !== "function") {
|
|
96
|
+
throw new TypeError(`${label}: wrap() must return a function, got ${describeValue(wrapped)}.`);
|
|
97
|
+
}
|
|
98
|
+
return wrapped as AnyMethod;
|
|
99
|
+
};
|
|
100
|
+
const method = function (this: Host, ...args: unknown[]): unknown {
|
|
101
|
+
// An object the constructor did not build (Object.create(prototype)) gets its wrapper here.
|
|
102
|
+
return (this[slot] ??= wrapFor(this)).apply(this, args) as unknown;
|
|
103
|
+
};
|
|
104
|
+
context.addInitializer(function () {
|
|
105
|
+
(this as Host)[slot] = wrapFor(this as object);
|
|
27
106
|
});
|
|
107
|
+
return sharedMethod(context, value, method);
|
|
28
108
|
};
|
|
29
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Builds a method decorator.
|
|
113
|
+
*
|
|
114
|
+
* With `setup`, the recommended form, the replacement is created once per decorated method and
|
|
115
|
+
* shared by every instance, which keeps any state in symbol slots: building an instance costs
|
|
116
|
+
* nothing.
|
|
117
|
+
*
|
|
118
|
+
* With `wrap`, a wrapper is created for each instance while it is built.
|
|
119
|
+
*
|
|
120
|
+
* Either way, reading the method from an instance returns it bound to that instance, the same
|
|
121
|
+
* function on every read, so `onClick={this.save}` works.
|
|
122
|
+
*/
|
|
123
|
+
export function createMethodDecorator<Host = object, M extends AnyMethod = AnyMethod>(
|
|
124
|
+
behavior: SharedMethodBehavior<Host, M>,
|
|
125
|
+
): MethodDecorator<Host, M>;
|
|
126
|
+
export function createMethodDecorator<T = object>(
|
|
127
|
+
behavior: MethodBehavior<T>,
|
|
128
|
+
): (
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
value: (this: object, ...args: any[]) => any,
|
|
131
|
+
context: ClassMethodDecoratorContext<StatefulComponent>,
|
|
132
|
+
) => void;
|
|
133
|
+
export function createMethodDecorator(behavior: SharedMethodBehavior | MethodBehavior): unknown {
|
|
134
|
+
return "setup" in behavior ? sharedMethodDecorator(behavior) : wrapMethodDecorator(behavior);
|
|
135
|
+
}
|
package/src/events/command.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type FieldDecorator, type FieldHost, sharedField } from "../field";
|
|
2
2
|
|
|
3
3
|
export interface Command<T = void> {
|
|
4
4
|
trigger(arg: T): void;
|
|
@@ -19,13 +19,15 @@ export function createCommand<T = void>(): Command<T> {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
22
|
-
export function Command<T = void>() {
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
export function Command<T = void>(): FieldDecorator {
|
|
23
|
+
return sharedField((name) => {
|
|
24
|
+
const command = Symbol(`praxis.command.${name}`);
|
|
25
|
+
return {
|
|
26
|
+
descriptor: {
|
|
27
|
+
get(this: FieldHost) {
|
|
28
|
+
return (this[command] ??= createCommand<T>());
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
30
32
|
});
|
|
31
33
|
}
|