assign-gingerly 0.0.74 → 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.
|
@@ -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
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.75",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"assignPermissions/**",
|
|
22
22
|
"handlers/**",
|
|
23
23
|
"inferencer/**",
|
|
24
|
+
"utils/**",
|
|
24
25
|
"README.md",
|
|
25
26
|
"LICENSE",
|
|
26
27
|
"types/assign-gingerly/types.d.ts"
|
|
@@ -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
|
+
}
|