assign-gingerly 0.0.73 → 0.0.75
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 +29 -92
- package/assignFeatures.js +145 -254
- package/assignFeatures.ts +713 -837
- package/defineWithFeatures.js +12 -72
- package/defineWithFeatures.ts +92 -167
- package/enhanceAll.js +8 -4
- package/enhanceAll.ts +8 -7
- package/inferencer/types/assign-gingerly/types.d.ts +2 -16
- package/inferencer/types/font-face-feature/types.d.ts +9 -0
- package/package.json +10 -1
- package/processHandlerCommands.js +11 -34
- package/processHandlerCommands.ts +13 -40
- package/resolveAndAssignFeatures.js +9 -40
- package/resolveAndAssignFeatures.ts +43 -79
- package/types/assign-gingerly/types.d.ts +2 -16
- package/utils/findClassPrototypeInPath.js +57 -0
- package/utils/findClassPrototypeInPath.ts +62 -0
- package/utils/isAsyncSpawn.js +20 -0
- package/utils/isAsyncSpawn.ts +17 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { resolveValues } from './resolveValues.js';
|
|
8
8
|
import { getValues } from './getValues.js';
|
|
9
9
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
10
|
-
import {
|
|
10
|
+
import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
11
11
|
import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
12
12
|
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
13
13
|
import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFromAsync.js';
|
|
@@ -26,23 +26,11 @@ const BUILT_IN_MAP: Record<string, string> = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Criteria for locating an assignFrom handler class: must expose an `assign` method
|
|
30
|
+
* on its prototype.
|
|
31
31
|
*/
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
if (module.default && typeof module.default === 'function'
|
|
35
|
-
&& module.default.prototype && 'assign' in module.default.prototype) {
|
|
36
|
-
return module.default;
|
|
37
|
-
}
|
|
38
|
-
// Search other exports
|
|
39
|
-
for (const key of Object.keys(module)) {
|
|
40
|
-
const exported = module[key];
|
|
41
|
-
if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
|
|
42
|
-
return exported as AssignFromHandlerConstructor;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return undefined;
|
|
32
|
+
function handlerCriteria(proto: any): boolean {
|
|
33
|
+
return 'assign' in proto.prototype;
|
|
46
34
|
}
|
|
47
35
|
|
|
48
36
|
/**
|
|
@@ -60,10 +48,9 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
|
|
|
60
48
|
if (cached) return cached;
|
|
61
49
|
const path = BUILT_IN_MAP[name];
|
|
62
50
|
if (!path) return undefined;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return cls;
|
|
51
|
+
const cls = await findClassPrototypeInPath(path, handlerCriteria);
|
|
52
|
+
handlerCache.set(name, cls);
|
|
53
|
+
return cls as AssignFromHandlerConstructor;
|
|
67
54
|
}
|
|
68
55
|
|
|
69
56
|
/**
|
|
@@ -71,8 +58,7 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
|
|
|
71
58
|
*/
|
|
72
59
|
async function resolveFromHandlers(
|
|
73
60
|
name: string,
|
|
74
|
-
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined
|
|
75
|
-
permissions?: AssignPermissions
|
|
61
|
+
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined
|
|
76
62
|
): Promise<AssignFromHandlerConstructor | undefined> {
|
|
77
63
|
if (!handlers || !(name in handlers)) return undefined;
|
|
78
64
|
|
|
@@ -90,22 +76,9 @@ async function resolveFromHandlers(
|
|
|
90
76
|
return loadBuiltIn(entry);
|
|
91
77
|
}
|
|
92
78
|
|
|
93
|
-
// Import path string — validate and
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
97
|
-
`Only same-origin paths or import-map-covered specifiers are allowed. ` +
|
|
98
|
-
`Pass { crossDomainImports: true } in permissions to override.`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
const module = await import(entry);
|
|
102
|
-
const HandlerClass = findHandlerInModule(module);
|
|
103
|
-
if (!HandlerClass) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
return HandlerClass;
|
|
79
|
+
// Import path string — validate and extract handler class via shared utility
|
|
80
|
+
const HandlerClass = await findClassPrototypeInPath(entry, handlerCriteria);
|
|
81
|
+
return HandlerClass as AssignFromHandlerConstructor;
|
|
109
82
|
}
|
|
110
83
|
|
|
111
84
|
return undefined;
|
|
@@ -204,7 +177,7 @@ export async function processHandlerCommands(
|
|
|
204
177
|
for (const config of configs) {
|
|
205
178
|
//return; //1.3ms
|
|
206
179
|
// 1. Check options.handlers (local, per-call)
|
|
207
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers
|
|
180
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
208
181
|
//return; // 1.4
|
|
209
182
|
// 2. Fallback to built-in auto-load
|
|
210
183
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* resolveAndAssignFeatures -
|
|
2
|
+
* resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Resolves all configured spawns (including async fallback spawns and string import paths)
|
|
5
|
+
* and installs feature getters on the class prototype, then delegates to the registry's
|
|
6
|
+
* assignFeatures implementation.
|
|
7
7
|
*
|
|
8
|
-
* This
|
|
9
|
-
*
|
|
8
|
+
* This is kept as a convenience entry point for callers that already await this function.
|
|
9
|
+
* The actual resolution logic lives in assignFeatures.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
|
|
@@ -22,46 +22,15 @@
|
|
|
22
22
|
* }
|
|
23
23
|
* });
|
|
24
24
|
*/
|
|
25
|
+
import { assignFeatures } from './assignFeatures.js';
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
function isAsyncSpawn(fn) {
|
|
29
|
-
if (typeof fn !== 'function')
|
|
30
|
-
return false;
|
|
31
|
-
if (fn.constructor.name === 'AsyncFunction')
|
|
32
|
-
return true;
|
|
33
|
-
if (fn.prototype === undefined)
|
|
34
|
-
return true;
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Resolves async fallback spawns for features that don't have an explicit spawn,
|
|
39
|
-
* then calls assignFeatures on the registry.
|
|
27
|
+
* Resolves all configured spawns and calls assignFeatures on the registry.
|
|
40
28
|
*
|
|
41
29
|
* @param ElementClass - The custom element class (must have static supportedFeatures)
|
|
42
30
|
* @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
|
|
43
31
|
* @param registry - Optional CustomElementRegistry (defaults to global customElements)
|
|
44
32
|
*/
|
|
45
33
|
export async function resolveAndAssignFeatures(ElementClass, featuresConfig, registry) {
|
|
46
|
-
const supportedFeatures = ElementClass.supportedFeatures;
|
|
47
|
-
if (!supportedFeatures) {
|
|
48
|
-
throw new Error(`resolveAndAssignFeatures: ${ElementClass.name || 'constructor'} does not define static supportedFeatures`);
|
|
49
|
-
}
|
|
50
|
-
// Resolve async fallback spawns in parallel for features without explicit spawn
|
|
51
|
-
await Promise.all(Object.entries(featuresConfig).map(async ([key, featureConfig]) => {
|
|
52
|
-
// Skip if spawn is already provided
|
|
53
|
-
if (featureConfig.spawn)
|
|
54
|
-
return;
|
|
55
|
-
const optIn = supportedFeatures[key];
|
|
56
|
-
if (!optIn?.fallbackSpawn)
|
|
57
|
-
return;
|
|
58
|
-
let spawn = optIn.fallbackSpawn;
|
|
59
|
-
if (isAsyncSpawn(spawn)) {
|
|
60
|
-
spawn = await spawn();
|
|
61
|
-
}
|
|
62
|
-
featureConfig.spawn = spawn;
|
|
63
|
-
}));
|
|
64
|
-
// Call assignFeatures on the registry
|
|
65
34
|
const reg = registry || customElements;
|
|
66
|
-
await
|
|
35
|
+
await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
|
|
67
36
|
}
|
|
@@ -1,79 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* resolveAndAssignFeatures -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
|
|
13
|
-
*
|
|
14
|
-
* await resolveAndAssignFeatures(MyElement, {
|
|
15
|
-
* roundabout: {
|
|
16
|
-
* customData: {...},
|
|
17
|
-
* withAttrs: {...},
|
|
18
|
-
* callbackForwarding: ['connectedCallback']
|
|
19
|
-
* },
|
|
20
|
-
* faceUp: {
|
|
21
|
-
* callbackForwarding: ['connectedCallback', 'disconnectedCallback']
|
|
22
|
-
* }
|
|
23
|
-
* });
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { FeatureConfigsMap
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @param registry - Optional CustomElementRegistry (defaults to global customElements)
|
|
45
|
-
*/
|
|
46
|
-
export async function resolveAndAssignFeatures(
|
|
47
|
-
ElementClass: Function,
|
|
48
|
-
featuresConfig: FeatureConfigsMap,
|
|
49
|
-
registry?: any
|
|
50
|
-
): Promise<void> {
|
|
51
|
-
const supportedFeatures: SupportedFeaturesMap | undefined = (ElementClass as any).supportedFeatures;
|
|
52
|
-
|
|
53
|
-
if (!supportedFeatures) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`resolveAndAssignFeatures: ${(ElementClass as any).name || 'constructor'} does not define static supportedFeatures`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Resolve async fallback spawns in parallel for features without explicit spawn
|
|
60
|
-
await Promise.all(
|
|
61
|
-
Object.entries(featuresConfig).map(async ([key, featureConfig]) => {
|
|
62
|
-
// Skip if spawn is already provided
|
|
63
|
-
if (featureConfig.spawn) return;
|
|
64
|
-
|
|
65
|
-
const optIn = supportedFeatures[key];
|
|
66
|
-
if (!optIn?.fallbackSpawn) return;
|
|
67
|
-
|
|
68
|
-
let spawn = optIn.fallbackSpawn;
|
|
69
|
-
if (isAsyncSpawn(spawn)) {
|
|
70
|
-
spawn = await (spawn as () => Promise<any>)();
|
|
71
|
-
}
|
|
72
|
-
(featureConfig as any).spawn = spawn;
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Call assignFeatures on the registry
|
|
77
|
-
const reg = registry || customElements;
|
|
78
|
-
await reg.assignFeatures(ElementClass, featuresConfig);
|
|
79
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* resolveAndAssignFeatures - Thin wrapper around assignFeatures for backward compatibility.
|
|
3
|
+
*
|
|
4
|
+
* Resolves all configured spawns (including async fallback spawns and string import paths)
|
|
5
|
+
* and installs feature getters on the class prototype, then delegates to the registry's
|
|
6
|
+
* assignFeatures implementation.
|
|
7
|
+
*
|
|
8
|
+
* This is kept as a convenience entry point for callers that already await this function.
|
|
9
|
+
* The actual resolution logic lives in assignFeatures.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { resolveAndAssignFeatures } from 'assign-gingerly/resolveAndAssignFeatures.js';
|
|
13
|
+
*
|
|
14
|
+
* await resolveAndAssignFeatures(MyElement, {
|
|
15
|
+
* roundabout: {
|
|
16
|
+
* customData: {...},
|
|
17
|
+
* withAttrs: {...},
|
|
18
|
+
* callbackForwarding: ['connectedCallback']
|
|
19
|
+
* },
|
|
20
|
+
* faceUp: {
|
|
21
|
+
* callbackForwarding: ['connectedCallback', 'disconnectedCallback']
|
|
22
|
+
* }
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { FeatureConfigsMap } from './types/assign-gingerly/types.js';
|
|
27
|
+
import { assignFeatures } from './assignFeatures.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolves all configured spawns and calls assignFeatures on the registry.
|
|
31
|
+
*
|
|
32
|
+
* @param ElementClass - The custom element class (must have static supportedFeatures)
|
|
33
|
+
* @param featuresConfig - Feature configurations (spawn will be resolved from fallbackSpawn if missing)
|
|
34
|
+
* @param registry - Optional CustomElementRegistry (defaults to global customElements)
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveAndAssignFeatures(
|
|
37
|
+
ElementClass: Function,
|
|
38
|
+
featuresConfig: FeatureConfigsMap,
|
|
39
|
+
registry?: any
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const reg = registry || customElements;
|
|
42
|
+
await assignFeatures(ElementClass, featuresConfig, reg.featuresRegistry);
|
|
43
|
+
}
|
|
@@ -591,21 +591,6 @@ export interface SupportedFeatureConfig {
|
|
|
591
591
|
callbackForwarding?: string[];
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
/**
|
|
595
|
-
* Class-level configuration for the features system.
|
|
596
|
-
* Declared as `static featuresConfig` on the class.
|
|
597
|
-
*/
|
|
598
|
-
export interface FeaturesClassConfig {
|
|
599
|
-
/**
|
|
600
|
-
* Lifecycle method configuration.
|
|
601
|
-
* true = install 'whenFeatureReady' method.
|
|
602
|
-
* Object = custom method name.
|
|
603
|
-
*/
|
|
604
|
-
lifecycleKeys?: true | {
|
|
605
|
-
whenFeatureReady?: string;
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
|
|
609
594
|
/**
|
|
610
595
|
* Configuration for a feature passed to assignFeatures.
|
|
611
596
|
*/
|
|
@@ -615,7 +600,8 @@ export interface FeatureConfig {
|
|
|
615
600
|
*/
|
|
616
601
|
spawn?:
|
|
617
602
|
| { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
|
|
618
|
-
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
|
|
603
|
+
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
|
|
604
|
+
| string // import path or builtIns.* alias
|
|
619
605
|
|
|
620
606
|
/** Attribute patterns for parsing element attributes into initVals. */
|
|
621
607
|
withAttrs?: AttrPatterns<any>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isAllowedImportPath } from '../assignPermissions/isAllowedImportPath.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a dynamic import path is not covered by the allowed-import policy.
|
|
4
|
+
*/
|
|
5
|
+
export class ImportNotAllowedError extends Error {
|
|
6
|
+
path;
|
|
7
|
+
constructor(path) {
|
|
8
|
+
super(`Import path "${path}" is not allowed.`);
|
|
9
|
+
this.path = path;
|
|
10
|
+
this.name = 'ImportNotAllowedError';
|
|
11
|
+
console.error(`ImportNotAllowedError: ${path}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when a module does not export a class that satisfies the required criteria.
|
|
16
|
+
*/
|
|
17
|
+
export class NoMatchingExportError extends Error {
|
|
18
|
+
path;
|
|
19
|
+
constructor(path) {
|
|
20
|
+
super(`Module "${path}" does not export a matching class with a prototype.`);
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.name = 'NoMatchingExportError';
|
|
23
|
+
console.error(`NoMatchingExportError: ${path}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Base check: value must be a function with a prototype (i.e., a class constructor).
|
|
28
|
+
*/
|
|
29
|
+
function isClassWithPrototype(value) {
|
|
30
|
+
return typeof value === 'function' && value.prototype !== undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Dynamically import a module at the given path, validate the path against the
|
|
34
|
+
* allowed-import policy, and return the first exported class whose prototype passes
|
|
35
|
+
* the optional criteria check.
|
|
36
|
+
*
|
|
37
|
+
* The default export is checked first. If it does not satisfy the checks, all named
|
|
38
|
+
* exports are scanned. If no matching class is found, a `NoMatchingExportError` is thrown.
|
|
39
|
+
*/
|
|
40
|
+
export async function findClassPrototypeInPath(path, criteria) {
|
|
41
|
+
if (!isAllowedImportPath(path)) {
|
|
42
|
+
throw new ImportNotAllowedError(path);
|
|
43
|
+
}
|
|
44
|
+
const module = await import(path);
|
|
45
|
+
const candidates = [
|
|
46
|
+
module.default,
|
|
47
|
+
...Object.values(module).filter((exported) => exported !== module.default),
|
|
48
|
+
];
|
|
49
|
+
for (const exported of candidates) {
|
|
50
|
+
if (!isClassWithPrototype(exported))
|
|
51
|
+
continue;
|
|
52
|
+
if (criteria && !criteria(exported))
|
|
53
|
+
continue;
|
|
54
|
+
return exported;
|
|
55
|
+
}
|
|
56
|
+
throw new NoMatchingExportError(path);
|
|
57
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isAllowedImportPath } from '../assignPermissions/isAllowedImportPath.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when a dynamic import path is not covered by the allowed-import policy.
|
|
5
|
+
*/
|
|
6
|
+
export class ImportNotAllowedError extends Error {
|
|
7
|
+
constructor(public readonly path: string) {
|
|
8
|
+
super(`Import path "${path}" is not allowed.`);
|
|
9
|
+
this.name = 'ImportNotAllowedError';
|
|
10
|
+
console.error(`ImportNotAllowedError: ${path}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when a module does not export a class that satisfies the required criteria.
|
|
16
|
+
*/
|
|
17
|
+
export class NoMatchingExportError extends Error {
|
|
18
|
+
constructor(public readonly path: string) {
|
|
19
|
+
super(`Module "${path}" does not export a matching class with a prototype.`);
|
|
20
|
+
this.name = 'NoMatchingExportError';
|
|
21
|
+
console.error(`NoMatchingExportError: ${path}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base check: value must be a function with a prototype (i.e., a class constructor).
|
|
27
|
+
*/
|
|
28
|
+
function isClassWithPrototype(value: any): boolean {
|
|
29
|
+
return typeof value === 'function' && value.prototype !== undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Dynamically import a module at the given path, validate the path against the
|
|
34
|
+
* allowed-import policy, and return the first exported class whose prototype passes
|
|
35
|
+
* the optional criteria check.
|
|
36
|
+
*
|
|
37
|
+
* The default export is checked first. If it does not satisfy the checks, all named
|
|
38
|
+
* exports are scanned. If no matching class is found, a `NoMatchingExportError` is thrown.
|
|
39
|
+
*/
|
|
40
|
+
export async function findClassPrototypeInPath<T = any>(
|
|
41
|
+
path: string,
|
|
42
|
+
criteria?: (proto: any) => boolean
|
|
43
|
+
): Promise<{ new(): T }> {
|
|
44
|
+
if (!isAllowedImportPath(path)) {
|
|
45
|
+
throw new ImportNotAllowedError(path);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const module = await import(path);
|
|
49
|
+
|
|
50
|
+
const candidates = [
|
|
51
|
+
module.default,
|
|
52
|
+
...Object.values(module).filter((exported: any) => exported !== module.default),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const exported of candidates) {
|
|
56
|
+
if (!isClassWithPrototype(exported)) continue;
|
|
57
|
+
if (criteria && !criteria(exported)) continue;
|
|
58
|
+
return exported as { new(): T };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new NoMatchingExportError(path);
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if a function is an async spawner (returns a Promise<Constructor>)
|
|
3
|
+
* rather than a synchronous constructor.
|
|
4
|
+
*
|
|
5
|
+
* Heuristic:
|
|
6
|
+
* - AsyncFunction (async () => ...) → async spawner
|
|
7
|
+
* - Arrow function (no .prototype) → async spawner (assumed to return Promise<Constructor>)
|
|
8
|
+
* - Class or function declaration (has .prototype) → synchronous constructor
|
|
9
|
+
*/
|
|
10
|
+
export function isAsyncSpawn(fn) {
|
|
11
|
+
if (typeof fn !== 'function')
|
|
12
|
+
return false;
|
|
13
|
+
// Explicit async function
|
|
14
|
+
if (fn.constructor.name === 'AsyncFunction')
|
|
15
|
+
return true;
|
|
16
|
+
// Arrow function or non-constructor function (no .prototype)
|
|
17
|
+
if (fn.prototype === undefined)
|
|
18
|
+
return true;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines if a function is an async spawner (returns a Promise<Constructor>)
|
|
3
|
+
* rather than a synchronous constructor.
|
|
4
|
+
*
|
|
5
|
+
* Heuristic:
|
|
6
|
+
* - AsyncFunction (async () => ...) → async spawner
|
|
7
|
+
* - Arrow function (no .prototype) → async spawner (assumed to return Promise<Constructor>)
|
|
8
|
+
* - Class or function declaration (has .prototype) → synchronous constructor
|
|
9
|
+
*/
|
|
10
|
+
export function isAsyncSpawn(fn: any): boolean {
|
|
11
|
+
if (typeof fn !== 'function') return false;
|
|
12
|
+
// Explicit async function
|
|
13
|
+
if (fn.constructor.name === 'AsyncFunction') return true;
|
|
14
|
+
// Arrow function or non-constructor function (no .prototype)
|
|
15
|
+
if (fn.prototype === undefined) return true;
|
|
16
|
+
return false;
|
|
17
|
+
}
|