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