recker 1.0.34 → 1.0.35-next.1b43fea

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/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './utils/rdap.js';
5
5
  export * from './utils/dns-toolkit.js';
6
6
  export * from './transport/fetch.js';
7
7
  export * from './core/errors.js';
8
+ export * from './core/error-handler.js';
8
9
  export * from './core/client.js';
9
10
  export * from './core/request.js';
10
11
  export * from './core/response.js';
@@ -40,6 +41,8 @@ export * from './plugins/har-recorder.js';
40
41
  export * from './plugins/har-player.js';
41
42
  export * from './plugins/graphql.js';
42
43
  export * from './plugins/xml.js';
44
+ export * from './plugins/yaml.js';
45
+ export * from './plugins/csv.js';
43
46
  export * from './plugins/scrape.js';
44
47
  export * from './scrape/index.js';
45
48
  export * from './seo/index.js';
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './utils/rdap.js';
5
5
  export * from './utils/dns-toolkit.js';
6
6
  export * from './transport/fetch.js';
7
7
  export * from './core/errors.js';
8
+ export * from './core/error-handler.js';
8
9
  export * from './core/client.js';
9
10
  export * from './core/request.js';
10
11
  export * from './core/response.js';
@@ -40,6 +41,8 @@ export * from './plugins/har-recorder.js';
40
41
  export * from './plugins/har-player.js';
41
42
  export * from './plugins/graphql.js';
42
43
  export * from './plugins/xml.js';
44
+ export * from './plugins/yaml.js';
45
+ export * from './plugins/csv.js';
43
46
  export * from './plugins/scrape.js';
44
47
  export * from './scrape/index.js';
45
48
  export * from './seo/index.js';
@@ -0,0 +1,42 @@
1
+ import type { ReckerResponse } from '../types/index.js';
2
+ export declare const CSV_MEDIA_TYPES: readonly ["text/csv", "application/csv", "text/x-csv", "application/x-csv"];
3
+ export declare const CSV_SUFFIX = "+csv";
4
+ export declare function isCsvContentType(contentType: string | null): boolean;
5
+ export interface CsvParseOptions {
6
+ headers?: boolean;
7
+ columns?: string[];
8
+ delimiter?: string;
9
+ quote?: string;
10
+ skipEmptyLines?: boolean;
11
+ trim?: boolean;
12
+ parseNumbers?: boolean;
13
+ parseBooleans?: boolean;
14
+ comment?: string;
15
+ maxRows?: number;
16
+ maxColumns?: number;
17
+ }
18
+ export interface CsvSerializeOptions {
19
+ headers?: boolean;
20
+ columns?: string[];
21
+ delimiter?: string;
22
+ quote?: string;
23
+ alwaysQuote?: boolean;
24
+ lineEnding?: string;
25
+ nullValue?: string;
26
+ }
27
+ export declare function parseCsv<T = Record<string, string>>(csv: string, options: CsvParseOptions & {
28
+ headers?: true;
29
+ }): T[];
30
+ export declare function parseCsv(csv: string, options: CsvParseOptions & {
31
+ headers: false;
32
+ }): string[][];
33
+ export declare function parseCsv<T = Record<string, string>>(csv: string, options?: CsvParseOptions): T[] | string[][];
34
+ export declare class CsvError extends Error {
35
+ row?: number;
36
+ constructor(message: string, row?: number);
37
+ }
38
+ export declare function serializeCsv(data: Record<string, any>[] | any[][], options?: CsvSerializeOptions): string;
39
+ export declare function csvResponse<T = Record<string, string>>(promise: Promise<ReckerResponse>, options?: CsvParseOptions): Promise<T[]>;
40
+ export declare function parseCsvTyped<T = Record<string, any>>(csv: string, options?: Omit<CsvParseOptions, 'parseNumbers' | 'parseBooleans'>): T[];
41
+ export declare function parseCsvStream<T = Record<string, string>>(stream: ReadableStream<Uint8Array> | null, options?: CsvParseOptions): AsyncGenerator<T>;
42
+ export { parseCsv as csvParse, serializeCsv as csvSerialize };
@@ -0,0 +1,386 @@
1
+ export const CSV_MEDIA_TYPES = [
2
+ 'text/csv',
3
+ 'application/csv',
4
+ 'text/x-csv',
5
+ 'application/x-csv',
6
+ ];
7
+ export const CSV_SUFFIX = '+csv';
8
+ export function isCsvContentType(contentType) {
9
+ if (!contentType)
10
+ return false;
11
+ const ct = contentType.toLowerCase().split(';')[0].trim();
12
+ return CSV_MEDIA_TYPES.includes(ct) || ct.endsWith(CSV_SUFFIX);
13
+ }
14
+ export function parseCsv(csv, options = {}) {
15
+ const opts = {
16
+ headers: options.headers ?? true,
17
+ columns: options.columns,
18
+ delimiter: options.delimiter ?? ',',
19
+ quote: options.quote ?? '"',
20
+ skipEmptyLines: options.skipEmptyLines ?? true,
21
+ trim: options.trim ?? false,
22
+ parseNumbers: options.parseNumbers ?? false,
23
+ parseBooleans: options.parseBooleans ?? false,
24
+ comment: options.comment,
25
+ maxRows: options.maxRows ?? 100000,
26
+ maxColumns: options.maxColumns ?? 1000,
27
+ };
28
+ if (!csv || !csv.trim()) {
29
+ return [];
30
+ }
31
+ const normalized = csv.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
32
+ const rows = [];
33
+ let currentRow = [];
34
+ let currentField = '';
35
+ let inQuotes = false;
36
+ let rowCount = 0;
37
+ for (let i = 0; i < normalized.length; i++) {
38
+ const char = normalized[i];
39
+ const nextChar = normalized[i + 1];
40
+ if (inQuotes) {
41
+ if (char === opts.quote) {
42
+ if (nextChar === opts.quote) {
43
+ currentField += opts.quote;
44
+ i++;
45
+ }
46
+ else {
47
+ inQuotes = false;
48
+ }
49
+ }
50
+ else {
51
+ currentField += char;
52
+ }
53
+ }
54
+ else {
55
+ if (char === opts.quote && currentField === '') {
56
+ inQuotes = true;
57
+ }
58
+ else if (char === opts.delimiter) {
59
+ currentRow.push(processField(currentField, opts));
60
+ currentField = '';
61
+ if (currentRow.length > opts.maxColumns) {
62
+ throw new CsvError(`Maximum columns (${opts.maxColumns}) exceeded`, rowCount);
63
+ }
64
+ }
65
+ else if (char === '\n') {
66
+ currentRow.push(processField(currentField, opts));
67
+ currentField = '';
68
+ if (opts.comment && currentRow.length === 1 && currentRow[0].startsWith(opts.comment)) {
69
+ currentRow = [];
70
+ continue;
71
+ }
72
+ if (opts.skipEmptyLines && currentRow.length === 1 && currentRow[0] === '') {
73
+ currentRow = [];
74
+ continue;
75
+ }
76
+ rows.push(currentRow);
77
+ currentRow = [];
78
+ rowCount++;
79
+ if (rowCount > opts.maxRows) {
80
+ throw new CsvError(`Maximum rows (${opts.maxRows}) exceeded`, rowCount);
81
+ }
82
+ }
83
+ else {
84
+ currentField += char;
85
+ }
86
+ }
87
+ }
88
+ if (currentField || currentRow.length > 0) {
89
+ currentRow.push(processField(currentField, opts));
90
+ if (!(opts.comment && currentRow.length === 1 && currentRow[0].startsWith(opts.comment))) {
91
+ if (!(opts.skipEmptyLines && currentRow.length === 1 && currentRow[0] === '')) {
92
+ rows.push(currentRow);
93
+ }
94
+ }
95
+ }
96
+ if (!opts.headers && !opts.columns) {
97
+ return rows;
98
+ }
99
+ let headers;
100
+ let dataRows;
101
+ if (opts.columns) {
102
+ headers = opts.columns;
103
+ dataRows = opts.headers ? rows.slice(1) : rows;
104
+ }
105
+ else {
106
+ if (rows.length === 0) {
107
+ return [];
108
+ }
109
+ headers = rows[0];
110
+ dataRows = rows.slice(1);
111
+ }
112
+ return dataRows.map((row) => {
113
+ const obj = {};
114
+ for (let i = 0; i < headers.length; i++) {
115
+ obj[headers[i]] = row[i] ?? '';
116
+ }
117
+ return obj;
118
+ });
119
+ }
120
+ function processField(field, opts) {
121
+ let value = opts.trim ? field.trim() : field;
122
+ if (opts.parseNumbers) {
123
+ const num = Number(value);
124
+ if (!isNaN(num) && value !== '') {
125
+ return num;
126
+ }
127
+ }
128
+ if (opts.parseBooleans) {
129
+ const lower = value.toLowerCase();
130
+ if (lower === 'true')
131
+ return true;
132
+ if (lower === 'false')
133
+ return false;
134
+ }
135
+ return value;
136
+ }
137
+ export class CsvError extends Error {
138
+ row;
139
+ constructor(message, row) {
140
+ super(row !== undefined ? `${message} at row ${row + 1}` : message);
141
+ this.name = 'CsvError';
142
+ this.row = row;
143
+ }
144
+ }
145
+ export function serializeCsv(data, options = {}) {
146
+ const opts = {
147
+ headers: options.headers ?? true,
148
+ columns: options.columns,
149
+ delimiter: options.delimiter ?? ',',
150
+ quote: options.quote ?? '"',
151
+ alwaysQuote: options.alwaysQuote ?? false,
152
+ lineEnding: options.lineEnding ?? '\r\n',
153
+ nullValue: options.nullValue ?? '',
154
+ };
155
+ if (!data || data.length === 0) {
156
+ return '';
157
+ }
158
+ const lines = [];
159
+ const isArrayOfArrays = Array.isArray(data[0]);
160
+ if (isArrayOfArrays) {
161
+ for (const row of data) {
162
+ lines.push(row.map((field) => quoteField(field, opts)).join(opts.delimiter));
163
+ }
164
+ }
165
+ else {
166
+ const objects = data;
167
+ let columns;
168
+ if (opts.columns) {
169
+ columns = opts.columns;
170
+ }
171
+ else {
172
+ const keySet = new Set();
173
+ for (const obj of objects) {
174
+ for (const key of Object.keys(obj)) {
175
+ keySet.add(key);
176
+ }
177
+ }
178
+ columns = Array.from(keySet);
179
+ }
180
+ if (opts.headers) {
181
+ lines.push(columns.map((col) => quoteField(col, opts)).join(opts.delimiter));
182
+ }
183
+ for (const obj of objects) {
184
+ const row = columns.map((col) => {
185
+ const value = obj[col];
186
+ return quoteField(value, opts);
187
+ });
188
+ lines.push(row.join(opts.delimiter));
189
+ }
190
+ }
191
+ return lines.join(opts.lineEnding);
192
+ }
193
+ function quoteField(value, opts) {
194
+ if (value === null || value === undefined) {
195
+ return opts.nullValue;
196
+ }
197
+ const str = String(value);
198
+ const needsQuoting = opts.alwaysQuote ||
199
+ str.includes(opts.quote) ||
200
+ str.includes(opts.delimiter) ||
201
+ str.includes('\n') ||
202
+ str.includes('\r');
203
+ if (needsQuoting) {
204
+ const escaped = str.replace(new RegExp(opts.quote, 'g'), opts.quote + opts.quote);
205
+ return opts.quote + escaped + opts.quote;
206
+ }
207
+ return str;
208
+ }
209
+ export async function csvResponse(promise, options) {
210
+ const response = await promise;
211
+ const text = await response.text();
212
+ return parseCsv(text, options);
213
+ }
214
+ export function parseCsvTyped(csv, options = {}) {
215
+ const rows = parseCsv(csv, { ...options, headers: true });
216
+ return rows.map((row) => {
217
+ const typed = {};
218
+ for (const [key, value] of Object.entries(row)) {
219
+ typed[key] = inferType(value);
220
+ }
221
+ return typed;
222
+ });
223
+ }
224
+ function inferType(value) {
225
+ if (value === '') {
226
+ return null;
227
+ }
228
+ const lower = value.toLowerCase();
229
+ if (lower === 'true')
230
+ return true;
231
+ if (lower === 'false')
232
+ return false;
233
+ if (/^-?\d+$/.test(value)) {
234
+ const num = parseInt(value, 10);
235
+ if (Number.isSafeInteger(num))
236
+ return num;
237
+ }
238
+ if (/^-?\d*\.?\d+([eE][-+]?\d+)?$/.test(value)) {
239
+ return parseFloat(value);
240
+ }
241
+ if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(value)) {
242
+ const date = new Date(value);
243
+ if (!isNaN(date.getTime())) {
244
+ return date;
245
+ }
246
+ }
247
+ return value;
248
+ }
249
+ export async function* parseCsvStream(stream, options = {}) {
250
+ if (!stream)
251
+ return;
252
+ const opts = {
253
+ headers: options.headers ?? true,
254
+ columns: options.columns,
255
+ delimiter: options.delimiter ?? ',',
256
+ quote: options.quote ?? '"',
257
+ skipEmptyLines: options.skipEmptyLines ?? true,
258
+ trim: options.trim ?? false,
259
+ parseNumbers: options.parseNumbers ?? false,
260
+ parseBooleans: options.parseBooleans ?? false,
261
+ comment: options.comment,
262
+ };
263
+ const decoder = new TextDecoder();
264
+ const reader = stream.getReader();
265
+ let buffer = '';
266
+ let headers = opts.columns || null;
267
+ let isFirstRow = !opts.columns;
268
+ try {
269
+ while (true) {
270
+ const { done, value } = await reader.read();
271
+ if (done) {
272
+ if (buffer.trim()) {
273
+ const row = parseRow(buffer, opts);
274
+ if (row) {
275
+ if (isFirstRow && opts.headers) {
276
+ headers = row;
277
+ isFirstRow = false;
278
+ }
279
+ else if (headers) {
280
+ yield rowToObject(row, headers);
281
+ }
282
+ else {
283
+ yield row;
284
+ }
285
+ }
286
+ }
287
+ break;
288
+ }
289
+ buffer += decoder.decode(value, { stream: true });
290
+ let newlineIndex;
291
+ while ((newlineIndex = findCompleteRow(buffer, opts.quote)) !== -1) {
292
+ const line = buffer.slice(0, newlineIndex);
293
+ buffer = buffer.slice(newlineIndex + 1);
294
+ if (buffer.startsWith('\r')) {
295
+ buffer = buffer.slice(1);
296
+ }
297
+ const row = parseRow(line, opts);
298
+ if (!row)
299
+ continue;
300
+ if (isFirstRow && opts.headers) {
301
+ headers = row;
302
+ isFirstRow = false;
303
+ continue;
304
+ }
305
+ if (headers) {
306
+ yield rowToObject(row, headers);
307
+ }
308
+ else {
309
+ yield row;
310
+ }
311
+ }
312
+ }
313
+ }
314
+ finally {
315
+ reader.releaseLock();
316
+ }
317
+ }
318
+ function findCompleteRow(buffer, quote) {
319
+ let inQuotes = false;
320
+ for (let i = 0; i < buffer.length; i++) {
321
+ const char = buffer[i];
322
+ if (char === quote) {
323
+ if (inQuotes && buffer[i + 1] === quote) {
324
+ i++;
325
+ }
326
+ else {
327
+ inQuotes = !inQuotes;
328
+ }
329
+ }
330
+ else if ((char === '\n' || char === '\r') && !inQuotes) {
331
+ return i;
332
+ }
333
+ }
334
+ return -1;
335
+ }
336
+ function parseRow(line, opts) {
337
+ if (opts.comment && line.startsWith(opts.comment)) {
338
+ return null;
339
+ }
340
+ if (opts.skipEmptyLines && !line.trim()) {
341
+ return null;
342
+ }
343
+ const fields = [];
344
+ let currentField = '';
345
+ let inQuotes = false;
346
+ for (let i = 0; i < line.length; i++) {
347
+ const char = line[i];
348
+ const nextChar = line[i + 1];
349
+ if (inQuotes) {
350
+ if (char === opts.quote) {
351
+ if (nextChar === opts.quote) {
352
+ currentField += opts.quote;
353
+ i++;
354
+ }
355
+ else {
356
+ inQuotes = false;
357
+ }
358
+ }
359
+ else {
360
+ currentField += char;
361
+ }
362
+ }
363
+ else {
364
+ if (char === opts.quote && currentField === '') {
365
+ inQuotes = true;
366
+ }
367
+ else if (char === opts.delimiter) {
368
+ fields.push(processField(currentField, opts));
369
+ currentField = '';
370
+ }
371
+ else {
372
+ currentField += char;
373
+ }
374
+ }
375
+ }
376
+ fields.push(processField(currentField, opts));
377
+ return fields;
378
+ }
379
+ function rowToObject(row, headers) {
380
+ const obj = {};
381
+ for (let i = 0; i < headers.length; i++) {
382
+ obj[headers[i]] = row[i] ?? '';
383
+ }
384
+ return obj;
385
+ }
386
+ export { parseCsv as csvParse, serializeCsv as csvSerialize };
@@ -271,11 +271,15 @@ export class HlsPromise {
271
271
  }
272
272
  }
273
273
  async info() {
274
- const content = await this.client.get(this.manifestUrl).text();
274
+ const content = await this.client.get(this.manifestUrl, {
275
+ signal: this.abortController.signal,
276
+ }).text();
275
277
  if (isMasterPlaylist(content)) {
276
278
  const master = parseMasterPlaylist(content, this.manifestUrl);
277
279
  const selectedVariant = selectVariant(master.variants, this.options.quality);
278
- const playlistContent = await this.client.get(selectedVariant.url).text();
280
+ const playlistContent = await this.client.get(selectedVariant.url, {
281
+ signal: this.abortController.signal,
282
+ }).text();
279
283
  const playlist = parseMediaPlaylist(playlistContent, selectedVariant.url);
280
284
  const totalDuration = playlist.endList
281
285
  ? playlist.segments.reduce((sum, s) => sum + s.duration, 0)
@@ -299,7 +303,9 @@ export class HlsPromise {
299
303
  };
300
304
  }
301
305
  async resolveMediaPlaylist() {
302
- const content = await this.client.get(this.manifestUrl).text();
306
+ const content = await this.client.get(this.manifestUrl, {
307
+ signal: this.abortController.signal,
308
+ }).text();
303
309
  if (!isMasterPlaylist(content)) {
304
310
  return this.manifestUrl;
305
311
  }
@@ -308,7 +314,9 @@ export class HlsPromise {
308
314
  return variant.url;
309
315
  }
310
316
  async fetchMediaPlaylist(url) {
311
- const content = await this.client.get(url).text();
317
+ const content = await this.client.get(url, {
318
+ signal: this.abortController.signal,
319
+ }).text();
312
320
  return parseMediaPlaylist(content, url);
313
321
  }
314
322
  async downloadSegment(segment) {
@@ -0,0 +1,28 @@
1
+ import type { ReckerResponse } from '../types/index.js';
2
+ export declare const YAML_MEDIA_TYPES: readonly ["application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"];
3
+ export declare const YAML_SUFFIX = "+yaml";
4
+ export declare function isYamlContentType(contentType: string | null): boolean;
5
+ export interface YamlParseOptions {
6
+ allowDuplicateKeys?: boolean;
7
+ parseDates?: boolean;
8
+ customTags?: Record<string, (value: string) => any>;
9
+ maxDepth?: number;
10
+ maxKeys?: number;
11
+ }
12
+ export interface YamlSerializeOptions {
13
+ indent?: number;
14
+ lineWidth?: number;
15
+ forceQuotes?: boolean;
16
+ flowStyle?: boolean;
17
+ sortKeys?: boolean;
18
+ skipUndefined?: boolean;
19
+ documentMarkers?: boolean;
20
+ }
21
+ export declare function parseYaml<T = any>(yaml: string, options?: YamlParseOptions): T;
22
+ export declare class YamlError extends Error {
23
+ line?: number;
24
+ constructor(message: string, line?: number);
25
+ }
26
+ export declare function serializeYaml(data: any, options?: YamlSerializeOptions): string;
27
+ export declare function yamlResponse<T = any>(promise: Promise<ReckerResponse>, options?: YamlParseOptions): Promise<T>;
28
+ export { parseYaml as yamlParse, serializeYaml as yamlSerialize };