@everdeep/pubmed 0.1.1 → 0.1.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.
package/README.md CHANGED
@@ -77,12 +77,14 @@ Cursors are versioned, base64url-encoded, unsigned, implementation-specific cont
77
77
 
78
78
  ### Count changes during pagination
79
79
 
80
- NCBI can return HTTP 200 with valid IDs but a different total on a later page. By default, the client stops with `PaginationConsistencyError` (`code: "PAGINATION_INCONSISTENT"`, `retryable: false`), not `INVALID_RESPONSE` or `SEARCH_LIMIT`. This is a consistency signal, **not provider unavailability**. Retrying may succeed, but does not establish consistent results; the client does not automatically retry or restart such searches.
80
+ NCBI can return HTTP 200 with valid IDs but a different total on a later page. By default, `search()` and `searchAll()` **continue retrieving valid pages**, returning count-drift diagnostics and emitting a `search-total-drift` event. The default `totalDriftPolicy` is `"warn"` (version 0.1.1 defaulted to `"error"`); no explicit opt-in or event handler is required. This is best-effort pagination, not a snapshot guarantee.
81
81
 
82
- If best-effort pagination is acceptable, opt in explicitly at client construction:
82
+ To stop on count changes, explicitly configure `totalDriftPolicy: "error"`. Strict mode throws `PaginationConsistencyError` (`code: "PAGINATION_INCONSISTENT"`, `retryable: false`) before fetching the changed page's records. This is a consistency signal, **not provider unavailability**. The client does not automatically retry or restart such searches.
83
+
84
+ With the default policy:
83
85
 
84
86
  ```ts
85
- const client = new PubMedClient({ email, tool, totalDriftPolicy: "warn" });
87
+ const client = new PubMedClient({ email, tool }); // totalDriftPolicy: "warn" by default
86
88
  const first = await client.search({ query: "cancer", pageSize: 2 });
87
89
  if (first.nextCursor) {
88
90
  const second = await client.search({ cursor: first.nextCursor });
@@ -94,7 +96,7 @@ In both modes, `SearchBatch.total` and the pagination bound remain the **initial
94
96
 
95
97
  Warn mode returns a `diagnostics` entry on each drifted page and emits a `search-total-drift` event. A consistency error exposes the same metadata through `.diagnostic` and `toJSON()`: `reason`, `originalTotal`, `observedTotal`, `offset`, `requestedIds`, and `returnedIds`. No queries, PMIDs, cursors, or history tokens are included. Record parse `warnings` remain separate.
96
98
 
97
- Neither mode guarantees snapshot enumeration: equal counts do not prove stable membership or ordering, and page-local uniqueness does not detect cross-page duplicates or omissions. Warn mode explicitly accepts that risk; it does not silently deduplicate or claim complete results. Consumers requiring exact enumeration should not opt in.
99
+ Neither mode guarantees snapshot enumeration: equal counts do not prove stable membership or ordering, and page-local uniqueness does not detect cross-page duplicates or omissions. Warn mode accepts that risk; it does not silently deduplicate or claim complete results. Consumers should persist by PMID idempotently and inspect `missingPmids` and `diagnostics`. Consumers requiring a stop on count drift should select `"error"`, but that alone does not establish exact enumeration.
98
100
 
99
101
  For progressive consumption, use `searchAll()`. `maxResults` is required so a caller must make the retrieval bound explicit:
100
102
 
package/dist/index.cjs CHANGED
@@ -1567,7 +1567,9 @@ var Transport = class {
1567
1567
  const correlationId = (0, import_node_crypto.randomUUID)();
1568
1568
  safeEvent(this.#onEvent, { type: "correlation-id", endpoint, correlationId });
1569
1569
  try {
1570
- return await this.#coordinate(endpoint, parameters, decode, options, correlationId);
1570
+ const value = await this.#coordinate(endpoint, parameters, decode, options, correlationId);
1571
+ if (options.signal?.aborted) throw new AbortedError();
1572
+ return value;
1571
1573
  } catch (error) {
1572
1574
  safeEvent(this.#onEvent, {
1573
1575
  type: "terminal-failure",
@@ -1591,6 +1593,7 @@ ${semantic.toString()}`);
1591
1593
  const cached = await this.#cache.read(key, signal);
1592
1594
  if (cached.status === "hit") {
1593
1595
  safeEvent(this.#onEvent, { type: "cache-hit", endpoint, correlationId });
1596
+ if (signal?.aborted) throw new AbortedError();
1594
1597
  if (encoder.encode(cached.value).byteLength > this.#maxResponseBytes) {
1595
1598
  await this.#cache.invalidate(key);
1596
1599
  } else {
@@ -1902,7 +1905,7 @@ var PubMedClient = class {
1902
1905
  if (options.totalDriftPolicy !== void 0 && options.totalDriftPolicy !== "error" && options.totalDriftPolicy !== "warn") {
1903
1906
  throw new ValidationError('totalDriftPolicy must be "error" or "warn"');
1904
1907
  }
1905
- this.#totalDriftPolicy = options.totalDriftPolicy ?? "error";
1908
+ this.#totalDriftPolicy = options.totalDriftPolicy ?? "warn";
1906
1909
  if (typeof options.email !== "string" || options.email.trim() === "") throw new ValidationError("email is required");
1907
1910
  if (typeof options.tool !== "string" || options.tool.trim() === "") throw new ValidationError("tool is required");
1908
1911
  if (options.apiKey !== void 0 && (typeof options.apiKey !== "string" || options.apiKey.trim() === "")) {
@@ -1966,6 +1969,7 @@ var PubMedClient = class {
1966
1969
  let ordered = input.flatMap((pmid) => byPmid.get(pmid) ?? []);
1967
1970
  if (unknown.length > 0) ordered = [...ordered, ...unknown];
1968
1971
  if (options.includeLinkOuts === true && ordered.length > 0) ordered = await this.#enrich(ordered, options.signal);
1972
+ if (options.signal?.aborted) throw new AbortedError();
1969
1973
  return {
1970
1974
  records: ordered,
1971
1975
  missingPmids: input.filter((pmid) => !byPmid.has(pmid)),
@@ -1994,6 +1998,7 @@ var PubMedClient = class {
1994
1998
  );
1995
1999
  received.push(...summaries);
1996
2000
  }
2001
+ if (options.signal?.aborted) throw new AbortedError();
1997
2002
  const byPmid = new Map(received.map((summary) => [summary.pmid, summary]));
1998
2003
  return {
1999
2004
  summaries: input.flatMap((pmid) => byPmid.get(pmid) ?? []),
package/dist/index.d.cts CHANGED
@@ -408,7 +408,7 @@ interface PubMedClientOptions {
408
408
  readonly maxResponseBytes?: number;
409
409
  readonly maxQueuedRequests?: number;
410
410
  readonly maxBatchSize?: number;
411
- /** Default "error". "warn" explicitly permits best-effort, non-snapshot pagination. */
411
+ /** Default "warn": continue valid pages with drift diagnostics. "error" opts into stopping on count changes. Neither guarantees a snapshot. */
412
412
  readonly totalDriftPolicy?: "error" | "warn";
413
413
  }
414
414
 
package/dist/index.d.ts CHANGED
@@ -408,7 +408,7 @@ interface PubMedClientOptions {
408
408
  readonly maxResponseBytes?: number;
409
409
  readonly maxQueuedRequests?: number;
410
410
  readonly maxBatchSize?: number;
411
- /** Default "error". "warn" explicitly permits best-effort, non-snapshot pagination. */
411
+ /** Default "warn": continue valid pages with drift diagnostics. "error" opts into stopping on count changes. Neither guarantees a snapshot. */
412
412
  readonly totalDriftPolicy?: "error" | "warn";
413
413
  }
414
414
 
package/dist/index.js CHANGED
@@ -1522,7 +1522,9 @@ var Transport = class {
1522
1522
  const correlationId = randomUUID();
1523
1523
  safeEvent(this.#onEvent, { type: "correlation-id", endpoint, correlationId });
1524
1524
  try {
1525
- return await this.#coordinate(endpoint, parameters, decode, options, correlationId);
1525
+ const value = await this.#coordinate(endpoint, parameters, decode, options, correlationId);
1526
+ if (options.signal?.aborted) throw new AbortedError();
1527
+ return value;
1526
1528
  } catch (error) {
1527
1529
  safeEvent(this.#onEvent, {
1528
1530
  type: "terminal-failure",
@@ -1546,6 +1548,7 @@ ${semantic.toString()}`);
1546
1548
  const cached = await this.#cache.read(key, signal);
1547
1549
  if (cached.status === "hit") {
1548
1550
  safeEvent(this.#onEvent, { type: "cache-hit", endpoint, correlationId });
1551
+ if (signal?.aborted) throw new AbortedError();
1549
1552
  if (encoder.encode(cached.value).byteLength > this.#maxResponseBytes) {
1550
1553
  await this.#cache.invalidate(key);
1551
1554
  } else {
@@ -1857,7 +1860,7 @@ var PubMedClient = class {
1857
1860
  if (options.totalDriftPolicy !== void 0 && options.totalDriftPolicy !== "error" && options.totalDriftPolicy !== "warn") {
1858
1861
  throw new ValidationError('totalDriftPolicy must be "error" or "warn"');
1859
1862
  }
1860
- this.#totalDriftPolicy = options.totalDriftPolicy ?? "error";
1863
+ this.#totalDriftPolicy = options.totalDriftPolicy ?? "warn";
1861
1864
  if (typeof options.email !== "string" || options.email.trim() === "") throw new ValidationError("email is required");
1862
1865
  if (typeof options.tool !== "string" || options.tool.trim() === "") throw new ValidationError("tool is required");
1863
1866
  if (options.apiKey !== void 0 && (typeof options.apiKey !== "string" || options.apiKey.trim() === "")) {
@@ -1921,6 +1924,7 @@ var PubMedClient = class {
1921
1924
  let ordered = input.flatMap((pmid) => byPmid.get(pmid) ?? []);
1922
1925
  if (unknown.length > 0) ordered = [...ordered, ...unknown];
1923
1926
  if (options.includeLinkOuts === true && ordered.length > 0) ordered = await this.#enrich(ordered, options.signal);
1927
+ if (options.signal?.aborted) throw new AbortedError();
1924
1928
  return {
1925
1929
  records: ordered,
1926
1930
  missingPmids: input.filter((pmid) => !byPmid.has(pmid)),
@@ -1949,6 +1953,7 @@ var PubMedClient = class {
1949
1953
  );
1950
1954
  received.push(...summaries);
1951
1955
  }
1956
+ if (options.signal?.aborted) throw new AbortedError();
1952
1957
  const byPmid = new Map(received.map((summary) => [summary.pmid, summary]));
1953
1958
  return {
1954
1959
  summaries: input.flatMap((pmid) => byPmid.get(pmid) ?? []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everdeep/pubmed",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Safe, typed TypeScript client for the NCBI PubMed E-utilities API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",