@azure-net/kit 6.0.0-next.6 → 6.0.0-next.7
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/README.md
CHANGED
|
@@ -68,27 +68,51 @@ import type { OrdersUseCases } from '#contexts/Orders/Application/UseCases/Order
|
|
|
68
68
|
export class OrdersPresenter {
|
|
69
69
|
constructor(private readonly useCases: OrdersUseCases) {}
|
|
70
70
|
|
|
71
|
-
collection
|
|
71
|
+
collection() {
|
|
72
|
+
return createAsyncResource(() => this.useCases.collection());
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
export const useOrdersPresenter = RegisterProvider(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
export const useOrdersPresenter = RegisterProvider(
|
|
77
|
+
() => {
|
|
78
|
+
const { Orders } = ApplicationProvider();
|
|
79
|
+
return new OrdersPresenter(Orders);
|
|
80
|
+
},
|
|
81
|
+
{ lifecycle: 'scoped', bindMethods: true }
|
|
82
|
+
);
|
|
78
83
|
```
|
|
79
84
|
|
|
80
85
|
Call `useOrdersPresenter()` during component initialization, not at module scope.
|
|
81
86
|
The registration itself is safe at module scope and does not resolve dependencies.
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
`RegisterProvider` returns the actual class instance, without a proxy. The optional
|
|
88
|
+
second argument is an options object: `{ lifecycle?: ..., bindMethods?: ... }`.
|
|
89
|
+
Defaults are `lifecycle: 'scoped'` and `bindMethods: false`. The old string argument
|
|
90
|
+
is no longer supported. With binding disabled, methods and property descriptors are
|
|
91
|
+
left untouched.
|
|
92
|
+
|
|
93
|
+
Use `bindMethods: true` to bind function-valued data properties, including inherited
|
|
94
|
+
methods, or `bindMethods: ['collection']` to select typed method names. Binding runs
|
|
95
|
+
only after the factory returns, once for each newly created instance. Accessors are
|
|
96
|
+
never evaluated or replaced, including Svelte rune accessors. Constructors and
|
|
97
|
+
Object.prototype methods are excluded. A selected accessor (even one returning a
|
|
98
|
+
function), missing method, or protected property throws instead of silently failing.
|
|
99
|
+
TypeScript cannot distinguish a function-returning getter from a method, so the
|
|
100
|
+
accessor check also happens at runtime.
|
|
101
|
+
|
|
102
|
+
Method references are stable after binding. Frozen/non-extensible instances cannot
|
|
103
|
+
receive inherited bound methods. Arbitrary proxies and exotic built-in objects are
|
|
104
|
+
not supported binding targets. Function-valued fields are also bound with `true`;
|
|
105
|
+
use an explicit list if the instance holds component constructors or other callable
|
|
106
|
+
dependencies. No global cache retains instances or bound functions. Binding does not
|
|
107
|
+
apply retroactively to callbacks captured during construction or methods replaced later.
|
|
84
108
|
Classes using Svelte runes such as `$state` must be placed in a `.svelte.ts` module
|
|
85
109
|
(or declared inside a Svelte component), not a regular `.ts` file.
|
|
86
110
|
|
|
87
|
-
| Lifecycle
|
|
88
|
-
|
|
|
89
|
-
| `scoped` (default)
|
|
90
|
-
| `transient`
|
|
91
|
-
| `singleton`
|
|
111
|
+
| Lifecycle | Server | Browser |
|
|
112
|
+
| ------------------ | ----------------------------------------- | ----------------------------- |
|
|
113
|
+
| `scoped` (default) | One instance per registration per request | New instance per call |
|
|
114
|
+
| `transient` | New instance per call | New instance per call |
|
|
115
|
+
| `singleton` | One instance per registration per request | One instance per registration |
|
|
92
116
|
|
|
93
117
|
Server caching requires `AzureNetPlugin` request context and fails
|
|
94
118
|
outside it. There is no process-wide server singleton. Request caches have weak
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createAsyncAction, createAsyncResource, type AsyncActionSettings, type AsyncHelperRetry, type AsyncResourceSettings, type AsyncActionResponse } from './injectable-dependencies/index.js';
|
|
2
|
-
export { RegisterProvider, type ProviderLifecycle } from './presenter/RegisterProvider.js';
|
|
2
|
+
export { RegisterProvider, type ProviderLifecycle, type RegisterProviderOptions, type ProviderMethodKey } from './presenter/RegisterProvider.js';
|
|
3
3
|
export { Bound } from './presenter/Bound.js';
|
|
4
4
|
export { createSchemaFactory, schema, type Schema, type RequestErrors, type ISchemaError, type ValidationResult, type ValidationRuleResult, type ValidationParams, type ValidationMessage, type ValidationErrorsMap } from './schema/index.js';
|
|
5
5
|
export { createRules, validationMessagesRu, validationMessagesI18n, validationMessagesEn, type ValidationRuleParams, type BaseValidationMessages } from './schema/rules/index.js';
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export type ProviderLifecycle = 'scoped' | 'transient' | 'singleton';
|
|
2
|
+
export type ProviderMethodKey<T> = Exclude<{
|
|
3
|
+
[K in keyof T]-?: T[K] extends (...args: never[]) => unknown ? K : never;
|
|
4
|
+
}[keyof T], 'constructor'>;
|
|
5
|
+
export interface RegisterProviderOptions<T> {
|
|
6
|
+
lifecycle?: ProviderLifecycle;
|
|
7
|
+
bindMethods?: boolean | readonly ProviderMethodKey<T>[];
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
10
|
* Registration is safe at module scope; the factory is called only by the returned accessor.
|
|
4
11
|
* scoped: per request on the server, per call in the browser.
|
|
@@ -6,4 +13,4 @@ export type ProviderLifecycle = 'scoped' | 'transient' | 'singleton';
|
|
|
6
13
|
* transient: a new instance on every call.
|
|
7
14
|
* No hydration, component context, effects or disposal are managed by this function.
|
|
8
15
|
*/
|
|
9
|
-
export declare function RegisterProvider<T extends object>(factory: () => T extends PromiseLike<unknown> ? never : T,
|
|
16
|
+
export declare function RegisterProvider<T extends object>(factory: () => T extends PromiseLike<unknown> ? never : T, options?: RegisterProviderOptions<NoInfer<T>>): () => T;
|
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { RequestContext } from '../../shared/request-context/Internal.js';
|
|
2
2
|
import { BROWSER } from '../../external/tools/index.js';
|
|
3
|
+
const bindInstanceMethods = (instance, selection) => {
|
|
4
|
+
const descriptors = new Map();
|
|
5
|
+
const selected = selection === true ? undefined : new Set(selection);
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
let target = instance;
|
|
8
|
+
while (target && target !== Object.prototype && target !== Function.prototype) {
|
|
9
|
+
for (const key of selected ?? Reflect.ownKeys(target)) {
|
|
10
|
+
if (key === 'constructor' || seen.has(key))
|
|
11
|
+
continue;
|
|
12
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
13
|
+
if (!descriptor)
|
|
14
|
+
continue;
|
|
15
|
+
seen.add(key);
|
|
16
|
+
// Never evaluate accessors, including getters returning functions or Svelte state.
|
|
17
|
+
if ('value' in descriptor && typeof descriptor.value === 'function')
|
|
18
|
+
descriptors.set(key, descriptor);
|
|
19
|
+
else if (selected)
|
|
20
|
+
throw new TypeError(`[RegisterProvider] Cannot bind non-method ${String(key)}.`);
|
|
21
|
+
}
|
|
22
|
+
if (selected && seen.size === selected.size)
|
|
23
|
+
break;
|
|
24
|
+
target = Object.getPrototypeOf(target);
|
|
25
|
+
}
|
|
26
|
+
if (selected) {
|
|
27
|
+
for (const key of selected) {
|
|
28
|
+
if (!descriptors.has(key))
|
|
29
|
+
throw new TypeError(`[RegisterProvider] Method ${String(key)} was not found or cannot be bound.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Validate every write before changing ordinary instances, avoiding partial binding on errors.
|
|
33
|
+
for (const key of descriptors.keys()) {
|
|
34
|
+
const own = Object.getOwnPropertyDescriptor(instance, key);
|
|
35
|
+
if (own ? !own.configurable && !own.writable : !Object.isExtensible(instance)) {
|
|
36
|
+
throw new TypeError(`[RegisterProvider] Cannot bind protected method ${String(key)}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const [key, descriptor] of descriptors) {
|
|
40
|
+
Object.defineProperty(instance, key, { ...descriptor, value: Function.prototype.bind.call(descriptor.value, instance) });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
3
43
|
/**
|
|
4
44
|
* Registration is safe at module scope; the factory is called only by the returned accessor.
|
|
5
45
|
* scoped: per request on the server, per call in the browser.
|
|
@@ -7,10 +47,16 @@ import { BROWSER } from '../../external/tools/index.js';
|
|
|
7
47
|
* transient: a new instance on every call.
|
|
8
48
|
* No hydration, component context, effects or disposal are managed by this function.
|
|
9
49
|
*/
|
|
10
|
-
export function RegisterProvider(factory,
|
|
50
|
+
export function RegisterProvider(factory, options = {}) {
|
|
51
|
+
if (!options || typeof options !== 'object' || Array.isArray(options))
|
|
52
|
+
throw new TypeError('[RegisterProvider] Options must be an object.');
|
|
53
|
+
const { lifecycle = 'scoped', bindMethods = false } = options;
|
|
11
54
|
if (!['scoped', 'transient', 'singleton'].includes(lifecycle)) {
|
|
12
55
|
throw new TypeError(`[RegisterProvider] Unknown lifecycle: ${lifecycle}`);
|
|
13
56
|
}
|
|
57
|
+
if (typeof bindMethods !== 'boolean' && !Array.isArray(bindMethods))
|
|
58
|
+
throw new TypeError('[RegisterProvider] bindMethods must be a boolean or method list.');
|
|
59
|
+
const selection = Array.isArray(bindMethods) ? [...bindMethods] : bindMethods;
|
|
14
60
|
const requests = new WeakMap();
|
|
15
61
|
let clientInstance;
|
|
16
62
|
let constructing = false;
|
|
@@ -28,6 +74,8 @@ export function RegisterProvider(factory, lifecycle = 'scoped') {
|
|
|
28
74
|
void Promise.resolve(instance).catch(() => undefined);
|
|
29
75
|
throw new TypeError('[RegisterProvider] Factory must be synchronous.');
|
|
30
76
|
}
|
|
77
|
+
if (selection === true || (Array.isArray(selection) && selection.length > 0))
|
|
78
|
+
bindInstanceMethods(instance, selection);
|
|
31
79
|
return instance;
|
|
32
80
|
}
|
|
33
81
|
finally {
|
package/package.json
CHANGED