jtlt 0.24.0 → 0.24.2

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.
@@ -101,7 +101,7 @@ class XPathTransformerContext {
101
101
  }
102
102
  }
103
103
  };
104
- /* c8 ignore stop */
104
+ /* c8 ignore stop -- See above */
105
105
 
106
106
  /**
107
107
  * @param {XPathTransformerContextConfig} config
@@ -301,7 +301,7 @@ class XPathTransformerContext {
301
301
  ? globalThis.XPathResult.ANY_TYPE
302
302
  : 0
303
303
  );
304
- /* c8 ignore stop */
304
+ /* c8 ignore stop -- See above */
305
305
  const resultObj = docTyped.evaluate(
306
306
  expr, contextNode, resolver, type, null
307
307
  );
@@ -336,14 +336,14 @@ class XPathTransformerContext {
336
336
  n = resultObj.iterateNext();
337
337
  }
338
338
  return nodes;
339
- /* c8 ignore stop */
339
+ /* c8 ignore stop -- See above */
340
340
  }
341
341
  /* c8 ignore start -- Default fallback for unsupported XPathResult
342
342
  * types; environment-dependent and not hit under jsdom. */
343
343
  default:
344
344
  // Fallback: return original context for unsupported types
345
345
  return this._contextNode;
346
- /* c8 ignore stop */
346
+ /* c8 ignore stop -- See above */
347
347
  }
348
348
  }
349
349
  if (version === 2) {
@@ -542,7 +542,7 @@ class XPathTransformerContext {
542
542
  } else {
543
543
  templateObj = DTR.transformScalars;
544
544
  }
545
- /* c8 ignore stop */
545
+ /* c8 ignore stop -- See above */
546
546
  } else {
547
547
  // Sort by priority (numeric or specificity resolver)
548
548
  pathMatchedTemplates.sort((a, b) => {
@@ -632,7 +632,7 @@ class XPathTransformerContext {
632
632
  'the XPath engine; compile with compileJSONTemplate() first.'
633
633
  );
634
634
  }
635
- /* c8 ignore stop */
635
+ /* c8 ignore stop -- See above */
636
636
  /**
637
637
  * The template may return synchronously or return a Promise (e.g. from
638
638
  * `await this.indexedDB(...)`), which is awaited unless `config.sync`.
@@ -674,7 +674,7 @@ class XPathTransformerContext {
674
674
  // @ts-expect-error -- _openTagState: StringJoiningTransformer
675
675
  joiner._openTagState = false;
676
676
  }
677
- /* c8 ignore stop */
677
+ /* c8 ignore stop -- See above */
678
678
  joiner.append(resolvedRet);
679
679
  }
680
680
  this._contextNode = node;
@@ -1001,20 +1001,22 @@ class XPathTransformerContext {
1001
1001
  currentGroup.push(node);
1002
1002
  const endMatch = evalInContext(groupEndingWith, node);
1003
1003
 
1004
- if (endMatch) {
1005
- const prevParams = this._params;
1006
- const prevContext = this._contextNode;
1007
- try {
1008
- this._contextNode = currentGroup[0];
1009
- /** @type {any} */ (this)._currentGroup = currentGroup;
1010
- cb.call(this, null, currentGroup, this);
1011
- } finally {
1012
- this._params = prevParams;
1013
- this._contextNode = prevContext;
1014
- delete /** @type {any} */ (this)._currentGroup;
1015
- }
1016
- currentGroup = [];
1004
+ if (!endMatch) {
1005
+ continue;
1017
1006
  }
1007
+
1008
+ const prevParams = this._params;
1009
+ const prevContext = this._contextNode;
1010
+ try {
1011
+ this._contextNode = currentGroup[0];
1012
+ /** @type {any} */ (this)._currentGroup = currentGroup;
1013
+ cb.call(this, null, currentGroup, this);
1014
+ } finally {
1015
+ this._params = prevParams;
1016
+ this._contextNode = prevContext;
1017
+ delete /** @type {any} */ (this)._currentGroup;
1018
+ }
1019
+ currentGroup = [];
1018
1020
  }
1019
1021
 
1020
1022
  // Process last group if not ended
@@ -1225,7 +1227,7 @@ class XPathTransformerContext {
1225
1227
  // Fall through to normal XPath evaluation
1226
1228
  // This allows fontoxpath registered functions to work natively
1227
1229
  }
1228
- /* c8 ignore stop */
1230
+ /* c8 ignore stop -- See above */
1229
1231
  }
1230
1232
 
1231
1233
  // Check for format-number() function call
@@ -1293,11 +1295,11 @@ class XPathTransformerContext {
1293
1295
  const resResult = this._evalXPath(selectStr, true);
1294
1296
  const res = /** @type {Node[]} */ (resResult);
1295
1297
  const first = res[0];
1296
- /* c8 ignore start */
1298
+ /* c8 ignore start -- Ok? */
1297
1299
  val = first && first.nodeType
1298
1300
  ? first.textContent
1299
1301
  : first;
1300
- /* c8 ignore stop */
1302
+ /* c8 ignore stop -- See above */
1301
1303
  }
1302
1304
  } else if (!selectStr || selectStr === '.') {
1303
1305
  if (this._contextNode.nodeType === 3) {
@@ -1345,9 +1347,9 @@ class XPathTransformerContext {
1345
1347
  if (select) {
1346
1348
  try {
1347
1349
  const res = this.get(select, true);
1348
- nodes = Array.isArray(res) ? res : /* c8 ignore next */ [];
1349
- } catch { /* c8 ignore next */
1350
- nodes = /* c8 ignore next */ [];
1350
+ nodes = Array.isArray(res) ? res : /* c8 ignore next -- Ok? */ [];
1351
+ } catch { /* c8 ignore next -- Should not err? */
1352
+ nodes = /* c8 ignore next -- Should not err? */ [];
1351
1353
  }
1352
1354
  } else {
1353
1355
  nodes = [this._contextNode];
@@ -1358,13 +1360,13 @@ class XPathTransformerContext {
1358
1360
  let deep;
1359
1361
  try {
1360
1362
  deep = /** @type {Node} */ (n.cloneNode(true));
1361
- } catch { /* c8 ignore start */
1363
+ } catch { /* c8 ignore start -- Should not err? */
1362
1364
  deep = /** @type {Node} */ (n.cloneNode(false));
1363
- } /* c8 ignore stop */
1365
+ } /* c8 ignore stop -- See above */
1364
1366
  this._getJoiningTransformer().append(/** @type {any} */ (deep));
1365
- } else { /* c8 ignore start */
1367
+ } else { /* c8 ignore start -- Ok? */
1366
1368
  this._getJoiningTransformer().append(/** @type {any} */ (n));
1367
- } /* c8 ignore stop */
1369
+ } /* c8 ignore stop -- See above */
1368
1370
  }
1369
1371
  } else if (select) { // Scalar path
1370
1372
  const scalar = this._evalXPath(select, false);
@@ -1378,7 +1380,7 @@ class XPathTransformerContext {
1378
1380
  ) {
1379
1381
  const node = /** @type {Node} */ (scalar);
1380
1382
  let txt = /** @type {any} */ (node.textContent);
1381
- if (/* c8 ignore start */
1383
+ if (/* c8 ignore start -- Ok? */
1382
1384
  (txt === null || typeof txt === 'undefined') &&
1383
1385
  /** @type {any} */ (node).nodeType === 9 // Document
1384
1386
  ) {
@@ -1387,9 +1389,9 @@ class XPathTransformerContext {
1387
1389
  /** @type {any} */ (node)
1388
1390
  ).documentElement;
1389
1391
  txt = /** @type {any} */ (docEl && docEl.textContent) || '';
1390
- } /* c8 ignore stop */
1392
+ } /* c8 ignore stop -- See above */
1391
1393
  this._getJoiningTransformer().append(
1392
- /** @type {any} */ (txt || /* c8 ignore next */ '')
1394
+ /** @type {any} */ (txt || /* c8 ignore next -- Ok? */ '')
1393
1395
  );
1394
1396
  } else {
1395
1397
  this._getJoiningTransformer().append(/** @type {any} */ (scalar));
@@ -1410,12 +1412,12 @@ class XPathTransformerContext {
1410
1412
  if (target && typeof target === 'object' && 'nodeType' in target) {
1411
1413
  try {
1412
1414
  clone = /** @type {Node} */ (target.cloneNode(false));
1413
- } catch { /* c8 ignore start */
1415
+ } catch { /* c8 ignore start -- Should not err? */
1414
1416
  clone = target;
1415
- } /* c8 ignore stop */
1416
- } else { /* c8 ignore start */
1417
+ } /* c8 ignore stop -- See above */
1418
+ } else { /* c8 ignore start -- Ok? */
1417
1419
  clone = target;
1418
- } /* c8 ignore stop */
1420
+ } /* c8 ignore stop -- See above */
1419
1421
  this._getJoiningTransformer().append(/** @type {any} */ (clone));
1420
1422
  return this;
1421
1423
  }
@@ -1463,10 +1465,9 @@ class XPathTransformerContext {
1463
1465
  if (this._params && Object.hasOwn(this._params, name)) {
1464
1466
  return {has: true, value: this._params[name]};
1465
1467
  }
1466
- if (Object.hasOwn(this._runtimeParams, name)) {
1467
- return {has: true, value: this._runtimeParams[name]};
1468
- }
1469
- return {has: false, value: undefined};
1468
+ return Object.hasOwn(this._runtimeParams, name)
1469
+ ? {has: true, value: this._runtimeParams[name]}
1470
+ : {has: false, value: undefined};
1470
1471
  }
1471
1472
 
1472
1473
  /**
@@ -1610,15 +1611,18 @@ class XPathTransformerContext {
1610
1611
  while (node) {
1611
1612
  positions.unshift(this._calculatePosition(count, undefined));
1612
1613
  node = node.parentNode;
1613
- if (from) {
1614
- const fromResult = /** @type {any} */ (
1615
- this._evalXPath(from, node)
1616
- );
1617
- if (fromResult && fromResult.length > 0) {
1618
- break;
1619
- }
1614
+ if (!from) {
1615
+ continue;
1616
+ }
1617
+
1618
+ const fromResult = /** @type {any} */ (
1619
+ this._evalXPath(from, node)
1620
+ );
1621
+ if (fromResult && fromResult.length > 0) {
1622
+ break;
1620
1623
  }
1621
1624
  }
1625
+
1622
1626
  value = positions.join('.');
1623
1627
 
1624
1628
  break;
@@ -1993,8 +1997,7 @@ class XPathTransformerContext {
1993
1997
  */
1994
1998
  object (objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets) {
1995
1999
  const jt = this._getJoiningTransformer();
1996
- // Union of transformers creates intersection types
1997
- // @ts-expect-error
2000
+ // @ts-expect-error -- Union of transformers creates intersection types
1998
2001
  jt.object(objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets,
1999
2002
  propSets);
2000
2003
  return this;
@@ -2026,8 +2029,7 @@ class XPathTransformerContext {
2026
2029
  */
2027
2030
  array (arrOrCb, cb) {
2028
2031
  const jt = this._getJoiningTransformer();
2029
- // Union of transformers creates intersection types
2030
- // @ts-expect-error
2032
+ // @ts-expect-error -- Union of transformers creates intersection types
2031
2033
  jt.array(arrOrCb, cb);
2032
2034
  return this;
2033
2035
  }
@@ -2142,7 +2144,7 @@ class XPathTransformerContext {
2142
2144
  `Function '${name}' must have either 'body' or 'sequence' attribute`
2143
2145
  );
2144
2146
  }
2145
- /* c8 ignore stop */
2147
+ /* c8 ignore stop -- See above */
2146
2148
 
2147
2149
  // If sequence is provided, create a body function that evaluates it
2148
2150
  const actualBody = sequence
@@ -2170,7 +2172,8 @@ class XPathTransformerContext {
2170
2172
  });
2171
2173
 
2172
2174
  // Evaluate the XPath expression with bound variables
2173
- const version = this._config.xpathVersion /* c8 ignore next */ ?? 1;
2175
+ // eslint-disable-next-line @stylistic/max-len -- Long
2176
+ const version = this._config.xpathVersion /* c8 ignore next -- Ok? */ ?? 1;
2174
2177
  // eslint-disable-next-line sonarjs/no-floating-point-equality -- Safe
2175
2178
  if (version === 3.1) {
2176
2179
  // Use fontoxpath for XPath 3.1
@@ -2178,10 +2181,7 @@ class XPathTransformerContext {
2178
2181
  /** @param {string} prefix */
2179
2182
  const namespaceResolver = (prefix) => {
2180
2183
  // Map common prefixes to URN namespaces
2181
- if (prefix) {
2182
- return `urn:${prefix}`;
2183
- }
2184
- return null;
2184
+ return prefix ? `urn:${prefix}` : null;
2185
2185
  };
2186
2186
  // // eslint-disable-next-line import/no-named-as-default-member -- OK
2187
2187
  const result = fontoxpath.evaluateXPath(
@@ -2259,7 +2259,7 @@ class XPathTransformerContext {
2259
2259
  if (!actualBody) {
2260
2260
  throw new Error(`Function body not defined for ${name}`);
2261
2261
  }
2262
- /* c8 ignore stop */
2262
+ /* c8 ignore stop -- See above */
2263
2263
  // For sequence functions, unwrap/wrap arrays as needed
2264
2264
  // For regular body functions, pass through as-is
2265
2265
  if (sequence) {
@@ -2274,11 +2274,10 @@ class XPathTransformerContext {
2274
2274
  const result = actualBody(...unwrappedArgs);
2275
2275
  // Fontoxpath expects sequences (item()*) to be arrays
2276
2276
  // But scalar types (xs:integer, xs:string) should be scalars
2277
- if (returnsSequence && !Array.isArray(result)) {
2278
- return [result];
2279
- }
2280
- return result;
2281
- /* c8 ignore stop */
2277
+ return returnsSequence && !Array.isArray(result)
2278
+ ? [result]
2279
+ : result;
2280
+ /* c8 ignore stop -- See above */
2282
2281
  }
2283
2282
  // Regular body function - pass through
2284
2283
  return actualBody(...args);
@@ -2290,7 +2289,7 @@ class XPathTransformerContext {
2290
2289
  // Ignore fontoxpath registration errors (e.g., if duplicate)
2291
2290
  // Function still available via invokeFunctionByArity
2292
2291
  }
2293
- /* c8 ignore stop */
2292
+ /* c8 ignore stop -- See above */
2294
2293
  }
2295
2294
 
2296
2295
  return this;
@@ -2374,8 +2373,7 @@ class XPathTransformerContext {
2374
2373
  const jt = this._getJoiningTransformer();
2375
2374
  // Only StringJoiningTransformer supports the third parameter
2376
2375
  if (typeof avoid !== 'undefined') {
2377
- // Union of transformers creates intersection types
2378
- // @ts-expect-error
2376
+ // @ts-expect-error -- Union of transformers creates intersection types
2379
2377
  jt.attribute(name, /** @type {string} */ (val), avoid);
2380
2378
  } else {
2381
2379
  jt.attribute(name, /** @type {string} */ (val));
@@ -2530,10 +2528,7 @@ class XPathTransformerContext {
2530
2528
  if (Array.isArray(val)) {
2531
2529
  return val.length > 0;
2532
2530
  }
2533
- if (val && typeof val === 'object') {
2534
- return true;
2535
- }
2536
- return Boolean(val);
2531
+ return val && typeof val === 'object' ? true : Boolean(val);
2537
2532
  }
2538
2533
 
2539
2534
  /**
@@ -2588,16 +2583,15 @@ class XPathTransformerContext {
2588
2583
  if (!passes && (/[\/@*]/v).test(select)) {
2589
2584
  try {
2590
2585
  const nodes = this.get(select, true);
2591
- // eslint-disable-next-line unicorn/prefer-ternary -- for coverage
2592
2586
  if (Array.isArray(nodes)) {
2593
2587
  passes = nodes.length > 0;
2594
2588
  // Defensive for non-array nodes, but _evalXPath with asNodes=true
2595
2589
  // always returns arrays in both v1 and v2.
2596
- /* c8 ignore start */
2590
+ /* c8 ignore start -- Ok? */
2597
2591
  } else {
2598
2592
  passes = Boolean(nodes);
2599
2593
  }
2600
- /* c8 ignore stop */
2594
+ /* c8 ignore stop -- See above */
2601
2595
  } catch {
2602
2596
  passes = false;
2603
2597
  }
@@ -2623,10 +2617,9 @@ class XPathTransformerContext {
2623
2617
  return null;
2624
2618
  }
2625
2619
  const right = this._parseLiteral(m.groups.right.trim());
2626
- if (!right) {
2627
- return null;
2628
- }
2629
- return {left: m.groups.left, op: m.groups.op, right: right.value};
2620
+ return !right
2621
+ ? null
2622
+ : {left: m.groups.left, op: m.groups.op, right: right.value};
2630
2623
  }
2631
2624
 
2632
2625
  /**
@@ -2657,10 +2650,7 @@ class XPathTransformerContext {
2657
2650
  if (str === 'null') {
2658
2651
  return {value: null};
2659
2652
  }
2660
- if (str === 'undefined') {
2661
- return {value: undefined};
2662
- }
2663
- return null;
2653
+ return str === 'undefined' ? {value: undefined} : null;
2664
2654
  }
2665
2655
 
2666
2656
  /**
@@ -2673,10 +2663,9 @@ class XPathTransformerContext {
2673
2663
  _resolveComparand (ref) {
2674
2664
  const paramRef = ref.match(/^\$(?<name>[\w\-]+)$/v);
2675
2665
  /* c8 ignore next 3 -- `_parseComparison` only yields a `$name` left side */
2676
- if (!paramRef || !paramRef.groups) {
2677
- return undefined;
2678
- }
2679
- return this._lookupParam(paramRef.groups.name).value;
2666
+ return !paramRef || !paramRef.groups
2667
+ ? undefined
2668
+ : this._lookupParam(paramRef.groups.name).value;
2680
2669
  }
2681
2670
 
2682
2671
  /**
@@ -2817,12 +2806,11 @@ class XPathTransformerContext {
2817
2806
  * @returns {string} - Captured group or empty string
2818
2807
  */
2819
2808
  const getRegexGroup = (groupNumber) => {
2820
- if (!currentCapturedGroups ||
2809
+ return !currentCapturedGroups ||
2821
2810
  groupNumber < 0 ||
2822
- groupNumber >= currentCapturedGroups.length) {
2823
- return '';
2824
- }
2825
- return currentCapturedGroups[groupNumber] || '';
2811
+ groupNumber >= currentCapturedGroups.length
2812
+ ? ''
2813
+ : currentCapturedGroups[groupNumber] || '';
2826
2814
  };
2827
2815
 
2828
2816
  // Save previous context to restore later
package/src/index.js CHANGED
@@ -678,6 +678,7 @@ class JTLT {
678
678
  : null
679
679
  );
680
680
  this.config.templates = query
681
+ // eslint-disable-next-line unicorn/prefer-minimal-ternary -- TS
681
682
  ? this.config.engineType === 'xpath'
682
683
  ? /** @type {XPathTemplateObject<joiningTypes>[]} */ ([
683
684
  {name: 'root', path: '//*', template: query}
package/src/indexedDB.js CHANGED
@@ -147,7 +147,7 @@ async function readAllRecords (target, range, direction, count) {
147
147
  key: cursor.key, primaryKey: cursor.primaryKey, value: cursor.value
148
148
  }), false)
149
149
  );
150
- /* c8 ignore stop */
150
+ /* c8 ignore stop -- See above */
151
151
  }
152
152
 
153
153
  /**
@@ -229,10 +229,9 @@ export async function queryIndexedDB (dbName, storeName, options = {}) {
229
229
 
230
230
  // Forward iteration has bulk APIs. For a store query the primary key and
231
231
  // the query key are the same, so `getAllKeys` serves both.
232
- if (resultType === 'value') {
233
- return target.getAll(range, count);
234
- }
235
- return target.getAllKeys(range, count);
232
+ return resultType === 'value'
233
+ ? target.getAll(range, count)
234
+ : target.getAllKeys(range, count);
236
235
  }
237
236
 
238
237
  /**
@@ -502,7 +501,7 @@ export async function evaluateXPathWithIndexedDB (selectStr, contextNode) {
502
501
  } catch {
503
502
  // Ignore: only the collected request list matters from this pass.
504
503
  }
505
- /* c8 ignore stop */
504
+ /* c8 ignore stop -- See above */
506
505
 
507
506
  // Phase 2: fetch each distinct query and cache the records.
508
507
  const fetched = new Set();
@@ -89,10 +89,7 @@ function classify (node) {
89
89
  if (typeof head === 'string') {
90
90
  return 'element';
91
91
  }
92
- if (isPlainObject(head)) {
93
- return 'operation';
94
- }
95
- return null;
92
+ return isPlainObject(head) ? 'operation' : null;
96
93
  }
97
94
 
98
95
  /**
@@ -287,22 +284,20 @@ function validateOperationHead (head) {
287
284
  return `Unrecognized operation-node key \`${keys[0]}\`.`;
288
285
  }
289
286
  const [extKey] = keys;
290
- if (!isPlainObject(head[extKey])) {
291
- return `\`${extKey}\` (an extension call) requires an object value, ` +
287
+ return !isPlainObject(head[extKey])
288
+ ? `\`${extKey}\` (an extension call) requires an object value, ` +
292
289
  `e.g. \`{${extKey}: {select: '$.path'}}\` — matching ` +
293
290
  '`$indexedDB`\'s own convention. Its contents are entirely up to ' +
294
291
  'the extension itself (e.g. a `select` it resolves via `this.get' +
295
292
  '(...)`, literal fields like `db`/`store`, …) — not interpreted ' +
296
- 'here.';
297
- }
298
- return null;
293
+ 'here.'
294
+ : null;
299
295
  }
300
- if (Object.hasOwn(head, '$text') && keys.length > 1) {
301
- return '`$text` combined with any other key (e.g. `$select`) is not ' +
296
+ return Object.hasOwn(head, '$text') && keys.length > 1
297
+ ? '`$text` combined with any other key (e.g. `$select`) is not ' +
302
298
  'valid jamilih (jamilih rejects the unrecognized companion alongside ' +
303
- 'its own reserved `$text`); use `$jtltText` for the combined form.';
304
- }
305
- return null;
299
+ 'its own reserved `$text`); use `$jtltText` for the combined form.'
300
+ : null;
306
301
  }
307
302
 
308
303
  /**
@@ -392,21 +387,24 @@ function validateNode (node, format, errors) {
392
387
  }
393
388
  return;
394
389
  }
395
- if (Object.hasOwn(head, '$indexedDB')) {
396
- // Unlike `$if`/`$forEach`, `$indexedDB`'s children are optional — a
397
- // bare prefetch (binding via `$as` for later use, or discarding the
398
- // rows entirely) with no immediate consumer is a legitimate leaf use.
399
- const [, childNodes] = arr;
400
- if (childNodes !== undefined && !Array.isArray(childNodes)) {
401
- errors.push(
402
- "`$indexedDB`'s children, when given, must be a node array."
403
- );
404
- return;
405
- }
406
- const idbChildren = childNodes || [];
407
- for (const child of idbChildren) {
408
- validateNode(child, format, errors);
409
- }
390
+
391
+ if (!Object.hasOwn(head, '$indexedDB')) {
392
+ return;
393
+ }
394
+
395
+ // Unlike `$if`/`$forEach`, `$indexedDB`'s children are optional — a
396
+ // bare prefetch (binding via `$as` for later use, or discarding the
397
+ // rows entirely) with no immediate consumer is a legitimate leaf use.
398
+ const [, childNodes] = arr;
399
+ if (childNodes !== undefined && !Array.isArray(childNodes)) {
400
+ errors.push(
401
+ "`$indexedDB`'s children, when given, must be a node array."
402
+ );
403
+ return;
404
+ }
405
+ const idbChildren = childNodes || [];
406
+ for (const child of idbChildren) {
407
+ validateNode(child, format, errors);
410
408
  }
411
409
  }
412
410
 
@@ -463,26 +461,28 @@ function collectReads (node, targets, errors) {
463
461
  }
464
462
  return;
465
463
  }
466
- if (Object.hasOwn(head, '$indexedDB')) {
467
- const spec = /** @type {any} */ (head.$indexedDB);
468
- if (
469
- !isPlainObject(spec) ||
470
- typeof spec.db !== 'string' || spec.db.length === 0 ||
471
- typeof spec.store !== 'string' || spec.store.length === 0
472
- ) {
473
- errors.push(
474
- '`$indexedDB` target could not be resolved statically — `db` and ' +
475
- '`store` must both be non-empty string literals: ' +
476
- JSON.stringify(head)
477
- );
478
- } else {
479
- targets.push({db: spec.db, store: spec.store});
480
- }
481
- const [, childNodes] = arr;
482
- const idbChildren = /** @type {unknown[]} */ (childNodes ?? []);
483
- for (const child of idbChildren) {
484
- collectReads(child, targets, errors);
485
- }
464
+ if (!Object.hasOwn(head, '$indexedDB')) {
465
+ return;
466
+ }
467
+
468
+ const spec = /** @type {any} */ (head.$indexedDB);
469
+ if (
470
+ !isPlainObject(spec) ||
471
+ typeof spec.db !== 'string' || spec.db.length === 0 ||
472
+ typeof spec.store !== 'string' || spec.store.length === 0
473
+ ) {
474
+ errors.push(
475
+ '`$indexedDB` target could not be resolved statically — `db` and ' +
476
+ '`store` must both be non-empty string literals: ' +
477
+ JSON.stringify(head)
478
+ );
479
+ } else {
480
+ targets.push({db: spec.db, store: spec.store});
481
+ }
482
+ const [, childNodes] = arr;
483
+ const idbChildren = /** @type {unknown[]} */ (childNodes ?? []);
484
+ for (const child of idbChildren) {
485
+ collectReads(child, targets, errors);
486
486
  }
487
487
  }
488
488
 
@@ -512,13 +512,18 @@ export function extractReads (nodes) {
512
512
  for (const node of nodes) {
513
513
  collectReads(node, targets, errors);
514
514
  }
515
- const seen = new Set();
515
+ /** @type {Map<string, Set<string>>} */
516
+ const seenStores = new Map();
516
517
  const reads = targets.filter(({db, store}) => {
517
- const key = `${db}${store}`;
518
- if (seen.has(key)) {
518
+ let stores = seenStores.get(db);
519
+ if (!stores) {
520
+ stores = new Set();
521
+ seenStores.set(db, stores);
522
+ }
523
+ if (stores.has(store)) {
519
524
  return false;
520
525
  }
521
- seen.add(key);
526
+ stores.add(store);
522
527
  return true;
523
528
  });
524
529
  return {reads, errors};
@@ -1,16 +1,10 @@
1
1
  {
2
+ "extends": "./tsconfig.json",
2
3
  "compilerOptions": {
3
- "lib": ["ESNext", "DOM"],
4
- "skipLibCheck": true,
5
- "moduleResolution": "nodenext",
6
- "module": "NodeNext",
7
- "allowJs": true,
8
- "checkJs": true,
9
4
  "noEmit": false,
10
5
  "declaration": true,
11
6
  "declarationMap": true,
12
7
  "emitDeclarationOnly": true,
13
- "strict": true,
14
8
  "target": "es2024",
15
9
  "rootDir": "src",
16
10
  "outDir": "dist"