assign-gingerly 0.0.58 → 0.0.60
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 +511 -142
- package/assignFrom-extension.js +25 -0
- package/assignFrom-extension.ts +53 -0
- package/assignFrom.js +306 -133
- package/assignFrom.ts +318 -232
- package/assignFromAsync-extension.js +28 -0
- package/assignFromAsync-extension.ts +58 -0
- package/assignFromAsync.js +120 -0
- package/assignFromAsync.ts +256 -0
- package/assignGingerly.js +50 -0
- package/assignGingerly.ts +51 -0
- package/builtInEmoji.js +25 -0
- package/builtInEmoji.ts +33 -0
- package/getValues.js +223 -0
- package/getValues.ts +255 -0
- package/handlers/join.ts +1 -1
- package/handlers/lazyLoad.js +33 -2
- package/handlers/lazyLoad.ts +47 -2
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.js +226 -0
- package/handlers/manageTemplateList.ts +263 -0
- package/handlers/microDataJoin.ts +1 -1
- package/index.js +1 -0
- package/index.ts +2 -0
- package/inferencer/inferencer.js +9 -21
- package/inferencer/inferencer.ts +10 -21
- package/inferredAssignments.js +35 -5
- package/inferredAssignments.ts +58 -8
- package/markerUtils.js +136 -127
- package/package.json +30 -1
- package/playwright.config.ts +3 -2
- package/processHandlerCommands.js +36 -4
- package/processHandlerCommands.ts +38 -8
- package/resolveIdRef.js +70 -19
- package/resolveIdRef.ts +73 -21
- package/resolveValues.js +41 -125
- package/resolveValues.ts +131 -255
- package/types/assign-gingerly/types.d.ts +61 -0
- package/waitForSettled.js +57 -0
- package/waitForSettled.ts +65 -0
- package/withIdsCorrector.js +47 -0
- package/withIdsCorrector.ts +59 -0
package/inferredAssignments.js
CHANGED
|
@@ -12,16 +12,19 @@ import { Infer } from './inferencer/inferencer.js';
|
|
|
12
12
|
* @param from - The source object containing values to distribute
|
|
13
13
|
* @param config - The inferredAssignments configuration
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
15
|
+
export function processInferredAssignments(target, from, config) {
|
|
16
16
|
if (!(target instanceof Element))
|
|
17
17
|
return;
|
|
18
18
|
if (!from || typeof from !== 'object')
|
|
19
19
|
return;
|
|
20
|
-
const { byItemprop } = config;
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const { byItemprop, byName } = config;
|
|
21
|
+
// Resolve aliases: '|' → byItemprop, '@' → byName
|
|
22
|
+
const effectiveByItemprop = byItemprop ?? config['|'];
|
|
23
|
+
const effectiveByName = byName ?? config['@'];
|
|
24
|
+
if (effectiveByItemprop) {
|
|
25
|
+
const keys = effectiveByItemprop === true
|
|
23
26
|
? Object.keys(from)
|
|
24
|
-
:
|
|
27
|
+
: effectiveByItemprop;
|
|
25
28
|
const infer = new Infer(target);
|
|
26
29
|
for (const key of keys) {
|
|
27
30
|
if (!(key in from))
|
|
@@ -35,4 +38,31 @@ export async function processInferredAssignments(target, from, config) {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
41
|
+
if (effectiveByName) {
|
|
42
|
+
// Normalize config
|
|
43
|
+
let keys;
|
|
44
|
+
let scopeBoundary;
|
|
45
|
+
if (effectiveByName === true) {
|
|
46
|
+
keys = Object.keys(from);
|
|
47
|
+
}
|
|
48
|
+
else if (Array.isArray(effectiveByName)) {
|
|
49
|
+
keys = effectiveByName;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Object form: { props, outside }
|
|
53
|
+
keys = effectiveByName.props === true ? Object.keys(from) : effectiveByName.props;
|
|
54
|
+
scopeBoundary = effectiveByName.outside;
|
|
55
|
+
}
|
|
56
|
+
const infer = new Infer(target);
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
if (!(key in from))
|
|
59
|
+
continue;
|
|
60
|
+
const value = from[key];
|
|
61
|
+
// Use inferencer's ['@'] method — by name attribute, optional scope boundary
|
|
62
|
+
const matches = infer['@'](key, scopeBoundary);
|
|
63
|
+
for (const match of matches) {
|
|
64
|
+
match.value = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
38
68
|
}
|
package/inferredAssignments.ts
CHANGED
|
@@ -20,8 +20,23 @@ export interface InferredAssignmentsConfig {
|
|
|
20
20
|
*/
|
|
21
21
|
byItemprop?: string[] | true;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/** Concise alias for byItemprop */
|
|
24
|
+
'|'?: string[] | true;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Array of property keys to distribute by name attribute.
|
|
28
|
+
* For each key, finds [name="${key}"] elements and sets the value using
|
|
29
|
+
* the inferred property (value, checked, etc.).
|
|
30
|
+
*
|
|
31
|
+
* Pass `true` to infer all keys from the `from` source object.
|
|
32
|
+
*
|
|
33
|
+
* Object form enables donut-hole scoping:
|
|
34
|
+
* { props: ['firstName', 'lastName'], outside: 'fieldset' }
|
|
35
|
+
*/
|
|
36
|
+
byName?: string[] | true | { props: string[] | true; outside: string };
|
|
37
|
+
|
|
38
|
+
/** Concise alias for byName */
|
|
39
|
+
'@'?: string[] | true | { props: string[] | true; outside: string };
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
/**
|
|
@@ -31,20 +46,24 @@ export interface InferredAssignmentsConfig {
|
|
|
31
46
|
* @param from - The source object containing values to distribute
|
|
32
47
|
* @param config - The inferredAssignments configuration
|
|
33
48
|
*/
|
|
34
|
-
export
|
|
49
|
+
export function processInferredAssignments(
|
|
35
50
|
target: any,
|
|
36
51
|
from: any,
|
|
37
52
|
config: InferredAssignmentsConfig
|
|
38
|
-
):
|
|
53
|
+
): void {
|
|
39
54
|
if (!(target instanceof Element)) return;
|
|
40
55
|
if (!from || typeof from !== 'object') return;
|
|
41
56
|
|
|
42
|
-
const { byItemprop } = config;
|
|
57
|
+
const { byItemprop, byName } = config;
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
// Resolve aliases: '|' → byItemprop, '@' → byName
|
|
60
|
+
const effectiveByItemprop = byItemprop ?? config['|'];
|
|
61
|
+
const effectiveByName = byName ?? config['@'];
|
|
62
|
+
|
|
63
|
+
if (effectiveByItemprop) {
|
|
64
|
+
const keys = effectiveByItemprop === true
|
|
46
65
|
? Object.keys(from)
|
|
47
|
-
:
|
|
66
|
+
: effectiveByItemprop;
|
|
48
67
|
|
|
49
68
|
const infer = new Infer(target);
|
|
50
69
|
|
|
@@ -62,4 +81,35 @@ export async function processInferredAssignments(
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
}
|
|
84
|
+
|
|
85
|
+
if (effectiveByName) {
|
|
86
|
+
// Normalize config
|
|
87
|
+
let keys: string[];
|
|
88
|
+
let scopeBoundary: string | undefined;
|
|
89
|
+
|
|
90
|
+
if (effectiveByName === true) {
|
|
91
|
+
keys = Object.keys(from);
|
|
92
|
+
} else if (Array.isArray(effectiveByName)) {
|
|
93
|
+
keys = effectiveByName;
|
|
94
|
+
} else {
|
|
95
|
+
// Object form: { props, outside }
|
|
96
|
+
keys = effectiveByName.props === true ? Object.keys(from) : effectiveByName.props;
|
|
97
|
+
scopeBoundary = effectiveByName.outside;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const infer = new Infer(target);
|
|
101
|
+
|
|
102
|
+
for (const key of keys) {
|
|
103
|
+
if (!(key in from)) continue;
|
|
104
|
+
|
|
105
|
+
const value = from[key];
|
|
106
|
+
|
|
107
|
+
// Use inferencer's ['@'] method — by name attribute, optional scope boundary
|
|
108
|
+
const matches: Infer[] = infer['@'](key, scopeBoundary);
|
|
109
|
+
|
|
110
|
+
for (const match of matches) {
|
|
111
|
+
match.value = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
65
115
|
}
|
package/markerUtils.js
CHANGED
|
@@ -1,127 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* markerUtils.
|
|
3
|
-
*
|
|
4
|
-
* Used by lazyLoad, microDataJoin, and future template loop handlers.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
null,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
target.prepend(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get all nodes between start and end markers.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
1
|
+
/**
|
|
2
|
+
* markerUtils.ts — Shared utilities for comment marker management.
|
|
3
|
+
*
|
|
4
|
+
* Used by lazyLoad, microDataJoin, and future template loop handlers.
|
|
5
|
+
* Provides finding, creating, and traversing comment marker pairs.
|
|
6
|
+
*
|
|
7
|
+
* Markers are HTML comment nodes with specific content:
|
|
8
|
+
* - Start: <!--?start name="markerName"-->
|
|
9
|
+
* - End: <!--?end-->
|
|
10
|
+
*/
|
|
11
|
+
export const MARKER_START_PREFIX = '?start name="';
|
|
12
|
+
export const MARKER_END = '?end';
|
|
13
|
+
/**
|
|
14
|
+
* Find existing start/end comment markers in a target element (TreeWalker approach).
|
|
15
|
+
* Searches the subtree of `target` for matching comment nodes.
|
|
16
|
+
*
|
|
17
|
+
* @param target - The element to search within
|
|
18
|
+
* @param name - The marker name to find
|
|
19
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
20
|
+
*/
|
|
21
|
+
export function findMarkers(target, name) {
|
|
22
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
23
|
+
let startMarker = null;
|
|
24
|
+
let endMarker = null;
|
|
25
|
+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_COMMENT);
|
|
26
|
+
let node;
|
|
27
|
+
while ((node = walker.nextNode())) {
|
|
28
|
+
if (!startMarker && node.data === startText) {
|
|
29
|
+
startMarker = node;
|
|
30
|
+
}
|
|
31
|
+
else if (startMarker && !endMarker && node.data === MARKER_END) {
|
|
32
|
+
endMarker = node;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [startMarker, endMarker];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Find existing start/end comment markers using XPath (alternative approach).
|
|
40
|
+
* May be faster in large DOMs due to engine-level indexing.
|
|
41
|
+
*
|
|
42
|
+
* @param target - The element to search within
|
|
43
|
+
* @param name - The marker name to find
|
|
44
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
45
|
+
*/
|
|
46
|
+
export function findMarkersXPath(target, name) {
|
|
47
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
48
|
+
const startResult = document.evaluate(`.//comment()[. = "${startText}"]`, target, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
49
|
+
const startMarker = startResult.singleNodeValue;
|
|
50
|
+
if (!startMarker)
|
|
51
|
+
return [null, null];
|
|
52
|
+
// Find the next sibling comment that is the end marker
|
|
53
|
+
const endResult = document.evaluate(`following-sibling::comment()[. = "${MARKER_END}"][1]`, startMarker, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
54
|
+
const endMarker = endResult.singleNodeValue;
|
|
55
|
+
return [startMarker, endMarker];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create start/end markers and insert them into the target.
|
|
59
|
+
*
|
|
60
|
+
* @param target - The element to insert markers into
|
|
61
|
+
* @param name - The marker name
|
|
62
|
+
* @param method - 'appendChild' (default) or 'prepend'
|
|
63
|
+
* @returns [startMarker, endMarker]
|
|
64
|
+
*/
|
|
65
|
+
export function createMarkers(target, name, method = 'appendChild') {
|
|
66
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
67
|
+
const endMarker = document.createComment(MARKER_END);
|
|
68
|
+
if (method === 'prepend') {
|
|
69
|
+
target.prepend(endMarker);
|
|
70
|
+
target.prepend(startMarker);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
target.appendChild(startMarker);
|
|
74
|
+
target.appendChild(endMarker);
|
|
75
|
+
}
|
|
76
|
+
return [startMarker, endMarker];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all nodes between start and end markers.
|
|
80
|
+
*
|
|
81
|
+
* @param start - The start comment marker
|
|
82
|
+
* @param end - The end comment marker
|
|
83
|
+
* @returns Array of nodes between the markers (exclusive of markers themselves)
|
|
84
|
+
*/
|
|
85
|
+
export function getNodesBetweenMarkers(start, end) {
|
|
86
|
+
const nodes = [];
|
|
87
|
+
let current = start.nextSibling;
|
|
88
|
+
while (current && current !== end) {
|
|
89
|
+
nodes.push(current);
|
|
90
|
+
current = current.nextSibling;
|
|
91
|
+
}
|
|
92
|
+
return nodes;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find existing start/end comment markers among siblings of an anchor element.
|
|
96
|
+
* Used for 'after' insertion mode where markers are siblings, not children.
|
|
97
|
+
*
|
|
98
|
+
* @param anchor - The element after which markers were inserted
|
|
99
|
+
* @param name - The marker name to find
|
|
100
|
+
* @returns [startMarker, endMarker] or [null, null] if not found
|
|
101
|
+
*/
|
|
102
|
+
export function findMarkersSibling(anchor, name) {
|
|
103
|
+
const startText = `${MARKER_START_PREFIX}${name}"`;
|
|
104
|
+
let startMarker = null;
|
|
105
|
+
let endMarker = null;
|
|
106
|
+
let current = anchor.nextSibling;
|
|
107
|
+
while (current) {
|
|
108
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
109
|
+
const comment = current;
|
|
110
|
+
if (!startMarker && comment.data === startText) {
|
|
111
|
+
startMarker = comment;
|
|
112
|
+
}
|
|
113
|
+
else if (startMarker && !endMarker && comment.data === MARKER_END) {
|
|
114
|
+
endMarker = comment;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
current = current.nextSibling;
|
|
119
|
+
}
|
|
120
|
+
return [startMarker, endMarker];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create start/end markers as siblings after an anchor element.
|
|
124
|
+
* Used for 'after' insertion mode.
|
|
125
|
+
*
|
|
126
|
+
* @param anchor - The element to insert markers after
|
|
127
|
+
* @param name - The marker name
|
|
128
|
+
* @returns [startMarker, endMarker]
|
|
129
|
+
*/
|
|
130
|
+
export function createMarkersSibling(anchor, name) {
|
|
131
|
+
const startMarker = document.createComment(`${MARKER_START_PREFIX}${name}"`);
|
|
132
|
+
const endMarker = document.createComment(MARKER_END);
|
|
133
|
+
// Insert after the anchor: anchor → startMarker → endMarker
|
|
134
|
+
anchor.after(startMarker, endMarker);
|
|
135
|
+
return [startMarker, endMarker];
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
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": {
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
"default": "./resolveValues.js",
|
|
63
63
|
"types": "./resolveValues.ts"
|
|
64
64
|
},
|
|
65
|
+
"./getValues.js": {
|
|
66
|
+
"default": "./getValues.js",
|
|
67
|
+
"types": "./getValues.ts"
|
|
68
|
+
},
|
|
65
69
|
"./resolveIdRef.js": {
|
|
66
70
|
"default": "./resolveIdRef.js",
|
|
67
71
|
"types": "./resolveIdRef.ts"
|
|
@@ -98,6 +102,10 @@
|
|
|
98
102
|
"default": "./handlers/microDataJoin.js",
|
|
99
103
|
"types": "./handlers/microDataJoin.ts"
|
|
100
104
|
},
|
|
105
|
+
"./handlers/manageTemplateList.js": {
|
|
106
|
+
"default": "./handlers/manageTemplateList.js",
|
|
107
|
+
"types": "./handlers/manageTemplateList.ts"
|
|
108
|
+
},
|
|
101
109
|
"./paths.js": {
|
|
102
110
|
"default": "./paths.js",
|
|
103
111
|
"types": "./paths.ts"
|
|
@@ -114,6 +122,10 @@
|
|
|
114
122
|
"default": "./markerUtils.js",
|
|
115
123
|
"types": "./markerUtils.ts"
|
|
116
124
|
},
|
|
125
|
+
"./waitForSettled.js": {
|
|
126
|
+
"default": "./waitForSettled.js",
|
|
127
|
+
"types": "./waitForSettled.ts"
|
|
128
|
+
},
|
|
117
129
|
"./inferredAssignments.js": {
|
|
118
130
|
"default": "./inferredAssignments.js",
|
|
119
131
|
"types": "./inferredAssignments.ts"
|
|
@@ -130,10 +142,26 @@
|
|
|
130
142
|
"default": "./inferencer/withScopePerimeter.js",
|
|
131
143
|
"types": "./inferencer/withScopePerimeter.ts"
|
|
132
144
|
},
|
|
145
|
+
"./assignFromAsync.js": {
|
|
146
|
+
"default": "./assignFromAsync.js",
|
|
147
|
+
"types": "./assignFromAsync.ts"
|
|
148
|
+
},
|
|
133
149
|
"./assignFrom.js": {
|
|
134
150
|
"default": "./assignFrom.js",
|
|
135
151
|
"types": "./assignFrom.ts"
|
|
136
152
|
},
|
|
153
|
+
"./assignFrom-extension.js": {
|
|
154
|
+
"default": "./assignFrom-extension.js",
|
|
155
|
+
"types": "./assignFrom-extension.ts"
|
|
156
|
+
},
|
|
157
|
+
"./assignFromAsync-extension.js": {
|
|
158
|
+
"default": "./assignFromAsync-extension.js",
|
|
159
|
+
"types": "./assignFromAsync-extension.ts"
|
|
160
|
+
},
|
|
161
|
+
"./builtInEmoji.js": {
|
|
162
|
+
"default": "./builtInEmoji.js",
|
|
163
|
+
"types": "./builtInEmoji.ts"
|
|
164
|
+
},
|
|
137
165
|
"./assignFeatures.js": {
|
|
138
166
|
"default": "./assignFeatures.js",
|
|
139
167
|
"types": "./assignFeatures.ts"
|
|
@@ -148,6 +176,7 @@
|
|
|
148
176
|
"scripts": {
|
|
149
177
|
"serve": "node ./node_modules/spa-ssi/serve.js",
|
|
150
178
|
"test": "playwright test",
|
|
179
|
+
"benchmark": "playwright test tests/benchmark.spec.ts --reporter=list --project=chromium",
|
|
151
180
|
"update": "ncu -u && npm install",
|
|
152
181
|
"safari": "npx playwright wk http://localhost:8000",
|
|
153
182
|
"chrome": "npx playwright cr http://localhost:8000"
|
package/playwright.config.ts
CHANGED
|
@@ -16,9 +16,9 @@ export default defineConfig({
|
|
|
16
16
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
17
17
|
forbidOnly: !!process.env.CI,
|
|
18
18
|
/* Retry on CI only */
|
|
19
|
-
retries: process.env.CI ?
|
|
19
|
+
retries: process.env.CI ? 1 : 0,
|
|
20
20
|
/* Opt out of parallel tests on CI. */
|
|
21
|
-
workers: process.env.CI ?
|
|
21
|
+
workers: process.env.CI ? 2 : undefined,
|
|
22
22
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
23
23
|
reporter: [ ['html', { open: 'never' }] ],
|
|
24
24
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
@@ -50,5 +50,6 @@ export default defineConfig({
|
|
|
50
50
|
command: 'npm run serve',
|
|
51
51
|
port: 8000,
|
|
52
52
|
reuseExistingServer: !process.env.CI,
|
|
53
|
+
timeout: 30000,
|
|
53
54
|
},
|
|
54
55
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Dynamically imported only when ` =>` keys are detected in the pattern.
|
|
5
5
|
*/
|
|
6
6
|
import { resolveValues } from './resolveValues.js';
|
|
7
|
+
import { getValues } from './getValues.js';
|
|
7
8
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
8
9
|
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
9
10
|
/**
|
|
@@ -15,6 +16,7 @@ const BUILT_IN_MAP = {
|
|
|
15
16
|
'builtIns.lazyLoadSwitch': './handlers/lazyLoadSwitch.js',
|
|
16
17
|
'builtIns.join': './handlers/join.js',
|
|
17
18
|
'builtIns.microDataJoin': './handlers/microDataJoin.js',
|
|
19
|
+
'builtIns.manageTemplateList': './handlers/manageTemplateList.js',
|
|
18
20
|
};
|
|
19
21
|
/**
|
|
20
22
|
* Find a handler class in a dynamically imported module.
|
|
@@ -35,16 +37,27 @@ function findHandlerInModule(module) {
|
|
|
35
37
|
}
|
|
36
38
|
return undefined;
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Cache for loaded built-in handler classes — avoids await on subsequent calls.
|
|
42
|
+
*/
|
|
43
|
+
const handlerCache = new Map();
|
|
38
44
|
/**
|
|
39
45
|
* Dynamically load a built-in handler by name.
|
|
40
46
|
* Returns the handler constructor, or undefined if the name isn't a recognized built-in.
|
|
47
|
+
* Cached after first load — subsequent calls are synchronous.
|
|
41
48
|
*/
|
|
42
49
|
async function loadBuiltIn(name) {
|
|
50
|
+
const cached = handlerCache.get(name);
|
|
51
|
+
if (cached)
|
|
52
|
+
return cached;
|
|
43
53
|
const path = BUILT_IN_MAP[name];
|
|
44
54
|
if (!path)
|
|
45
55
|
return undefined;
|
|
46
56
|
const module = await import(path);
|
|
47
|
-
|
|
57
|
+
const cls = findHandlerInModule(module);
|
|
58
|
+
if (cls)
|
|
59
|
+
handlerCache.set(name, cls);
|
|
60
|
+
return cls;
|
|
48
61
|
}
|
|
49
62
|
/**
|
|
50
63
|
* Resolve a handler from options.handlers (class constructor or import path).
|
|
@@ -57,8 +70,13 @@ async function resolveFromHandlers(name, handlers, permissions) {
|
|
|
57
70
|
if (typeof entry === 'function') {
|
|
58
71
|
return entry;
|
|
59
72
|
}
|
|
60
|
-
//
|
|
73
|
+
// String value — could be a built-in alias or an import path
|
|
61
74
|
if (typeof entry === 'string') {
|
|
75
|
+
// Built-in alias: redirect to built-in loader
|
|
76
|
+
if (entry.startsWith('builtIns.')) {
|
|
77
|
+
return loadBuiltIn(entry);
|
|
78
|
+
}
|
|
79
|
+
// Import path string — validate and dynamically import
|
|
62
80
|
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
63
81
|
throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
64
82
|
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
|
|
@@ -157,27 +175,41 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
157
175
|
}
|
|
158
176
|
// Execute handlers sequentially, sharing the same lhsTarget
|
|
159
177
|
for (const config of configs) {
|
|
178
|
+
//return; //1.3ms
|
|
160
179
|
// 1. Check options.handlers (local, per-call)
|
|
161
180
|
let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
|
|
181
|
+
//return; // 1.4
|
|
162
182
|
// 2. Fallback to built-in auto-load
|
|
163
183
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
|
164
184
|
HandlerClass = await loadBuiltIn(config.do);
|
|
165
185
|
}
|
|
186
|
+
//return; //1.4
|
|
166
187
|
if (!HandlerClass) {
|
|
167
188
|
throw new Error(`assignFrom: unknown handler "${config.do}". Provide it in options.handlers.`);
|
|
168
189
|
}
|
|
169
|
-
// Resolve '
|
|
190
|
+
// Resolve 'get' map synchronously (no thread yield)
|
|
170
191
|
let resolvedParams = {};
|
|
192
|
+
if (config.get) {
|
|
193
|
+
resolvedParams = getValues(config.get, options.from, {
|
|
194
|
+
withMethods: options.withMethods,
|
|
195
|
+
aka: options.aka,
|
|
196
|
+
protocols: options.protocols
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// Resolve 'resolve' map asynchronously (yields to microtask queue)
|
|
171
200
|
if (config.resolve) {
|
|
172
|
-
|
|
201
|
+
const asyncResolved = await resolveValues(config.resolve, options.from, {
|
|
173
202
|
withMethods: options.withMethods,
|
|
174
203
|
aka: options.aka,
|
|
175
204
|
protocols: options.protocols
|
|
176
205
|
});
|
|
206
|
+
Object.assign(resolvedParams, asyncResolved);
|
|
177
207
|
}
|
|
178
208
|
// Instantiate and invoke the handler
|
|
179
209
|
const handler = new HandlerClass(config);
|
|
210
|
+
//return; //1.5ms
|
|
180
211
|
const result = await handler.assign(lhsTarget, resolvedParams, options);
|
|
212
|
+
//return; //1.5ms
|
|
181
213
|
// Return-value protocol: if handler returns a non-undefined value,
|
|
182
214
|
// assign it back to the LHS path
|
|
183
215
|
if (result !== undefined && lhsParent != null && lhsKey != null) {
|