assign-gingerly 0.0.73 → 0.0.74
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 -1
- package/inferencer/types/font-face-feature/types.d.ts +9 -0
- package/package.json +9 -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/defineWithFeatures.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* defineWithFeatures - Declaratively define a custom element with features from JSON config.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A thin wrapper around assignFeatures: waits for the base class, creates a subclass,
|
|
5
|
+
* optionally calls onSubclassCreated, awaits assignFeatures(NewClass, config.assignFeatures),
|
|
6
|
+
* then defines the custom element in the registry.
|
|
7
7
|
*
|
|
8
8
|
* Designed to support cede scripts and other declarative custom element definition patterns.
|
|
9
9
|
*
|
|
@@ -20,30 +20,13 @@
|
|
|
20
20
|
* });
|
|
21
21
|
*/
|
|
22
22
|
import { assignFeatures } from './assignFeatures.js';
|
|
23
|
-
/**
|
|
24
|
-
* Determines if a function is an async spawner (same heuristic as assignFeatures).
|
|
25
|
-
*/
|
|
26
|
-
function isAsyncSpawn(fn) {
|
|
27
|
-
if (typeof fn !== 'function')
|
|
28
|
-
return false;
|
|
29
|
-
if (fn.constructor.name === 'AsyncFunction')
|
|
30
|
-
return true;
|
|
31
|
-
if (fn.prototype === undefined)
|
|
32
|
-
return true;
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Cache for resolved fallback spawns.
|
|
37
|
-
* Key: BaseClass, Value: Map<featureKey, resolvedConstructor>
|
|
38
|
-
*/
|
|
39
|
-
const resolvedSpawnCache = new WeakMap();
|
|
40
23
|
/**
|
|
41
24
|
* Declaratively define a custom element with features.
|
|
42
25
|
*
|
|
43
26
|
* 1. Waits for the base class to be defined (if not already).
|
|
44
|
-
* 2.
|
|
45
|
-
* 3.
|
|
46
|
-
* 4. Calls
|
|
27
|
+
* 2. Creates a subclass extending the base class.
|
|
28
|
+
* 3. Calls the optional onSubclassCreated callback.
|
|
29
|
+
* 4. Calls assignFeatures with the JSON config and the registry's featuresRegistry.
|
|
47
30
|
* 5. Defines the new custom element in the registry.
|
|
48
31
|
*
|
|
49
32
|
* @param tagName - The custom element tag name to define (e.g., 'time-ticker')
|
|
@@ -63,62 +46,19 @@ export async function defineWithFeatures(tagName, baseTagName, config, registry,
|
|
|
63
46
|
if (!BaseClass) {
|
|
64
47
|
throw new Error(`defineWithFeatures: base class "${baseTagName}" could not be resolved`);
|
|
65
48
|
}
|
|
66
|
-
|
|
67
|
-
if (!supportedFeatures) {
|
|
68
|
-
throw new Error(`defineWithFeatures: "${baseTagName}" does not define static supportedFeatures`);
|
|
69
|
-
}
|
|
70
|
-
// 2. Resolve all async fallback spawns (with caching)
|
|
71
|
-
let classCache = resolvedSpawnCache.get(BaseClass);
|
|
72
|
-
if (!classCache) {
|
|
73
|
-
classCache = new Map();
|
|
74
|
-
resolvedSpawnCache.set(BaseClass, classCache);
|
|
75
|
-
}
|
|
76
|
-
const { assignFeatures: af } = config;
|
|
77
|
-
let resolvedSpawns;
|
|
78
|
-
if (af) {
|
|
79
|
-
const featureKeys = Object.keys(af);
|
|
80
|
-
resolvedSpawns = new Map();
|
|
81
|
-
await Promise.all(featureKeys.map(async (key) => {
|
|
82
|
-
const optIn = supportedFeatures[key];
|
|
83
|
-
if (!optIn) {
|
|
84
|
-
throw new Error(`defineWithFeatures: feature "${key}" not found in ${baseTagName}.supportedFeatures`);
|
|
85
|
-
}
|
|
86
|
-
// Check cache first
|
|
87
|
-
if (classCache.has(key)) {
|
|
88
|
-
resolvedSpawns.set(key, classCache.get(key));
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
let spawn = optIn.fallbackSpawn;
|
|
92
|
-
if (spawn && isAsyncSpawn(spawn)) {
|
|
93
|
-
// Resolve the async spawner
|
|
94
|
-
spawn = await spawn();
|
|
95
|
-
}
|
|
96
|
-
// Cache the resolved spawn
|
|
97
|
-
if (spawn) {
|
|
98
|
-
classCache.set(key, spawn);
|
|
99
|
-
}
|
|
100
|
-
resolvedSpawns.set(key, spawn);
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
// 3. Create subclass
|
|
49
|
+
// 2. Create subclass
|
|
104
50
|
const NewClass = class extends BaseClass {
|
|
105
51
|
};
|
|
106
|
-
//
|
|
52
|
+
// 3. Optional subclass callback
|
|
107
53
|
if (options?.onSubclassCreated) {
|
|
108
54
|
options.onSubclassCreated(NewClass);
|
|
109
55
|
}
|
|
56
|
+
// 4. Assign features (assignFeatures resolves all spawns and installs getters)
|
|
57
|
+
const { assignFeatures: af } = config;
|
|
110
58
|
if (af) {
|
|
111
|
-
|
|
112
|
-
const featuresMap = {};
|
|
113
|
-
for (const [key, jsonConfig] of Object.entries(af)) {
|
|
114
|
-
featuresMap[key] = {
|
|
115
|
-
spawn: resolvedSpawns.get(key),
|
|
116
|
-
...jsonConfig
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
// 5. assignFeatures (sequential onAssigned) + define
|
|
120
|
-
await assignFeatures(NewClass, featuresMap, reg.featuresRegistry);
|
|
59
|
+
await assignFeatures(NewClass, af, reg.featuresRegistry);
|
|
121
60
|
}
|
|
61
|
+
// 5. Define the custom element
|
|
122
62
|
reg.define(tagName, NewClass);
|
|
123
63
|
return NewClass;
|
|
124
64
|
}
|
package/defineWithFeatures.ts
CHANGED
|
@@ -1,167 +1,92 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* defineWithFeatures - Declaratively define a custom element with features from JSON config.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Designed to support cede scripts and other declarative custom element definition patterns.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* await defineWithFeatures('time-ticker', 'el-maker', {
|
|
12
|
-
* assignFeatures: {
|
|
13
|
-
* timeTicker: {},
|
|
14
|
-
* roundabout: {
|
|
15
|
-
* customData: {...},
|
|
16
|
-
* withAttrs: {...},
|
|
17
|
-
* callbackForwarding: ['connectedCallback']
|
|
18
|
-
* }
|
|
19
|
-
* }
|
|
20
|
-
* });
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { assignFeatures, FeatureConfigsMap
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Configuration passed to defineWithFeatures (JSON-serializable).
|
|
27
|
-
*/
|
|
28
|
-
export interface DefineWithFeaturesConfig {
|
|
29
|
-
assignFeatures:
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const supportedFeatures: SupportedFeaturesMap | undefined = BaseClass.supportedFeatures;
|
|
95
|
-
if (!supportedFeatures) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`defineWithFeatures: "${baseTagName}" does not define static supportedFeatures`
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 2. Resolve all async fallback spawns (with caching)
|
|
102
|
-
let classCache = resolvedSpawnCache.get(BaseClass);
|
|
103
|
-
if (!classCache) {
|
|
104
|
-
classCache = new Map();
|
|
105
|
-
resolvedSpawnCache.set(BaseClass, classCache);
|
|
106
|
-
}
|
|
107
|
-
const {assignFeatures: af} = config;
|
|
108
|
-
let resolvedSpawns: Map<string, any> | undefined;
|
|
109
|
-
if (af) {
|
|
110
|
-
const featureKeys = Object.keys(af);
|
|
111
|
-
resolvedSpawns = new Map<string, any>();
|
|
112
|
-
|
|
113
|
-
await Promise.all(featureKeys.map(async (key) => {
|
|
114
|
-
const optIn = supportedFeatures[key];
|
|
115
|
-
if (!optIn) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
`defineWithFeatures: feature "${key}" not found in ${baseTagName}.supportedFeatures`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check cache first
|
|
122
|
-
if (classCache!.has(key)) {
|
|
123
|
-
resolvedSpawns!.set(key, classCache!.get(key));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
let spawn = optIn.fallbackSpawn;
|
|
128
|
-
if (spawn && isAsyncSpawn(spawn)) {
|
|
129
|
-
// Resolve the async spawner
|
|
130
|
-
spawn = await (spawn as () => Promise<any>)();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Cache the resolved spawn
|
|
134
|
-
if (spawn) {
|
|
135
|
-
classCache!.set(key, spawn);
|
|
136
|
-
}
|
|
137
|
-
resolvedSpawns!.set(key, spawn);
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// 3. Create subclass
|
|
143
|
-
const NewClass = class extends (BaseClass as any) { };
|
|
144
|
-
|
|
145
|
-
// 3b. Call onSubclassCreated callback (before define, before features if needed)
|
|
146
|
-
if (options?.onSubclassCreated) {
|
|
147
|
-
options.onSubclassCreated(NewClass);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if(af){
|
|
151
|
-
// 4. Build FeatureConfigsMap: resolved spawns + JSON config
|
|
152
|
-
const featuresMap: FeatureConfigsMap = {};
|
|
153
|
-
for (const [key, jsonConfig] of Object.entries(af)) {
|
|
154
|
-
featuresMap[key] = {
|
|
155
|
-
spawn: resolvedSpawns!.get(key),
|
|
156
|
-
...jsonConfig
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 5. assignFeatures (sequential onAssigned) + define
|
|
161
|
-
await assignFeatures(NewClass, featuresMap, (reg as any).featuresRegistry);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
(reg as any).define(tagName, NewClass);
|
|
165
|
-
|
|
166
|
-
return NewClass;
|
|
167
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* defineWithFeatures - Declaratively define a custom element with features from JSON config.
|
|
3
|
+
*
|
|
4
|
+
* A thin wrapper around assignFeatures: waits for the base class, creates a subclass,
|
|
5
|
+
* optionally calls onSubclassCreated, awaits assignFeatures(NewClass, config.assignFeatures),
|
|
6
|
+
* then defines the custom element in the registry.
|
|
7
|
+
*
|
|
8
|
+
* Designed to support cede scripts and other declarative custom element definition patterns.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* await defineWithFeatures('time-ticker', 'el-maker', {
|
|
12
|
+
* assignFeatures: {
|
|
13
|
+
* timeTicker: {},
|
|
14
|
+
* roundabout: {
|
|
15
|
+
* customData: {...},
|
|
16
|
+
* withAttrs: {...},
|
|
17
|
+
* callbackForwarding: ['connectedCallback']
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { assignFeatures, FeatureConfigsMap } from './assignFeatures.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration passed to defineWithFeatures (JSON-serializable).
|
|
27
|
+
*/
|
|
28
|
+
export interface DefineWithFeaturesConfig {
|
|
29
|
+
assignFeatures: FeatureConfigsMap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for defineWithFeatures.
|
|
34
|
+
*/
|
|
35
|
+
export interface DefineWithFeaturesOptions {
|
|
36
|
+
/** Called after the subclass is created but before registry.define(). */
|
|
37
|
+
onSubclassCreated?: (NewCtr: Function) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Declaratively define a custom element with features.
|
|
42
|
+
*
|
|
43
|
+
* 1. Waits for the base class to be defined (if not already).
|
|
44
|
+
* 2. Creates a subclass extending the base class.
|
|
45
|
+
* 3. Calls the optional onSubclassCreated callback.
|
|
46
|
+
* 4. Calls assignFeatures with the JSON config and the registry's featuresRegistry.
|
|
47
|
+
* 5. Defines the new custom element in the registry.
|
|
48
|
+
*
|
|
49
|
+
* @param tagName - The custom element tag name to define (e.g., 'time-ticker')
|
|
50
|
+
* @param baseTagName - The tag name of the base class to extend (e.g., 'el-maker')
|
|
51
|
+
* @param config - JSON-serializable configuration specifying which features to activate
|
|
52
|
+
* @param registry - Optional custom element registry (defaults to global `customElements`)
|
|
53
|
+
* @returns The newly created and defined custom element class
|
|
54
|
+
*/
|
|
55
|
+
export async function defineWithFeatures(
|
|
56
|
+
tagName: string,
|
|
57
|
+
baseTagName: string,
|
|
58
|
+
config: DefineWithFeaturesConfig,
|
|
59
|
+
registry?: CustomElementRegistry,
|
|
60
|
+
options?: DefineWithFeaturesOptions
|
|
61
|
+
): Promise<Function> {
|
|
62
|
+
const reg = registry || customElements;
|
|
63
|
+
|
|
64
|
+
// 1. Resolve base class — wait for it if not yet defined
|
|
65
|
+
let BaseClass = (reg as any).get(baseTagName);
|
|
66
|
+
if (!BaseClass) {
|
|
67
|
+
await (reg as any).whenDefined(baseTagName);
|
|
68
|
+
BaseClass = (reg as any).get(baseTagName);
|
|
69
|
+
}
|
|
70
|
+
if (!BaseClass) {
|
|
71
|
+
throw new Error(`defineWithFeatures: base class "${baseTagName}" could not be resolved`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Create subclass
|
|
75
|
+
const NewClass = class extends (BaseClass as any) { };
|
|
76
|
+
|
|
77
|
+
// 3. Optional subclass callback
|
|
78
|
+
if (options?.onSubclassCreated) {
|
|
79
|
+
options.onSubclassCreated(NewClass);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. Assign features (assignFeatures resolves all spawns and installs getters)
|
|
83
|
+
const { assignFeatures: af } = config;
|
|
84
|
+
if (af) {
|
|
85
|
+
await assignFeatures(NewClass, af, (reg as any).featuresRegistry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. Define the custom element
|
|
89
|
+
(reg as any).define(tagName, NewClass);
|
|
90
|
+
|
|
91
|
+
return NewClass;
|
|
92
|
+
}
|
package/enhanceAll.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* ]);
|
|
17
17
|
*/
|
|
18
18
|
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
19
|
+
import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
19
20
|
/**
|
|
20
21
|
* Apply enhancements in bulk to matching elements within a target.
|
|
21
22
|
*
|
|
@@ -85,13 +86,16 @@ async function resolveAndRegister(enhConfig, target) {
|
|
|
85
86
|
if (existing)
|
|
86
87
|
return existing;
|
|
87
88
|
}
|
|
88
|
-
// Not registered — dynamically import the spawn module
|
|
89
|
+
// Not registered — dynamically import the spawn module and extract the spawn class
|
|
89
90
|
if (!spawnPath)
|
|
90
91
|
return null;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
let SpawnClass;
|
|
93
|
+
try {
|
|
94
|
+
SpawnClass = await findClassPrototypeInPath(spawnPath);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
94
97
|
return null;
|
|
98
|
+
}
|
|
95
99
|
// Build and register the registry item
|
|
96
100
|
const registryItem = {
|
|
97
101
|
spawn: SpawnClass,
|
package/enhanceAll.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
20
|
+
import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
20
21
|
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -112,15 +113,15 @@ async function resolveAndRegister(enhConfig: any, target: Element): Promise<any>
|
|
|
112
113
|
if (existing) return existing;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
// Not registered — dynamically import the spawn module
|
|
116
|
+
// Not registered — dynamically import the spawn module and extract the spawn class
|
|
116
117
|
if (!spawnPath) return null;
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
let SpawnClass;
|
|
120
|
+
try {
|
|
121
|
+
SpawnClass = await findClassPrototypeInPath(spawnPath);
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
124
125
|
|
|
125
126
|
// Build and register the registry item
|
|
126
127
|
const registryItem: any = {
|
|
@@ -615,7 +615,8 @@ export interface FeatureConfig {
|
|
|
615
615
|
*/
|
|
616
616
|
spawn?:
|
|
617
617
|
| { new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }
|
|
618
|
-
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
|
|
618
|
+
| (() => Promise<{ new(hostElement: any, ctx: FeatureSpawnContext, initVals?: any): any }>)
|
|
619
|
+
| string // import path or builtIns.* alias
|
|
619
620
|
|
|
620
621
|
/** Attribute patterns for parsing element attributes into initVals. */
|
|
621
622
|
withAttrs?: AttrPatterns<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.74",
|
|
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": {
|
|
@@ -148,6 +148,14 @@
|
|
|
148
148
|
"default": "./assignPermissions/isAllowedImportPath.js",
|
|
149
149
|
"types": "./assignPermissions/isAllowedImportPath.ts"
|
|
150
150
|
},
|
|
151
|
+
"./utils/findClassPrototypeInPath.js": {
|
|
152
|
+
"default": "./utils/findClassPrototypeInPath.js",
|
|
153
|
+
"types": "./utils/findClassPrototypeInPath.ts"
|
|
154
|
+
},
|
|
155
|
+
"./utils/isAsyncSpawn.js": {
|
|
156
|
+
"default": "./utils/isAsyncSpawn.js",
|
|
157
|
+
"types": "./utils/isAsyncSpawn.ts"
|
|
158
|
+
},
|
|
151
159
|
"./markerUtils.js": {
|
|
152
160
|
"default": "./markerUtils.js",
|
|
153
161
|
"types": "./markerUtils.ts"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { resolveValues } from './resolveValues.js';
|
|
7
7
|
import { getValues } from './getValues.js';
|
|
8
8
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
9
|
-
import {
|
|
9
|
+
import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
10
10
|
import { buildRestrictedPropSet, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
11
11
|
/**
|
|
12
12
|
* Map of built-in handler names to their module paths.
|
|
@@ -21,23 +21,11 @@ const BUILT_IN_MAP = {
|
|
|
21
21
|
'builtIns.rangeSelector': './handlers/rangeSelector.js',
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Criteria for locating an assignFrom handler class: must expose an `assign` method
|
|
25
|
+
* on its prototype.
|
|
26
26
|
*/
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
if (module.default && typeof module.default === 'function'
|
|
30
|
-
&& module.default.prototype && 'assign' in module.default.prototype) {
|
|
31
|
-
return module.default;
|
|
32
|
-
}
|
|
33
|
-
// Search other exports
|
|
34
|
-
for (const key of Object.keys(module)) {
|
|
35
|
-
const exported = module[key];
|
|
36
|
-
if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
|
|
37
|
-
return exported;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return undefined;
|
|
27
|
+
function handlerCriteria(proto) {
|
|
28
|
+
return 'assign' in proto.prototype;
|
|
41
29
|
}
|
|
42
30
|
/**
|
|
43
31
|
* Cache for loaded built-in handler classes — avoids await on subsequent calls.
|
|
@@ -55,16 +43,14 @@ async function loadBuiltIn(name) {
|
|
|
55
43
|
const path = BUILT_IN_MAP[name];
|
|
56
44
|
if (!path)
|
|
57
45
|
return undefined;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
if (cls)
|
|
61
|
-
handlerCache.set(name, cls);
|
|
46
|
+
const cls = await findClassPrototypeInPath(path, handlerCriteria);
|
|
47
|
+
handlerCache.set(name, cls);
|
|
62
48
|
return cls;
|
|
63
49
|
}
|
|
64
50
|
/**
|
|
65
51
|
* Resolve a handler from options.handlers (class constructor or import path).
|
|
66
52
|
*/
|
|
67
|
-
async function resolveFromHandlers(name, handlers
|
|
53
|
+
async function resolveFromHandlers(name, handlers) {
|
|
68
54
|
if (!handlers || !(name in handlers))
|
|
69
55
|
return undefined;
|
|
70
56
|
const entry = handlers[name];
|
|
@@ -78,17 +64,8 @@ async function resolveFromHandlers(name, handlers, permissions) {
|
|
|
78
64
|
if (entry.startsWith('builtIns.')) {
|
|
79
65
|
return loadBuiltIn(entry);
|
|
80
66
|
}
|
|
81
|
-
// Import path string — validate and
|
|
82
|
-
|
|
83
|
-
throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
84
|
-
`Only same-origin paths or import-map-covered specifiers are allowed. ` +
|
|
85
|
-
`Pass { crossDomainImports: true } in permissions to override.`);
|
|
86
|
-
}
|
|
87
|
-
const module = await import(entry);
|
|
88
|
-
const HandlerClass = findHandlerInModule(module);
|
|
89
|
-
if (!HandlerClass) {
|
|
90
|
-
throw new Error(`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`);
|
|
91
|
-
}
|
|
67
|
+
// Import path string — validate and extract handler class via shared utility
|
|
68
|
+
const HandlerClass = await findClassPrototypeInPath(entry, handlerCriteria);
|
|
92
69
|
return HandlerClass;
|
|
93
70
|
}
|
|
94
71
|
return undefined;
|
|
@@ -180,7 +157,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
180
157
|
for (const config of configs) {
|
|
181
158
|
//return; //1.3ms
|
|
182
159
|
// 1. Check options.handlers (local, per-call)
|
|
183
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers
|
|
160
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
184
161
|
//return; // 1.4
|
|
185
162
|
// 2. Fallback to built-in auto-load
|
|
186
163
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
|
@@ -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.')) {
|