@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.
- package/CHANGELOG.md +41 -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
package/src/events/emit.ts
CHANGED
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
8
|
-
|
|
9
|
-
const
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
if (typeof callback !== "function") return result;
|
|
11
|
+
const callback = readProp(this, propName);
|
|
12
|
+
if (typeof callback !== "function") return result;
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
});
|
|
22
|
+
return result;
|
|
23
|
+
});
|
|
24
|
+
};
|
|
31
25
|
}
|
package/src/events/on-command.ts
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
+
import type { StatefulComponent } from "@praxisjs/core";
|
|
1
2
|
|
|
2
|
-
import {
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/functions/bind.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
|
|
2
2
|
|
|
3
3
|
export function Bind() {
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
return function (value: AnyMethod, context: MethodContext): AnyMethod {
|
|
5
|
+
return sharedMethod(context, value);
|
|
6
|
+
};
|
|
7
7
|
}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { addLifecycleHooks } from "../lifecycle";
|
|
2
|
+
import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
type Timer = ReturnType<typeof setTimeout>;
|
|
4
5
|
|
|
5
6
|
export function Debounce(ms: number) {
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|
package/src/functions/log.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
}
|
package/src/functions/memo.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed } from "@praxisjs/core/internal";
|
|
2
2
|
import type { Computed } from "@praxisjs/shared";
|
|
3
3
|
|
|
4
|
-
import {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
}
|
|
70
|
+
}
|
|
71
|
+
return memoized();
|
|
72
|
+
});
|
|
73
|
+
};
|
|
72
74
|
}
|
package/src/functions/once.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
|
|
2
2
|
|
|
3
3
|
export function Once() {
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
}
|
package/src/functions/retry.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
32
|
+
throw lastError;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
37
35
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
|
|
2
2
|
|
|
3
3
|
export function Throttle(ms: number) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
}
|
package/src/functions/until.ts
CHANGED
|
@@ -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 {
|
|
3
|
+
import { isReactive } from "@praxisjs/shared/internal";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type AnyMethod, type MethodContext, sharedMethod } from "../shared-method";
|
|
6
6
|
|
|
7
7
|
export function Until(propName: string) {
|
|
8
|
-
return
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
}
|