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.74",
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
+ }