@rickcedwhat/playwright-smart-table 6.8.2 → 6.10.0
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 +0 -2
- package/dist/engine/rowFinder.js +153 -168
- package/dist/engine/tableIteration.js +105 -123
- package/dist/engine/tableMapper.js +88 -103
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/columns.js +23 -32
- package/dist/presets/glide/headers.js +18 -27
- package/dist/presets/glide/index.d.ts +9 -9
- package/dist/presets/glide/index.js +24 -33
- package/dist/presets/mui.js +197 -124
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +32 -41
- package/dist/smartRow.js +163 -96
- package/dist/strategies/columns.js +2 -11
- package/dist/strategies/dedupe.js +3 -12
- package/dist/strategies/fill.js +20 -29
- package/dist/strategies/headers.js +22 -31
- package/dist/strategies/index.d.ts +4 -0
- package/dist/strategies/index.js +3 -0
- package/dist/strategies/loading.js +24 -33
- package/dist/strategies/pagination.js +21 -30
- package/dist/strategies/sorting.js +18 -31
- package/dist/strategies/stabilization.js +28 -37
- package/dist/strategies/viewport.d.ts +52 -0
- package/dist/strategies/viewport.js +123 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +100 -9
- package/dist/types.d.ts +100 -9
- package/dist/useTable.js +98 -76
- package/dist/utils/debugUtils.js +5 -16
- package/dist/utils/elementTracker.js +27 -40
- package/dist/utils/mutex.js +5 -16
- package/dist/utils/navigationBarrier.js +25 -36
- package/dist/utils/paginationPath.js +102 -115
- package/dist/utils/smartRowArray.js +2 -11
- package/dist/utils.js +4 -13
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -106,8 +106,6 @@ When your pagination strategy supports bulk jumps (`goNextBulk`), pass `{ useBul
|
|
|
106
106
|
> **`map` + UI interactions:** `map` defaults to `concurrency: 'parallel'`. If your callback opens popovers,
|
|
107
107
|
> fills inputs, or otherwise mutates UI state, pass `{ concurrency: 'sequential' }` (or `'synchronized'` if you need lock-step navigation with serialized work).
|
|
108
108
|
|
|
109
|
-
The legacy `{ parallel: true | false }` option still works but is deprecated; prefer `concurrency`.
|
|
110
|
-
|
|
111
109
|
### `filter` vs `findRows`
|
|
112
110
|
|
|
113
111
|
| Use case | Best tool |
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.RowFinder = void 0;
|
|
13
4
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
@@ -28,175 +19,169 @@ class RowFinder {
|
|
|
28
19
|
log(msg) {
|
|
29
20
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', msg);
|
|
30
21
|
}
|
|
31
|
-
findRow(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return smartRow;
|
|
48
|
-
});
|
|
22
|
+
async findRow(filters, options = {}) {
|
|
23
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Searching for row', filters);
|
|
24
|
+
await this.tableMapper.getMap();
|
|
25
|
+
const rowLocator = await this.findRowLocator(filters, options);
|
|
26
|
+
if (rowLocator) {
|
|
27
|
+
(0, debugUtils_1.logDebug)(this.config, 'info', 'Row found');
|
|
28
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
29
|
+
return this.makeSmartRow(rowLocator, await this.tableMapper.getMap(), 0);
|
|
30
|
+
}
|
|
31
|
+
(0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
|
|
32
|
+
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
33
|
+
const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
|
|
34
|
+
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
35
|
+
const smartRow = this.makeSmartRow(sentinel, await this.tableMapper.getMap(), 0);
|
|
36
|
+
smartRow[sentinel_1.SENTINEL_ROW] = true;
|
|
37
|
+
return smartRow;
|
|
49
38
|
}
|
|
50
|
-
findRows() {
|
|
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
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
allRows.push(smartRow);
|
|
80
|
-
added++;
|
|
81
|
-
}
|
|
82
|
-
this.log(`findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
83
|
-
});
|
|
84
|
-
// Scan first page
|
|
85
|
-
yield collectMatches();
|
|
86
|
-
// Pagination Loop
|
|
87
|
-
while (pagesScanned < effectiveMaxPages && this.config.strategies.pagination) {
|
|
88
|
-
const context = {
|
|
89
|
-
root: this.rootLocator,
|
|
90
|
-
config: this.config,
|
|
91
|
-
resolve: this.resolve,
|
|
92
|
-
page: this.rootLocator.page()
|
|
93
|
-
};
|
|
94
|
-
let paginationResult;
|
|
95
|
-
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
96
|
-
paginationResult = yield this.config.strategies.pagination.goNextBulk(context);
|
|
97
|
-
}
|
|
98
|
-
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
99
|
-
paginationResult = yield this.config.strategies.pagination.goNext(context);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
this.log(`findRows: no pagination primitive — stopping`);
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
106
|
-
if (!didPaginate) {
|
|
107
|
-
this.log(`findRows: pagination returned false — end of data`);
|
|
108
|
-
break;
|
|
39
|
+
async findRows(filters = {}, options) {
|
|
40
|
+
var _a, _b, _c, _d;
|
|
41
|
+
const filtersRecord = filters;
|
|
42
|
+
const map = await this.tableMapper.getMap();
|
|
43
|
+
const allRows = [];
|
|
44
|
+
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
45
|
+
let pagesScanned = 1;
|
|
46
|
+
this.log(`findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
|
|
47
|
+
const tracker = new elementTracker_1.ElementTracker('findRows');
|
|
48
|
+
try {
|
|
49
|
+
const collectMatches = async () => {
|
|
50
|
+
var _a, _b;
|
|
51
|
+
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
52
|
+
// Only apply filters if we have them
|
|
53
|
+
if (Object.keys(filtersRecord).length > 0) {
|
|
54
|
+
rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page(), this.rootLocator);
|
|
55
|
+
}
|
|
56
|
+
// Get only newly seen matched rows
|
|
57
|
+
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
58
|
+
const currentRows = await rowLocators.all();
|
|
59
|
+
const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
|
|
60
|
+
let added = 0;
|
|
61
|
+
for (const idx of newIndices) {
|
|
62
|
+
const smartRow = this.makeSmartRow(currentRows[idx], map, allRows.length, this.tableState.currentPageIndex);
|
|
63
|
+
if (isRowLoading && await isRowLoading(smartRow)) {
|
|
64
|
+
this.log(`findRows: page ${this.tableState.currentPageIndex} — row skipped (isRowLoading=true)`);
|
|
65
|
+
continue;
|
|
109
66
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
pagesScanned += pagesJumped;
|
|
113
|
-
this.log(`findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
114
|
-
yield collectMatches();
|
|
67
|
+
allRows.push(smartRow);
|
|
68
|
+
added++;
|
|
115
69
|
}
|
|
70
|
+
this.log(`findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
71
|
+
};
|
|
72
|
+
// Scan first page
|
|
73
|
+
await collectMatches();
|
|
74
|
+
// Pagination Loop
|
|
75
|
+
while (pagesScanned < effectiveMaxPages && this.config.strategies.pagination) {
|
|
76
|
+
const context = {
|
|
77
|
+
root: this.rootLocator,
|
|
78
|
+
config: this.config,
|
|
79
|
+
resolve: this.resolve,
|
|
80
|
+
page: this.rootLocator.page()
|
|
81
|
+
};
|
|
82
|
+
let paginationResult;
|
|
83
|
+
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
84
|
+
paginationResult = await this.config.strategies.pagination.goNextBulk(context);
|
|
85
|
+
}
|
|
86
|
+
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
87
|
+
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.log(`findRows: no pagination primitive — stopping`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
94
|
+
if (!didPaginate) {
|
|
95
|
+
this.log(`findRows: pagination returned false — end of data`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
99
|
+
this.tableState.currentPageIndex += pagesJumped;
|
|
100
|
+
pagesScanned += pagesJumped;
|
|
101
|
+
this.log(`findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
102
|
+
await collectMatches();
|
|
116
103
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await tracker.cleanup(this.rootLocator.page());
|
|
107
|
+
}
|
|
108
|
+
this.log(`findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
109
|
+
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
123
110
|
}
|
|
124
|
-
findRowLocator(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
111
|
+
async findRowLocator(filters, options = {}) {
|
|
112
|
+
var _a, _b, _c, _d;
|
|
113
|
+
const map = await this.tableMapper.getMap();
|
|
114
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
115
|
+
let pagesScanned = 1;
|
|
116
|
+
this.log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
117
|
+
while (true) {
|
|
118
|
+
// Check Loading
|
|
119
|
+
if ((_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isTableLoading) {
|
|
120
|
+
const isLoading = await this.config.strategies.loading.isTableLoading({
|
|
121
|
+
root: this.rootLocator,
|
|
122
|
+
config: this.config,
|
|
123
|
+
page: this.rootLocator.page(),
|
|
124
|
+
resolve: this.resolve
|
|
125
|
+
});
|
|
126
|
+
if (isLoading) {
|
|
127
|
+
this.log('Table is loading... waiting');
|
|
128
|
+
await this.rootLocator.page().waitForTimeout(200);
|
|
129
|
+
continue;
|
|
145
130
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
131
|
+
}
|
|
132
|
+
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
133
|
+
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
|
|
134
|
+
const count = await matchedRows.count();
|
|
135
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
136
|
+
if (count > 1) {
|
|
137
|
+
const sampleData = [];
|
|
138
|
+
try {
|
|
139
|
+
const firstFewRows = await matchedRows.all();
|
|
140
|
+
const sampleCount = Math.min(firstFewRows.length, 3);
|
|
141
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
142
|
+
const rowData = await this.makeSmartRow(firstFewRows[i], map, 0, this.tableState.currentPageIndex).toJSON();
|
|
143
|
+
sampleData.push(JSON.stringify(rowData));
|
|
159
144
|
}
|
|
160
|
-
catch (e) { }
|
|
161
|
-
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
162
|
-
throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${this.tableState.currentPageIndex}. ` +
|
|
163
|
-
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
164
145
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
146
|
+
catch (e) { }
|
|
147
|
+
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
148
|
+
throw new Error(`Ambiguous Row: Found ${count} rows matching ${JSON.stringify(filters)} on page ${this.tableState.currentPageIndex}. ` +
|
|
149
|
+
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
150
|
+
}
|
|
151
|
+
if (count === 1)
|
|
152
|
+
return matchedRows.first();
|
|
153
|
+
if (pagesScanned < effectiveMaxPages) {
|
|
154
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
|
|
155
|
+
const context = {
|
|
156
|
+
root: this.rootLocator,
|
|
157
|
+
config: this.config,
|
|
158
|
+
resolve: this.resolve,
|
|
159
|
+
page: this.rootLocator.page()
|
|
160
|
+
};
|
|
161
|
+
let paginationResult;
|
|
162
|
+
if ((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk) {
|
|
163
|
+
paginationResult = await this.config.strategies.pagination.goNextBulk(context);
|
|
164
|
+
}
|
|
165
|
+
else if ((_d = this.config.strategies.pagination) === null || _d === void 0 ? void 0 : _d.goNext) {
|
|
166
|
+
paginationResult = await this.config.strategies.pagination.goNext(context);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (no goNext or goNextBulk primitive).`);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
173
|
+
if (didLoadMore) {
|
|
174
|
+
const pagesJumped = typeof paginationResult === 'number' ? paginationResult : 1;
|
|
175
|
+
this.tableState.currentPageIndex += pagesJumped;
|
|
176
|
+
pagesScanned += pagesJumped;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.log(`Page ${this.tableState.currentPageIndex}: Pagination failed (end of data).`);
|
|
196
181
|
}
|
|
197
|
-
return null;
|
|
198
182
|
}
|
|
199
|
-
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
200
185
|
}
|
|
201
186
|
}
|
|
202
187
|
exports.RowFinder = RowFinder;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.runForEach = runForEach;
|
|
13
4
|
exports.runMap = runMap;
|
|
@@ -21,137 +12,128 @@ function log(config, msg) {
|
|
|
21
12
|
}
|
|
22
13
|
const SKIP = Symbol('skip');
|
|
23
14
|
/** Delegates to {@link runMap}; void results are discarded. */
|
|
24
|
-
function runForEach(
|
|
25
|
-
|
|
26
|
-
yield runMap(env, callback, options, 'forEach');
|
|
27
|
-
});
|
|
15
|
+
async function runForEach(env, callback, options = {}) {
|
|
16
|
+
await runMap(env, callback, options, 'forEach');
|
|
28
17
|
}
|
|
29
18
|
/**
|
|
30
19
|
* Row iteration for map (and forEach/filter via label).
|
|
31
20
|
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
32
21
|
*/
|
|
33
|
-
function runMap(
|
|
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
|
-
|
|
22
|
+
async function runMap(env, callback, options = {}, label = 'map') {
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
24
|
+
const map = env.getMap();
|
|
25
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
26
|
+
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
27
|
+
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
28
|
+
const defaultMode = label === 'map' ? 'parallel' : 'sequential';
|
|
29
|
+
const concurrency = (_d = (_c = options.concurrency) !== null && _c !== void 0 ? _c : env.config.concurrency) !== null && _d !== void 0 ? _d : defaultMode;
|
|
30
|
+
const useBarrier = concurrency !== 'sequential';
|
|
31
|
+
// Mutex must not pair with the navigation barrier: synchronized mode needs every row
|
|
32
|
+
// to enter barrier.sync concurrently; serializing callbacks here deadlocks (first row waits
|
|
33
|
+
// for batchSize peers that never reach the barrier).
|
|
34
|
+
const useMutex = concurrency === 'sequential';
|
|
35
|
+
const useBulk = (_e = options.useBulkPagination) !== null && _e !== void 0 ? _e : false;
|
|
36
|
+
const tracker = new elementTracker_1.ElementTracker(label);
|
|
37
|
+
log(env.config, `${label}: starting (maxPages=${effectiveMaxPages}, mode=${concurrency}, dedupe=${!!dedupeStrategy})`);
|
|
38
|
+
const results = [];
|
|
39
|
+
try {
|
|
40
|
+
let rowIndex = 0;
|
|
41
|
+
let stopped = false;
|
|
42
|
+
let stoppedIndex = Infinity;
|
|
43
|
+
let pagesScanned = 1;
|
|
44
|
+
const stop = (idx) => {
|
|
45
|
+
if (!stopped) {
|
|
46
|
+
log(env.config, `${label}: stop() called at row ${idx} — halting`);
|
|
47
|
+
stopped = true;
|
|
48
|
+
stoppedIndex = idx;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
while (!stopped) {
|
|
52
|
+
const rowLocators = env.getRowLocators();
|
|
53
|
+
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
54
|
+
const pageRows = await rowLocators.all();
|
|
55
|
+
const batchSize = newIndices.length;
|
|
56
|
+
if (batchSize === 0) {
|
|
57
|
+
log(env.config, `${label}: page ${pagesScanned} — no new row(s) found`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
log(env.config, `${label}: scanning page ${pagesScanned} — ${batchSize} new row(s)`);
|
|
61
|
+
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
62
|
+
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
63
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, pagesScanned - 1, barrier));
|
|
64
|
+
const processRow = async (row) => {
|
|
65
|
+
try {
|
|
66
|
+
if (stopped && row.rowIndex > stoppedIndex) {
|
|
67
|
+
return SKIP;
|
|
68
|
+
}
|
|
69
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
70
|
+
const key = await dedupeStrategy(row);
|
|
71
|
+
if (dedupeKeys.has(key)) {
|
|
72
|
+
log(env.config, `${label}: dedupe skip key="${key}"`);
|
|
82
73
|
return SKIP;
|
|
83
74
|
}
|
|
84
|
-
|
|
85
|
-
const key = yield dedupeStrategy(row);
|
|
86
|
-
if (dedupeKeys.has(key)) {
|
|
87
|
-
log(env.config, `${label}: dedupe skip key="${key}"`);
|
|
88
|
-
return SKIP;
|
|
89
|
-
}
|
|
90
|
-
dedupeKeys.add(key);
|
|
91
|
-
}
|
|
92
|
-
// Execute callback (optionally serialized via mutex)
|
|
93
|
-
const runCallback = () => __awaiter(this, void 0, void 0, function* () {
|
|
94
|
-
if (stopped && row.rowIndex > stoppedIndex)
|
|
95
|
-
return SKIP;
|
|
96
|
-
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
97
|
-
return yield callback({ row, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
98
|
-
});
|
|
99
|
-
if (actionMutex) {
|
|
100
|
-
return yield actionMutex.run(runCallback);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
return yield runCallback();
|
|
104
|
-
}
|
|
75
|
+
dedupeKeys.add(key);
|
|
105
76
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
77
|
+
// Execute callback (optionally serialized via mutex)
|
|
78
|
+
const runCallback = async () => {
|
|
79
|
+
if (stopped && row.rowIndex > stoppedIndex)
|
|
80
|
+
return SKIP;
|
|
81
|
+
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
82
|
+
return await callback({ row, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
83
|
+
};
|
|
84
|
+
if (actionMutex) {
|
|
85
|
+
return await actionMutex.run(runCallback);
|
|
109
86
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (concurrency === 'sequential') {
|
|
113
|
-
for (const row of smartRows) {
|
|
114
|
-
pageResults.push(yield processRow(row));
|
|
87
|
+
else {
|
|
88
|
+
return await runCallback();
|
|
115
89
|
}
|
|
116
90
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
91
|
+
finally {
|
|
92
|
+
// Ensure the barrier is notified once per row, even on error or skip.
|
|
93
|
+
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
120
94
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
results.push(r);
|
|
95
|
+
};
|
|
96
|
+
const pageResults = [];
|
|
97
|
+
if (concurrency === 'sequential') {
|
|
98
|
+
for (const row of smartRows) {
|
|
99
|
+
pageResults.push(await processRow(row));
|
|
127
100
|
}
|
|
128
|
-
rowIndex += batchSize;
|
|
129
101
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (!(yield env.advancePage(useBulk))) {
|
|
134
|
-
log(env.config, `${label}: no more pages — done`);
|
|
135
|
-
break;
|
|
102
|
+
else {
|
|
103
|
+
const results = await Promise.all(smartRows.map(processRow));
|
|
104
|
+
pageResults.push(...results);
|
|
136
105
|
}
|
|
137
|
-
|
|
106
|
+
for (let i = 0; i < pageResults.length; i++) {
|
|
107
|
+
if (smartRows[i].rowIndex > stoppedIndex)
|
|
108
|
+
break;
|
|
109
|
+
const r = pageResults[i];
|
|
110
|
+
if (r !== SKIP)
|
|
111
|
+
results.push(r);
|
|
112
|
+
}
|
|
113
|
+
rowIndex += batchSize;
|
|
138
114
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
115
|
+
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
116
|
+
break;
|
|
117
|
+
log(env.config, `${label}: advancing to next page (${pagesScanned} → ${pagesScanned + 1})`);
|
|
118
|
+
if (!await env.advancePage(useBulk)) {
|
|
119
|
+
log(env.config, `${label}: no more pages — done`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
pagesScanned++;
|
|
143
123
|
}
|
|
144
|
-
|
|
145
|
-
}
|
|
124
|
+
log(env.config, `${label}: complete — ${results.length} result(s) across ${pagesScanned} page(s)`);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await tracker.cleanup(env.getPage());
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
146
130
|
}
|
|
147
131
|
/** Filter via map; returns matched rows as {@link SmartRowArray}. */
|
|
148
|
-
function runFilter(
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return env.createSmartRowArray(results);
|
|
156
|
-
});
|
|
132
|
+
async function runFilter(env, predicate, options = {}) {
|
|
133
|
+
const mapCallback = async (ctx) => {
|
|
134
|
+
const matched = await predicate(ctx);
|
|
135
|
+
return matched ? ctx.row : SKIP;
|
|
136
|
+
};
|
|
137
|
+
const results = await runMap(env, mapCallback, options, 'filter');
|
|
138
|
+
return env.createSmartRowArray(results);
|
|
157
139
|
}
|