arxiv-api-wrapper 2.0.1 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arxiv-api-wrapper",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Provides functions wrapping the arXiv API",
5
5
  "keywords": [
6
6
  "arxiv"
package/src/oaiClient.ts CHANGED
@@ -33,6 +33,7 @@ import type {
33
33
  } from './oaiTypes.js';
34
34
 
35
35
  const OAI_BASE_URL = 'https://oaipmh.arxiv.org/oai';
36
+ const OAI_EARLIEST_DATE = '2005-09-16';
36
37
 
37
38
  const DEFAULT_USER_AGENT = 'arxiv-api-wrapper/1.0 (+https://export.arxiv.org)';
38
39
 
@@ -53,7 +54,7 @@ interface OaiParams {
53
54
  resumptionToken?: string;
54
55
  }
55
56
 
56
- function hasValue(value: string | undefined): boolean {
57
+ function hasValue(value: string | undefined): value is string {
57
58
  return value != null && value !== '';
58
59
  }
59
60
 
@@ -81,6 +82,40 @@ function throwResumptionTokenExclusiveError(context: 'request params' | 'list op
81
82
  );
82
83
  }
83
84
 
85
+ function parseDatePrefix(dateValue: string): string | undefined {
86
+ const trimmed = dateValue.trim();
87
+ if (!trimmed) return undefined;
88
+ const match = /^(\d{4}-\d{2}-\d{2})(?:$|T\d{2}:\d{2}:\d{2}Z$)/.exec(trimmed);
89
+ return match?.[1];
90
+ }
91
+
92
+ function validateFromDateNotTooEarly(from: string | undefined): void {
93
+ if (!hasValue(from)) return;
94
+ const normalizedDate = parseDatePrefix(from);
95
+ if (!normalizedDate) return;
96
+ if (normalizedDate < OAI_EARLIEST_DATE) {
97
+ throw new OaiError(
98
+ 'badArgument',
99
+ `Invalid list options: from=${from} is earlier than arXiv's earliest supported OAI datestamp ` +
100
+ `(${OAI_EARLIEST_DATE}). Use from >= ${OAI_EARLIEST_DATE} or omit from.`
101
+ );
102
+ }
103
+ }
104
+
105
+ function validateUntilDateNotTooLate(until: string | undefined): void {
106
+ if (!hasValue(until)) return;
107
+ const normalizedDate = parseDatePrefix(until);
108
+ if (!normalizedDate) return;
109
+ const todayUtc = new Date().toISOString().slice(0, 10);
110
+ if (normalizedDate > todayUtc) {
111
+ throw new OaiError(
112
+ 'badArgument',
113
+ `Invalid list options: until=${until} is later than today's UTC date (${todayUtc}). ` +
114
+ 'Use until <= today (UTC) or omit until.'
115
+ );
116
+ }
117
+ }
118
+
84
119
  /** Build OAI-PMH request URL (exported for unit tests). */
85
120
  export function buildOaiUrl(verb: OaiVerb, params: OaiParams): string {
86
121
  if (hasResumptionTokenConflicts(params)) {
@@ -241,6 +276,8 @@ export async function oaiListIdentifiers(
241
276
  const from = listOptions?.from;
242
277
  const until = listOptions?.until;
243
278
  const set = listOptions?.set;
279
+ validateFromDateNotTooEarly(from);
280
+ validateUntilDateNotTooLate(until);
244
281
  const params: OaiParams = {};
245
282
  if (hasValue(resumptionToken)) {
246
283
  params.resumptionToken = resumptionToken;
@@ -272,6 +309,8 @@ export async function oaiListRecords(
272
309
  const from = listOptions?.from;
273
310
  const until = listOptions?.until;
274
311
  const set = listOptions?.set;
312
+ validateFromDateNotTooEarly(from);
313
+ validateUntilDateNotTooLate(until);
275
314
  const params: OaiParams = {};
276
315
  if (hasValue(resumptionToken)) {
277
316
  params.resumptionToken = resumptionToken;
package/tests/oai.test.ts CHANGED
@@ -294,3 +294,58 @@ describe('resumptionToken validation', () => {
294
294
  });
295
295
  });
296
296
  });
297
+
298
+ describe('from date validation', () => {
299
+ it('throws a local OaiError when from is earlier than arXiv minimum date', async () => {
300
+ await expect(
301
+ oaiListRecords('oai_dc', { from: '2005-09-15' })
302
+ ).rejects.toMatchObject({
303
+ name: 'OaiError',
304
+ code: 'badArgument',
305
+ });
306
+ await expect(oaiListRecords('oai_dc', { from: '2005-09-15' })).rejects.toThrow(
307
+ "earlier than arXiv's earliest supported OAI datestamp (2005-09-16)"
308
+ );
309
+ });
310
+
311
+ it('throws for earlier datetime form and allows earliest date', async () => {
312
+ await expect(
313
+ oaiListIdentifiers('oai_dc', { from: '2005-09-15T23:59:59Z' })
314
+ ).rejects.toMatchObject({
315
+ name: 'OaiError',
316
+ code: 'badArgument',
317
+ });
318
+ const url = buildOaiUrl('ListIdentifiers', { metadataPrefix: 'oai_dc', from: '2005-09-16' });
319
+ expect(url).toContain('from=2005-09-16');
320
+ });
321
+ });
322
+
323
+ describe('until date validation', () => {
324
+ it('throws a local OaiError when until is in the future', async () => {
325
+ const tomorrowUtc = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
326
+
327
+ await expect(
328
+ oaiListRecords('oai_dc', { until: tomorrowUtc })
329
+ ).rejects.toMatchObject({
330
+ name: 'OaiError',
331
+ code: 'badArgument',
332
+ });
333
+ await expect(oaiListRecords('oai_dc', { until: tomorrowUtc })).rejects.toThrow(
334
+ "later than today's UTC date"
335
+ );
336
+ });
337
+
338
+ it('throws for future datetime form and allows today', async () => {
339
+ const tomorrowUtc = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
340
+ const todayUtc = new Date().toISOString().slice(0, 10);
341
+
342
+ await expect(
343
+ oaiListIdentifiers('oai_dc', { until: `${tomorrowUtc}T00:00:00Z` })
344
+ ).rejects.toMatchObject({
345
+ name: 'OaiError',
346
+ code: 'badArgument',
347
+ });
348
+ const url = buildOaiUrl('ListIdentifiers', { metadataPrefix: 'oai_dc', until: todayUtc });
349
+ expect(url).toContain(`until=${todayUtc}`);
350
+ });
351
+ });