From 4ee9261073d8de089e1fb91a9e5cd2e67367c8b0 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sat, 2 Dec 2023 10:33:00 +0700 Subject: [PATCH 01/20] Updated IO --- src/file-info.ts | 2 +- src/index.ts | 4 +- src/io/file.ts | 76 ++++++++++++++++++++++--- src/types.ts | 4 +- tests/file-io.test.ts | 129 ++++++++++++++---------------------------- 5 files changed, 115 insertions(+), 100 deletions(-) diff --git a/src/file-info.ts b/src/file-info.ts index edbcc1e..3b26577 100644 --- a/src/file-info.ts +++ b/src/file-info.ts @@ -9,7 +9,7 @@ export type GTFSFileInfo = { /** * GTFS Files Information */ -export const GTFSFileInfos: Record = { +export const GTFS_FILES: Record = { agency: { name: 'agency.txt', columns: { diff --git a/src/index.ts b/src/index.ts index 3f16e4c..a96b0d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -export { default as GTFSFileIO } from './io/file'; +export { GTFSFileIO } from './io/file'; -export { GTFSFileInfos } from './file-info'; +export { GTFS_FILES } from './file-info'; export { GTFSCalendarDateException } from './files/calendar-date'; export { GTFSCalendarAvailability } from './files/calendar'; diff --git a/src/io/file.ts b/src/io/file.ts index 269506d..3e81130 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,11 +1,21 @@ import { stringify, parse } from 'csv/sync'; -import { GTFSRow } from '../types'; +import { GTFSFileRecords, GTFSFileRow } from '../types'; import { GTFSFileInfo } from '../file-info'; +import type { Options as CSVStringifyOptions } from 'csv-stringify'; -export default class GTFSFileIO { - private constructor() {} +/** + * File IO + */ +export class GTFSFileIO { + protected constructor() {} - public static *read(file: GTFSFileInfo, lines: IterableIterator): IterableIterator { + /** + * Read lines and returns records. + * @param file File information + * @param lines Iterable file contents by line + * @returns Iterable records + */ + public static *read(file: GTFSFileInfo, lines: IterableIterator): IterableIterator { const head = lines.next(); if (head.done) return; if (!head.value || !head.value.trim()) return; @@ -22,20 +32,70 @@ export default class GTFSFileIO { const column = file.columns[columns[i]]; record[columns[i]] = column === 'string' ? row[i].toString() : parseInt(row[i]); } - yield record as FileType; + yield record as RowType; } return; } - public static *write(file: GTFSFileInfo, records: IterableIterator): IterableIterator { + /** + * Write records into line contents. + * @param file File Information + * @param records Iterable records + * @param newLine New line delimiter + * @returns Iterable file contents by line + */ + public static *write(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { const columns = Object.keys(file.columns); - yield stringify([columns], { record_delimiter: '' }); + yield stringify([columns], { record_delimiter: newLine }); for (const record of records) { - yield stringify([columns.map(c => (record as Record)[c] ?? undefined)], { record_delimiter: '' }); + yield stringify([columns.map(c => (record as Record)[c] ?? undefined)], { record_delimiter: newLine }); } return; } + + /** + * Read array of content lines into records array. + * @param file File information + * @param lines Array of content lines + * @returns Array of records + */ + public static readLinesArray(file: GTFSFileInfo, lines: string[]): RowType[] { + return [...GTFSFileIO.read(file, lines.values())]; + } + + /** + * Read file content into records array. + * @param file File information + * @param content File content + * @param newLine Line separator (default: \n) + * @returns Array of record + */ + public static readArray(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { + return GTFSFileIO.readLinesArray(file, content.split(newLine)); + } + + /** + * Write records into array of lines content. + * @param file File information + * @param records Row records + * @param newLine Line separator (default: \n) + * @returns Lines array + */ + public static writeLinesArray(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { + return [...GTFSFileIO.write(file, records.values(), newLine)]; + } + + /** + * Write records into file content. + * @param file File information + * @param records Row records + * @param newLine Line separator (default: \n) + * @returns File content + */ + public static writeArray(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { + return GTFSFileIO.writeLinesArray(file, records, newLine).join(''); + } }; diff --git a/src/types.ts b/src/types.ts index ca1023e..dc727d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,7 +26,7 @@ import type { GTFSTranslation } from './files/translation'; import type { GTFSTrip } from './files/trip'; /** A row record in a GTFS file */ -export type GTFSRow = GTFSAgency +export type GTFSFileRow = GTFSAgency | GTFSStop | GTFSRoute | GTFSTrip @@ -54,7 +54,7 @@ export type GTFSRow = GTFSAgency | GTFSAttribution; /** GTFS file records */ -export type GTFSFileRecords = IterableIterator|FileType[]; +export type GTFSFileRecords = IterableIterator|RowType[]; /** GTFS Dataset feed */ export type GTFSFeed = { diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index fb67f39..e8261d6 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -1,106 +1,61 @@ import { describe, expect, it } from 'vitest'; -import { parse } from 'csv/sync'; -import { GTFSAgency, GTFSFileIO, GTFSFileInfos, GTFSTripDirection } from '../dist'; -import type { GTFSRoute, GTFSStop, GTFSTrip } from '../dist'; +import { GTFSAgency, GTFSFileIO, GTFS_FILES, GTFSStopLocationType, GTFSTripDirection } from '../dist'; +import type { GTFSFileRecords, GTFSRoute, GTFSStop, GTFSTrip } from '../dist'; describe('Test GTFSFileIO reading', () => { it('reads rows into records', () => { - const input = function*(): IterableIterator { - yield 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station'; - yield 'STOP_01,"Test",1001,,50.25,-23.28,1,'; - yield '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01'; - yield ''; - return; - }; - - let readRecords = [...GTFSFileIO.read(GTFSFileInfos.stops, input())]; - expect(readRecords.length).toEqual(2); - expect(readRecords[0].stop_id).toEqual('STOP_01'); - expect(readRecords[0].stop_code).toBeTypeOf('string'); - expect(readRecords[0].stop_code).toEqual('1001'); - expect(readRecords[0].stop_name).toEqual('Test'); - expect(readRecords[0].location_type).toBeTypeOf('number'); - expect(readRecords[0].location_type).toEqual(1); - expect(readRecords[0].parent_station).toBeFalsy(); - expect(readRecords[1].stop_id).toBeTruthy(); - expect(readRecords[1].stop_id).toEqual('STOP_02'); - expect(readRecords[1].stop_code).toEqual('1002'); - expect(readRecords[1].stop_name).toEqual('Test2'); - expect(readRecords[1].location_type).toBeTypeOf('number'); - expect(readRecords[1].location_type).toEqual(0); - expect(readRecords[1].parent_station).toEqual('STOP_01'); + const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' + + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' + + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n'; + const records = GTFSFileIO.readArray(GTFS_FILES.stops, content); + expect(records.length).toEqual(2); + expect(records[0].stop_id).toEqual('STOP_01'); + expect(records[0].stop_code).toBeTypeOf('string'); + expect(records[0].stop_code).toEqual('1001'); + expect(records[0].stop_name).toEqual('Test'); + expect(records[0].location_type).toBeTypeOf('number'); + expect(records[0].location_type).toEqual(1); + expect(records[0].location_type).toEqual(GTFSStopLocationType.Station); + expect(records[0].parent_station).toBeFalsy(); + expect(records[1].stop_id).toBeTruthy(); + expect(records[1].stop_id).toEqual('STOP_02'); + expect(records[1].stop_code).toEqual('1002'); + expect(records[1].stop_name).toEqual('Test2'); + expect(records[1].location_type).toBeTypeOf('number'); + expect(records[1].location_type).toEqual(0); + expect(records[1].location_type).toEqual(GTFSStopLocationType.Stop); + expect(records[1].parent_station).toEqual('STOP_01'); }); it('handles empty file content', () => { - const input = function*(): IterableIterator { - yield 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station'; - yield ''; - return; - }; - const records = [...GTFSFileIO.read(GTFSFileInfos.stops, input())]; + const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; + const records = GTFSFileIO.readArray(GTFS_FILES.stops, content); expect(records.length).toEqual(0); - const emptyInput = function*(): IterableIterator { - return; - } - expect([...GTFSFileIO.read(GTFSFileInfos.agency, emptyInput())].length).toEqual(0); + expect(GTFSFileIO.readArray(GTFS_FILES.agency, '').length).toEqual(0); }); }); describe('Test GTFSFileIO writing', () => { it('writes records into rows', () => { - const input = function*(): IterableIterator { - yield { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, - yield { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' } - return; - }; - const lines = [...GTFSFileIO.write(GTFSFileInfos.trips, input())]; - expect(lines.length).toEqual(3); - - expect(parse(lines[0])).toContainEqual([ - 'route_id', - 'service_id', - 'trip_id', - 'trip_headsign', - 'trip_short_name', - 'direction_id', - 'block_id', - 'shape_id', - 'wheelchair_accessible', - 'bikes_allowed' - ]); - - const records = parse(lines.join('\n'), { columns: true }); - expect(records[0].route_id).toEqual('R01'); - expect(records[0].service_id).toEqual('S01'); - expect(records[0].trip_id).toEqual('T01'); - expect(parseInt(records[0].direction_id)).toEqual(0); - expect(records[0].trip_headsign).toBeFalsy(); - expect(records[0].shape_id).toBeFalsy(); - expect(records[1].route_id).toEqual('R01'); - expect(records[1].service_id).toEqual('S02'); - expect(records[1].trip_id).toEqual('T02'); - expect(records[1].trip_headsign).toEqual('HEADSIGN'); + const records: GTFSFileRecords = [ + { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, + { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' } + ]; + const content = GTFSFileIO.writeArray(GTFS_FILES.trips, records); + expect(content).toEqual( + 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + + 'R01,S01,T01,,,0,,,,\n' + + 'R01,S02,T02,HEADSIGN,,,,,,\n' + ); }); it('handles empty input', () => { - const input = function*(): IterableIterator { return; }; - const output = [...GTFSFileIO.write(GTFSFileInfos.routes, input())]; - expect(output.length).toEqual(1); - expect(parse(output[0])).toContainEqual([ - 'route_id', - 'agency_id', - 'route_short_name', - 'route_long_name', - 'route_desc', - 'route_type', - 'route_url', - 'route_color', - 'route_text_color', - 'route_sort_order', - 'continuous_pickup', - 'continuous_drop_off', - 'network_id' - ]); + const records: GTFSFileRecords = []; + const content = GTFSFileIO.writeArray(GTFS_FILES.routes, records); + expect(content).toEqual( + 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + + 'route_text_color,route_sort_order,continuous_pickup,continuous_drop_off,network_id\n' + ); }); }); From a3db59d9eb1d0b2b0bceafd895bb047947ecd3a0 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sat, 2 Dec 2023 11:16:41 +0700 Subject: [PATCH 02/20] Added feed file IO --- src/file-info.ts | 34 ++- src/index.ts | 31 ++- src/io/feed-file.ts | 166 +++++++++++++++ src/io/file.ts | 15 +- tests/feed-file-io.test.ts | 421 +++++++++++++++++++++++++++++++++++++ tests/file-io.test.ts | 10 +- 6 files changed, 661 insertions(+), 16 deletions(-) create mode 100644 src/io/feed-file.ts create mode 100644 tests/feed-file-io.test.ts diff --git a/src/file-info.ts b/src/file-info.ts index 3b26577..aa88e86 100644 --- a/src/file-info.ts +++ b/src/file-info.ts @@ -7,9 +7,39 @@ export type GTFSFileInfo = { }; /** - * GTFS Files Information + * GTFS file name */ -export const GTFS_FILES: Record = { +export type GTFSFileName = 'agency' + | 'stops' + | 'routes' + | 'trips' + | 'stop_times' + | 'calendar' + | 'calendar_dates' + | 'fare_attributes' + | 'fare_rules' + | 'timeframes' + | 'fare_media' + | 'fare_products' + | 'fare_leg_rules' + | 'fare_transfer_rules' + | 'areas' + | 'stop_areas' + | 'networks' + | 'route_networks' + | 'shapes' + | 'frequencies' + | 'transfers' + | 'pathways' + | 'levels' + | 'translations' + | 'feed_info' + | 'attributions'; + +/** + * GTFS files information + */ +export const GTFS_FILES: Record = { agency: { name: 'agency.txt', columns: { diff --git a/src/index.ts b/src/index.ts index a96b0d5..df81f99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,33 @@ -export { GTFSFileIO } from './io/file'; +export { default as GTFSFileIO } from './io/file'; +export { + GTFSFeedFileIO, + GTFSAgencyIO, + GTFSStopIO, + GTFSRouteIO, + GTFSTripIO, + GTFSStopTimeIO, + GTFSCalendarIO, + GTFSCalendarDateIO, + GTFSFareAttributeIO, + GTFSFareRuleIO, + GTFSTimeframeIO, + GTFSFareMediaIO, + GTFSFareProductIO, + GTFSFareLegRuleIO, + GTFSFareTransferRuleIO, + GTFSAreaIO, + GTFSStopAreaIO, + GTFSNetworkIO, + GTFSRouteNetworkIO, + GTFSShapeIO, + GTFSFrequencyIO, + GTFSTransferIO, + GTFSPathwayIO, + GTFSLevelIO, + GTFSTranslationIO, + GTFSFeedInfoIO, + GTFSAttributionIO +} from './io/feed-file'; export { GTFS_FILES } from './file-info'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts new file mode 100644 index 0000000..3efc499 --- /dev/null +++ b/src/io/feed-file.ts @@ -0,0 +1,166 @@ +import GTFSFileIO from './file'; +import { GTFS_FILES } from '../file-info'; +import type { GTFSFileInfo } from '../file-info'; +import type { GTFSFileRow } from '../types'; +import type { GTFSAgency } from '../files/agency'; +import type { GTFSStop } from '../files/stop'; +import type { GTFSRoute } from '../files/route'; +import type { GTFSTrip } from '../files/trip'; +import type { GTFSStopTime } from '../files/stop-time'; +import type { GTFSCalendar } from '../files/calendar'; +import type { GTFSCalendarDate } from '../files/calendar-date'; +import type { GTFSFareAttribute } from '../files/fare-attribute'; +import type { GTFSFareRule } from '../files/fare-rule'; +import type { GTFSTimeframe } from '../files/timeframe'; +import type { GTFSFareMedia } from '../files/fare-media'; +import type { GTFSFareProduct } from '../files/fare-product'; +import type { GTFSFareLegRule } from '../files/fare-leg-rule'; +import type { GTFSFareTransferRule } from '../files/fare-transfer-rule'; +import type { GTFSArea } from '../files/area'; +import type { GTFSStopArea } from '../files/stop-area'; +import type { GTFSNetwork } from '../files/network'; +import type { GTFSRouteNetwork } from '../files/route-network'; +import type { GTFSShape } from '../files/shape'; +import type { GTFSFrequency } from '../files/frequency'; +import type { GTFSTransfer } from '../files/transfer'; +import type { GTFSPathway } from '../files/pathway'; +import type { GTFSLevel } from '../files/level'; +import type { GTFSTranslation } from '../files/translation'; +import type { GTFSFeedInfo } from '../files/feed-info'; +import type { GTFSAttribution } from '../files/attribution'; + +/** + * Class for IO operations on a GTFS feed file + */ +export class GTFSFeedFileIO { + protected fileInfo: GTFSFileInfo; + + /** + * File name including .txt. + */ + public get fileName(): string { + return this.fileInfo.name; + } + + /** + * File columns + */ + public get columns(): string[] { + return Object.keys(this.fileInfo.columns); + } + + /** + * Constructor + * @param file File information + */ + public constructor(file: GTFSFileInfo) { + this.fileInfo = file; + } + + /** + * Read lines into records. + * @param lines Iterable lines + * @returns Iterable records + */ + public *read(lines: IterableIterator): IterableIterator { + return GTFSFileIO.read(this.fileInfo, lines); + } + + /** + * Read line strings array into records array. + * @param lines Lines array + * @returns Records array + */ + public readLines(lines: string[]): RowType[] { + return GTFSFileIO.readLines(this.fileInfo, lines); + } + + /** + * Read file content into records array. + * @param content File content + * @returns Records array + */ + public readContent(content: string): RowType[] { + return GTFSFileIO.readContent(this.fileInfo, content); + } + + /** + * Write records into lines. + * @param records Iterable records + * @returns Iterable lines + */ + public *write(records: IterableIterator): IterableIterator { + return GTFSFileIO.write(this.fileInfo, records); + } + + /** + * Write records array into line strings array. + * @param records Records array + * @returns Lines array + */ + public writeLines(records: RowType[]): string[] { + return GTFSFileIO.writeLines(this.fileInfo, records); + } + + /** + * Write records array into file content. + * @param records Records array + * @returns File content + */ + public writeContent(records: RowType[]): string { + return GTFSFileIO.writeContent(this.fileInfo, records); + } +}; + +/** IO Operations for agency.txt file */ +export class GTFSAgencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.agency); } } +/** IO Operations for stops.txt file */ +export class GTFSStopIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stops); } } +/** IO Operations for routes.txt file */ +export class GTFSRouteIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.routes); } } +/** IO Operations for trips.txt file */ +export class GTFSTripIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.trips); } } +/** IO Operations for stop_times.txt file */ +export class GTFSStopTimeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_times); } } +/** IO Operations for calendar.txt file */ +export class GTFSCalendarIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar); } } +/** IO Operations for calendar_dates.txt file */ +export class GTFSCalendarDateIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar_dates); } } +/** IO Operations for fare_attributes.txt file */ +export class GTFSFareAttributeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_attributes); } } +/** IO Operations for fare_rules.txt file */ +export class GTFSFareRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_rules); } } +/** IO Operations for timeframes.txt file */ +export class GTFSTimeframeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.timeframes); } } +/** IO Operations for fare_media.txt file */ +export class GTFSFareMediaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_media); } } +/** IO Operations for fare_products.txt file */ +export class GTFSFareProductIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_products); } } +/** IO Operations for fare_leg_rules.txt file */ +export class GTFSFareLegRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_leg_rules); } } +/** IO Operations for fare_transfer_rules.txt file */ +export class GTFSFareTransferRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_transfer_rules); } } +/** IO Operations for areas.txt file */ +export class GTFSAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.areas); } } +/** IO Operations for stop_areas.txt file */ +export class GTFSStopAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_areas); } } +/** IO Operations for networks.txt file */ +export class GTFSNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.networks); } } +/** IO Operations for route_networks.txt file */ +export class GTFSRouteNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.route_networks); } } +/** IO Operations for shapes.txt file */ +export class GTFSShapeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.shapes); } } +/** IO Operations for frequencies.txt file */ +export class GTFSFrequencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.frequencies); } } +/** IO Operations for transfers.txt file */ +export class GTFSTransferIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.transfers); } } +/** IO Operations for pathways.txt file */ +export class GTFSPathwayIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.pathways); } } +/** IO Operations for levels.txt file */ +export class GTFSLevelIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.levels); } } +/** IO Operations for translations.txt file */ +export class GTFSTranslationIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.translations); } } +/** IO Operations for feed_info.txt file */ +export class GTFSFeedInfoIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.feed_info); } } +/** IO Operations for attributions.txt file */ +export class GTFSAttributionIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.attributions); } } diff --git a/src/io/file.ts b/src/io/file.ts index 3e81130..ab0fb9a 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,12 +1,11 @@ import { stringify, parse } from 'csv/sync'; import { GTFSFileRecords, GTFSFileRow } from '../types'; import { GTFSFileInfo } from '../file-info'; -import type { Options as CSVStringifyOptions } from 'csv-stringify'; /** * File IO */ -export class GTFSFileIO { +export default class GTFSFileIO { protected constructor() {} /** @@ -62,7 +61,7 @@ export class GTFSFileIO { * @param lines Array of content lines * @returns Array of records */ - public static readLinesArray(file: GTFSFileInfo, lines: string[]): RowType[] { + public static readLines(file: GTFSFileInfo, lines: string[]): RowType[] { return [...GTFSFileIO.read(file, lines.values())]; } @@ -73,8 +72,8 @@ export class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Array of record */ - public static readArray(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { - return GTFSFileIO.readLinesArray(file, content.split(newLine)); + public static readContent(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { + return GTFSFileIO.readLines(file, content.split(newLine)); } /** @@ -84,7 +83,7 @@ export class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Lines array */ - public static writeLinesArray(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { + public static writeLines(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { return [...GTFSFileIO.write(file, records.values(), newLine)]; } @@ -95,7 +94,7 @@ export class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns File content */ - public static writeArray(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { - return GTFSFileIO.writeLinesArray(file, records, newLine).join(''); + public static writeContent(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { + return GTFSFileIO.writeLines(file, records, newLine).join(''); } }; diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts new file mode 100644 index 0000000..f9c3d4b --- /dev/null +++ b/tests/feed-file-io.test.ts @@ -0,0 +1,421 @@ +import { test, expect } from 'vitest'; +import { + GTFS_FILES, + GTFSAgencyIO, + GTFSStopIO, + GTFSRouteIO, + GTFSTripIO, + GTFSStopTimeIO, + GTFSCalendarIO, + GTFSCalendarDateIO, + GTFSFareAttributeIO, + GTFSFareRuleIO, + GTFSTimeframeIO, + GTFSFareMediaIO, + GTFSFareProductIO, + GTFSFareLegRuleIO, + GTFSFareTransferRuleIO, + GTFSAreaIO, + GTFSStopAreaIO, + GTFSNetworkIO, + GTFSRouteNetworkIO, + GTFSShapeIO, + GTFSFrequencyIO, + GTFSTransferIO, + GTFSPathwayIO, + GTFSLevelIO, + GTFSTranslationIO, + GTFSFeedInfoIO, + GTFSAttributionIO +} from '../dist'; + +test('Test agency.txt IO', () => { + const io = new GTFSAgencyIO(); + expect(io.fileName).toEqual('agency.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.agency.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test stops.txt IO', () => { + const io = new GTFSStopIO(); + expect(io.fileName).toEqual('stops.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.stops.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test routes.txt IO', () => { + const io = new GTFSRouteIO(); + expect(io.fileName).toEqual('routes.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.routes.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test trips.txt IO', () => { + const io = new GTFSTripIO(); + expect(io.fileName).toEqual('trips.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.trips.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test stop_times.txt IO', () => { + const io = new GTFSStopTimeIO(); + expect(io.fileName).toEqual('stop_times.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_times.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test calendar.txt IO', () => { + const io = new GTFSCalendarIO(); + expect(io.fileName).toEqual('calendar.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test calendar_dates.txt IO', () => { + const io = new GTFSCalendarDateIO(); + expect(io.fileName).toEqual('calendar_dates.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar_dates.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_attributes.txt IO', () => { + const io = new GTFSFareAttributeIO(); + expect(io.fileName).toEqual('fare_attributes.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_attributes.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_rules.txt IO', () => { + const io = new GTFSFareRuleIO(); + expect(io.fileName).toEqual('fare_rules.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_rules.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test timeframes.txt IO', () => { + const io = new GTFSTimeframeIO(); + expect(io.fileName).toEqual('timeframes.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.timeframes.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_media.txt IO', () => { + const io = new GTFSFareMediaIO(); + expect(io.fileName).toEqual('fare_media.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_media.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_products.txt IO', () => { + const io = new GTFSFareProductIO(); + expect(io.fileName).toEqual('fare_products.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_products.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_leg_rules.txt IO', () => { + const io = new GTFSFareLegRuleIO(); + expect(io.fileName).toEqual('fare_leg_rules.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_leg_rules.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test fare_transfer_rules.txt IO', () => { + const io = new GTFSFareTransferRuleIO(); + expect(io.fileName).toEqual('fare_transfer_rules.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_transfer_rules.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test areas.txt IO', () => { + const io = new GTFSAreaIO(); + expect(io.fileName).toEqual('areas.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.areas.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test stop_areas.txt IO', () => { + const io = new GTFSStopAreaIO(); + expect(io.fileName).toEqual('stop_areas.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_areas.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test networks.txt IO', () => { + const io = new GTFSNetworkIO(); + expect(io.fileName).toEqual('networks.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.networks.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test route_networks.txt IO', () => { + const io = new GTFSRouteNetworkIO(); + expect(io.fileName).toEqual('route_networks.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.route_networks.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test shapes.txt IO', () => { + const io = new GTFSShapeIO(); + expect(io.fileName).toEqual('shapes.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.shapes.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test frequencies.txt IO', () => { + const io = new GTFSFrequencyIO(); + expect(io.fileName).toEqual('frequencies.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.frequencies.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test transfers.txt IO', () => { + const io = new GTFSTransferIO(); + expect(io.fileName).toEqual('transfers.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.transfers.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test pathways.txt IO', () => { + const io = new GTFSPathwayIO(); + expect(io.fileName).toEqual('pathways.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.pathways.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test levels.txt IO', () => { + const io = new GTFSLevelIO(); + expect(io.fileName).toEqual('levels.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.levels.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test translations.txt IO', () => { + const io = new GTFSTranslationIO(); + expect(io.fileName).toEqual('translations.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.translations.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test feed_info.txt IO', () => { + const io = new GTFSFeedInfoIO(); + expect(io.fileName).toEqual('feed_info.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.feed_info.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + +test('Test attributions.txt IO', () => { + const io = new GTFSAttributionIO(); + expect(io.fileName).toEqual('attributions.txt'); + expect(io.columns).toEqual(Object.keys(GTFS_FILES.attributions.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = io.readContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const lines = io.writeLines(records); + expect(lines.length).toEqual(2); + expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +}); + diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index e8261d6..506a394 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -7,7 +7,7 @@ describe('Test GTFSFileIO reading', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n'; - const records = GTFSFileIO.readArray(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); expect(records.length).toEqual(2); expect(records[0].stop_id).toEqual('STOP_01'); expect(records[0].stop_code).toBeTypeOf('string'); @@ -29,10 +29,10 @@ describe('Test GTFSFileIO reading', () => { it('handles empty file content', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; - const records = GTFSFileIO.readArray(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); expect(records.length).toEqual(0); - expect(GTFSFileIO.readArray(GTFS_FILES.agency, '').length).toEqual(0); + expect(GTFSFileIO.readContent(GTFS_FILES.agency, '').length).toEqual(0); }); }); @@ -42,7 +42,7 @@ describe('Test GTFSFileIO writing', () => { { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' } ]; - const content = GTFSFileIO.writeArray(GTFS_FILES.trips, records); + const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + 'R01,S01,T01,,,0,,,,\n' @@ -52,7 +52,7 @@ describe('Test GTFSFileIO writing', () => { it('handles empty input', () => { const records: GTFSFileRecords = []; - const content = GTFSFileIO.writeArray(GTFS_FILES.routes, records); + const content = GTFSFileIO.writeContent(GTFS_FILES.routes, records); expect(content).toEqual( 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 'route_text_color,route_sort_order,continuous_pickup,continuous_drop_off,network_id\n' From 5b0054921d83bf74efaad29799d6f149b3c98640 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 08:51:28 +0700 Subject: [PATCH 03/20] Added feed reader --- package-lock.json | 33 +++++ package.json | 3 + src/file-info.ts | 126 +++++++++--------- src/files/common.ts | 12 ++ src/files/trip.ts | 18 +-- src/index.ts | 39 +----- src/io/feed-file.ts | 68 +++++++++- src/io/feed-reader.ts | 199 +++++++++++++++++++++++++++++ src/io/file.ts | 16 ++- src/types.ts | 68 +++++++++- tests/data/INFO.md | 1 + tests/data/gtfs.zip | Bin 0 -> 1809 bytes tests/data/gtfs/agency.txt | 2 + tests/data/gtfs/calendar.txt | 3 + tests/data/gtfs/calendar_dates.txt | 1 + tests/data/gtfs/routes.txt | 3 + tests/data/gtfs/shapes.txt | 5 + tests/data/gtfs/stop_times.txt | 3 + tests/data/gtfs/stops.txt | 3 + tests/data/gtfs/trips.txt | 2 + tests/feed-reader.test.ts | 178 ++++++++++++++++++++++++++ tests/file-io.test.ts | 15 ++- 22 files changed, 687 insertions(+), 111 deletions(-) create mode 100644 src/io/feed-reader.ts create mode 100644 tests/data/INFO.md create mode 100644 tests/data/gtfs.zip create mode 100644 tests/data/gtfs/agency.txt create mode 100644 tests/data/gtfs/calendar.txt create mode 100644 tests/data/gtfs/calendar_dates.txt create mode 100644 tests/data/gtfs/routes.txt create mode 100644 tests/data/gtfs/shapes.txt create mode 100644 tests/data/gtfs/stop_times.txt create mode 100644 tests/data/gtfs/stops.txt create mode 100644 tests/data/gtfs/trips.txt create mode 100644 tests/feed-reader.test.ts diff --git a/package-lock.json b/package-lock.json index a1959ad..5739803 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "0.0.0", "license": "ISC", "dependencies": { + "adm-zip": "^0.5.10", "csv": "^6.3.5" }, "devDependencies": { + "@types/adm-zip": "^0.5.5", "@types/node": "^20.10.1", "typescript": "^5.3.2", "vitest": "^0.34.6" @@ -549,6 +551,15 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@types/adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "4.3.11", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", @@ -662,6 +673,14 @@ "node": ">=0.4.0" } }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "engines": { + "node": ">=6.0" + } + }, "node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -1593,6 +1612,15 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "@types/adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/chai": { "version": "4.3.11", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", @@ -1682,6 +1710,11 @@ "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", "dev": true }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" + }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", diff --git a/package.json b/package.json index 1edb415..c2f530a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "main": "dist/index.js", "scripts": { "build": "npx tsc", + "prepare-test": "curl -L -o ./temp/gtfs.zip https://storage.googleapis.com/storage/v1/b/mdb-latest/o/es-valenciana-generalitat-valenciana-gtfs-1870.zip?generation=1701303560208582&alt=media", "test": "npm run build && npx vitest run --config ./vitest.config.ts" }, "repository": { @@ -22,11 +23,13 @@ }, "homepage": "https://github.com/ponlawat-w/gtfs-io#readme", "devDependencies": { + "@types/adm-zip": "^0.5.5", "@types/node": "^20.10.1", "typescript": "^5.3.2", "vitest": "^0.34.6" }, "dependencies": { + "adm-zip": "^0.5.10", "csv": "^6.3.5" } } diff --git a/src/file-info.ts b/src/file-info.ts index aa88e86..c210d49 100644 --- a/src/file-info.ts +++ b/src/file-info.ts @@ -2,8 +2,14 @@ export type GTFSFileInfo = { /** File name including .txt */ name: string, - /** Columns in file name, key being column name and value being type */ - columns: Record + /** + * Columns in file name, key being column name and value being type: + * string: interpret value as string (using .toString()), + * int: interpret value as integer (using parseInt()), + * float: interpret value as float (using parseFloat()), + * ioe: interpret value as integer or empty string (for enumeration) + */ + columns: Record }; /** @@ -61,14 +67,14 @@ export const GTFS_FILES: Record = { stop_name: 'string', tts_stop_name: 'string', stop_desc: 'string', - stop_lat: 'number', - stop_lon: 'number', + stop_lat: 'float', + stop_lon: 'float', zone_id: 'string', stop_url: 'string', - location_type: 'number', + location_type: 'int', parent_station: 'string', stop_timezone: 'string', - wheelchair_boarding: 'string', + wheelchair_boarding: 'ioe', level_id: 'string', platform_code: 'string' } @@ -81,13 +87,13 @@ export const GTFS_FILES: Record = { route_short_name: 'string', route_long_name: 'string', route_desc: 'string', - route_type: 'number', + route_type: 'int', route_url: 'string', route_color: 'string', route_text_color: 'string', - route_sort_order: 'number', - continuous_pickup: 'number', - continuous_drop_off: 'number', + route_sort_order: 'int', + continuous_pickup: 'ioe', + continuous_drop_off: 'ioe', network_id: 'string' } }, @@ -99,11 +105,11 @@ export const GTFS_FILES: Record = { trip_id: 'string', trip_headsign: 'string', trip_short_name: 'string', - direction_id: 'number', + direction_id: 'int', block_id: 'string', shape_id: 'string', - wheelchair_accessible: 'number', - bikes_allowed: 'number' + wheelchair_accessible: 'ioe', + bikes_allowed: 'ioe' } }, stop_times: { @@ -113,27 +119,27 @@ export const GTFS_FILES: Record = { arrival_time: 'string', departure_time: 'string', stop_id: 'string', - stop_sequence: 'number', + stop_sequence: 'int', stop_headsign: 'string', - pickup_type: 'number', - drop_off_type: 'number', - continuous_pickup: 'number', - continuous_drop_off: 'number', - shape_dist_traveled: 'number', - timepoint: 'number' + pickup_type: 'ioe', + drop_off_type: 'ioe', + continuous_pickup: 'ioe', + continuous_drop_off: 'ioe', + shape_dist_traveled: 'float', + timepoint: 'ioe' } }, calendar: { name: 'calendar.txt', columns: { service_id: 'string', - monday: 'number', - tuesday: 'number', - wednesday: 'number', - thursday: 'number', - friday: 'number', - saturday: 'number', - sunday: 'number', + monday: 'int', + tuesday: 'int', + wednesday: 'int', + thursday: 'int', + friday: 'int', + saturday: 'int', + sunday: 'int', start_date: 'string', end_date: 'string' } @@ -143,19 +149,19 @@ export const GTFS_FILES: Record = { columns: { service_id: 'string', date: 'string', - exception_type: 'number' + exception_type: 'int' } }, fare_attributes: { name: 'fare_attributes.txt', columns: { fare_id: 'string', - price: 'number', + price: 'float', currency_type: 'string', - payment_method: 'number', - transfers: 'number', + payment_method: 'int', + transfers: 'ioe', agency_id: 'string', - transfer_duration: 'number' + transfer_duration: 'float' } }, fare_rules: { @@ -182,7 +188,7 @@ export const GTFS_FILES: Record = { columns: { fare_media_id: 'string', fare_media_name: 'string', - fare_media_type: 'number' + fare_media_type: 'int' } }, fare_products: { @@ -191,7 +197,7 @@ export const GTFS_FILES: Record = { fare_product_id: 'string', fare_product_name: 'string', fare_media_id: 'string', - amount: 'number', + amount: 'float', currency: 'string' } }, @@ -212,10 +218,10 @@ export const GTFS_FILES: Record = { columns: { from_leg_group_id: 'string', to_leg_group_id: 'string', - transfer_count: 'number', - duration_limit: 'number', - duration_limit_type: 'number', - fare_transfer_type: 'number', + transfer_count: 'int', + duration_limit: 'float', + duration_limit_type: 'int', + fare_transfer_type: 'int', fare_product_id: 'string' } }, @@ -251,10 +257,10 @@ export const GTFS_FILES: Record = { name: 'shapes.txt', columns: { shape_id: 'string', - shape_pt_lat: 'number', - shape_pt_lon: 'number', - shape_pt_sequence: 'number', - shape_dist_traveled: 'number' + shape_pt_lat: 'float', + shape_pt_lon: 'float', + shape_pt_sequence: 'int', + shape_dist_traveled: 'float' } }, frequencies: { @@ -263,8 +269,8 @@ export const GTFS_FILES: Record = { trip_id: 'string', start_time: 'string', end_time: 'string', - headway_secs: 'number', - exact_times: 'number' + headway_secs: 'float', + exact_times: 'ioe' } }, transfers: { @@ -276,8 +282,8 @@ export const GTFS_FILES: Record = { to_route_id: 'string', from_trip_id: 'string', to_trip_id: 'string', - transfer_type: 'number', - min_transfer_time: 'number' + transfer_type: 'ioe', + min_transfer_time: 'float' } }, pathways: { @@ -286,13 +292,13 @@ export const GTFS_FILES: Record = { pathway_id: 'string', from_stop_id: 'string', to_stop_id: 'string', - pathway_mode: 'number', - is_bidirectional: 'number', - length: 'number', - traversal_time: 'number', - stair_count: 'number', - max_slope: 'number', - min_width: 'number', + pathway_mode: 'int', + is_bidirectional: 'int', + length: 'float', + traversal_time: 'float', + stair_count: 'float', + max_slope: 'float', + min_width: 'float', signposted_as: 'string', reversed_signposted_as: 'string' } @@ -301,7 +307,7 @@ export const GTFS_FILES: Record = { name: 'levels.txt', columns: { level_id: 'string', - level_index: 'number', + level_index: 'float', level_name: 'string' } }, @@ -339,12 +345,18 @@ export const GTFS_FILES: Record = { route_id: 'string', trip_id: 'string', organization_name: 'string', - is_producer: 'number', - is_operator: 'number', - is_authority: 'number', + is_producer: 'int', + is_operator: 'int', + is_authority: 'int', attribution_url: 'string', attribution_email: 'string', attribution_phone: 'string', } } }; + +/** + * Get array of GTFS file infos + * @returns Array of GTFS file infos + */ +export const getGTFSFileInfos = () => Object.keys(GTFS_FILES).map(name => (GTFS_FILES as any)[name] as GTFSFileInfo); diff --git a/src/files/common.ts b/src/files/common.ts index 61a7fe8..6cc0ef1 100644 --- a/src/files/common.ts +++ b/src/files/common.ts @@ -12,3 +12,15 @@ export enum GTFSContinuousPickupDropOff { /** Must coordinate with driver to arrange continuous stopping pickup / drop off. */ Driver = 3 }; + +/** Indicates wheelchair accessibility. */ +export enum GTFSWheelchairAccessbility { + /** No accessibility information. */ + Default = '', + /** No accessibility information. */ + NoInformation = 0, + /** Accessible. */ + Accessible = 1, + /** No accessible. */ + Inaccessible = 2 +}; diff --git a/src/files/trip.ts b/src/files/trip.ts index 591afc2..479b1eb 100644 --- a/src/files/trip.ts +++ b/src/files/trip.ts @@ -1,3 +1,5 @@ +import { GTFSWheelchairAccessbility } from './common'; + /** Indicates the direction of travel for a trip. */ export enum GTFSTripDirection { /** Travel in one direction (e.g. outbound travel). */ @@ -6,18 +8,10 @@ export enum GTFSTripDirection { OppositeDirection = 1 }; -/** Indicates wheelchair accessibility. */ -export enum GTFSTripWheelchairAccessbility { - /** No accessibility information for the trip. */ - NoInformation = 0, - /** Vehicle being used on this particular trip can accommodate at least one rider in a wheelchair. */ - Accessible = 1, - /** No riders in wheelchairs can be accommodated on this trip. */ - Inaccessible = 2 -}; - /** Indicates whether bikes are allowed. */ export enum GTFSTripBikesAllowed { + /** No bike information for the trip. */ + Empty = '', /** No bike information for the trip. */ NoInformation = 0, /** Vehicle being used on this particular trip can accommodate at least one bicycle. */ @@ -45,7 +39,7 @@ export type GTFSTrip = { /** Identifies a geospatial shape describing the vehicle travel path for a trip. */ shape_id?: string, /** Indicates wheelchair accessibility. */ - wheelchair_accessible?: GTFSTripWheelchairAccessbility|'', + wheelchair_accessible?: GTFSWheelchairAccessbility, /** Indicates whether bikes are allowed. */ - bikes_allowed?: GTFSTripBikesAllowed|'', + bikes_allowed?: GTFSTripBikesAllowed, }; diff --git a/src/index.ts b/src/index.ts index df81f99..04d1229 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,39 +1,11 @@ export { default as GTFSFileIO } from './io/file'; -export { - GTFSFeedFileIO, - GTFSAgencyIO, - GTFSStopIO, - GTFSRouteIO, - GTFSTripIO, - GTFSStopTimeIO, - GTFSCalendarIO, - GTFSCalendarDateIO, - GTFSFareAttributeIO, - GTFSFareRuleIO, - GTFSTimeframeIO, - GTFSFareMediaIO, - GTFSFareProductIO, - GTFSFareLegRuleIO, - GTFSFareTransferRuleIO, - GTFSAreaIO, - GTFSStopAreaIO, - GTFSNetworkIO, - GTFSRouteNetworkIO, - GTFSShapeIO, - GTFSFrequencyIO, - GTFSTransferIO, - GTFSPathwayIO, - GTFSLevelIO, - GTFSTranslationIO, - GTFSFeedInfoIO, - GTFSAttributionIO -} from './io/feed-file'; - -export { GTFS_FILES } from './file-info'; +export { default as GTFSFeedReader } from './io/feed-reader'; +export * from './io/feed-file'; +export * from './file-info'; export { GTFSCalendarDateException } from './files/calendar-date'; export { GTFSCalendarAvailability } from './files/calendar'; -export { GTFSContinuousPickupDropOff } from './files/common'; +export { GTFSContinuousPickupDropOff, GTFSWheelchairAccessbility } from './files/common'; export { GTFSFareAttributePaymentMethod, GTFSFareAttributeTransfer } from './files/fare-attribute'; export { GTFSFareMediaType } from './files/fare-media'; export { GTFSFareTransferRuleDurationLimit, GTFSFareTransferRuleType } from './files/fare-transfer-rule'; @@ -43,10 +15,11 @@ export { GTFSRouteType } from './files/route'; export { GTFSStopTimePickupDropOff, GTFSStopTimeTimepoint } from './files/stop-time'; export { GTFSStopLocationType } from './files/stop'; export { GTFSTranferType } from './files/transfer'; -export { GTFSTripDirection, GTFSTripWheelchairAccessbility, GTFSTripBikesAllowed } from './files/trip'; +export { GTFSTripDirection, GTFSTripBikesAllowed } from './files/trip'; export type * from './file-info'; export type * from './types'; +export type * from './io/feed-reader'; export type * from './files/agency'; export type * from './files/area'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 3efc499..9be41e8 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -63,7 +63,8 @@ export class GTFSFeedFileIO { * @returns Iterable records */ public *read(lines: IterableIterator): IterableIterator { - return GTFSFileIO.read(this.fileInfo, lines); + yield *GTFSFileIO.read(this.fileInfo, lines); + return; } /** @@ -90,7 +91,8 @@ export class GTFSFeedFileIO { * @returns Iterable lines */ public *write(records: IterableIterator): IterableIterator { - return GTFSFileIO.write(this.fileInfo, records); + yield *GTFSFileIO.write(this.fileInfo, records); + return; } /** @@ -114,53 +116,115 @@ export class GTFSFeedFileIO { /** IO Operations for agency.txt file */ export class GTFSAgencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.agency); } } + /** IO Operations for stops.txt file */ export class GTFSStopIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stops); } } + /** IO Operations for routes.txt file */ export class GTFSRouteIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.routes); } } + /** IO Operations for trips.txt file */ export class GTFSTripIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.trips); } } + /** IO Operations for stop_times.txt file */ export class GTFSStopTimeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_times); } } + /** IO Operations for calendar.txt file */ export class GTFSCalendarIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar); } } + /** IO Operations for calendar_dates.txt file */ export class GTFSCalendarDateIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar_dates); } } + /** IO Operations for fare_attributes.txt file */ export class GTFSFareAttributeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_attributes); } } + /** IO Operations for fare_rules.txt file */ export class GTFSFareRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_rules); } } + /** IO Operations for timeframes.txt file */ export class GTFSTimeframeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.timeframes); } } + /** IO Operations for fare_media.txt file */ export class GTFSFareMediaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_media); } } + /** IO Operations for fare_products.txt file */ export class GTFSFareProductIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_products); } } + /** IO Operations for fare_leg_rules.txt file */ export class GTFSFareLegRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_leg_rules); } } + /** IO Operations for fare_transfer_rules.txt file */ export class GTFSFareTransferRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_transfer_rules); } } + /** IO Operations for areas.txt file */ export class GTFSAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.areas); } } + /** IO Operations for stop_areas.txt file */ export class GTFSStopAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_areas); } } + /** IO Operations for networks.txt file */ export class GTFSNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.networks); } } + /** IO Operations for route_networks.txt file */ export class GTFSRouteNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.route_networks); } } + /** IO Operations for shapes.txt file */ export class GTFSShapeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.shapes); } } + /** IO Operations for frequencies.txt file */ export class GTFSFrequencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.frequencies); } } + /** IO Operations for transfers.txt file */ export class GTFSTransferIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.transfers); } } + /** IO Operations for pathways.txt file */ export class GTFSPathwayIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.pathways); } } + /** IO Operations for levels.txt file */ export class GTFSLevelIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.levels); } } + /** IO Operations for translations.txt file */ export class GTFSTranslationIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.translations); } } + /** IO Operations for feed_info.txt file */ export class GTFSFeedInfoIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.feed_info); } } + /** IO Operations for attributions.txt file */ export class GTFSAttributionIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.attributions); } } + +/** + * Get feed file IO instance from file name. + * @param fileName File name with .txt + * @returns GTFSFeedFileIO + */ +export const getIOFromFileName = (fileName: string): GTFSFeedFileIO => { + switch(fileName) { + case 'agency.txt': return new GTFSAgencyIO(); + case 'stops.txt': return new GTFSStopIO(); + case 'routes.txt': return new GTFSRouteIO(); + case 'trips.txt': return new GTFSTripIO(); + case 'stop_times.txt': return new GTFSStopTimeIO(); + case 'calendar.txt': return new GTFSCalendarIO(); + case 'calendar_dates.txt': return new GTFSCalendarDateIO(); + case 'fare_attributes.txt': return new GTFSFareAttributeIO(); + case 'fare_rules.txt': return new GTFSFareRuleIO(); + case 'timeframes.txt': return new GTFSTimeframeIO(); + case 'fare_media.txt': return new GTFSFareMediaIO(); + case 'fare_products.txt': return new GTFSFareProductIO(); + case 'fare_leg_rules.txt': return new GTFSFareLegRuleIO(); + case 'fare_transfer_rules.txt': return new GTFSFareTransferRuleIO(); + case 'areas.txt': return new GTFSAreaIO(); + case 'stop_areas.txt': return new GTFSStopAreaIO(); + case 'networks.txt': return new GTFSNetworkIO(); + case 'route_networks.txt': return new GTFSRouteNetworkIO(); + case 'shapes.txt': return new GTFSShapeIO(); + case 'frequencies.txt': return new GTFSFrequencyIO(); + case 'transfers.txt': return new GTFSTransferIO(); + case 'pathways.txt': return new GTFSPathwayIO(); + case 'levels.txt': return new GTFSLevelIO(); + case 'translations.txt': return new GTFSTranslationIO(); + case 'feed_info.txt': return new GTFSFeedInfoIO(); + case 'attributions.txt': return new GTFSAttributionIO(); + } + throw new Error(`Unknown file name ${fileName}`); +} diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts new file mode 100644 index 0000000..2f03bfb --- /dev/null +++ b/src/io/feed-reader.ts @@ -0,0 +1,199 @@ +import AdmZip from 'adm-zip'; +import { + existsSync as exists, + openSync as openFile, + readSync as readFile +} from 'fs'; +import { join as joinPath } from 'path'; +import { getIOFromFileName } from './feed-file'; +import { getGTFSFileInfos } from '../file-info'; +import type { GTFSFileInfo, GTFSFileName } from '../file-info'; +import type { GTFSFeed, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles, GTFSLoadedFeed } from '../types'; + +/** + * GTFS file object to read + */ +type GTFSFile = { + /** File information */ + info: GTFSFileInfo, + /** File path */ + path?: string, + /** File content buffer */ + buffer?: Buffer +}; + +/** GTFS feed file content */ +export type GTFSFileContent = { + /** File name with .txt */ + name: string, + /** File content */ + content: string|Buffer +}; + +/** + * Generator of iterable lines from a file path. + * @param filePath File path + * @returns Iterable file lines + */ +function *readLine(filePath: string): IterableIterator { + const file = openFile(filePath, 'r'); + const bufferSize = 1024; + const buffer = Buffer.alloc(bufferSize); + let leftOver = ''; + + let readPos; + while ((readPos = readFile(file, buffer, 0, bufferSize, null)) !== 0) { + const lines = (leftOver + buffer.toString('utf8', 0, readPos)).split(/\r?\n/g); + for (const line of lines.splice(0, lines.length - 1)) { + yield line; + } + leftOver = lines[0]; + } + if (leftOver) yield leftOver; + return; +} + +function *readZip(zipEntry: AdmZip.IZipEntry): IterableIterator { + const lines = zipEntry.getData().toString().split(/\r?\n/g); + yield *lines; + return; +} + +/** + * GTFS feed writer. + * Do not use constructor, instead, use the following static methods to initiate an instance: + * GTFSFeedWriter.fromZip, + * GTFSFeedWriter.fromDir, + * GTFSFeedWriter.fromFiles + */ +export default class GTFSFeedWriter { + /** Zip object */ + private zip?: AdmZip = undefined; + + /** File objects */ + private files?: GTFSFile[] = undefined; + + /** + * Constructor, the object of this class is to be created by static methods. + * The constructor is limited for internal calling. + * @param zip Zip file path or buffer + * @param directoryPath Directory path + * @param fileContents Array of file content objects + */ + private constructor( + zip?: string|Buffer, + directoryPath?: string, + fileContents?: GTFSFileContent[] + ) { + if (zip) { + this.zip = new AdmZip(zip); + return; + } + this.files = []; + if (directoryPath) { + for (const info of getGTFSFileInfos()) { + const path = joinPath(directoryPath, info.name); + if (!exists(path)) continue; + this.files.push({ info, path }); + } + return; + } + if (fileContents) { + for (const info of getGTFSFileInfos()) { + const file = fileContents.filter(f => f.name === info.name); + if (!file.length) continue; + this.files.push({ info, buffer: Buffer.from(file[0].content) }) + } + return; + } + } + + /** + * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). + * @returns Iterable feed files + */ + public *getIterableFiles(): GTFSIterableFeedFiles { + for (const info of getGTFSFileInfos()) { + if (this.zip) { + const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); + if (!entry.length) continue; + const fileIO = getIOFromFileName(info.name); + yield { info, records: fileIO.read(readZip(entry[0])) }; + } else if (this.files) { + const file = this.files.filter(f => f.info.name === info.name); + if (!file.length) continue; + const fileIO = getIOFromFileName(info.name); + if (file[0].path) { + yield { info, records: fileIO.read(readLine(file[0].path)) }; + } else if (file[0].buffer) { + const lines = file[0].buffer.toString().replace(/\r\n/g, '\n').split('\n').values(); + yield { info, records: fileIO.read(lines) }; + } + } + } + return; + } + + /** + * Get feed object with row being file name without .txt and value being iterable records. + * @returns Feed object with row being file name without .txt and value being iterable records. + */ + public getFeed(): GTFSFeed { + const results: Partial> = { + agency: [], + stops: [], + routes: [], + trips: [], + stop_times: [] + }; + + for (const file of this.getIterableFiles()) { + const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; + results[key] = file.records; + } + + return results as GTFSFeed; + } + + /** + * Get feed object with row being file name without .txt and value being array of records. + * @returns Feed object with row being file name without .txt and value being array of records. + */ + public loadFeed(): GTFSLoadedFeed { + const feed = this.getFeed(); + const results: Partial> = {}; + + for (const key of Object.keys(feed) as GTFSFileName[]) { + results[key] = [...feed[key]!]; + } + + return results as GTFSLoadedFeed; + } + + /** + * Create an instance of GTFSFeedWriter from zip file. + * @param zip Zip file path or content buffer + * @returns GTFSFeedWriter instance + */ + public static fromZip(zip: string|Buffer): GTFSFeedWriter { + return new GTFSFeedWriter(zip); + } + + /** + * Create an instance of GTFSFeedWriter from directory path. + * @param dirPath Path to GTFS feed directory + * @returns GTFSFeedWriter instance + */ + public static fromDir(dirPath: string): GTFSFeedWriter { + return new GTFSFeedWriter(undefined, dirPath); + } + + /** + * Create an instance of GTFSFeedWriter from in-memory file contents. + * @param files Feed files object + * @returns GTFSFeedWriter instance + */ + public static fromFiles(files: GTFSFileContent[]): GTFSFeedWriter { + return new GTFSFeedWriter(undefined, undefined, files); + } +}; diff --git a/src/io/file.ts b/src/io/file.ts index ab0fb9a..b967d93 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,6 +1,6 @@ import { stringify, parse } from 'csv/sync'; -import { GTFSFileRecords, GTFSFileRow } from '../types'; -import { GTFSFileInfo } from '../file-info'; +import type { GTFSFileInfo } from '../file-info'; +import type { GTFSFileRecords, GTFSFileRow } from '../types'; /** * File IO @@ -28,8 +28,16 @@ export default class GTFSFileIO { let record: Record = {}; for (let i = 0; i < row.length; i++) { - const column = file.columns[columns[i]]; - record[columns[i]] = column === 'string' ? row[i].toString() : parseInt(row[i]); + const columnType = file.columns[columns[i]]; + if (columnType === 'int') { + record[columns[i]] = parseInt(row[i]); + } else if (columnType === 'float') { + record[columns[i]] = parseFloat(row[i]); + } else if (columnType === 'ioe') { + record[columns[i]] = row[i].trim() ? parseInt(row[i]) : ''; + } else { + record[columns[i]] = row[i].toString(); + } } yield record as RowType; } diff --git a/src/types.ts b/src/types.ts index dc727d9..f62447d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ import type { GTFSTimeframe } from './files/timeframe'; import type { GTFSTransfer } from './files/transfer'; import type { GTFSTranslation } from './files/translation'; import type { GTFSTrip } from './files/trip'; +import type { GTFSFileInfo } from './file-info'; /** A row record in a GTFS file */ export type GTFSFileRow = GTFSAgency @@ -56,6 +57,15 @@ export type GTFSFileRow = GTFSAgency /** GTFS file records */ export type GTFSFileRecords = IterableIterator|RowType[]; +/** GTFS Iterable for an individual file */ +export type GTFSIterableFeedFile = { + info: GTFSFileInfo, + records: IterableIterator +}; + +/** GTFS Iterable feed files */ +export type GTFSIterableFeedFiles = IterableIterator; + /** GTFS Dataset feed */ export type GTFSFeed = { /** Transit agencies with service represented in this dataset. */ @@ -107,7 +117,63 @@ export type GTFSFeed = { /** Translations of customer-facing dataset values. */ translations?: GTFSFileRecords, /** Dataset metadata, including publisher, version, and expiration information. */ - feed_infos?: GTFSFileRecords, + feed_info?: GTFSFileRecords, /** Dataset attributions. */ attributions?: GTFSFileRecords }; + +/** GTFS Dataset feed loaded to memory */ +export type GTFSLoadedFeed = { + /** Transit agencies with service represented in this dataset. */ + agency: GTFSAgency[], + /** Stops where vehicles pick up or drop off riders. */ + stops: GTFSStop[], + /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ + routes: GTFSRoute[], + /** Trips for each route. */ + trips: GTFSTrip[], + /** Times that a vehicle arrives at and departs from stops for each trip. */ + stop_times: GTFSStopTime[], + /** Service dates specified using a weekly schedule with start and end dates. */ + calendar?: GTFSCalendar[], + /** Exceptions for the services defined in the `calendar.txt`. */ + calendar_dates?: GTFSCalendarDate[], + /** Fare information for a transit agency's routes. */ + fare_attributes?: GTFSFareAttribute[], + /** Rules to apply fares for itineraries. */ + fare_rules?: GTFSFareRule[], + /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ + timeframes?: GTFSTimeframe[], + /** To describe the fare media that can be employed to use fare products. */ + fare_media?: GTFSFareMedia[], + /** To describe the different types of tickets or fares that can be purchased by riders. */ + fare_products?: GTFSFareProduct[], + /** Fare rules for individual legs of travel. */ + fare_leg_rules?: GTFSFareLegRule[], + /** Fare rules for transfers between legs of travel. */ + fare_transfer_rules?: GTFSFareTransferRule[], + /** Area grouping of locations. */ + areas?: GTFSArea[], + /** Rules to assign stops to areas. */ + stop_areas?: GTFSStopArea[], + /** Network grouping of routes. */ + networks?: GTFSNetwork[], + /** Rules to assign routes to networks. */ + route_networks?: GTFSRouteNetwork[], + /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ + shapes?: GTFSShape[], + /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ + frequencies?: GTFSFrequency[], + /** Rules for making connections at transfer points between routes. */ + transfers?: GTFSTransfer[], + /** Pathways linking together locations within stations. */ + pathways?: GTFSPathway[], + /** Levels within stations. */ + levels?: GTFSLevel[], + /** Translations of customer-facing dataset values. */ + translations?: GTFSTranslation[], + /** Dataset metadata, including publisher, version, and expiration information. */ + feed_info?: GTFSFeedInfo[], + /** Dataset attributions. */ + attributions?: GTFSAttribution[] +}; diff --git a/tests/data/INFO.md b/tests/data/INFO.md new file mode 100644 index 0000000..d526368 --- /dev/null +++ b/tests/data/INFO.md @@ -0,0 +1 @@ +Test dataset from https://github.com/MobilityData/mobility-database-catalogs/blob/main/catalogs/sources/gtfs/schedule/es-barcelona-tram-gtfs-1003.json. diff --git a/tests/data/gtfs.zip b/tests/data/gtfs.zip new file mode 100644 index 0000000000000000000000000000000000000000..b80051b61deea95b8f5fc871f5d821243f9c2a45 GIT binary patch literal 1809 zcmWIWW@Zs#U|`^2h;(iae}9?Zt`5k%0mNKDoS2@Pmt3h=Qc>dVtMI zq3~7cqL8o^zXDY@vGh1ONQyqoG!)x6Lo9vH_H}2O_q?5S`Lc(2{StAQQy3U39Gb&h z`nc{X0G$BBLO=|2N_WtqvT@tG+)ATgcPisaOSlFa#(kR9-Ha zc^nMtF3sUne15ji2AU(y2#J8A{8Fenp?!gT%?dni_ou(%lUu-l%5MYrn}gf}>_;W1 zPGTy&S9ieRVw;+M{4dkgF2<)z5+yFKvQ^hRQQy(L<#3m%PRjAm@r%Pv+wRJmdM{TO~ERM2(h=BpwnPn_dNc%cR81>gSjTophs90p=g8Y<36 zEPw<>=;=VNLk0pY7yditZ)!`~eAm?c!+qwK1Tmc{QNCaPTnjV!clJ#3DN|jk<2MIr4qC}nV?`1@|FWId*{PCH~ar1!g`T@jzAorEz7sQuj z=0e=)d-fpTAq5_`2fI7-FG%3oT#Xvxh!>a)Y_+ zL(k;kI>8Drji{DuX4x@kiqfmZx&G?Qa@Ia`Z#eRIv&!=MA@jZG9!ZSYZO8UBAn9bD z>;IE(iv2TARGxbkQn0MkX3JB}q6>fD<*nJs@T|menb@5y2C;R*`%Wm&sCp&xMorqr z>_!T^tj>~u3H*0=Zq!Hk% z;@&fdk!_onN}HS4V+~W+0;zY0j(S{Xa-NfZ`u=8HgP+$jB$mZbJQitrQ`vz3t-ZI` z;{|D{s`^bbHYXpRe=4RE5j2T+=QMe_gQpw%7Kg7o8=F%#$6!Z!M_B2lmTx{r8IRW` z6uMYT{<3)Hw9tR`+VJ+|eXSyX@5N^aGBw;tO^todWLBv*YnfKEZht=aga)Rd8@DT3 z{xS7xtY*0qsw%c%Mgvk3vT|(>f0$}@x)JE1wLlE=SV>W40oY@%up^SvWza=Wy7=zx$aj~PVi(jqJKC`2D)`M?>MQn>Y4sMKS_gMNtiZkrqiU{#!yOZZH$7UMj%6XZ` z)@fYUoF2VggJJWQ*=?_FYJmk2Ba;XN?gA6&SO!LLX#ip&ice&H*orWOzGXlrtP}y2 z9|)b;OB{qwLtrw1=|q%22>sYg7=->tpngPogRBo*wnpf4VM5O2$ojBlH-x??AblvA t4_Pm^1dhbi7CLFl?^1t0fc9Oe%EIO@c;=LeVPCO literal 0 HcmV?d00001 diff --git a/tests/data/gtfs/agency.txt b/tests/data/gtfs/agency.txt new file mode 100644 index 0000000..ce32ca8 --- /dev/null +++ b/tests/data/gtfs/agency.txt @@ -0,0 +1,2 @@ +agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url,agency_email +A01,This Agency,https://test.agency,Europe/Madrid,en,+34000000000,https://test.agency/fare,contact@test.agency diff --git a/tests/data/gtfs/calendar.txt b/tests/data/gtfs/calendar.txt new file mode 100644 index 0000000..790a9c1 --- /dev/null +++ b/tests/data/gtfs/calendar.txt @@ -0,0 +1,3 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +S01,1,1,1,1,1,0,0,20231201,20231231 +S02,0,0,0,0,0,1,1,20231201,20231231 diff --git a/tests/data/gtfs/calendar_dates.txt b/tests/data/gtfs/calendar_dates.txt new file mode 100644 index 0000000..1731afc --- /dev/null +++ b/tests/data/gtfs/calendar_dates.txt @@ -0,0 +1 @@ +service_id,date,exception_type diff --git a/tests/data/gtfs/routes.txt b/tests/data/gtfs/routes.txt new file mode 100644 index 0000000..5d21e7e --- /dev/null +++ b/tests/data/gtfs/routes.txt @@ -0,0 +1,3 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color,route_sort_order,continuous_pickup,continuous_drop_off,network_id +R01,A01,01,Route Number 1,,3,,ffffff,000000,1,,1, +R02,A01,02,Route Number 2,,3,,000000,ffffff,2,1,1, diff --git a/tests/data/gtfs/shapes.txt b/tests/data/gtfs/shapes.txt new file mode 100644 index 0000000..50b5323 --- /dev/null +++ b/tests/data/gtfs/shapes.txt @@ -0,0 +1,5 @@ +shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled +SH01,39.46717,-0.37727,1,0.0 +SH01,39.46694,-0.37485,2,0.2 +SH01,39.47166,-0.36901,3,1.0 +SH01,39.47324,-0.36546,4,1.29 diff --git a/tests/data/gtfs/stop_times.txt b/tests/data/gtfs/stop_times.txt new file mode 100644 index 0000000..39a54aa --- /dev/null +++ b/tests/data/gtfs/stop_times.txt @@ -0,0 +1,3 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,continuous_pickup,continuous_drop_off,shape_dist_traveled,timepoint +T0101,,08:00:00,ST01,1,Alameda,0,0,,,0,1 +T0101,08:03:00,,ST02,2,,0,0,1,,1.29,1 diff --git a/tests/data/gtfs/stops.txt b/tests/data/gtfs/stops.txt new file mode 100644 index 0000000..b9a2386 --- /dev/null +++ b/tests/data/gtfs/stops.txt @@ -0,0 +1,3 @@ +stop_id,stop_code,stop_name,tts_stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,stop_timezone,wheelchair_boarding,level_id,platform_code +ST01,ST01,Xàtiva,,,39.46603,-0.37754,,,0,,,1,, +ST02,ST02,Alameda,,,39.47345,-0.36553,,,0,,,2,, diff --git a/tests/data/gtfs/trips.txt b/tests/data/gtfs/trips.txt new file mode 100644 index 0000000..68e55a4 --- /dev/null +++ b/tests/data/gtfs/trips.txt @@ -0,0 +1,2 @@ +route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed +R01,S01,T0101,Headsign,Short Name,0,,SH01,1,2 diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts new file mode 100644 index 0000000..7b95314 --- /dev/null +++ b/tests/feed-reader.test.ts @@ -0,0 +1,178 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { test, expect } from 'vitest'; +import { + GTFSContinuousPickupDropOff, + GTFSFeedReader, + GTFSRouteType, + GTFSStopLocationType, + GTFSStopTimePickupDropOff, + GTFSStopTimeTimepoint, + GTFSTripBikesAllowed, + GTFSTripDirection, + GTFSWheelchairAccessbility +} from '../dist'; +import type { GTFSFileContent, GTFSLoadedFeed } from '../dist'; + +const ZIP_PATH = './tests/data/gtfs.zip'; +const DIR_PATH = './tests/data/gtfs'; + +const assert = (feed: GTFSLoadedFeed) => { + expect(feed.agency).toBeDefined(); + expect(feed.calendar_dates).toBeDefined(); + expect(feed.calendar).toBeDefined(); + expect(feed.routes).toBeDefined(); + expect(feed.shapes).toBeDefined(); + expect(feed.stop_times).toBeDefined(); + expect(feed.stops).toBeDefined(); + expect(feed.trips).toBeDefined(); + + expect(feed.fare_attributes).toBeUndefined(); + expect(feed.fare_rules).toBeUndefined(); + expect(feed.timeframes).toBeUndefined(); + expect(feed.fare_media).toBeUndefined(); + expect(feed.fare_products).toBeUndefined(); + expect(feed.fare_leg_rules).toBeUndefined(); + expect(feed.fare_transfer_rules).toBeUndefined(); + expect(feed.areas).toBeUndefined(); + expect(feed.stop_areas).toBeUndefined(); + expect(feed.networks).toBeUndefined(); + expect(feed.route_networks).toBeUndefined(); + expect(feed.frequencies).toBeUndefined(); + expect(feed.transfers).toBeUndefined(); + expect(feed.pathways).toBeUndefined(); + expect(feed.levels).toBeUndefined(); + expect(feed.translations).toBeUndefined(); + expect(feed.feed_info).toBeUndefined(); + expect(feed.attributions).toBeUndefined(); + + expect(feed.agency.length).toEqual(1); + expect(feed.agency[0].agency_id).toEqual('A01'); + expect(feed.agency[0].agency_name).toEqual('This Agency'); + expect(feed.agency[0].agency_url).toEqual('https://test.agency'); + expect(feed.agency[0].agency_timezone).toEqual('Europe/Madrid'); + expect(feed.agency[0].agency_lang).toEqual('en'); + expect(feed.agency[0].agency_phone).toEqual('+34000000000'); + expect(feed.agency[0].agency_fare_url).toEqual('https://test.agency/fare'); + expect(feed.agency[0].agency_email).toEqual('contact@test.agency'); + + expect(feed.calendar_dates!.length).toEqual(0); + + expect(feed.calendar!.length).toEqual(2); + expect(feed.calendar![0].monday).toBeTypeOf('number'); + expect(feed.calendar![0].tuesday).toBeTypeOf('number'); + expect(feed.calendar![0].wednesday).toBeTypeOf('number'); + expect(feed.calendar![0].thursday).toBeTypeOf('number'); + expect(feed.calendar![0].friday).toBeTypeOf('number'); + expect(feed.calendar![0].saturday).toBeTypeOf('number'); + expect(feed.calendar![0].sunday).toBeTypeOf('number'); + expect(feed.calendar![1].service_id).toEqual('S02'); + expect(feed.calendar![1].monday).toEqual(0); + expect(feed.calendar![1].tuesday).toEqual(0); + expect(feed.calendar![1].wednesday).toEqual(0); + expect(feed.calendar![1].thursday).toEqual(0); + expect(feed.calendar![1].friday).toEqual(0); + expect(feed.calendar![1].saturday).toEqual(1); + expect(feed.calendar![1].sunday).toEqual(1); + expect(feed.calendar![1].start_date).toEqual('20231201'); + expect(feed.calendar![1].end_date).toEqual('20231231'); + + expect(feed.routes.length).toEqual(2); + expect(feed.routes![1].route_type).toBeTypeOf('number'); + expect(feed.routes![1].route_sort_order).toBeTypeOf('number'); + expect(feed.routes![1].continuous_pickup).toBeTypeOf('number'); + expect(feed.routes![1].continuous_drop_off).toBeTypeOf('number'); + expect(feed.routes![0].route_id).toEqual('R01'); + expect(feed.routes![0].agency_id).toEqual('A01'); + expect(feed.routes![0].route_short_name).toEqual('01'); + expect(feed.routes![0].route_long_name).toEqual('Route Number 1'); + expect(feed.routes![0].route_type).toEqual(GTFSRouteType.Bus); + expect(feed.routes![0].route_color).toEqual('ffffff'); + expect(feed.routes![0].route_text_color).toEqual('000000'); + expect(feed.routes![0].route_sort_order).toEqual(1); + expect(feed.routes![0].continuous_pickup).toEqual(GTFSContinuousPickupDropOff.Default); + expect(feed.routes![0].continuous_drop_off).toEqual(GTFSContinuousPickupDropOff.NoContinuous); + + expect(feed.shapes!.length).toEqual(4); + expect(feed.shapes![1].shape_pt_lat).toBeTypeOf('number'); + expect(feed.shapes![1].shape_pt_lon).toBeTypeOf('number'); + expect(feed.shapes![1].shape_pt_sequence).toBeTypeOf('number'); + expect(feed.shapes![1].shape_dist_traveled).toBeTypeOf('number'); + expect(feed.shapes![3].shape_id).toEqual('SH01'); + expect(feed.shapes![3].shape_pt_lat).toEqual(39.47324); + expect(feed.shapes![3].shape_pt_lon).toEqual(-0.36546); + expect(feed.shapes![3].shape_pt_sequence).toEqual(4); + expect(feed.shapes![3].shape_dist_traveled).toEqual(1.29); + + expect(feed.stop_times.length).toEqual(2); + expect(feed.stop_times[0].shape_dist_traveled).toBeTypeOf('number'); + expect(feed.stop_times[0].stop_sequence).toBeTypeOf('number'); + expect(feed.stop_times[1].trip_id).toEqual('T0101'); + expect(feed.stop_times[1].arrival_time).toEqual('08:03:00'); + expect(feed.stop_times[1].departure_time).toBeFalsy(); + expect(feed.stop_times[1].stop_id).toEqual('ST02'); + expect(feed.stop_times[1].stop_sequence).toEqual(2); + expect(feed.stop_times[1].pickup_type).toEqual(GTFSStopTimePickupDropOff.Scheduled); + expect(feed.stop_times[1].drop_off_type).toEqual(GTFSStopTimePickupDropOff.Scheduled); + expect(feed.stop_times[1].continuous_pickup).toEqual(GTFSContinuousPickupDropOff.NoContinuous); + expect(feed.stop_times[1].continuous_drop_off).toEqual(GTFSContinuousPickupDropOff.Default); + expect(feed.stop_times[1].shape_dist_traveled).toEqual(1.29); + expect(feed.stop_times[1].timepoint).toEqual(GTFSStopTimeTimepoint.Exact); + + expect(feed.stops.length).toEqual(2); + expect(feed.stops[0].stop_lat).toBeTypeOf('number'); + expect(feed.stops[0].stop_lon).toBeTypeOf('number'); + expect(feed.stops[0].stop_id).toEqual('ST01'); + expect(feed.stops[0].stop_code).toEqual('ST01'); + expect(feed.stops[0].stop_name).toEqual('Xàtiva'); + expect(feed.stops[1].stop_lat).toEqual(39.47345); + expect(feed.stops[1].stop_lon).toEqual(-0.36553); + expect(feed.stops[1].location_type).toEqual(GTFSStopLocationType.Stop); + expect(feed.stops[1].wheelchair_boarding).toEqual(GTFSWheelchairAccessbility.Inaccessible); + + expect(feed.trips.length).toEqual(1); + expect(feed.trips[0].route_id).toEqual('R01'); + expect(feed.trips[0].service_id).toEqual('S01'); + expect(feed.trips[0].trip_id).toEqual('T0101'); + expect(feed.trips[0].trip_headsign).toEqual('Headsign'); + expect(feed.trips[0].trip_short_name).toEqual('Short Name'); + expect(feed.trips[0].direction_id).toEqual(GTFSTripDirection.OneDirection); + expect(feed.trips[0].shape_id).toEqual('SH01'); + expect(feed.trips[0].wheelchair_accessible).toEqual(GTFSWheelchairAccessbility.Accessible); + expect(feed.trips[0].bikes_allowed).toEqual(GTFSTripBikesAllowed.NotAllowed); +} + +test('Test FeedReader: zip path', () => { + const reader = GTFSFeedReader.fromZip(ZIP_PATH); + const feed = reader.loadFeed(); + assert(feed); +}, { timeout: 10000 }); + +test('Test FeedReader: zip content', () => { + const zip = readFileSync(ZIP_PATH); + const reader = GTFSFeedReader.fromZip(zip); + const feed = reader.loadFeed(); + assert(feed); +}, { timeout: 10000 }); + +test('Test FeedReader: directory path', () => { + const reader = GTFSFeedReader.fromDir(DIR_PATH); + const feed = reader.loadFeed(); + assert(feed); +}, { timeout: 10000 }); + +test('Test FeedReader: file contents', () => { + const files: GTFSFileContent[] = [ + { name: 'agency.txt', content: readFileSync(join(DIR_PATH, 'agency.txt')) }, + { name: 'calendar_dates.txt', content: readFileSync(join(DIR_PATH, 'calendar_dates.txt')) }, + { name: 'calendar.txt', content: readFileSync(join(DIR_PATH, 'calendar.txt')) }, + { name: 'routes.txt', content: readFileSync(join(DIR_PATH, 'routes.txt')) }, + { name: 'shapes.txt', content: readFileSync(join(DIR_PATH, 'shapes.txt')) }, + { name: 'stop_times.txt', content: readFileSync(join(DIR_PATH, 'stop_times.txt')) }, + { name: 'stops.txt', content: readFileSync(join(DIR_PATH, 'stops.txt')) }, + { name: 'trips.txt', content: readFileSync(join(DIR_PATH, 'trips.txt')) } + ]; + const reader = GTFSFeedReader.fromFiles(files); + const feed = reader.loadFeed(); + assert(feed); +}, { timeout: 10000 }); diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 506a394..cd97e57 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -1,6 +1,17 @@ import { describe, expect, it } from 'vitest'; -import { GTFSAgency, GTFSFileIO, GTFS_FILES, GTFSStopLocationType, GTFSTripDirection } from '../dist'; -import type { GTFSFileRecords, GTFSRoute, GTFSStop, GTFSTrip } from '../dist'; +import { + GTFS_FILES, + GTFSFileIO, + GTFSStopLocationType, + GTFSTripDirection +} from '../dist'; +import type { + GTFSFileRecords, + GTFSAgency, + GTFSRoute, + GTFSStop, + GTFSTrip +} from '../dist'; describe('Test GTFSFileIO reading', () => { it('reads rows into records', () => { From f97a78c65b432add4bb9345f363a4b9e74ea1cf1 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 09:03:08 +0700 Subject: [PATCH 04/20] Handling IO values with commas and newlines --- src/io/file.ts | 19 ++++++++++++++++--- tests/file-io.test.ts | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/io/file.ts b/src/io/file.ts index b967d93..928c57f 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -22,11 +22,24 @@ export default class GTFSFileIO { .map(c => c.trim()) .filter(c => file.columns[c]); + let lineContent = ''; for (const line of lines) { - if (!line.trim()) continue; - let row = (parse(line) as string[][])[0].slice(0, columns.length); - let record: Record = {}; + lineContent += line; + if (!lineContent.trim()) continue; + let row: string[]; + try { + row = (parse(lineContent) as string[][])[0].slice(0, columns.length); + lineContent = ''; + } catch (ex: any) { + if (ex && ex.code && ex.code === 'CSV_QUOTE_NOT_CLOSED') { + lineContent += '\n'; + continue; + } + throw ex; + } + + let record: Record = {}; for (let i = 0; i < row.length; i++) { const columnType = file.columns[columns[i]]; if (columnType === 'int') { diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index cd97e57..967a9ca 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -17,9 +17,11 @@ describe('Test GTFSFileIO reading', () => { it('reads rows into records', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' - + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n'; + + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' + + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' + + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); - expect(records.length).toEqual(2); + expect(records.length).toEqual(4); expect(records[0].stop_id).toEqual('STOP_01'); expect(records[0].stop_code).toBeTypeOf('string'); expect(records[0].stop_code).toEqual('1001'); @@ -36,6 +38,12 @@ describe('Test GTFSFileIO reading', () => { expect(records[1].location_type).toEqual(0); expect(records[1].location_type).toEqual(GTFSStopLocationType.Stop); expect(records[1].parent_station).toEqual('STOP_01'); + expect(records[2].stop_name).toEqual('NameWith,Comma'); + expect(records[2].stop_code).toEqual('1003'); + expect(records[3].stop_id).toEqual('STOP_04'); + expect(records[3].stop_desc).toEqual('Description with\nNew line'); + expect(records[3].stop_lat).toEqual(50.31); + expect(records[3].stop_lon).toEqual(-23.48); }); it('handles empty file content', () => { @@ -51,13 +59,18 @@ describe('Test GTFSFileIO writing', () => { it('writes records into rows', () => { const records: GTFSFileRecords = [ { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, - { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' } + { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' }, + { route_id: 'R03', service_id: 'S01', trip_id: 'T03', trip_headsign: 'with,comma' }, + { route_id: 'R02', service_id: 'S02', trip_id: 'T04', trip_headsign: 'with\nnewline' } ]; const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + 'R01,S01,T01,,,0,,,,\n' + 'R01,S02,T02,HEADSIGN,,,,,,\n' + + 'R03,S01,T03,"with,comma",,,,,,\n' + + 'R02,S02,T04,"with\n' + + 'newline",,,,,,\n' ); }); From 007e3a02703b065792ff8fd31d3a2692c98c4d05 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 10:18:09 +0700 Subject: [PATCH 05/20] Added feed writer --- .gitignore | 2 + src/files/stop.ts | 4 +- src/index.ts | 2 +- src/io/feed-reader.ts | 32 +++----- src/io/feed-writer.ts | 106 +++++++++++++++++++++++++++ src/types.ts | 8 ++ tests/data/gtfs.zip | Bin 1809 -> 1805 bytes tests/data/gtfs/agency.txt | 4 +- tests/data/gtfs/shapes.txt | 4 +- tests/feed-writer.test.ts | 145 +++++++++++++++++++++++++++++++++++++ 10 files changed, 281 insertions(+), 26 deletions(-) create mode 100644 src/io/feed-writer.ts create mode 100644 tests/feed-writer.test.ts diff --git a/.gitignore b/.gitignore index bdff158..a4ede99 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,5 @@ dist .ionide # End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode + +tests/data/ignore/* diff --git a/src/files/stop.ts b/src/files/stop.ts index 06e19fc..fb5ce5d 100644 --- a/src/files/stop.ts +++ b/src/files/stop.ts @@ -1,3 +1,5 @@ +import type { GTFSWheelchairAccessbility } from './common'; + /** Location Type */ export enum GTFSStopLocationType { /** A location where passengers board or disembark from a transit vehicle. */ @@ -30,7 +32,7 @@ type StopBase = { /** Timezone of the location. */ stop_timezone?: string, /** Indicates whether wheelchair boardings are possible from the location. */ - wheelchair_boarding?: string, + wheelchair_boarding?: GTFSWheelchairAccessbility, /** Level of the location. */ level_id?: string, /** Platform identifier for a platform stop (a stop belonging to a station). */ diff --git a/src/index.ts b/src/index.ts index 04d1229..3b18d7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { default as GTFSFileIO } from './io/file'; export { default as GTFSFeedReader } from './io/feed-reader'; +export { default as GTFSFeedWriter, getIterableFeedFiles } from './io/feed-writer'; export * from './io/feed-file'; export * from './file-info'; @@ -19,7 +20,6 @@ export { GTFSTripDirection, GTFSTripBikesAllowed } from './files/trip'; export type * from './file-info'; export type * from './types'; -export type * from './io/feed-reader'; export type * from './files/agency'; export type * from './files/area'; diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 2f03bfb..17ea36d 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -8,7 +8,7 @@ import { join as joinPath } from 'path'; import { getIOFromFileName } from './feed-file'; import { getGTFSFileInfos } from '../file-info'; import type { GTFSFileInfo, GTFSFileName } from '../file-info'; -import type { GTFSFeed, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles, GTFSLoadedFeed } from '../types'; +import type { GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles, GTFSLoadedFeed } from '../types'; /** * GTFS file object to read @@ -22,14 +22,6 @@ type GTFSFile = { buffer?: Buffer }; -/** GTFS feed file content */ -export type GTFSFileContent = { - /** File name with .txt */ - name: string, - /** File content */ - content: string|Buffer -}; - /** * Generator of iterable lines from a file path. * @param filePath File path @@ -60,13 +52,13 @@ function *readZip(zipEntry: AdmZip.IZipEntry): IterableIterator { } /** - * GTFS feed writer. + * GTFS feed reader. * Do not use constructor, instead, use the following static methods to initiate an instance: - * GTFSFeedWriter.fromZip, - * GTFSFeedWriter.fromDir, - * GTFSFeedWriter.fromFiles + * GTFSFeedReader.fromZip, + * GTFSFeedReader.fromDir, + * GTFSFeedReader.fromFiles */ -export default class GTFSFeedWriter { +export default class GTFSFeedReader { /** Zip object */ private zip?: AdmZip = undefined; @@ -175,8 +167,8 @@ export default class GTFSFeedWriter { * @param zip Zip file path or content buffer * @returns GTFSFeedWriter instance */ - public static fromZip(zip: string|Buffer): GTFSFeedWriter { - return new GTFSFeedWriter(zip); + public static fromZip(zip: string|Buffer): GTFSFeedReader { + return new GTFSFeedReader(zip); } /** @@ -184,8 +176,8 @@ export default class GTFSFeedWriter { * @param dirPath Path to GTFS feed directory * @returns GTFSFeedWriter instance */ - public static fromDir(dirPath: string): GTFSFeedWriter { - return new GTFSFeedWriter(undefined, dirPath); + public static fromDir(dirPath: string): GTFSFeedReader { + return new GTFSFeedReader(undefined, dirPath); } /** @@ -193,7 +185,7 @@ export default class GTFSFeedWriter { * @param files Feed files object * @returns GTFSFeedWriter instance */ - public static fromFiles(files: GTFSFileContent[]): GTFSFeedWriter { - return new GTFSFeedWriter(undefined, undefined, files); + public static fromFiles(files: GTFSFileContent[]): GTFSFeedReader { + return new GTFSFeedReader(undefined, undefined, files); } }; diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts new file mode 100644 index 0000000..d718302 --- /dev/null +++ b/src/io/feed-writer.ts @@ -0,0 +1,106 @@ +import AdmZip from 'adm-zip'; +import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'fs'; +import { join as joinPath } from 'path'; +import { getIOFromFileName } from './feed-file'; +import { GTFSFileName, GTFS_FILES } from '../file-info'; +import type { GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSIterableFeedFiles } from '../types'; + +/** + * Generator of feed by file. + * @param feed GTFS Feed + * @returns Iterable feed files + */ +export function *getIterableFeedFiles(feed: GTFSFeed): GTFSIterableFeedFiles { + const keys = Object.keys(feed) as GTFSFileName[]; + for (const key of keys) { + const info = GTFS_FILES[key]; + if (!info) continue; + const records = feed[key] as GTFSFileRecords; + yield { info, records: Array.isArray(records) ? records.values() : records }; + } + return; +} + +/** + * GTFS feed writer. + * For static usage only. + */ +export default class GTFSFeedWriter { + private constructor() {} + + /** + * Create a zip instance in memory. + * @param feed GTFS Feed + * @returns AdmZip instance + */ + public static createZip(feed: GTFSFeed): AdmZip { + const zip = new AdmZip(); + const files = getIterableFeedFiles(feed); + for (const file of files) { + const io = getIOFromFileName(file.info.name); + zip.addFile(io.fileName, Buffer.from([...io.write(file.records)].join(''))); + } + return zip; + } + + /** + * Create file contents in memory. + * @param feed GTFS Feed + * @returns File contents + */ + public static createFileContents(feed: GTFSFeed): GTFSFileContent[] { + const fileContents: GTFSFileContent[] = []; + const files = getIterableFeedFiles(feed); + for (const file of files) { + const io = getIOFromFileName(file.info.name); + fileContents.push({ + name: io.fileName, + content: [...io.write(file.records)].join('') + }); + } + return fileContents; + } + + /** + * Create a zip of GTFS feed and write to the specific path. + * @param feed GTFS Feed + * @param path Path to output zip file + */ + public static writeZip(feed: GTFSFeed, path: string): void { + GTFSFeedWriter.createZip(feed).writeZip(path); + } + + /** + * Write GTFS feed to the specific directory path. + * @param feed GTFS Feed + * @param path Path to output directory + * @param mkdirIfNotExists True to recursively create a directory at the path if does not exist + * @returns File names without directory path, with .txt extension. + */ + public static writeDirectory(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { + if (!existsSync(path) && mkdirIfNotExists) { + mkdirSync(path, { recursive: true }); + } + const files = getIterableFeedFiles(feed); + const fileNames = []; + const bufferLineSize = 64; + for (const file of files) { + let chunks = []; + const io = getIOFromFileName(file.info.name); + const filePath = joinPath(path, io.fileName); + writeFileSync(filePath, ''); + for (const row of io.write(file.records)) { + chunks.push(row); + if (chunks.length > bufferLineSize) { + appendFileSync(filePath, chunks.join('')); + chunks = []; + } + } + if (chunks.length) { + appendFileSync(filePath, chunks.join('')); + } + fileNames.push(io.fileName); + } + return fileNames; + } +}; diff --git a/src/types.ts b/src/types.ts index f62447d..59ae553 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,6 +66,14 @@ export type GTFSIterableFeedFile = { /** GTFS Iterable feed files */ export type GTFSIterableFeedFiles = IterableIterator; +/** GTFS feed file content */ +export type GTFSFileContent = { + /** File name with .txt */ + name: string, + /** File content */ + content: string|Buffer +}; + /** GTFS Dataset feed */ export type GTFSFeed = { /** Transit agencies with service represented in this dataset. */ diff --git a/tests/data/gtfs.zip b/tests/data/gtfs.zip index b80051b61deea95b8f5fc871f5d821243f9c2a45..d9815c730db915ab65e72dc1fcb759222ee7123e 100644 GIT binary patch delta 521 zcmbQp*UKj!;LXe;!oa}5!N3sE9A3!2RHO#TyEakWJ*ddU;0ou-6FOl9EZwhCrc4nL ziGKNd`Vz6}FC0T&?U*L^RD8*+-)hT580Su&wR~}IP7(w2jglA5Z!USBJFo5OqgUuK z=>Zc1!UAKTiJNXRHTq2MWjt8#546qwBKOBKpl$nsmN_e9x!o9R-$)}8UrH=EY@_Y9Nukx)t`#ikC>8jiK@`^|`O>j|I9ADB)sDfmv_#eAK~%Xji*mKD4xAvD>8Ra@TJr#akA z;CxFW(DjRfmxj1MN)u*rhlHk(bxVihw`bxD2!INXpGd-*npKTNec-3T;!Ef8~p UYybj?+nOi8XOm&mW(A1=0N|&nnE(I) delta 502 zcmeC>o5&{~;LXe;!oa}5!4T=(9RB_?zg-=WcVnWsd&tuw6N4+9Cr{{v6|iu>PMI=A zL?rs<>*-6xroV8U@@mI4v8UooUj0^ECc=1k@~q{Hb90gym~WK4Xx6#xdG5Tnr;lEt z!=wjKnHUgO_&H77aFa>hWpX#;!TJ`L=5XKs^IR1``wjy!7Z4X`Bo?F=>y=cLgq{xM zI%FWga^b&I{-(B+&38@BKip?-Nf6VS66O2l&$Tdve`n7mpEA{zI?j{I?cE!^%6I44 z{Xt*;UHQ4_zH0x{W4}D~=bC)K`!hxMM?%L@PMI|yl{6k4kh-nKerswU^Mh5&H&$3Y z^R=uIE2xkExTC&Ofh|hZ`Tkx;glm)CCcj}i!Svo`@;2t{OjfRw`&m}-qJ+|97glX1 zKc~rstTK$tCik&gG8#@k!Kw)I;cHeI%SJ{9hNAq^5{QoiycwB97$9Md&F?_(GcYnJ z07;k04s4Q4K9c}#001#NtA+po diff --git a/tests/data/gtfs/agency.txt b/tests/data/gtfs/agency.txt index ce32ca8..f0c5375 100644 --- a/tests/data/gtfs/agency.txt +++ b/tests/data/gtfs/agency.txt @@ -1,2 +1,2 @@ -agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url,agency_email -A01,This Agency,https://test.agency,Europe/Madrid,en,+34000000000,https://test.agency/fare,contact@test.agency +agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,agency_fare_url,agency_email +A01,This Agency,https://test.agency,Europe/Madrid,en,+34000000000,https://test.agency/fare,contact@test.agency diff --git a/tests/data/gtfs/shapes.txt b/tests/data/gtfs/shapes.txt index 50b5323..471232e 100644 --- a/tests/data/gtfs/shapes.txt +++ b/tests/data/gtfs/shapes.txt @@ -1,5 +1,5 @@ shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled -SH01,39.46717,-0.37727,1,0.0 +SH01,39.46717,-0.37727,1,0 SH01,39.46694,-0.37485,2,0.2 -SH01,39.47166,-0.36901,3,1.0 +SH01,39.47166,-0.36901,3,1 SH01,39.47324,-0.36546,4,1.29 diff --git a/tests/feed-writer.test.ts b/tests/feed-writer.test.ts new file mode 100644 index 0000000..405ba96 --- /dev/null +++ b/tests/feed-writer.test.ts @@ -0,0 +1,145 @@ +import { expect, test } from 'vitest'; +import AdmZip from 'adm-zip'; +import { existsSync, readFileSync, readdirSync, rmSync } from 'fs'; +import { join as joinPath } from 'path'; +import { + GTFSContinuousPickupDropOff, + GTFSFeed, + GTFSFeedWriter, + GTFSRouteType, + GTFSStopTimePickupDropOff, + GTFSStopTimeTimepoint, + GTFSTripBikesAllowed, + GTFSTripDirection, + GTFSWheelchairAccessbility +} from '../dist'; + +const OUTPUT_DIR = './tests/data/ignore'; +const COMPARE_DIR = './tests/data/gtfs'; + +const getTestFeed = (): GTFSFeed => ({ + agency: [{ + agency_id: 'A01', + agency_name: 'This Agency', + agency_url: 'https://test.agency', + agency_timezone: 'Europe/Madrid', + agency_lang: 'en', + agency_phone: '+34000000000', + agency_fare_url: 'https://test.agency/fare', + agency_email: 'contact@test.agency' + }], + calendar_dates: [], + calendar: [ + { service_id: 'S01', monday: 1, tuesday: 1, wednesday: 1, thursday: 1, friday: 1, saturday: 0, sunday: 0, start_date: '20231201', end_date: '20231231' }, + { service_id: 'S02', monday: 0, tuesday: 0, wednesday: 0, thursday: 0, friday: 0, saturday: 1, sunday: 1, start_date: '20231201', end_date: '20231231' } + ], + routes: [ + { + route_id: 'R01', agency_id: 'A01', route_short_name: '01', route_long_name: 'Route Number 1', route_desc: '', route_type: GTFSRouteType.Bus, + route_url: '', route_color: 'ffffff', route_text_color: '000000', route_sort_order: 1, + continuous_pickup: GTFSContinuousPickupDropOff.Default, continuous_drop_off: GTFSContinuousPickupDropOff.NoContinuous, network_id: '' + }, + { + route_id: 'R02', agency_id: 'A01', route_short_name: '02', route_long_name: 'Route Number 2', route_desc: '', route_type: GTFSRouteType.Bus, + route_url: '', route_color: '000000', route_text_color: 'ffffff', route_sort_order: 2, + continuous_pickup: GTFSContinuousPickupDropOff.NoContinuous, continuous_drop_off: GTFSContinuousPickupDropOff.NoContinuous, network_id: '' + } + ], + shapes: [ + { shape_id: 'SH01', shape_pt_lat: 39.46717, shape_pt_lon: -0.37727, shape_pt_sequence: 1, shape_dist_traveled: 0.0 }, + { shape_id: 'SH01', shape_pt_lat: 39.46694, shape_pt_lon: -0.37485, shape_pt_sequence: 2, shape_dist_traveled: 0.2 }, + { shape_id: 'SH01', shape_pt_lat: 39.47166, shape_pt_lon: -0.36901, shape_pt_sequence: 3, shape_dist_traveled: 1.0 }, + { shape_id: 'SH01', shape_pt_lat: 39.47324, shape_pt_lon: -0.36546, shape_pt_sequence: 4, shape_dist_traveled: 1.29 } + ], + stop_times: [ + { + trip_id: 'T0101', arrival_time: '', departure_time: '08:00:00', stop_id: 'ST01', stop_sequence: 1, stop_headsign: 'Alameda', + pickup_type: GTFSStopTimePickupDropOff.Scheduled, drop_off_type: GTFSStopTimePickupDropOff.Scheduled, + continuous_pickup: GTFSContinuousPickupDropOff.Default, continuous_drop_off: GTFSContinuousPickupDropOff.Default, + shape_dist_traveled: 0, timepoint: GTFSStopTimeTimepoint.Exact + }, + { + trip_id: 'T0101', arrival_time: '08:03:00', departure_time: '', stop_id: 'ST02', stop_sequence: 2, stop_headsign: '', + pickup_type: GTFSStopTimePickupDropOff.Scheduled, drop_off_type: GTFSStopTimePickupDropOff.Scheduled, + continuous_pickup: GTFSContinuousPickupDropOff.NoContinuous, continuous_drop_off: GTFSContinuousPickupDropOff.Default, + shape_dist_traveled: 1.29, timepoint: GTFSStopTimeTimepoint.Exact + } + ], + stops: [ + { + stop_id: 'ST01', stop_code: 'ST01', stop_name: 'Xàtiva', stop_lat: 39.46603, stop_lon: -0.37754, + location_type: 0, wheelchair_boarding: GTFSWheelchairAccessbility.Accessible + }, + { + stop_id: 'ST02', stop_code: 'ST02', stop_name: 'Alameda', stop_lat: 39.47345, stop_lon: -0.36553, + location_type: 0, wheelchair_boarding: GTFSWheelchairAccessbility.Inaccessible + } + ], + trips: [{ + route_id: 'R01', service_id: 'S01', trip_id: 'T0101', trip_headsign: 'Headsign', trip_short_name: 'Short Name', + direction_id: GTFSTripDirection.OneDirection, block_id: '', shape_id: 'SH01', + wheelchair_accessible: GTFSWheelchairAccessbility.Accessible, bikes_allowed: GTFSTripBikesAllowed.NotAllowed + }] +}); + +test('Test FeedWriter: zip', () => { + const path = joinPath(OUTPUT_DIR, 'gtfs.zip'); + GTFSFeedWriter.writeZip(getTestFeed(), path); + + expect(existsSync(path)).toBeTruthy(); + + const zip = new AdmZip(path); + const entries = zip.getEntries(); + expect(entries.length).toEqual(8); + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + expect(entries.map(x => x.entryName).every(x => files.indexOf(x) > -1)).toBeTruthy(); + + for (const entry of entries) { + expect( + entry.getData().toString().split(/\r?\n/g), + entry.entryName + ).toEqual( + readFileSync(joinPath(COMPARE_DIR, entry.entryName)).toString().split(/\r?\n/g) + ); + } +}); + +test('Test FeedWriter: dir', () => { + if (existsSync(OUTPUT_DIR)) { + for (const file of readdirSync(OUTPUT_DIR).filter(x => x.endsWith('.txt')).map(x => joinPath(OUTPUT_DIR, x))) { + rmSync(file); + } + } + + GTFSFeedWriter.writeDirectory(getTestFeed(), OUTPUT_DIR); + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + + expect(files.every(x => existsSync(joinPath(OUTPUT_DIR, x)))).toBeTruthy(); + for (const file of files) { + expect( + readFileSync(joinPath(OUTPUT_DIR, file)).toString().split(/\r?\n/g), + file + ).toEqual( + readFileSync(joinPath(COMPARE_DIR, file)).toString().split(/\r?\n/g) + ); + } +}); + +test('Test FeedWriter: contents', () => { + const contents = GTFSFeedWriter.createFileContents(getTestFeed()); + expect(contents.length).toEqual(8); + + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + expect(contents.every(c => files.indexOf(c.name) > -1)).toBeTruthy(); + + for (const content of contents) { + expect(content.content.toString().split(/\r?\n/g), content.name) + .toEqual(readFileSync(joinPath(COMPARE_DIR, content.name)).toString().split(/\r?\n/g)); + } +}); From 6020cb858171b586ad2303980e7192442867617d Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 11:45:19 +0700 Subject: [PATCH 06/20] Added webpack --- package-lock.json | 4045 ++++++++++++++++++++++++++++++++++++++++++-- package.json | 12 +- tests/webpack.html | 89 + webpack.config.js | 48 + 4 files changed, 4072 insertions(+), 122 deletions(-) create mode 100644 tests/webpack.html create mode 100644 webpack.config.js diff --git a/package-lock.json b/package-lock.json index 5739803..a22dc85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,25 @@ "devDependencies": { "@types/adm-zip": "^0.5.5", "@types/node": "^20.10.1", + "assert": "^2.1.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", + "stream-browserify": "^3.0.0", "typescript": "^5.3.2", - "vitest": "^0.34.6" + "util": "^0.12.5", + "vitest": "^0.34.6", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" } }, "node_modules/@esbuild/android-arm": { @@ -383,12 +400,64 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", @@ -575,6 +644,38 @@ "@types/chai": "*" } }, + "node_modules/@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/node": { "version": "20.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", @@ -652,6 +753,208 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -664,6 +967,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-walk": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", @@ -681,6 +993,31 @@ "node": ">=6.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -693,6 +1030,37 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -702,6 +1070,194 @@ "node": "*" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -711,6 +1267,40 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/chai": { "version": "4.3.10", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", @@ -741,16 +1331,140 @@ "node": "*" } }, - "node_modules/csv": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.5.tgz", - "integrity": "sha512-Y+KTCAUljtq2JaGP42ZL1bymqlU5BkfnFpZhxRczGFDZox2VXhlRHnG5DRshyUrwQzmCdEiLjSqNldCfm1OVCA==", - "dependencies": { - "csv-generate": "^4.3.0", - "csv-parse": "^5.5.2", - "csv-stringify": "^6.4.4", - "stream-transform": "^3.2.10" - }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/csv": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.5.tgz", + "integrity": "sha512-Y+KTCAUljtq2JaGP42ZL1bymqlU5BkfnFpZhxRczGFDZox2VXhlRHnG5DRshyUrwQzmCdEiLjSqNldCfm1OVCA==", + "dependencies": { + "csv-generate": "^4.3.0", + "csv-parse": "^5.5.2", + "csv-stringify": "^6.4.4", + "stream-transform": "^3.2.10" + }, "engines": { "node": ">= 0.1.90" } @@ -799,6 +1513,47 @@ "node": ">=6" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -808,6 +1563,81 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.601", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", + "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, "node_modules/esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -845,6 +1675,129 @@ "@esbuild/win32-x64": "0.19.8" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -859,6 +1812,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -868,102 +1830,674 @@ "node": "*" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dev": true, - "engines": { - "node": ">=14" + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.1" + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "dev": true, "dependencies": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "ufo": "^1.3.0" + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "dependencies": { - "yocto-queue": "^1.0.0" + "has-symbols": "^1.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true - }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -973,12 +2507,40 @@ "node": "*" } }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -1032,12 +2594,134 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/rollup": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", @@ -1066,12 +2750,135 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -1081,6 +2888,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -1093,11 +2910,30 @@ "integrity": "sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==", "dev": true }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "node_modules/stream-transform": { "version": "3.2.10", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.2.10.tgz", "integrity": "sha512-Yu+x7zcWbWdyB0Td8dFzHt2JEyD6694CNq2lqh1rbuEBVxPtjb/GZ7xDnZcdYiU5E/RtufM54ClSEOzZDeWguA==" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-literal": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", @@ -1110,6 +2946,94 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, "node_modules/tinybench": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", @@ -1162,10 +3086,68 @@ "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "dev": true }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, "node_modules/vite": { @@ -1323,6 +3305,177 @@ } } }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", @@ -1339,6 +3492,12 @@ "node": ">=8" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, "node_modules/yocto-queue": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", @@ -1353,6 +3512,12 @@ } }, "dependencies": { + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, "@esbuild/android-arm": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", @@ -1516,12 +3681,55 @@ "@sinclair/typebox": "^0.27.8" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@rollup/rollup-android-arm-eabi": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz", @@ -1636,6 +3844,38 @@ "@types/chai": "*" } }, + "@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "@types/node": { "version": "20.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz", @@ -1684,47 +3924,416 @@ "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", "dev": true, "requires": { - "tinyspy": "^2.1.1" + "tinyspy": "^2.1.1" + } + }, + "@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "requires": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "dev": true + }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" } }, - "@vitest/utils": { - "version": "0.34.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", - "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", "dev": true, "requires": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" } }, - "acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } }, - "acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", - "dev": true + "browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + } }, - "adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, "cac": { @@ -1733,6 +4342,23 @@ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true }, + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + } + }, + "caniuse-lite": { + "version": "1.0.30001565", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", + "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "dev": true + }, "chai": { "version": "4.3.10", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", @@ -1757,6 +4383,120 @@ "get-func-name": "^2.0.2" } }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, "csv": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.5.tgz", @@ -1801,12 +4541,114 @@ "type-detect": "^4.0.0" } }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.4.601", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", + "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", + "dev": true + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true + }, + "es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, "esbuild": { "version": "0.19.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", @@ -1837,17 +4679,356 @@ "@esbuild/win32-x64": "0.19.8" } }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dev": true, - "optional": true + "requires": { + "which-typed-array": "^1.1.11" + } }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "jsonc-parser": { @@ -1856,12 +5037,33 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, "local-pkg": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", "dev": true }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -1880,6 +5082,68 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -1904,6 +5168,46 @@ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, "p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -1913,6 +5217,69 @@ "yocto-queue": "^1.0.0" } }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "pathe": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", @@ -1925,12 +5292,34 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, "pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -1964,12 +5353,115 @@ "react-is": "^18.0.0" } }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "rollup": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz", @@ -1991,18 +5483,112 @@ "fsevents": "~2.3.2" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -2015,11 +5601,30 @@ "integrity": "sha512-JGUEaALvL0Mf6JCfYnJOTcobY+Nc7sG/TemDRBqCA0wEr4DER7zDchaaixTlmOxAjG1uRJmX82EQcxwTQTkqVA==", "dev": true }, + "stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "requires": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "stream-transform": { "version": "3.2.10", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.2.10.tgz", "integrity": "sha512-Yu+x7zcWbWdyB0Td8dFzHt2JEyD6694CNq2lqh1rbuEBVxPtjb/GZ7xDnZcdYiU5E/RtufM54ClSEOzZDeWguA==" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "strip-literal": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", @@ -2029,6 +5634,52 @@ "acorn": "^8.10.0" } }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + } + }, "tinybench": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", @@ -2071,6 +5722,44 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "vite": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.4.tgz", @@ -2129,6 +5818,116 @@ "why-is-node-running": "^2.2.2" } }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + }, + "webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", @@ -2139,6 +5938,12 @@ "stackback": "0.0.2" } }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, "yocto-queue": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", diff --git a/package.json b/package.json index c2f530a..65964e0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "dist/index.js", "scripts": { "build": "npx tsc", - "prepare-test": "curl -L -o ./temp/gtfs.zip https://storage.googleapis.com/storage/v1/b/mdb-latest/o/es-valenciana-generalitat-valenciana-gtfs-1870.zip?generation=1701303560208582&alt=media", + "pack": "npm run build && npx webpack", "test": "npm run build && npx vitest run --config ./vitest.config.ts" }, "repository": { @@ -25,8 +25,16 @@ "devDependencies": { "@types/adm-zip": "^0.5.5", "@types/node": "^20.10.1", + "assert": "^2.1.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", + "stream-browserify": "^3.0.0", "typescript": "^5.3.2", - "vitest": "^0.34.6" + "util": "^0.12.5", + "vitest": "^0.34.6", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" }, "dependencies": { "adm-zip": "^0.5.10", diff --git a/tests/webpack.html b/tests/webpack.html new file mode 100644 index 0000000..52edd6e --- /dev/null +++ b/tests/webpack.html @@ -0,0 +1,89 @@ + + + + GTFS + + + + + + + + + + diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..632d32e --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,48 @@ +import { createRequire } from 'module'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import webpack from 'webpack'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); + +export default { + mode: 'production', + entry: './dist/index.js', + module: { + rules: [{ + test: /\.(js)$/, + resolve: { + fullySpecified: false + } + }] + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_DEBUG': JSON.stringify('') + }), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'] + }) + ], + resolve: { + fallback: { + path: false, + fs: false, + assert: require.resolve('assert'), + buffer: require.resolve('buffer'), + crypto: require.resolve('crypto-browserify'), + stream: require.resolve('stream-browserify'), + util: require.resolve('util'), + zlib: require.resolve('browserify-zlib') + } + }, + output: { + path: path.resolve(__dirname, 'dist', 'bundle'), + filename: 'index.js', + library: { + type: 'var', + name: 'GTFSIO' + } + }, +}; From 568b75a084fbaa2a62373c5bb4cba09495915100 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 15:14:46 +0700 Subject: [PATCH 07/20] Made function names as sync --- src/io/feed-file.ts | 24 ++++----- src/io/feed-reader.ts | 22 ++++---- src/io/feed-writer.ts | 10 ++-- src/io/file.ts | 20 +++---- tests/feed-file-io.test.ts | 104 ++++++++++++++++++------------------- tests/feed-reader.test.ts | 8 +-- tests/feed-writer.test.ts | 4 +- tests/file-io.test.ts | 10 ++-- tests/webpack.html | 5 +- 9 files changed, 103 insertions(+), 104 deletions(-) diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 9be41e8..f263317 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -62,8 +62,8 @@ export class GTFSFeedFileIO { * @param lines Iterable lines * @returns Iterable records */ - public *read(lines: IterableIterator): IterableIterator { - yield *GTFSFileIO.read(this.fileInfo, lines); + public *readSync(lines: IterableIterator): IterableIterator { + yield *GTFSFileIO.readSync(this.fileInfo, lines); return; } @@ -72,8 +72,8 @@ export class GTFSFeedFileIO { * @param lines Lines array * @returns Records array */ - public readLines(lines: string[]): RowType[] { - return GTFSFileIO.readLines(this.fileInfo, lines); + public readLinesSync(lines: string[]): RowType[] { + return GTFSFileIO.readLinesSync(this.fileInfo, lines); } /** @@ -81,8 +81,8 @@ export class GTFSFeedFileIO { * @param content File content * @returns Records array */ - public readContent(content: string): RowType[] { - return GTFSFileIO.readContent(this.fileInfo, content); + public readContentSync(content: string): RowType[] { + return GTFSFileIO.readContentSync(this.fileInfo, content); } /** @@ -90,8 +90,8 @@ export class GTFSFeedFileIO { * @param records Iterable records * @returns Iterable lines */ - public *write(records: IterableIterator): IterableIterator { - yield *GTFSFileIO.write(this.fileInfo, records); + public *writeSync(records: IterableIterator): IterableIterator { + yield *GTFSFileIO.writeSync(this.fileInfo, records); return; } @@ -100,8 +100,8 @@ export class GTFSFeedFileIO { * @param records Records array * @returns Lines array */ - public writeLines(records: RowType[]): string[] { - return GTFSFileIO.writeLines(this.fileInfo, records); + public writeLinesSync(records: RowType[]): string[] { + return GTFSFileIO.writeLinesSync(this.fileInfo, records); } /** @@ -109,8 +109,8 @@ export class GTFSFeedFileIO { * @param records Records array * @returns File content */ - public writeContent(records: RowType[]): string { - return GTFSFileIO.writeContent(this.fileInfo, records); + public writeContentSync(records: RowType[]): string { + return GTFSFileIO.writeContentSync(this.fileInfo, records); } }; diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 17ea36d..98e1ffe 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -73,12 +73,12 @@ export default class GTFSFeedReader { * @param fileContents Array of file content objects */ private constructor( - zip?: string|Buffer, + zip?: string|Buffer|ArrayBuffer, directoryPath?: string, fileContents?: GTFSFileContent[] ) { if (zip) { - this.zip = new AdmZip(zip); + this.zip = new AdmZip(typeof zip === 'string' || Buffer.isBuffer(zip) ? zip : Buffer.from(zip)); return; } this.files = []; @@ -104,22 +104,22 @@ export default class GTFSFeedReader { * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). * @returns Iterable feed files */ - public *getIterableFiles(): GTFSIterableFeedFiles { + public *getIterableFilesSync(): GTFSIterableFeedFiles { for (const info of getGTFSFileInfos()) { if (this.zip) { const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); if (!entry.length) continue; const fileIO = getIOFromFileName(info.name); - yield { info, records: fileIO.read(readZip(entry[0])) }; + yield { info, records: fileIO.readSync(readZip(entry[0])) }; } else if (this.files) { const file = this.files.filter(f => f.info.name === info.name); if (!file.length) continue; const fileIO = getIOFromFileName(info.name); if (file[0].path) { - yield { info, records: fileIO.read(readLine(file[0].path)) }; + yield { info, records: fileIO.readSync(readLine(file[0].path)) }; } else if (file[0].buffer) { const lines = file[0].buffer.toString().replace(/\r\n/g, '\n').split('\n').values(); - yield { info, records: fileIO.read(lines) }; + yield { info, records: fileIO.readSync(lines) }; } } } @@ -130,7 +130,7 @@ export default class GTFSFeedReader { * Get feed object with row being file name without .txt and value being iterable records. * @returns Feed object with row being file name without .txt and value being iterable records. */ - public getFeed(): GTFSFeed { + public getFeedSync(): GTFSFeed { const results: Partial> = { agency: [], stops: [], @@ -139,7 +139,7 @@ export default class GTFSFeedReader { stop_times: [] }; - for (const file of this.getIterableFiles()) { + for (const file of this.getIterableFilesSync()) { const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; results[key] = file.records; } @@ -151,8 +151,8 @@ export default class GTFSFeedReader { * Get feed object with row being file name without .txt and value being array of records. * @returns Feed object with row being file name without .txt and value being array of records. */ - public loadFeed(): GTFSLoadedFeed { - const feed = this.getFeed(); + public loadFeedSync(): GTFSLoadedFeed { + const feed = this.getFeedSync(); const results: Partial> = {}; for (const key of Object.keys(feed) as GTFSFileName[]) { @@ -167,7 +167,7 @@ export default class GTFSFeedReader { * @param zip Zip file path or content buffer * @returns GTFSFeedWriter instance */ - public static fromZip(zip: string|Buffer): GTFSFeedReader { + public static fromZip(zip: string|Buffer|ArrayBuffer): GTFSFeedReader { return new GTFSFeedReader(zip); } diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index d718302..c314015 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -38,7 +38,7 @@ export default class GTFSFeedWriter { const files = getIterableFeedFiles(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); - zip.addFile(io.fileName, Buffer.from([...io.write(file.records)].join(''))); + zip.addFile(io.fileName, Buffer.from([...io.writeSync(file.records)].join(''))); } return zip; } @@ -55,7 +55,7 @@ export default class GTFSFeedWriter { const io = getIOFromFileName(file.info.name); fileContents.push({ name: io.fileName, - content: [...io.write(file.records)].join('') + content: [...io.writeSync(file.records)].join('') }); } return fileContents; @@ -66,7 +66,7 @@ export default class GTFSFeedWriter { * @param feed GTFS Feed * @param path Path to output zip file */ - public static writeZip(feed: GTFSFeed, path: string): void { + public static writeZipSync(feed: GTFSFeed, path: string): void { GTFSFeedWriter.createZip(feed).writeZip(path); } @@ -77,7 +77,7 @@ export default class GTFSFeedWriter { * @param mkdirIfNotExists True to recursively create a directory at the path if does not exist * @returns File names without directory path, with .txt extension. */ - public static writeDirectory(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { + public static writeDirectorySync(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { if (!existsSync(path) && mkdirIfNotExists) { mkdirSync(path, { recursive: true }); } @@ -89,7 +89,7 @@ export default class GTFSFeedWriter { const io = getIOFromFileName(file.info.name); const filePath = joinPath(path, io.fileName); writeFileSync(filePath, ''); - for (const row of io.write(file.records)) { + for (const row of io.writeSync(file.records)) { chunks.push(row); if (chunks.length > bufferLineSize) { appendFileSync(filePath, chunks.join('')); diff --git a/src/io/file.ts b/src/io/file.ts index 928c57f..2f03d51 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -14,7 +14,7 @@ export default class GTFSFileIO { * @param lines Iterable file contents by line * @returns Iterable records */ - public static *read(file: GTFSFileInfo, lines: IterableIterator): IterableIterator { + public static *readSync(file: GTFSFileInfo, lines: IterableIterator): IterableIterator { const head = lines.next(); if (head.done) return; if (!head.value || !head.value.trim()) return; @@ -65,7 +65,7 @@ export default class GTFSFileIO { * @param newLine New line delimiter * @returns Iterable file contents by line */ - public static *write(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { + public static *writeSync(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { const columns = Object.keys(file.columns); yield stringify([columns], { record_delimiter: newLine }); @@ -82,8 +82,8 @@ export default class GTFSFileIO { * @param lines Array of content lines * @returns Array of records */ - public static readLines(file: GTFSFileInfo, lines: string[]): RowType[] { - return [...GTFSFileIO.read(file, lines.values())]; + public static readLinesSync(file: GTFSFileInfo, lines: string[]): RowType[] { + return [...GTFSFileIO.readSync(file, lines.values())]; } /** @@ -93,8 +93,8 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Array of record */ - public static readContent(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { - return GTFSFileIO.readLines(file, content.split(newLine)); + public static readContentSync(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { + return GTFSFileIO.readLinesSync(file, content.split(newLine)); } /** @@ -104,8 +104,8 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Lines array */ - public static writeLines(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { - return [...GTFSFileIO.write(file, records.values(), newLine)]; + public static writeLinesSync(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { + return [...GTFSFileIO.writeSync(file, records.values(), newLine)]; } /** @@ -115,7 +115,7 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns File content */ - public static writeContent(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { - return GTFSFileIO.writeLines(file, records, newLine).join(''); + public static writeContentSync(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { + return GTFSFileIO.writeLinesSync(file, records, newLine).join(''); } }; diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts index f9c3d4b..3119bce 100644 --- a/tests/feed-file-io.test.ts +++ b/tests/feed-file-io.test.ts @@ -35,11 +35,11 @@ test('Test agency.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.agency.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -50,11 +50,11 @@ test('Test stops.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.stops.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -65,11 +65,11 @@ test('Test routes.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.routes.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -80,11 +80,11 @@ test('Test trips.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.trips.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -95,11 +95,11 @@ test('Test stop_times.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_times.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -110,11 +110,11 @@ test('Test calendar.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -125,11 +125,11 @@ test('Test calendar_dates.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar_dates.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -140,11 +140,11 @@ test('Test fare_attributes.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_attributes.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -155,11 +155,11 @@ test('Test fare_rules.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_rules.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -170,11 +170,11 @@ test('Test timeframes.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.timeframes.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -185,11 +185,11 @@ test('Test fare_media.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_media.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -200,11 +200,11 @@ test('Test fare_products.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_products.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -215,11 +215,11 @@ test('Test fare_leg_rules.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_leg_rules.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -230,11 +230,11 @@ test('Test fare_transfer_rules.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_transfer_rules.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -245,11 +245,11 @@ test('Test areas.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.areas.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -260,11 +260,11 @@ test('Test stop_areas.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_areas.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -275,11 +275,11 @@ test('Test networks.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.networks.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -290,11 +290,11 @@ test('Test route_networks.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.route_networks.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -305,11 +305,11 @@ test('Test shapes.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.shapes.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -320,11 +320,11 @@ test('Test frequencies.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.frequencies.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -335,11 +335,11 @@ test('Test transfers.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.transfers.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -350,11 +350,11 @@ test('Test pathways.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.pathways.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -365,11 +365,11 @@ test('Test levels.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.levels.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -380,11 +380,11 @@ test('Test translations.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.translations.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -395,11 +395,11 @@ test('Test feed_info.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.feed_info.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); @@ -410,11 +410,11 @@ test('Test attributions.txt IO', () => { expect(io.columns).toEqual(Object.keys(GTFS_FILES.attributions.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContent(content); + const records = io.readContentSync(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); + const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); }); diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts index 7b95314..46a3b80 100644 --- a/tests/feed-reader.test.ts +++ b/tests/feed-reader.test.ts @@ -144,20 +144,20 @@ const assert = (feed: GTFSLoadedFeed) => { test('Test FeedReader: zip path', () => { const reader = GTFSFeedReader.fromZip(ZIP_PATH); - const feed = reader.loadFeed(); + const feed = reader.loadFeedSync(); assert(feed); }, { timeout: 10000 }); test('Test FeedReader: zip content', () => { const zip = readFileSync(ZIP_PATH); const reader = GTFSFeedReader.fromZip(zip); - const feed = reader.loadFeed(); + const feed = reader.loadFeedSync(); assert(feed); }, { timeout: 10000 }); test('Test FeedReader: directory path', () => { const reader = GTFSFeedReader.fromDir(DIR_PATH); - const feed = reader.loadFeed(); + const feed = reader.loadFeedSync(); assert(feed); }, { timeout: 10000 }); @@ -173,6 +173,6 @@ test('Test FeedReader: file contents', () => { { name: 'trips.txt', content: readFileSync(join(DIR_PATH, 'trips.txt')) } ]; const reader = GTFSFeedReader.fromFiles(files); - const feed = reader.loadFeed(); + const feed = reader.loadFeedSync(); assert(feed); }, { timeout: 10000 }); diff --git a/tests/feed-writer.test.ts b/tests/feed-writer.test.ts index 405ba96..f65dcd8 100644 --- a/tests/feed-writer.test.ts +++ b/tests/feed-writer.test.ts @@ -84,7 +84,7 @@ const getTestFeed = (): GTFSFeed => ({ test('Test FeedWriter: zip', () => { const path = joinPath(OUTPUT_DIR, 'gtfs.zip'); - GTFSFeedWriter.writeZip(getTestFeed(), path); + GTFSFeedWriter.writeZipSync(getTestFeed(), path); expect(existsSync(path)).toBeTruthy(); @@ -113,7 +113,7 @@ test('Test FeedWriter: dir', () => { } } - GTFSFeedWriter.writeDirectory(getTestFeed(), OUTPUT_DIR); + GTFSFeedWriter.writeDirectorySync(getTestFeed(), OUTPUT_DIR); const files = [ 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' ]; diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 967a9ca..573fee1 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -20,7 +20,7 @@ describe('Test GTFSFileIO reading', () => { + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; - const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); expect(records.length).toEqual(4); expect(records[0].stop_id).toEqual('STOP_01'); expect(records[0].stop_code).toBeTypeOf('string'); @@ -48,10 +48,10 @@ describe('Test GTFSFileIO reading', () => { it('handles empty file content', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; - const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); expect(records.length).toEqual(0); - expect(GTFSFileIO.readContent(GTFS_FILES.agency, '').length).toEqual(0); + expect(GTFSFileIO.readContentSync(GTFS_FILES.agency, '').length).toEqual(0); }); }); @@ -63,7 +63,7 @@ describe('Test GTFSFileIO writing', () => { { route_id: 'R03', service_id: 'S01', trip_id: 'T03', trip_headsign: 'with,comma' }, { route_id: 'R02', service_id: 'S02', trip_id: 'T04', trip_headsign: 'with\nnewline' } ]; - const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records); + const content = GTFSFileIO.writeContentSync(GTFS_FILES.trips, records); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + 'R01,S01,T01,,,0,,,,\n' @@ -76,7 +76,7 @@ describe('Test GTFSFileIO writing', () => { it('handles empty input', () => { const records: GTFSFileRecords = []; - const content = GTFSFileIO.writeContent(GTFS_FILES.routes, records); + const content = GTFSFileIO.writeContentSync(GTFS_FILES.routes, records); expect(content).toEqual( 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 'route_text_color,route_sort_order,continuous_pickup,continuous_drop_off,network_id\n' diff --git a/tests/webpack.html b/tests/webpack.html index 52edd6e..15b224f 100644 --- a/tests/webpack.html +++ b/tests/webpack.html @@ -15,7 +15,6 @@ var FEED = undefined; const display = () => { - if (!FEED) return; document.getElementById('textarea').value = ''; for (const file of Object.keys(FEED)) { FEED[file] = [...FEED[file]]; @@ -29,7 +28,7 @@ const readZip = file => { const reader = new FileReader(); reader.onload = e => { - FEED = GTFSIO.GTFSFeedReader.fromZip(e.target.result).getFeed(); + FEED = GTFSIO.GTFSFeedReader.fromZip(e.target.result).getFeedSync(); display(); }; reader.readAsArrayBuffer(file); @@ -52,7 +51,7 @@ const content = await readFile(file); fileContents.push({ name: file.name, content }); } - FEED = GTFSIO.GTFSFeedReader.fromFiles(fileContents).getFeed(); + FEED = GTFSIO.GTFSFeedReader.fromFiles(fileContents).getFeedSync(); display(); }; From a5175832408a93402cee5d036640274b317d4f42 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 16:55:22 +0700 Subject: [PATCH 08/20] Updated reader from lines to chunks --- src/index.ts | 2 +- src/io/feed-file.ts | 15 +---- src/io/feed-reader.ts | 53 ++++++---------- src/io/feed-writer.ts | 38 ++++++------ src/io/file.ts | 126 ++++++++++++++++++++++++++------------ tests/feed-reader.test.ts | 8 +-- tests/file-io.test.ts | 23 +++++++ 7 files changed, 156 insertions(+), 109 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3b18d7c..452d88b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export { default as GTFSFileIO } from './io/file'; export { default as GTFSFeedReader } from './io/feed-reader'; -export { default as GTFSFeedWriter, getIterableFeedFiles } from './io/feed-writer'; +export { default as GTFSFeedWriter } from './io/feed-writer'; export * from './io/feed-file'; export * from './file-info'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index f263317..3d59b81 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -59,23 +59,14 @@ export class GTFSFeedFileIO { /** * Read lines into records. - * @param lines Iterable lines + * @param chunks Iterable file content chunks * @returns Iterable records */ - public *readSync(lines: IterableIterator): IterableIterator { - yield *GTFSFileIO.readSync(this.fileInfo, lines); + public *readSync(chunks: IterableIterator): IterableIterator { + yield *GTFSFileIO.readSync(this.fileInfo, chunks); return; } - /** - * Read line strings array into records array. - * @param lines Lines array - * @returns Records array - */ - public readLinesSync(lines: string[]): RowType[] { - return GTFSFileIO.readLinesSync(this.fileInfo, lines); - } - /** * Read file content into records array. * @param content File content diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 98e1ffe..f1337e2 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -22,35 +22,6 @@ type GTFSFile = { buffer?: Buffer }; -/** - * Generator of iterable lines from a file path. - * @param filePath File path - * @returns Iterable file lines - */ -function *readLine(filePath: string): IterableIterator { - const file = openFile(filePath, 'r'); - const bufferSize = 1024; - const buffer = Buffer.alloc(bufferSize); - let leftOver = ''; - - let readPos; - while ((readPos = readFile(file, buffer, 0, bufferSize, null)) !== 0) { - const lines = (leftOver + buffer.toString('utf8', 0, readPos)).split(/\r?\n/g); - for (const line of lines.splice(0, lines.length - 1)) { - yield line; - } - leftOver = lines[0]; - } - if (leftOver) yield leftOver; - return; -} - -function *readZip(zipEntry: AdmZip.IZipEntry): IterableIterator { - const lines = zipEntry.getData().toString().split(/\r?\n/g); - yield *lines; - return; -} - /** * GTFS feed reader. * Do not use constructor, instead, use the following static methods to initiate an instance: @@ -65,6 +36,23 @@ export default class GTFSFeedReader { /** File objects */ private files?: GTFSFile[] = undefined; + /** + * Generator of iterable chunks from a file path. + * @param filePath File path + * @returns Iterable file chunks + */ + private static *readFileChunks(filePath: string): IterableIterator { + const file = openFile(filePath, 'r'); + const bufferSize = 1024; + const buffer = Buffer.alloc(bufferSize); + + let readPos; + while ((readPos = readFile(file, buffer, 0, bufferSize, null)) !== 0) { + yield buffer.toString('utf8', 0, readPos); + } + return; + } + /** * Constructor, the object of this class is to be created by static methods. * The constructor is limited for internal calling. @@ -110,16 +98,15 @@ export default class GTFSFeedReader { const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); if (!entry.length) continue; const fileIO = getIOFromFileName(info.name); - yield { info, records: fileIO.readSync(readZip(entry[0])) }; + yield { info, records: fileIO.readSync([entry[0].getData().toString()].values()) }; } else if (this.files) { const file = this.files.filter(f => f.info.name === info.name); if (!file.length) continue; const fileIO = getIOFromFileName(info.name); if (file[0].path) { - yield { info, records: fileIO.readSync(readLine(file[0].path)) }; + yield { info, records: fileIO.readSync(GTFSFeedReader.readFileChunks(file[0].path)) }; } else if (file[0].buffer) { - const lines = file[0].buffer.toString().replace(/\r\n/g, '\n').split('\n').values(); - yield { info, records: fileIO.readSync(lines) }; + yield { info, records: fileIO.readSync([file[0].buffer.toString()].values()) }; } } } diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index c314015..c3a49fa 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -5,22 +5,6 @@ import { getIOFromFileName } from './feed-file'; import { GTFSFileName, GTFS_FILES } from '../file-info'; import type { GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSIterableFeedFiles } from '../types'; -/** - * Generator of feed by file. - * @param feed GTFS Feed - * @returns Iterable feed files - */ -export function *getIterableFeedFiles(feed: GTFSFeed): GTFSIterableFeedFiles { - const keys = Object.keys(feed) as GTFSFileName[]; - for (const key of keys) { - const info = GTFS_FILES[key]; - if (!info) continue; - const records = feed[key] as GTFSFileRecords; - yield { info, records: Array.isArray(records) ? records.values() : records }; - } - return; -} - /** * GTFS feed writer. * For static usage only. @@ -28,6 +12,22 @@ export function *getIterableFeedFiles(feed: GTFSFeed): GTFSIterableFeedFiles { export default class GTFSFeedWriter { private constructor() {} + /** + * Generator of feed by file. + * @param feed GTFS Feed + * @returns Iterable feed files + */ + private static *getIterableFilesSync(feed: GTFSFeed): GTFSIterableFeedFiles { + const keys = Object.keys(feed) as GTFSFileName[]; + for (const key of keys) { + const info = GTFS_FILES[key]; + if (!info) continue; + const records = feed[key] as GTFSFileRecords; + yield { info, records: Array.isArray(records) ? records.values() : records }; + } + return; + } + /** * Create a zip instance in memory. * @param feed GTFS Feed @@ -35,7 +35,7 @@ export default class GTFSFeedWriter { */ public static createZip(feed: GTFSFeed): AdmZip { const zip = new AdmZip(); - const files = getIterableFeedFiles(feed); + const files = GTFSFeedWriter.getIterableFilesSync(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); zip.addFile(io.fileName, Buffer.from([...io.writeSync(file.records)].join(''))); @@ -50,7 +50,7 @@ export default class GTFSFeedWriter { */ public static createFileContents(feed: GTFSFeed): GTFSFileContent[] { const fileContents: GTFSFileContent[] = []; - const files = getIterableFeedFiles(feed); + const files = GTFSFeedWriter.getIterableFilesSync(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); fileContents.push({ @@ -81,7 +81,7 @@ export default class GTFSFeedWriter { if (!existsSync(path) && mkdirIfNotExists) { mkdirSync(path, { recursive: true }); } - const files = getIterableFeedFiles(feed); + const files = GTFSFeedWriter.getIterableFilesSync(feed); const fileNames = []; const bufferLineSize = 64; for (const file of files) { diff --git a/src/io/file.ts b/src/io/file.ts index 2f03d51..aa5eb6b 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -2,59 +2,115 @@ import { stringify, parse } from 'csv/sync'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSFileRecords, GTFSFileRow } from '../types'; +type ReadChunkObject = { + file: GTFSFileInfo, + columns: string[]|undefined, + /** To redefine before reading chunk. */ + chunk: string, + /** For returning. */ + records: RowType[], + leftOver: string|undefined +}; + /** * File IO */ export default class GTFSFileIO { protected constructor() {} - /** - * Read lines and returns records. - * @param file File information - * @param lines Iterable file contents by line - * @returns Iterable records - */ - public static *readSync(file: GTFSFileInfo, lines: IterableIterator): IterableIterator { - const head = lines.next(); - if (head.done) return; - if (!head.value || !head.value.trim()) return; - let columns = (parse(head.value) as string[][])[0] - .map(c => c.trim()) - .filter(c => file.columns[c]); + private static readChunk(params: ReadChunkObject) { + params.records = []; - let lineContent = ''; - for (const line of lines) { - lineContent += line; - if (!lineContent.trim()) continue; + let content: string; + if (params.leftOver === undefined) { + content = params.chunk; + } else { + let lastNewLineIdx = params.chunk.lastIndexOf('\n'); + if (lastNewLineIdx < 0) { + params.leftOver += params.chunk; + params.chunk = ''; + return; + } + content = params.leftOver + params.chunk.slice(0, lastNewLineIdx); + params.leftOver = params.chunk.slice(lastNewLineIdx + 1); + } - let row: string[]; + let rows: string[][] = []; + while (content) { try { - row = (parse(lineContent) as string[][])[0].slice(0, columns.length); - lineContent = ''; + rows = parse(content) as string[][]; + break; } catch (ex: any) { if (ex && ex.code && ex.code === 'CSV_QUOTE_NOT_CLOSED') { - lineContent += '\n'; + let lastNewLineIdx = content.lastIndexOf('\n'); + content = content.slice(0, lastNewLineIdx); + params.leftOver = content.slice(lastNewLineIdx) + params.leftOver; continue; } throw ex; } + } + + if (!rows.length) { + return; + } + if (params.columns === undefined) { + params.columns = rows[0]; + rows.splice(0, 1); + } + + const { columns } = params; + params.records = rows.map(row => { let record: Record = {}; for (let i = 0; i < row.length; i++) { - const columnType = file.columns[columns[i]]; + const field = columns ? columns[i] : undefined; + if (!field) continue; + const columnType = columns ? params.file.columns[field] : undefined; + if (!columnType) continue; if (columnType === 'int') { - record[columns[i]] = parseInt(row[i]); + record[columns![i]] = parseInt(row[i]); } else if (columnType === 'float') { - record[columns[i]] = parseFloat(row[i]); + record[columns![i]] = parseFloat(row[i]); } else if (columnType === 'ioe') { - record[columns[i]] = row[i].trim() ? parseInt(row[i]) : ''; + record[columns![i]] = row[i].trim() ? parseInt(row[i]) : ''; } else { - record[columns[i]] = row[i].toString(); + record[columns![i]] = row[i].toString(); } } - yield record as RowType; - } + return record as RowType; + }); + } + /** + * Read lines and returns records. + * @param file File information + * @param chunks Iterable file content chunks + * @returns Iterable records + */ + public static *readSync(file: GTFSFileInfo, chunks: IterableIterator): IterableIterator { + const params: ReadChunkObject = { + file, + columns: undefined, + chunk: '', + records: [], + leftOver: '' + }; + for (const chunk of chunks) { + params.chunk = chunk.replace(/\r?\n/g, '\n'); + GTFSFileIO.readChunk(params); + for (const record of params.records) { + yield record; + } + } + if (params.leftOver) { + params.chunk = params.leftOver; + params.leftOver = undefined; + GTFSFileIO.readChunk(params); + for (const record of params.records) { + yield record; + } + } return; } @@ -76,16 +132,6 @@ export default class GTFSFileIO { return; } - /** - * Read array of content lines into records array. - * @param file File information - * @param lines Array of content lines - * @returns Array of records - */ - public static readLinesSync(file: GTFSFileInfo, lines: string[]): RowType[] { - return [...GTFSFileIO.readSync(file, lines.values())]; - } - /** * Read file content into records array. * @param file File information @@ -93,8 +139,8 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Array of record */ - public static readContentSync(file: GTFSFileInfo, content: string, newLine = '\n'): RowType[] { - return GTFSFileIO.readLinesSync(file, content.split(newLine)); + public static readContentSync(file: GTFSFileInfo, content: string): RowType[] { + return [...GTFSFileIO.readSync(file, [content].values())]; } /** diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts index 46a3b80..3a36582 100644 --- a/tests/feed-reader.test.ts +++ b/tests/feed-reader.test.ts @@ -146,20 +146,20 @@ test('Test FeedReader: zip path', () => { const reader = GTFSFeedReader.fromZip(ZIP_PATH); const feed = reader.loadFeedSync(); assert(feed); -}, { timeout: 10000 }); +}); test('Test FeedReader: zip content', () => { const zip = readFileSync(ZIP_PATH); const reader = GTFSFeedReader.fromZip(zip); const feed = reader.loadFeedSync(); assert(feed); -}, { timeout: 10000 }); +}); test('Test FeedReader: directory path', () => { const reader = GTFSFeedReader.fromDir(DIR_PATH); const feed = reader.loadFeedSync(); assert(feed); -}, { timeout: 10000 }); +}); test('Test FeedReader: file contents', () => { const files: GTFSFileContent[] = [ @@ -175,4 +175,4 @@ test('Test FeedReader: file contents', () => { const reader = GTFSFeedReader.fromFiles(files); const feed = reader.loadFeedSync(); assert(feed); -}, { timeout: 10000 }); +}); diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 573fee1..62815cd 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -46,6 +46,29 @@ describe('Test GTFSFileIO reading', () => { expect(records[3].stop_lon).toEqual(-23.48); }); + it('reads chunks into records', () => { + const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' + + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' + + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' + + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' + + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; + const chunks = [ + 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_', 'lon,location_type,parent_station\n', + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n"STOP_02",Test2,"1002",,50', + '.25,-23.28,"0",STOP_01\n', + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\nSTOP_04,Test4', + ',1004,"Description with', + '\nNew line",50.31,-23.48,0,\n' + ]; + const recordsFromContent = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); + const recordsFromChunks = [...GTFSFileIO.readSync(GTFS_FILES.stops, chunks.values())]; + expect(recordsFromChunks.length).toEqual(recordsFromContent.length); + expect(recordsFromChunks[0]).toEqual(recordsFromContent[0]); + expect(recordsFromChunks[1]).toEqual(recordsFromContent[1]); + expect(recordsFromChunks[2]).toEqual(recordsFromContent[2]); + expect(recordsFromChunks[3]).toEqual(recordsFromContent[3]); + }); + it('handles empty file content', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); From e0291a134a62429fa9763a73fc099b78e4029a15 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Sun, 3 Dec 2023 18:20:35 +0700 Subject: [PATCH 09/20] Updated --- src/io/file.ts | 20 ++- tests/feed-file-io.test.ts | 316 +++++-------------------------------- tests/file-io.test.ts | 45 +++--- 3 files changed, 74 insertions(+), 307 deletions(-) diff --git a/src/io/file.ts b/src/io/file.ts index aa5eb6b..8c2fbef 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -2,13 +2,23 @@ import { stringify, parse } from 'csv/sync'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSFileRecords, GTFSFileRow } from '../types'; +/** + * Parameter for GTFSFileIO.readChunk + */ type ReadChunkObject = { + /** GTFS file information, to define once. */ file: GTFSFileInfo, + /** Columns (table header), array if have been read, undefined if not yet. The method alters this property. */ columns: string[]|undefined, - /** To redefine before reading chunk. */ + /** Input, to redefine this property before calling GTFSFileIO.readChunk. */ chunk: string, - /** For returning. */ + /** Output, this will get set by the method. */ records: RowType[], + /** + * Input, leave as is or set to undefine for the last iteration. + * It contains string (or empty string) when there is left over string from the previous iteration to prepend to the current chunk. + * It contains undefuned for the last iteration. + */ leftOver: string|undefined }; @@ -18,6 +28,10 @@ type ReadChunkObject = { export default class GTFSFileIO { protected constructor() {} + /** + * Read chunk to records, this alter params object in the argument. + * @param params Chunk reading parameters + */ private static readChunk(params: ReadChunkObject) { params.records = []; @@ -83,7 +97,7 @@ export default class GTFSFileIO { } /** - * Read lines and returns records. + * Read chunks and returns records. * @param file File information * @param chunks Iterable file content chunks * @returns Iterable records diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts index 3119bce..1b4ad9e 100644 --- a/tests/feed-file-io.test.ts +++ b/tests/feed-file-io.test.ts @@ -28,11 +28,13 @@ import { GTFSFeedInfoIO, GTFSAttributionIO } from '../dist'; +import type { + GTFSFeedFileIO, GTFSFileInfo +} from '../dist'; -test('Test agency.txt IO', () => { - const io = new GTFSAgencyIO(); - expect(io.fileName).toEqual('agency.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.agency.columns)); +const assert = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => { + expect(io.fileName).toEqual(fileName); + expect(io.columns).toEqual(Object.keys(fileInfo.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); const records = io.readContentSync(content); @@ -42,380 +44,134 @@ test('Test agency.txt IO', () => { const lines = io.writeLinesSync(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); +} + +test('Test agency.txt IO', () => { + const io = new GTFSAgencyIO(); + assert(io, 'agency.txt', GTFS_FILES.agency); }); test('Test stops.txt IO', () => { const io = new GTFSStopIO(); - expect(io.fileName).toEqual('stops.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.stops.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'stops.txt', GTFS_FILES.stops); }); test('Test routes.txt IO', () => { const io = new GTFSRouteIO(); - expect(io.fileName).toEqual('routes.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.routes.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'routes.txt', GTFS_FILES.routes); }); test('Test trips.txt IO', () => { const io = new GTFSTripIO(); - expect(io.fileName).toEqual('trips.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.trips.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'trips.txt', GTFS_FILES.trips); }); test('Test stop_times.txt IO', () => { const io = new GTFSStopTimeIO(); - expect(io.fileName).toEqual('stop_times.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_times.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'stop_times.txt', GTFS_FILES.stop_times); }); test('Test calendar.txt IO', () => { const io = new GTFSCalendarIO(); - expect(io.fileName).toEqual('calendar.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'calendar.txt', GTFS_FILES.calendar); }); test('Test calendar_dates.txt IO', () => { const io = new GTFSCalendarDateIO(); - expect(io.fileName).toEqual('calendar_dates.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.calendar_dates.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'calendar_dates.txt', GTFS_FILES.calendar_dates); }); test('Test fare_attributes.txt IO', () => { const io = new GTFSFareAttributeIO(); - expect(io.fileName).toEqual('fare_attributes.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_attributes.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_attributes.txt', GTFS_FILES.fare_attributes); }); test('Test fare_rules.txt IO', () => { const io = new GTFSFareRuleIO(); - expect(io.fileName).toEqual('fare_rules.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_rules.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_rules.txt', GTFS_FILES.fare_rules); }); test('Test timeframes.txt IO', () => { const io = new GTFSTimeframeIO(); - expect(io.fileName).toEqual('timeframes.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.timeframes.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'timeframes.txt', GTFS_FILES.timeframes); }); test('Test fare_media.txt IO', () => { const io = new GTFSFareMediaIO(); - expect(io.fileName).toEqual('fare_media.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_media.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_media.txt', GTFS_FILES.fare_media); }); test('Test fare_products.txt IO', () => { const io = new GTFSFareProductIO(); - expect(io.fileName).toEqual('fare_products.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_products.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_products.txt', GTFS_FILES.fare_products); }); test('Test fare_leg_rules.txt IO', () => { const io = new GTFSFareLegRuleIO(); - expect(io.fileName).toEqual('fare_leg_rules.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_leg_rules.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_leg_rules.txt', GTFS_FILES.fare_leg_rules); }); test('Test fare_transfer_rules.txt IO', () => { const io = new GTFSFareTransferRuleIO(); - expect(io.fileName).toEqual('fare_transfer_rules.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.fare_transfer_rules.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'fare_transfer_rules.txt', GTFS_FILES.fare_transfer_rules); }); test('Test areas.txt IO', () => { const io = new GTFSAreaIO(); - expect(io.fileName).toEqual('areas.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.areas.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'areas.txt', GTFS_FILES.areas); }); test('Test stop_areas.txt IO', () => { const io = new GTFSStopAreaIO(); - expect(io.fileName).toEqual('stop_areas.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.stop_areas.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'stop_areas.txt', GTFS_FILES.stop_areas); }); test('Test networks.txt IO', () => { const io = new GTFSNetworkIO(); - expect(io.fileName).toEqual('networks.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.networks.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'networks.txt', GTFS_FILES.networks); }); test('Test route_networks.txt IO', () => { const io = new GTFSRouteNetworkIO(); - expect(io.fileName).toEqual('route_networks.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.route_networks.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'route_networks.txt', GTFS_FILES.route_networks); }); test('Test shapes.txt IO', () => { const io = new GTFSShapeIO(); - expect(io.fileName).toEqual('shapes.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.shapes.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'shapes.txt', GTFS_FILES.shapes); }); test('Test frequencies.txt IO', () => { const io = new GTFSFrequencyIO(); - expect(io.fileName).toEqual('frequencies.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.frequencies.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'frequencies.txt', GTFS_FILES.frequencies); }); test('Test transfers.txt IO', () => { const io = new GTFSTransferIO(); - expect(io.fileName).toEqual('transfers.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.transfers.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'transfers.txt', GTFS_FILES.transfers); }); test('Test pathways.txt IO', () => { const io = new GTFSPathwayIO(); - expect(io.fileName).toEqual('pathways.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.pathways.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'pathways.txt', GTFS_FILES.pathways); }); test('Test levels.txt IO', () => { const io = new GTFSLevelIO(); - expect(io.fileName).toEqual('levels.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.levels.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'levels.txt', GTFS_FILES.levels); }); test('Test translations.txt IO', () => { const io = new GTFSTranslationIO(); - expect(io.fileName).toEqual('translations.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.translations.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'translations.txt', GTFS_FILES.translations); }); test('Test feed_info.txt IO', () => { const io = new GTFSFeedInfoIO(); - expect(io.fileName).toEqual('feed_info.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.feed_info.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'feed_info.txt', GTFS_FILES.feed_info); }); test('Test attributions.txt IO', () => { const io = new GTFSAttributionIO(); - expect(io.fileName).toEqual('attributions.txt'); - expect(io.columns).toEqual(Object.keys(GTFS_FILES.attributions.columns)); - - const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); - expect(records.length).toEqual(1); - expect(Object.keys(records[0])).toEqual(io.columns); - - const lines = io.writeLinesSync(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + assert(io, 'attributions.txt', GTFS_FILES.attributions); }); - diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 62815cd..228bcb5 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -14,12 +14,21 @@ import type { } from '../dist'; describe('Test GTFSFileIO reading', () => { + const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' + + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' + + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' + + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' + + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; + const chunks = [ + 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_', 'lon,location_type,parent_station\n', + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n"STOP_02",Test2,"1002",,50', + '.25,-23.28,"0",STOP_01\n', + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\nSTOP_04,Test4', + ',1004,"Description with', + '\nNew line",50.31,-23.48,0,\n' + ]; + it('reads rows into records', () => { - const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' - + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' - + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' - + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' - + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); expect(records.length).toEqual(4); expect(records[0].stop_id).toEqual('STOP_01'); @@ -47,19 +56,6 @@ describe('Test GTFSFileIO reading', () => { }); it('reads chunks into records', () => { - const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n' - + 'STOP_01,"Test",1001,,50.25,-23.28,1,\n' - + '"STOP_02",Test2,"1002",,50.25,-23.28,"0",STOP_01\n' - + 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\n' - + 'STOP_04,Test4,1004,"Description with\nNew line",50.31,-23.48,0,\n'; - const chunks = [ - 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_', 'lon,location_type,parent_station\n', - 'STOP_01,"Test",1001,,50.25,-23.28,1,\n"STOP_02",Test2,"1002",,50', - '.25,-23.28,"0",STOP_01\n', - 'STOP_03,"NameWith,Comma",1003,,50.30,-23.30,0,\nSTOP_04,Test4', - ',1004,"Description with', - '\nNew line",50.31,-23.48,0,\n' - ]; const recordsFromContent = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); const recordsFromChunks = [...GTFSFileIO.readSync(GTFS_FILES.stops, chunks.values())]; expect(recordsFromChunks.length).toEqual(recordsFromContent.length); @@ -79,13 +75,14 @@ describe('Test GTFSFileIO reading', () => { }); describe('Test GTFSFileIO writing', () => { + const records: GTFSFileRecords = [ + { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, + { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' }, + { route_id: 'R03', service_id: 'S01', trip_id: 'T03', trip_headsign: 'with,comma' }, + { route_id: 'R02', service_id: 'S02', trip_id: 'T04', trip_headsign: 'with\nnewline' } + ]; + it('writes records into rows', () => { - const records: GTFSFileRecords = [ - { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, - { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' }, - { route_id: 'R03', service_id: 'S01', trip_id: 'T03', trip_headsign: 'with,comma' }, - { route_id: 'R02', service_id: 'S02', trip_id: 'T04', trip_headsign: 'with\nnewline' } - ]; const content = GTFSFileIO.writeContentSync(GTFS_FILES.trips, records); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' From e95004992086eb22ddd7106117416b4fd2edb311 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 09:54:43 +0700 Subject: [PATCH 10/20] Back to sync path = =" --- src/io/feed-file.ts | 20 ++++++++++---------- src/io/feed-reader.ts | 16 ++++++++-------- src/io/feed-writer.ts | 18 +++++++++--------- src/io/file.ts | 16 ++++++++-------- tests/feed-file-io.test.ts | 4 ++-- tests/feed-reader.test.ts | 8 ++++---- tests/feed-writer.test.ts | 21 +++++++++++++-------- tests/file-io.test.ts | 14 +++++++------- 8 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 3d59b81..8f1054a 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -62,8 +62,8 @@ export class GTFSFeedFileIO { * @param chunks Iterable file content chunks * @returns Iterable records */ - public *readSync(chunks: IterableIterator): IterableIterator { - yield *GTFSFileIO.readSync(this.fileInfo, chunks); + public *read(chunks: IterableIterator): IterableIterator { + yield *GTFSFileIO.read(this.fileInfo, chunks); return; } @@ -72,8 +72,8 @@ export class GTFSFeedFileIO { * @param content File content * @returns Records array */ - public readContentSync(content: string): RowType[] { - return GTFSFileIO.readContentSync(this.fileInfo, content); + public readContent(content: string): RowType[] { + return GTFSFileIO.readContent(this.fileInfo, content); } /** @@ -81,8 +81,8 @@ export class GTFSFeedFileIO { * @param records Iterable records * @returns Iterable lines */ - public *writeSync(records: IterableIterator): IterableIterator { - yield *GTFSFileIO.writeSync(this.fileInfo, records); + public *write(records: IterableIterator): IterableIterator { + yield *GTFSFileIO.write(this.fileInfo, records); return; } @@ -91,8 +91,8 @@ export class GTFSFeedFileIO { * @param records Records array * @returns Lines array */ - public writeLinesSync(records: RowType[]): string[] { - return GTFSFileIO.writeLinesSync(this.fileInfo, records); + public writeLines(records: RowType[]): string[] { + return GTFSFileIO.writeLines(this.fileInfo, records); } /** @@ -100,8 +100,8 @@ export class GTFSFeedFileIO { * @param records Records array * @returns File content */ - public writeContentSync(records: RowType[]): string { - return GTFSFileIO.writeContentSync(this.fileInfo, records); + public writeContent(records: RowType[]): string { + return GTFSFileIO.writeContent(this.fileInfo, records); } }; diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index f1337e2..432e539 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -92,21 +92,21 @@ export default class GTFSFeedReader { * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). * @returns Iterable feed files */ - public *getIterableFilesSync(): GTFSIterableFeedFiles { + public *getIterableFiles(): GTFSIterableFeedFiles { for (const info of getGTFSFileInfos()) { if (this.zip) { const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); if (!entry.length) continue; const fileIO = getIOFromFileName(info.name); - yield { info, records: fileIO.readSync([entry[0].getData().toString()].values()) }; + yield { info, records: fileIO.read([entry[0].getData().toString()].values()) }; } else if (this.files) { const file = this.files.filter(f => f.info.name === info.name); if (!file.length) continue; const fileIO = getIOFromFileName(info.name); if (file[0].path) { - yield { info, records: fileIO.readSync(GTFSFeedReader.readFileChunks(file[0].path)) }; + yield { info, records: fileIO.read(GTFSFeedReader.readFileChunks(file[0].path)) }; } else if (file[0].buffer) { - yield { info, records: fileIO.readSync([file[0].buffer.toString()].values()) }; + yield { info, records: fileIO.read([file[0].buffer.toString()].values()) }; } } } @@ -117,7 +117,7 @@ export default class GTFSFeedReader { * Get feed object with row being file name without .txt and value being iterable records. * @returns Feed object with row being file name without .txt and value being iterable records. */ - public getFeedSync(): GTFSFeed { + public getFeed(): GTFSFeed { const results: Partial> = { agency: [], stops: [], @@ -126,7 +126,7 @@ export default class GTFSFeedReader { stop_times: [] }; - for (const file of this.getIterableFilesSync()) { + for (const file of this.getIterableFiles()) { const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; results[key] = file.records; } @@ -138,8 +138,8 @@ export default class GTFSFeedReader { * Get feed object with row being file name without .txt and value being array of records. * @returns Feed object with row being file name without .txt and value being array of records. */ - public loadFeedSync(): GTFSLoadedFeed { - const feed = this.getFeedSync(); + public loadFeed(): GTFSLoadedFeed { + const feed = this.getFeed(); const results: Partial> = {}; for (const key of Object.keys(feed) as GTFSFileName[]) { diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index c3a49fa..e12ef77 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -17,7 +17,7 @@ export default class GTFSFeedWriter { * @param feed GTFS Feed * @returns Iterable feed files */ - private static *getIterableFilesSync(feed: GTFSFeed): GTFSIterableFeedFiles { + private static *getIterableFiles(feed: GTFSFeed): GTFSIterableFeedFiles { const keys = Object.keys(feed) as GTFSFileName[]; for (const key of keys) { const info = GTFS_FILES[key]; @@ -35,10 +35,10 @@ export default class GTFSFeedWriter { */ public static createZip(feed: GTFSFeed): AdmZip { const zip = new AdmZip(); - const files = GTFSFeedWriter.getIterableFilesSync(feed); + const files = GTFSFeedWriter.getIterableFiles(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); - zip.addFile(io.fileName, Buffer.from([...io.writeSync(file.records)].join(''))); + zip.addFile(io.fileName, Buffer.from([...io.write(file.records)].join(''))); } return zip; } @@ -50,12 +50,12 @@ export default class GTFSFeedWriter { */ public static createFileContents(feed: GTFSFeed): GTFSFileContent[] { const fileContents: GTFSFileContent[] = []; - const files = GTFSFeedWriter.getIterableFilesSync(feed); + const files = GTFSFeedWriter.getIterableFiles(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); fileContents.push({ name: io.fileName, - content: [...io.writeSync(file.records)].join('') + content: [...io.write(file.records)].join('') }); } return fileContents; @@ -66,7 +66,7 @@ export default class GTFSFeedWriter { * @param feed GTFS Feed * @param path Path to output zip file */ - public static writeZipSync(feed: GTFSFeed, path: string): void { + public static writeZip(feed: GTFSFeed, path: string): void { GTFSFeedWriter.createZip(feed).writeZip(path); } @@ -77,11 +77,11 @@ export default class GTFSFeedWriter { * @param mkdirIfNotExists True to recursively create a directory at the path if does not exist * @returns File names without directory path, with .txt extension. */ - public static writeDirectorySync(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { + public static writeDirectory(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { if (!existsSync(path) && mkdirIfNotExists) { mkdirSync(path, { recursive: true }); } - const files = GTFSFeedWriter.getIterableFilesSync(feed); + const files = GTFSFeedWriter.getIterableFiles(feed); const fileNames = []; const bufferLineSize = 64; for (const file of files) { @@ -89,7 +89,7 @@ export default class GTFSFeedWriter { const io = getIOFromFileName(file.info.name); const filePath = joinPath(path, io.fileName); writeFileSync(filePath, ''); - for (const row of io.writeSync(file.records)) { + for (const row of io.write(file.records)) { chunks.push(row); if (chunks.length > bufferLineSize) { appendFileSync(filePath, chunks.join('')); diff --git a/src/io/file.ts b/src/io/file.ts index 8c2fbef..8e49c47 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -102,7 +102,7 @@ export default class GTFSFileIO { * @param chunks Iterable file content chunks * @returns Iterable records */ - public static *readSync(file: GTFSFileInfo, chunks: IterableIterator): IterableIterator { + public static *read(file: GTFSFileInfo, chunks: IterableIterator): IterableIterator { const params: ReadChunkObject = { file, columns: undefined, @@ -135,7 +135,7 @@ export default class GTFSFileIO { * @param newLine New line delimiter * @returns Iterable file contents by line */ - public static *writeSync(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { + public static *write(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { const columns = Object.keys(file.columns); yield stringify([columns], { record_delimiter: newLine }); @@ -153,8 +153,8 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Array of record */ - public static readContentSync(file: GTFSFileInfo, content: string): RowType[] { - return [...GTFSFileIO.readSync(file, [content].values())]; + public static readContent(file: GTFSFileInfo, content: string): RowType[] { + return [...GTFSFileIO.read(file, [content].values())]; } /** @@ -164,8 +164,8 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns Lines array */ - public static writeLinesSync(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { - return [...GTFSFileIO.writeSync(file, records.values(), newLine)]; + public static writeLines(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { + return [...GTFSFileIO.write(file, records.values(), newLine)]; } /** @@ -175,7 +175,7 @@ export default class GTFSFileIO { * @param newLine Line separator (default: \n) * @returns File content */ - public static writeContentSync(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { - return GTFSFileIO.writeLinesSync(file, records, newLine).join(''); + public static writeContent(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { + return GTFSFileIO.writeLines(file, records, newLine).join(''); } }; diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts index 1b4ad9e..4c9ae8f 100644 --- a/tests/feed-file-io.test.ts +++ b/tests/feed-file-io.test.ts @@ -37,11 +37,11 @@ const assert = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => expect(io.columns).toEqual(Object.keys(fileInfo.columns)); const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); - const records = io.readContentSync(content); + const records = io.readContent(content); expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLinesSync(records); + const lines = io.writeLines(records); expect(lines.length).toEqual(2); expect(lines[0]).toEqual(io.columns.join(',') + '\n'); } diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts index 3a36582..894d07b 100644 --- a/tests/feed-reader.test.ts +++ b/tests/feed-reader.test.ts @@ -144,20 +144,20 @@ const assert = (feed: GTFSLoadedFeed) => { test('Test FeedReader: zip path', () => { const reader = GTFSFeedReader.fromZip(ZIP_PATH); - const feed = reader.loadFeedSync(); + const feed = reader.loadFeed(); assert(feed); }); test('Test FeedReader: zip content', () => { const zip = readFileSync(ZIP_PATH); const reader = GTFSFeedReader.fromZip(zip); - const feed = reader.loadFeedSync(); + const feed = reader.loadFeed(); assert(feed); }); test('Test FeedReader: directory path', () => { const reader = GTFSFeedReader.fromDir(DIR_PATH); - const feed = reader.loadFeedSync(); + const feed = reader.loadFeed(); assert(feed); }); @@ -173,6 +173,6 @@ test('Test FeedReader: file contents', () => { { name: 'trips.txt', content: readFileSync(join(DIR_PATH, 'trips.txt')) } ]; const reader = GTFSFeedReader.fromFiles(files); - const feed = reader.loadFeedSync(); + const feed = reader.loadFeed(); assert(feed); }); diff --git a/tests/feed-writer.test.ts b/tests/feed-writer.test.ts index f65dcd8..5aa6125 100644 --- a/tests/feed-writer.test.ts +++ b/tests/feed-writer.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; import AdmZip from 'adm-zip'; -import { existsSync, readFileSync, readdirSync, rmSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'fs'; import { join as joinPath } from 'path'; import { GTFSContinuousPickupDropOff, @@ -83,8 +83,12 @@ const getTestFeed = (): GTFSFeed => ({ }); test('Test FeedWriter: zip', () => { - const path = joinPath(OUTPUT_DIR, 'gtfs.zip'); - GTFSFeedWriter.writeZipSync(getTestFeed(), path); + const dir = joinPath(OUTPUT_DIR, 'zip'); + const path = joinPath(dir, 'gtfs.zip'); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + GTFSFeedWriter.writeZip(getTestFeed(), path); expect(existsSync(path)).toBeTruthy(); @@ -107,21 +111,22 @@ test('Test FeedWriter: zip', () => { }); test('Test FeedWriter: dir', () => { - if (existsSync(OUTPUT_DIR)) { - for (const file of readdirSync(OUTPUT_DIR).filter(x => x.endsWith('.txt')).map(x => joinPath(OUTPUT_DIR, x))) { + const path = joinPath(OUTPUT_DIR, 'gtfs'); + if (existsSync(path)) { + for (const file of readdirSync(path).filter(x => x.endsWith('.txt')).map(x => joinPath(path, x))) { rmSync(file); } } - GTFSFeedWriter.writeDirectorySync(getTestFeed(), OUTPUT_DIR); + GTFSFeedWriter.writeDirectory(getTestFeed(), path); const files = [ 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' ]; - expect(files.every(x => existsSync(joinPath(OUTPUT_DIR, x)))).toBeTruthy(); + expect(files.every(x => existsSync(joinPath(path, x)))).toBeTruthy(); for (const file of files) { expect( - readFileSync(joinPath(OUTPUT_DIR, file)).toString().split(/\r?\n/g), + readFileSync(joinPath(path, file)).toString().split(/\r?\n/g), file ).toEqual( readFileSync(joinPath(COMPARE_DIR, file)).toString().split(/\r?\n/g) diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 228bcb5..72d7f11 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -29,7 +29,7 @@ describe('Test GTFSFileIO reading', () => { ]; it('reads rows into records', () => { - const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); expect(records.length).toEqual(4); expect(records[0].stop_id).toEqual('STOP_01'); expect(records[0].stop_code).toBeTypeOf('string'); @@ -56,8 +56,8 @@ describe('Test GTFSFileIO reading', () => { }); it('reads chunks into records', () => { - const recordsFromContent = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); - const recordsFromChunks = [...GTFSFileIO.readSync(GTFS_FILES.stops, chunks.values())]; + const recordsFromContent = GTFSFileIO.readContent(GTFS_FILES.stops, content); + const recordsFromChunks = [...GTFSFileIO.read(GTFS_FILES.stops, chunks.values())]; expect(recordsFromChunks.length).toEqual(recordsFromContent.length); expect(recordsFromChunks[0]).toEqual(recordsFromContent[0]); expect(recordsFromChunks[1]).toEqual(recordsFromContent[1]); @@ -67,10 +67,10 @@ describe('Test GTFSFileIO reading', () => { it('handles empty file content', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; - const records = GTFSFileIO.readContentSync(GTFS_FILES.stops, content); + const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); expect(records.length).toEqual(0); - expect(GTFSFileIO.readContentSync(GTFS_FILES.agency, '').length).toEqual(0); + expect(GTFSFileIO.readContent(GTFS_FILES.agency, '').length).toEqual(0); }); }); @@ -83,7 +83,7 @@ describe('Test GTFSFileIO writing', () => { ]; it('writes records into rows', () => { - const content = GTFSFileIO.writeContentSync(GTFS_FILES.trips, records); + const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + 'R01,S01,T01,,,0,,,,\n' @@ -96,7 +96,7 @@ describe('Test GTFSFileIO writing', () => { it('handles empty input', () => { const records: GTFSFileRecords = []; - const content = GTFSFileIO.writeContentSync(GTFS_FILES.routes, records); + const content = GTFSFileIO.writeContent(GTFS_FILES.routes, records); expect(content).toEqual( 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' + 'route_text_color,route_sort_order,continuous_pickup,continuous_drop_off,network_id\n' From 2b4c4222b9a1f860ac1250228057d9bc31996abb Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 10:46:53 +0700 Subject: [PATCH 11/20] Restructured file IO --- src/index.ts | 2 + src/io/feed-file.ts | 9 --- src/io/file.ts | 156 ++++++++----------------------------- src/io/reader.ts | 113 +++++++++++++++++++++++++++ src/io/types.ts | 7 ++ src/io/writer.ts | 48 ++++++++++++ tests/feed-file-io.test.ts | 5 +- tests/file-io.test.ts | 2 +- 8 files changed, 205 insertions(+), 137 deletions(-) create mode 100644 src/io/reader.ts create mode 100644 src/io/types.ts create mode 100644 src/io/writer.ts diff --git a/src/index.ts b/src/index.ts index 452d88b..1f31c70 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,8 @@ export { GTFSTripDirection, GTFSTripBikesAllowed } from './files/trip'; export type * from './file-info'; export type * from './types'; +export type * from './io/types'; + export type * from './files/agency'; export type * from './files/area'; export type * from './files/attribution'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 8f1054a..3729f1a 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -86,15 +86,6 @@ export class GTFSFeedFileIO { return; } - /** - * Write records array into line strings array. - * @param records Records array - * @returns Lines array - */ - public writeLines(records: RowType[]): string[] { - return GTFSFileIO.writeLines(this.fileInfo, records); - } - /** * Write records array into file content. * @param records Records array diff --git a/src/io/file.ts b/src/io/file.ts index 8e49c47..a21327f 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,26 +1,15 @@ -import { stringify, parse } from 'csv/sync'; +import { getInitialReadChunkParams, readChunk } from './reader'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSFileRecords, GTFSFileRow } from '../types'; +import { getInitialWriteChunkParams, getRecordsHeader, writeRecords } from './writer'; +import { GTFSIOWriteOptions } from './types'; -/** - * Parameter for GTFSFileIO.readChunk - */ -type ReadChunkObject = { - /** GTFS file information, to define once. */ - file: GTFSFileInfo, - /** Columns (table header), array if have been read, undefined if not yet. The method alters this property. */ - columns: string[]|undefined, - /** Input, to redefine this property before calling GTFSFileIO.readChunk. */ - chunk: string, - /** Output, this will get set by the method. */ - records: RowType[], - /** - * Input, leave as is or set to undefine for the last iteration. - * It contains string (or empty string) when there is left over string from the previous iteration to prepend to the current chunk. - * It contains undefuned for the last iteration. - */ - leftOver: string|undefined -}; +function normaliseWriteOptions(options?: GTFSIOWriteOptions): GTFSIOWriteOptions { + if (!options) options = {}; + options.newline = options.newline ?? '\n'; + options.recordsBufferSize = options.recordsBufferSize ?? 64; + return options; +} /** * File IO @@ -28,74 +17,6 @@ type ReadChunkObject = { export default class GTFSFileIO { protected constructor() {} - /** - * Read chunk to records, this alter params object in the argument. - * @param params Chunk reading parameters - */ - private static readChunk(params: ReadChunkObject) { - params.records = []; - - let content: string; - if (params.leftOver === undefined) { - content = params.chunk; - } else { - let lastNewLineIdx = params.chunk.lastIndexOf('\n'); - if (lastNewLineIdx < 0) { - params.leftOver += params.chunk; - params.chunk = ''; - return; - } - content = params.leftOver + params.chunk.slice(0, lastNewLineIdx); - params.leftOver = params.chunk.slice(lastNewLineIdx + 1); - } - - let rows: string[][] = []; - while (content) { - try { - rows = parse(content) as string[][]; - break; - } catch (ex: any) { - if (ex && ex.code && ex.code === 'CSV_QUOTE_NOT_CLOSED') { - let lastNewLineIdx = content.lastIndexOf('\n'); - content = content.slice(0, lastNewLineIdx); - params.leftOver = content.slice(lastNewLineIdx) + params.leftOver; - continue; - } - throw ex; - } - } - - if (!rows.length) { - return; - } - - if (params.columns === undefined) { - params.columns = rows[0]; - rows.splice(0, 1); - } - - const { columns } = params; - params.records = rows.map(row => { - let record: Record = {}; - for (let i = 0; i < row.length; i++) { - const field = columns ? columns[i] : undefined; - if (!field) continue; - const columnType = columns ? params.file.columns[field] : undefined; - if (!columnType) continue; - if (columnType === 'int') { - record[columns![i]] = parseInt(row[i]); - } else if (columnType === 'float') { - record[columns![i]] = parseFloat(row[i]); - } else if (columnType === 'ioe') { - record[columns![i]] = row[i].trim() ? parseInt(row[i]) : ''; - } else { - record[columns![i]] = row[i].toString(); - } - } - return record as RowType; - }); - } - /** * Read chunks and returns records. * @param file File information @@ -103,27 +24,17 @@ export default class GTFSFileIO { * @returns Iterable records */ public static *read(file: GTFSFileInfo, chunks: IterableIterator): IterableIterator { - const params: ReadChunkObject = { - file, - columns: undefined, - chunk: '', - records: [], - leftOver: '' - }; + const params = getInitialReadChunkParams(file); for (const chunk of chunks) { params.chunk = chunk.replace(/\r?\n/g, '\n'); - GTFSFileIO.readChunk(params); - for (const record of params.records) { - yield record; - } + readChunk(params); + for (const record of params.records) yield record; } if (params.leftOver) { params.chunk = params.leftOver; params.leftOver = undefined; - GTFSFileIO.readChunk(params); - for (const record of params.records) { - yield record; - } + readChunk(params); + for (const record of params.records) yield record; } return; } @@ -132,17 +43,25 @@ export default class GTFSFileIO { * Write records into line contents. * @param file File Information * @param records Iterable records - * @param newLine New line delimiter + * @param options write options * @returns Iterable file contents by line */ - public static *write(file: GTFSFileInfo, records: GTFSFileRecords, newLine = '\n'): IterableIterator { - const columns = Object.keys(file.columns); - yield stringify([columns], { record_delimiter: newLine }); - + public static *write(file: GTFSFileInfo, records: GTFSFileRecords, options?: GTFSIOWriteOptions): IterableIterator { + options = normaliseWriteOptions(options); + const params = getInitialWriteChunkParams(file); + yield getRecordsHeader(params, options); for (const record of records) { - yield stringify([columns.map(c => (record as Record)[c] ?? undefined)], { record_delimiter: newLine }); + params.records.push(record); + if (params.records.length >= options.recordsBufferSize!) { + writeRecords(params, options); + yield params.output; + params.records = []; + } + } + if (params.records.length) { + writeRecords(params, options); + yield params.output; } - return; } @@ -157,25 +76,14 @@ export default class GTFSFileIO { return [...GTFSFileIO.read(file, [content].values())]; } - /** - * Write records into array of lines content. - * @param file File information - * @param records Row records - * @param newLine Line separator (default: \n) - * @returns Lines array - */ - public static writeLines(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string[] { - return [...GTFSFileIO.write(file, records.values(), newLine)]; - } - /** * Write records into file content. * @param file File information * @param records Row records - * @param newLine Line separator (default: \n) + * @param options Write options * @returns File content */ - public static writeContent(file: GTFSFileInfo, records: RowType[], newLine = '\n'): string { - return GTFSFileIO.writeLines(file, records, newLine).join(''); + public static writeContent(file: GTFSFileInfo, records: RowType[], options?: GTFSIOWriteOptions): string { + return [...GTFSFileIO.write(file, records.values(), options)].join(''); } }; diff --git a/src/io/reader.ts b/src/io/reader.ts new file mode 100644 index 0000000..a7b2f25 --- /dev/null +++ b/src/io/reader.ts @@ -0,0 +1,113 @@ +import { parse } from 'csv/sync'; +import type { GTFSFileInfo } from '../file-info'; +import type { GTFSFileRow } from '../types'; + +/** + * Parameter for readChunk() + */ +export type ReadChunkParam = { + /** GTFS file information, to be defined once. */ + file: GTFSFileInfo, + /** Columns (table header), array if have been read, undefined if not yet. The method alters this property. */ + columns: string[]|undefined, + /** Input, to redefine this property before calling GTFSFileIO.readChunk. */ + chunk: string, + /** Output, this will get set by the method. */ + records: RowType[], + /** + * Input, leave as is or set to undefine for the last iteration. + * It contains string (or empty string) when there is left over string from the previous iteration to prepend to the current chunk. + * It contains undefuned for the last iteration. + */ + leftOver: string|undefined +}; + +/** + * Get initial state of read chunk parameters. + * @param file File information + * @returns Object of chunk reading parameters in the initial state + */ +export function getInitialReadChunkParams(file: GTFSFileInfo): ReadChunkParam { + return { + file, + columns: undefined, + chunk: '', + records: [], + leftOver: '' + }; +}; + +/** + * Read chunk to records, this alter params object in the argument. + * @param params Chunk reading parameters + */ +export function readChunk(params: ReadChunkParam) { + params.records = []; + + // Cut the current chunk until the last occurence of new line, in case of the last line of current chunk is not complete. + // Read chunk to content by appending to the previous leftover, + // and set the next leftover to be the substring after the last occurence of new line. + let content: string; + if (params.leftOver === undefined) { + content = params.chunk; + } else { + let lastNewLineIdx = params.chunk.lastIndexOf('\n'); + if (lastNewLineIdx < 0) { + params.leftOver += params.chunk; + params.chunk = ''; + return; + } + content = params.leftOver + params.chunk.slice(0, lastNewLineIdx); + params.leftOver = params.chunk.slice(lastNewLineIdx + 1); + } + + // If content cannot be parsed due to quotes not being closed, + // cut the last line out of the content and prepend it to the leftover. + let rows: string[][] = []; + while (content) { + try { + rows = parse(content) as string[][]; + break; + } catch (ex: any) { + if (ex && ex.code && ex.code === 'CSV_QUOTE_NOT_CLOSED') { + let lastNewLineIdx = content.lastIndexOf('\n'); + content = content.slice(0, lastNewLineIdx); + params.leftOver = content.slice(lastNewLineIdx) + params.leftOver; + continue; + } + throw ex; + } + } + + if (!rows.length) { + return; + } + + // If columns are not defined, then the first row is. + if (params.columns === undefined) { + params.columns = rows[0]; + rows.splice(0, 1); + } + + // Parse rows and set records. + const { columns } = params; + params.records = rows.map(row => { + let record: Record = {}; + for (let i = 0; i < row.length; i++) { + const field = columns ? columns[i] : undefined; + if (!field) continue; + const columnType = columns ? params.file.columns[field] : undefined; + if (!columnType) continue; + if (columnType === 'int') { + record[columns![i]] = row[i].trim() ? parseInt(row[i]) : undefined; + } else if (columnType === 'float') { + record[columns![i]] = row[i].trim() ? parseFloat(row[i]) : undefined; + } else if (columnType === 'ioe') { + record[columns![i]] = row[i].trim() ? parseInt(row[i]) : ''; + } else { + record[columns![i]] = row[i].toString(); + } + } + return record as RowType; + }); +}; diff --git a/src/io/types.ts b/src/io/types.ts new file mode 100644 index 0000000..fdb7d6c --- /dev/null +++ b/src/io/types.ts @@ -0,0 +1,7 @@ +/** Options for GTFSFileIO.write */ +export type GTFSIOWriteOptions = { + /** New line delimiter */ + newline?: string, + /** Number of records to be collected for each yielded result. */ + recordsBufferSize?: number +}; diff --git a/src/io/writer.ts b/src/io/writer.ts new file mode 100644 index 0000000..070a03e --- /dev/null +++ b/src/io/writer.ts @@ -0,0 +1,48 @@ +import { stringify } from 'csv/sync'; +import type { GTFSIOWriteOptions } from './types'; +import type { GTFSFileInfo } from '../file-info'; +import type { GTFSFileRow } from '../types'; + +/** + * Parameter for writeRecords() + */ +export type WriteParam = { + /** GTFS file information, to be defined once. */ + file: GTFSFileInfo, + /** Columns (table header), to be internally altered by writeRecords(). */ + columns: string[]|undefined, + /** Records to be written. */ + records: RowType[], + /** Output string */ + output: string +}; + +/** + * Get the initial state of write records parameters. + * @param file File information + * @returns Object of writeRecords parameters in the initial state + */ +export function getInitialWriteChunkParams(file: GTFSFileInfo): WriteParam { + return { + file, + columns: undefined, + records: [], + output: '' + }; +}; + +export function getRecordsHeader(params: WriteParam, options: GTFSIOWriteOptions): string { + params.columns = Object.keys(params.file.columns); + return stringify([params.columns], { record_delimiter: options.newline }); +} + +/** + * Write records into contetn string. + * @param params WriteParam + */ +export function writeRecords(params: WriteParam, options: GTFSIOWriteOptions) { + const rows: (string|number|undefined)[][] = params.records.map( + record => params.columns!.map(column => (record as Record)[column] ?? undefined) + ); + params.output = stringify(rows, { record_delimiter: options.newline }); +} diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts index 4c9ae8f..93f38eb 100644 --- a/tests/feed-file-io.test.ts +++ b/tests/feed-file-io.test.ts @@ -41,9 +41,8 @@ const assert = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => expect(records.length).toEqual(1); expect(Object.keys(records[0])).toEqual(io.columns); - const lines = io.writeLines(records); - expect(lines.length).toEqual(2); - expect(lines[0]).toEqual(io.columns.join(',') + '\n'); + const writtenContent = io.writeContent(records); + expect(writtenContent).toEqual(content + '\n'); } test('Test agency.txt IO', () => { diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 72d7f11..de4713a 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -83,7 +83,7 @@ describe('Test GTFSFileIO writing', () => { ]; it('writes records into rows', () => { - const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records); + const content = GTFSFileIO.writeContent(GTFS_FILES.trips, records, { recordsBufferSize: 2 }); expect(content).toEqual( 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + 'R01,S01,T01,,,0,,,,\n' From 38ac973298dcf7fbaebb2b43eff3b85e65e3b4e4 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 11:18:47 +0700 Subject: [PATCH 12/20] Added async file IO --- src/index.ts | 2 +- src/io/feed-file.ts | 2 +- src/io/feed-writer.ts | 20 +++----- src/io/file.ts | 111 +++++++++++++++++++++++++++++++++++++++++- src/types.ts | 72 ++++++++++++++++++++++++++- tests/file-io.test.ts | 25 ++++++++++ 6 files changed, 213 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1f31c70..d0e44d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { default as GTFSFileIO } from './io/file'; +export { GTFSFileIO, GTFSAsyncFileIO } from './io/file'; export { default as GTFSFeedReader } from './io/feed-reader'; export { default as GTFSFeedWriter } from './io/feed-writer'; export * from './io/feed-file'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 3729f1a..3f03782 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -1,4 +1,4 @@ -import GTFSFileIO from './file'; +import { GTFSFileIO } from './file'; import { GTFS_FILES } from '../file-info'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSFileRow } from '../types'; diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index e12ef77..0deea1e 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -38,7 +38,9 @@ export default class GTFSFeedWriter { const files = GTFSFeedWriter.getIterableFiles(feed); for (const file of files) { const io = getIOFromFileName(file.info.name); - zip.addFile(io.fileName, Buffer.from([...io.write(file.records)].join(''))); + zip.addFile(io.fileName, Buffer.from( + Array.isArray(file.records) ? io.writeContent(file.records) : [...io.write(file.records)].join('') + )); } return zip; } @@ -55,7 +57,7 @@ export default class GTFSFeedWriter { const io = getIOFromFileName(file.info.name); fileContents.push({ name: io.fileName, - content: [...io.write(file.records)].join('') + content: Array.isArray(file.records) ? io.writeContent(file.records) : [...io.write(file.records)].join('') }); } return fileContents; @@ -83,21 +85,13 @@ export default class GTFSFeedWriter { } const files = GTFSFeedWriter.getIterableFiles(feed); const fileNames = []; - const bufferLineSize = 64; for (const file of files) { - let chunks = []; const io = getIOFromFileName(file.info.name); const filePath = joinPath(path, io.fileName); writeFileSync(filePath, ''); - for (const row of io.write(file.records)) { - chunks.push(row); - if (chunks.length > bufferLineSize) { - appendFileSync(filePath, chunks.join('')); - chunks = []; - } - } - if (chunks.length) { - appendFileSync(filePath, chunks.join('')); + const contents = Array.isArray(file.records) ? [io.writeContent(file.records)] : io.write(file.records); + for (const content of contents) { + appendFileSync(filePath, content); } fileNames.push(io.fileName); } diff --git a/src/io/file.ts b/src/io/file.ts index a21327f..227ec02 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,6 +1,6 @@ import { getInitialReadChunkParams, readChunk } from './reader'; import type { GTFSFileInfo } from '../file-info'; -import type { GTFSFileRecords, GTFSFileRow } from '../types'; +import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow } from '../types'; import { getInitialWriteChunkParams, getRecordsHeader, writeRecords } from './writer'; import { GTFSIOWriteOptions } from './types'; @@ -14,7 +14,7 @@ function normaliseWriteOptions(options?: GTFSIOWriteOptions): GTFSIOWriteOptions /** * File IO */ -export default class GTFSFileIO { +export class GTFSFileIO { protected constructor() {} /** @@ -87,3 +87,110 @@ export default class GTFSFileIO { return [...GTFSFileIO.write(file, records.values(), options)].join(''); } }; + +/** + * File Async IO + */ +export class GTFSAsyncFileIO { + protected constructor() {} + + /** + * Read chunks and returns records. + * @param file File information + * @param chunks Iterable file content chunks + * @returns Iterable records + */ + public static async *read(file: GTFSFileInfo, chunks: AsyncIterableIterator): AsyncIterableIterator { + const params = getInitialReadChunkParams(file); + for await (const chunk of chunks) { + params.chunk = chunk.replace(/\r?\n/g, '\n'); + readChunk(params); + for (const record of params.records) yield record; + } + if (params.leftOver) { + params.chunk = params.leftOver; + params.leftOver = undefined; + readChunk(params); + for (const record of params.records) yield record; + } + return; + } + + /** + * Write records into line contents. + * @param file File Information + * @param records Iterable records + * @param options write options + * @returns Iterable file contents by line + */ + public static async *write(file: GTFSFileInfo, records: GTFSAsyncFileRecords, options?: GTFSIOWriteOptions): AsyncIterableIterator { + options = normaliseWriteOptions(options); + const params = getInitialWriteChunkParams(file); + yield getRecordsHeader(params, options); + for await (const record of records) { + params.records.push(record); + if (params.records.length >= options.recordsBufferSize!) { + writeRecords(params, options); + yield params.output; + params.records = []; + } + } + if (params.records.length) { + writeRecords(params, options); + yield params.output; + } + return; + } + + /** + * Await for all chunks and return all the records. + * @param file File information + * @param chunks Chunks generator + * @returns Promise of all records + */ + public static async readAll(file: GTFSFileInfo, chunks: AsyncIterableIterator): Promise { + const records: RowType[] = []; + for await (const record of GTFSAsyncFileIO.read(file, chunks)) { + records.push(record); + } + return records; + } + + /** + * Read file content and return all the records. + * @param file File information + * @param content File content + * @returns Promise of all records + */ + public static async readAllContent(file: GTFSFileInfo, content: string): Promise { + const generator = async function*() { yield content; }; + return GTFSAsyncFileIO.readAll(file, generator()); + } + + /** + * Await for all records and return file content. + * @param file File information + * @param records Records generator + * @param options Write options + * @returns Promise of file content + */ + public static async writeAll(file: GTFSFileInfo, records: AsyncIterableIterator, options?: GTFSIOWriteOptions): Promise { + let result = ''; + for await (const content of GTFSAsyncFileIO.write(file, records, options)) { + result += content; + } + return result; + } + + /** + * Write file content from given records. + * @param file File information + * @param records Records array + * @param options Write options + * @returns Promise of all records + */ + public static async writeAllRecords(file: GTFSFileInfo, records: RowType[], options?: GTFSIOWriteOptions): Promise { + const generator = async function*() { for (const record of records) yield record; }; + return GTFSAsyncFileIO.writeAll(file, generator(), options); + } +} diff --git a/src/types.ts b/src/types.ts index 59ae553..30768ca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -57,15 +57,27 @@ export type GTFSFileRow = GTFSAgency /** GTFS file records */ export type GTFSFileRecords = IterableIterator|RowType[]; -/** GTFS Iterable for an individual file */ +/** GTFS async file records or loaded array */ +export type GTFSAsyncFileRecords = AsyncIterableIterator|RowType[]; + +/** GTFS iterable for an individual file */ export type GTFSIterableFeedFile = { info: GTFSFileInfo, - records: IterableIterator + records: GTFSFileRecords +}; + +/** GTFS async iterable for an individual file */ +export type GTFSAsyncIterableFeedFile = { + info: GTFSFileInfo, + records: GTFSAsyncFileRecords }; /** GTFS Iterable feed files */ export type GTFSIterableFeedFiles = IterableIterator; +/** GTFS Async Iterable feed files */ +export type GTFSAsyncIterableFeedFiles = AsyncIterableIterator; + /** GTFS feed file content */ export type GTFSFileContent = { /** File name with .txt */ @@ -130,6 +142,62 @@ export type GTFSFeed = { attributions?: GTFSFileRecords }; +/** GTFS Async Dataset feed */ +export type GTFSAsyncFeed = { + /** Transit agencies with service represented in this dataset. */ + agency: GTFSAsyncFileRecords, + /** Stops where vehicles pick up or drop off riders. */ + stops: GTFSAsyncFileRecords, + /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ + routes: GTFSAsyncFileRecords, + /** Trips for each route. */ + trips: GTFSAsyncFileRecords, + /** Times that a vehicle arrives at and departs from stops for each trip. */ + stop_times: GTFSAsyncFileRecords, + /** Service dates specified using a weekly schedule with start and end dates. */ + calendar?: GTFSAsyncFileRecords, + /** Exceptions for the services defined in the `calendar.txt`. */ + calendar_dates?: GTFSAsyncFileRecords, + /** Fare information for a transit agency's routes. */ + fare_attributes?: GTFSAsyncFileRecords, + /** Rules to apply fares for itineraries. */ + fare_rules?: GTFSAsyncFileRecords, + /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ + timeframes?: GTFSAsyncFileRecords, + /** To describe the fare media that can be employed to use fare products. */ + fare_media?: GTFSAsyncFileRecords, + /** To describe the different types of tickets or fares that can be purchased by riders. */ + fare_products?: GTFSAsyncFileRecords, + /** Fare rules for individual legs of travel. */ + fare_leg_rules?: GTFSAsyncFileRecords, + /** Fare rules for transfers between legs of travel. */ + fare_transfer_rules?: GTFSAsyncFileRecords, + /** Area grouping of locations. */ + areas?: GTFSAsyncFileRecords, + /** Rules to assign stops to areas. */ + stop_areas?: GTFSAsyncFileRecords, + /** Network grouping of routes. */ + networks?: GTFSAsyncFileRecords, + /** Rules to assign routes to networks. */ + route_networks?: GTFSAsyncFileRecords, + /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ + shapes?: GTFSAsyncFileRecords, + /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ + frequencies?: GTFSAsyncFileRecords, + /** Rules for making connections at transfer points between routes. */ + transfers?: GTFSAsyncFileRecords, + /** Pathways linking together locations within stations. */ + pathways?: GTFSAsyncFileRecords, + /** Levels within stations. */ + levels?: GTFSAsyncFileRecords, + /** Translations of customer-facing dataset values. */ + translations?: GTFSAsyncFileRecords, + /** Dataset metadata, including publisher, version, and expiration information. */ + feed_info?: GTFSAsyncFileRecords, + /** Dataset attributions. */ + attributions?: GTFSAsyncFileRecords +}; + /** GTFS Dataset feed loaded to memory */ export type GTFSLoadedFeed = { /** Transit agencies with service represented in this dataset. */ diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index de4713a..9004e38 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { GTFS_FILES, + GTFSAsyncFileIO, GTFSFileIO, GTFSStopLocationType, GTFSTripDirection @@ -65,6 +66,17 @@ describe('Test GTFSFileIO reading', () => { expect(recordsFromChunks[3]).toEqual(recordsFromContent[3]); }); + it('asynchronously reads chunks into records', async() => { + const recordsFromContent = GTFSFileIO.readContent(GTFS_FILES.stops, content); + const chunksGenerator = async function*() { for (const chunk of chunks) yield chunk; }; + const recordsFromChunks = await GTFSAsyncFileIO.readAll(GTFS_FILES.stops, chunksGenerator()); + expect(recordsFromChunks.length).toEqual(recordsFromContent.length); + expect(recordsFromChunks[0]).toEqual(recordsFromContent[0]); + expect(recordsFromChunks[1]).toEqual(recordsFromContent[1]); + expect(recordsFromChunks[2]).toEqual(recordsFromContent[2]); + expect(recordsFromChunks[3]).toEqual(recordsFromContent[3]); + }); + it('handles empty file content', () => { const content = 'stop_id,stop_name,stop_code,stop_desc,stop_lat,stop_lon,location_type,parent_station\n'; const records = GTFSFileIO.readContent(GTFS_FILES.stops, content); @@ -94,6 +106,19 @@ describe('Test GTFSFileIO writing', () => { ); }); + it('asynchronously writes records into rows', async() => { + const recordsGenerator = async function*() { for (const record of records) yield record; }; + const content = await GTFSAsyncFileIO.writeAll(GTFS_FILES.trips, recordsGenerator(), { recordsBufferSize: 2 }); + expect(content).toEqual( + 'route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,wheelchair_accessible,bikes_allowed\n' + + 'R01,S01,T01,,,0,,,,\n' + + 'R01,S02,T02,HEADSIGN,,,,,,\n' + + 'R03,S01,T03,"with,comma",,,,,,\n' + + 'R02,S02,T04,"with\n' + + 'newline",,,,,,\n' + ); + }); + it('handles empty input', () => { const records: GTFSFileRecords = []; const content = GTFSFileIO.writeContent(GTFS_FILES.routes, records); From 3147e85f60b04f6f946e6a007157036de993c701 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 11:33:19 +0700 Subject: [PATCH 13/20] Added async feed file io --- src/io/feed-file.ts | 211 ++++++++++++++++++++++++------ tests/feed-file-io.test.ts | 261 +++++++++++++++++++++---------------- 2 files changed, 319 insertions(+), 153 deletions(-) diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 3f03782..d23f717 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -1,4 +1,4 @@ -import { GTFSFileIO } from './file'; +import { GTFSAsyncFileIO, GTFSFileIO } from './file'; import { GTFS_FILES } from '../file-info'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSFileRow } from '../types'; @@ -28,11 +28,10 @@ import type { GTFSLevel } from '../files/level'; import type { GTFSTranslation } from '../files/translation'; import type { GTFSFeedInfo } from '../files/feed-info'; import type { GTFSAttribution } from '../files/attribution'; +import { GTFSIOWriteOptions } from './types'; -/** - * Class for IO operations on a GTFS feed file - */ -export class GTFSFeedFileIO { +abstract class FeedFileIO { + /** File information */ protected fileInfo: GTFSFileInfo; /** @@ -56,7 +55,12 @@ export class GTFSFeedFileIO { public constructor(file: GTFSFileInfo) { this.fileInfo = file; } +} +/** + * Class for IO operations on a GTFS feed file + */ +export class GTFSFeedFileIO extends FeedFileIO { /** * Read lines into records. * @param chunks Iterable file content chunks @@ -67,6 +71,16 @@ export class GTFSFeedFileIO { return; } + /** + * Write records into lines. + * @param records Iterable records + * @returns Iterable lines + */ + public *write(records: IterableIterator): IterableIterator { + yield *GTFSFileIO.write(this.fileInfo, records); + return; + } + /** * Read file content into records array. * @param content File content @@ -76,137 +90,254 @@ export class GTFSFeedFileIO { return GTFSFileIO.readContent(this.fileInfo, content); } + /** + * Write records array into file content. + * @param records Records array + * @returns File content + */ + public writeContent(records: RowType[]): string { + return GTFSFileIO.writeContent(this.fileInfo, records); + } +}; + +export class GTFSAsyncFeedFileIO extends FeedFileIO { + /** + * Read lines into records. + * @param chunks Iterable file content chunks + * @returns Iterable records + */ + public async *read(chunks: AsyncIterableIterator): AsyncIterableIterator { + yield *GTFSAsyncFileIO.read(this.fileInfo, chunks); + return; + } + /** * Write records into lines. * @param records Iterable records * @returns Iterable lines */ - public *write(records: IterableIterator): IterableIterator { - yield *GTFSFileIO.write(this.fileInfo, records); + public async *write(records: AsyncIterableIterator): AsyncIterableIterator { + yield *GTFSAsyncFileIO.write(this.fileInfo, records); return; } /** - * Write records array into file content. + * Await for all chunks and return all the records. + * @param chunks Chunks generator + * @returns Promise of all records + */ + public async readAll(chunks: AsyncIterableIterator): Promise { + return GTFSAsyncFileIO.readAll(this.fileInfo, chunks); + } + + /** + * Read file content and return all the records. + * @param content File content + * @returns Promise of all records + */ + public async readAllContent(content: string): Promise { + return GTFSAsyncFileIO.readAllContent(this.fileInfo, content); + } + + /** + * Await for all records and return file content. + * @param records Records generator + * @param options Write options + * @returns Promise of file content + */ + public async writeAll(records: AsyncIterableIterator, options?: GTFSIOWriteOptions): Promise { + return GTFSAsyncFileIO.writeAll(this.fileInfo, records, options); + } + + /** + * Write file content from given records. * @param records Records array - * @returns File content + * @param options Write options + * @returns Promise of all records */ - public writeContent(records: RowType[]): string { - return GTFSFileIO.writeContent(this.fileInfo, records); + public async writeAllRecords(records: RowType[], options?: GTFSIOWriteOptions): Promise { + return GTFSAsyncFileIO.writeAllRecords(this.fileInfo, records, options); } -}; +} /** IO Operations for agency.txt file */ export class GTFSAgencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.agency); } } +/** Async IO Operations for agency.txt file */ +export class GTFSAsyncAgencyIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.agency); } } /** IO Operations for stops.txt file */ export class GTFSStopIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stops); } } +/** Async IO Operations for stops.txt file */ +export class GTFSAsyncStopIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.stops); } } /** IO Operations for routes.txt file */ export class GTFSRouteIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.routes); } } +/** Async IO Operations for routes.txt file */ +export class GTFSAsyncRouteIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.routes); } } /** IO Operations for trips.txt file */ export class GTFSTripIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.trips); } } +/** Async IO Operations for trips.txt file */ +export class GTFSAsyncTripIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.trips); } } /** IO Operations for stop_times.txt file */ export class GTFSStopTimeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_times); } } +/** Async IO Operations for stop_times.txt file */ +export class GTFSAsyncStopTimeIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.stop_times); } } /** IO Operations for calendar.txt file */ export class GTFSCalendarIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar); } } +/** Async IO Operations for calendar.txt file */ +export class GTFSAsyncCalendarIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.calendar); } } /** IO Operations for calendar_dates.txt file */ export class GTFSCalendarDateIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.calendar_dates); } } +/** Async IO Operations for calendar_dates.txt file */ +export class GTFSAsyncCalendarDateIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.calendar_dates); } } /** IO Operations for fare_attributes.txt file */ export class GTFSFareAttributeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_attributes); } } +/** Async IO Operations for fare_attributes.txt file */ +export class GTFSAsyncFareAttributeIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_attributes); } } /** IO Operations for fare_rules.txt file */ export class GTFSFareRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_rules); } } +/** Async IO Operations for fare_rules.txt file */ +export class GTFSAsyncFareRuleIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_rules); } } /** IO Operations for timeframes.txt file */ export class GTFSTimeframeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.timeframes); } } +/** Async IO Operations for timeframes.txt file */ +export class GTFSAsyncTimeframeIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.timeframes); } } /** IO Operations for fare_media.txt file */ export class GTFSFareMediaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_media); } } +/** Async IO Operations for fare_media.txt file */ +export class GTFSAsyncFareMediaIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_media); } } /** IO Operations for fare_products.txt file */ export class GTFSFareProductIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_products); } } +/** Async IO Operations for fare_products.txt file */ +export class GTFSAsyncFareProductIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_products); } } /** IO Operations for fare_leg_rules.txt file */ export class GTFSFareLegRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_leg_rules); } } +/** Async IO Operations for fare_leg_rules.txt file */ +export class GTFSAsyncFareLegRuleIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_leg_rules); } } /** IO Operations for fare_transfer_rules.txt file */ export class GTFSFareTransferRuleIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.fare_transfer_rules); } } +/** Async IO Operations for fare_transfer_rules.txt file */ +export class GTFSAsyncFareTransferRuleIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.fare_transfer_rules); } } /** IO Operations for areas.txt file */ export class GTFSAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.areas); } } +/** Async IO Operations for areas.txt file */ +export class GTFSAsyncAreaIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.areas); } } /** IO Operations for stop_areas.txt file */ export class GTFSStopAreaIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.stop_areas); } } +/** Async IO Operations for stop_areas.txt file */ +export class GTFSAsyncStopAreaIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.stop_areas); } } /** IO Operations for networks.txt file */ export class GTFSNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.networks); } } +/** Async IO Operations for networks.txt file */ +export class GTFSAsyncNetworkIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.networks); } } /** IO Operations for route_networks.txt file */ export class GTFSRouteNetworkIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.route_networks); } } +/** Async IO Operations for route_networks.txt file */ +export class GTFSAsyncRouteNetworkIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.route_networks); } } /** IO Operations for shapes.txt file */ export class GTFSShapeIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.shapes); } } +/** Async IO Operations for shapes.txt file */ +export class GTFSAsyncShapeIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.shapes); } } /** IO Operations for frequencies.txt file */ export class GTFSFrequencyIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.frequencies); } } +/** Async IO Operations for frequencies.txt file */ +export class GTFSAsyncFrequencyIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.frequencies); } } /** IO Operations for transfers.txt file */ export class GTFSTransferIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.transfers); } } +/** Async IO Operations for transfers.txt file */ +export class GTFSAsyncTransferIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.transfers); } } /** IO Operations for pathways.txt file */ export class GTFSPathwayIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.pathways); } } +/** Async IO Operations for pathways.txt file */ +export class GTFSAsyncPathwayIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.pathways); } } /** IO Operations for levels.txt file */ export class GTFSLevelIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.levels); } } +/** Async IO Operations for levels.txt file */ +export class GTFSAsyncLevelIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.levels); } } /** IO Operations for translations.txt file */ export class GTFSTranslationIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.translations); } } +/** Async IO Operations for translations.txt file */ +export class GTFSAsyncTranslationIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.translations); } } /** IO Operations for feed_info.txt file */ export class GTFSFeedInfoIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.feed_info); } } +/** Async IO Operations for feed_info.txt file */ +export class GTFSAsyncFeedInfoIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.feed_info); } } /** IO Operations for attributions.txt file */ export class GTFSAttributionIO extends GTFSFeedFileIO { public constructor() { super(GTFS_FILES.attributions); } } +/** Async IO Operations for attributions.txt file */ +export class GTFSAsyncAttributionIO extends GTFSAsyncFeedFileIO { public constructor() { super(GTFS_FILES.attributions); } } /** * Get feed file IO instance from file name. * @param fileName File name with .txt + * @param async True for async IO class instance * @returns GTFSFeedFileIO */ -export const getIOFromFileName = (fileName: string): GTFSFeedFileIO => { +const fileNameToIO = (fileName: string, async: boolean): GTFSFeedFileIO|GTFSAsyncFileIO => { switch(fileName) { - case 'agency.txt': return new GTFSAgencyIO(); - case 'stops.txt': return new GTFSStopIO(); - case 'routes.txt': return new GTFSRouteIO(); - case 'trips.txt': return new GTFSTripIO(); - case 'stop_times.txt': return new GTFSStopTimeIO(); - case 'calendar.txt': return new GTFSCalendarIO(); - case 'calendar_dates.txt': return new GTFSCalendarDateIO(); - case 'fare_attributes.txt': return new GTFSFareAttributeIO(); - case 'fare_rules.txt': return new GTFSFareRuleIO(); - case 'timeframes.txt': return new GTFSTimeframeIO(); - case 'fare_media.txt': return new GTFSFareMediaIO(); - case 'fare_products.txt': return new GTFSFareProductIO(); - case 'fare_leg_rules.txt': return new GTFSFareLegRuleIO(); - case 'fare_transfer_rules.txt': return new GTFSFareTransferRuleIO(); - case 'areas.txt': return new GTFSAreaIO(); - case 'stop_areas.txt': return new GTFSStopAreaIO(); - case 'networks.txt': return new GTFSNetworkIO(); - case 'route_networks.txt': return new GTFSRouteNetworkIO(); - case 'shapes.txt': return new GTFSShapeIO(); - case 'frequencies.txt': return new GTFSFrequencyIO(); - case 'transfers.txt': return new GTFSTransferIO(); - case 'pathways.txt': return new GTFSPathwayIO(); - case 'levels.txt': return new GTFSLevelIO(); - case 'translations.txt': return new GTFSTranslationIO(); - case 'feed_info.txt': return new GTFSFeedInfoIO(); - case 'attributions.txt': return new GTFSAttributionIO(); + case 'agency.txt': return async ? new GTFSAsyncAgencyIO() : new GTFSAgencyIO(); + case 'stops.txt': return async ? new GTFSAsyncStopIO() : new GTFSStopIO(); + case 'routes.txt': return async ? new GTFSAsyncRouteIO() : new GTFSRouteIO(); + case 'trips.txt': return async ? new GTFSAsyncTripIO() : new GTFSTripIO(); + case 'stop_times.txt': return async ? new GTFSAsyncStopTimeIO() : new GTFSStopTimeIO(); + case 'calendar.txt': return async ? new GTFSAsyncCalendarIO() : new GTFSCalendarIO(); + case 'calendar_dates.txt': return async ? new GTFSAsyncCalendarDateIO() : new GTFSCalendarDateIO(); + case 'fare_attributes.txt': return async ? new GTFSAsyncFareAttributeIO() : new GTFSFareAttributeIO(); + case 'fare_rules.txt': return async ? new GTFSAsyncFareRuleIO() : new GTFSFareRuleIO(); + case 'timeframes.txt': return async ? new GTFSAsyncTimeframeIO() : new GTFSTimeframeIO(); + case 'fare_media.txt': return async ? new GTFSAsyncFareMediaIO() : new GTFSFareMediaIO(); + case 'fare_products.txt': return async ? new GTFSAsyncFareProductIO() : new GTFSFareProductIO(); + case 'fare_leg_rules.txt': return async ? new GTFSAsyncFareLegRuleIO() : new GTFSFareLegRuleIO(); + case 'fare_transfer_rules.txt': return async ? new GTFSAsyncFareTransferRuleIO() : new GTFSFareTransferRuleIO(); + case 'areas.txt': return async ? new GTFSAsyncAreaIO() : new GTFSAreaIO(); + case 'stop_areas.txt': return async ? new GTFSAsyncStopAreaIO() : new GTFSStopAreaIO(); + case 'networks.txt': return async ? new GTFSAsyncNetworkIO() : new GTFSNetworkIO(); + case 'route_networks.txt': return async ? new GTFSAsyncRouteNetworkIO() : new GTFSRouteNetworkIO(); + case 'shapes.txt': return async ? new GTFSAsyncShapeIO() : new GTFSShapeIO(); + case 'frequencies.txt': return async ? new GTFSAsyncFrequencyIO() : new GTFSFrequencyIO(); + case 'transfers.txt': return async ? new GTFSAsyncTransferIO() : new GTFSTransferIO(); + case 'pathways.txt': return async ? new GTFSAsyncPathwayIO() : new GTFSPathwayIO(); + case 'levels.txt': return async ? new GTFSAsyncLevelIO() : new GTFSLevelIO(); + case 'translations.txt': return async ? new GTFSAsyncTranslationIO() : new GTFSTranslationIO(); + case 'feed_info.txt': return async ? new GTFSAsyncFeedInfoIO() : new GTFSFeedInfoIO(); + case 'attributions.txt': return async ? new GTFSAsyncAttributionIO() : new GTFSAttributionIO(); } throw new Error(`Unknown file name ${fileName}`); -} +}; + +/** + * Get feed file IO instance from file name. + * @param fileName File name with .txt + * @returns GTFSFeedFileIO + */ +export const getIOFromFileName = (fileName: string): GTFSFeedFileIO => fileNameToIO(fileName, false) as GTFSFeedFileIO; + +/** + * Get feed file async IO instance from file name. + * @param fileName File name with .txt + * @returns GTFSAsyncFeedFileIO + */ +export const getAsyncIOFromFileName = (fileName: string): GTFSAsyncFeedFileIO => fileNameToIO(fileName, true) as GTFSAsyncFeedFileIO; diff --git a/tests/feed-file-io.test.ts b/tests/feed-file-io.test.ts index 93f38eb..12bc4a8 100644 --- a/tests/feed-file-io.test.ts +++ b/tests/feed-file-io.test.ts @@ -1,38 +1,8 @@ import { test, expect } from 'vitest'; -import { - GTFS_FILES, - GTFSAgencyIO, - GTFSStopIO, - GTFSRouteIO, - GTFSTripIO, - GTFSStopTimeIO, - GTFSCalendarIO, - GTFSCalendarDateIO, - GTFSFareAttributeIO, - GTFSFareRuleIO, - GTFSTimeframeIO, - GTFSFareMediaIO, - GTFSFareProductIO, - GTFSFareLegRuleIO, - GTFSFareTransferRuleIO, - GTFSAreaIO, - GTFSStopAreaIO, - GTFSNetworkIO, - GTFSRouteNetworkIO, - GTFSShapeIO, - GTFSFrequencyIO, - GTFSTransferIO, - GTFSPathwayIO, - GTFSLevelIO, - GTFSTranslationIO, - GTFSFeedInfoIO, - GTFSAttributionIO -} from '../dist'; -import type { - GTFSFeedFileIO, GTFSFileInfo -} from '../dist'; - -const assert = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => { +import { GTFS_FILES, getIOFromFileName, getAsyncIOFromFileName } from '../dist'; +import type { GTFSAsyncFeedFileIO, GTFSFeedFileIO, GTFSFileInfo } from '../dist'; + +const assertSync = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => { expect(io.fileName).toEqual(fileName); expect(io.columns).toEqual(Object.keys(fileInfo.columns)); @@ -43,134 +13,199 @@ const assert = (io: GTFSFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => const writtenContent = io.writeContent(records); expect(writtenContent).toEqual(content + '\n'); -} +}; + +const assertAsync = async(io: GTFSAsyncFeedFileIO, fileName: string, fileInfo: GTFSFileInfo) => { + expect(io.fileName).toEqual(fileName); + expect(io.columns).toEqual(Object.keys(fileInfo.columns)); + + const content = io.columns.join(',') + '\n' + io.columns.map(_ => '').join(','); + const records = await io.readAllContent(content); + expect(records.length).toEqual(1); + expect(Object.keys(records[0])).toEqual(io.columns); + + const writtenContent = await io.writeAllRecords(records); + expect(writtenContent).toEqual(content + '\n'); +}; -test('Test agency.txt IO', () => { - const io = new GTFSAgencyIO(); - assert(io, 'agency.txt', GTFS_FILES.agency); +test('Test agency.txt IO', async() => { + const io = getIOFromFileName('agency.txt'); + assertSync(io, 'agency.txt', GTFS_FILES.agency); + const asyncIO = getAsyncIOFromFileName('agency.txt'); + assertAsync(asyncIO, 'agency.txt', GTFS_FILES.agency); }); -test('Test stops.txt IO', () => { - const io = new GTFSStopIO(); - assert(io, 'stops.txt', GTFS_FILES.stops); +test('Test stops.txt IO', async() => { + const io = getIOFromFileName('stops.txt'); + assertSync(io, 'stops.txt', GTFS_FILES.stops); + const asyncIO = getAsyncIOFromFileName('stops.txt'); + assertAsync(asyncIO, 'stops.txt', GTFS_FILES.stops); }); -test('Test routes.txt IO', () => { - const io = new GTFSRouteIO(); - assert(io, 'routes.txt', GTFS_FILES.routes); +test('Test routes.txt IO', async() => { + const io = getIOFromFileName('routes.txt'); + assertSync(io, 'routes.txt', GTFS_FILES.routes); + const asyncIO = getAsyncIOFromFileName('routes.txt'); + assertAsync(asyncIO, 'routes.txt', GTFS_FILES.routes); }); -test('Test trips.txt IO', () => { - const io = new GTFSTripIO(); - assert(io, 'trips.txt', GTFS_FILES.trips); +test('Test trips.txt IO', async() => { + const io = getIOFromFileName('trips.txt'); + assertSync(io, 'trips.txt', GTFS_FILES.trips); + const asyncIO = getAsyncIOFromFileName('trips.txt'); + assertAsync(asyncIO, 'trips.txt', GTFS_FILES.trips); }); -test('Test stop_times.txt IO', () => { - const io = new GTFSStopTimeIO(); - assert(io, 'stop_times.txt', GTFS_FILES.stop_times); +test('Test stop_times.txt IO', async() => { + const io = getIOFromFileName('stop_times.txt'); + assertSync(io, 'stop_times.txt', GTFS_FILES.stop_times); + const asyncIO = getAsyncIOFromFileName('stop_times.txt'); + assertAsync(asyncIO, 'stop_times.txt', GTFS_FILES.stop_times); }); -test('Test calendar.txt IO', () => { - const io = new GTFSCalendarIO(); - assert(io, 'calendar.txt', GTFS_FILES.calendar); +test('Test calendar.txt IO', async() => { + const io = getIOFromFileName('calendar.txt'); + assertSync(io, 'calendar.txt', GTFS_FILES.calendar); + const asyncIO = getAsyncIOFromFileName('calendar.txt'); + assertAsync(asyncIO, 'calendar.txt', GTFS_FILES.calendar); }); -test('Test calendar_dates.txt IO', () => { - const io = new GTFSCalendarDateIO(); - assert(io, 'calendar_dates.txt', GTFS_FILES.calendar_dates); +test('Test calendar_dates.txt IO', async() => { + const io = getIOFromFileName('calendar_dates.txt'); + assertSync(io, 'calendar_dates.txt', GTFS_FILES.calendar_dates); + const asyncIO = getAsyncIOFromFileName('calendar_dates.txt'); + assertAsync(asyncIO, 'calendar_dates.txt', GTFS_FILES.calendar_dates); }); -test('Test fare_attributes.txt IO', () => { - const io = new GTFSFareAttributeIO(); - assert(io, 'fare_attributes.txt', GTFS_FILES.fare_attributes); +test('Test fare_attributes.txt IO', async() => { + const io = getIOFromFileName('fare_attributes.txt'); + assertSync(io, 'fare_attributes.txt', GTFS_FILES.fare_attributes); + const asyncIO = getAsyncIOFromFileName('fare_attributes.txt'); + assertAsync(asyncIO, 'fare_attributes.txt', GTFS_FILES.fare_attributes); }); -test('Test fare_rules.txt IO', () => { - const io = new GTFSFareRuleIO(); - assert(io, 'fare_rules.txt', GTFS_FILES.fare_rules); +test('Test fare_rules.txt IO', async() => { + const io = getIOFromFileName('fare_rules.txt'); + assertSync(io, 'fare_rules.txt', GTFS_FILES.fare_rules); + const asyncIO = getAsyncIOFromFileName('fare_rules.txt'); + assertAsync(asyncIO, 'fare_rules.txt', GTFS_FILES.fare_rules); }); -test('Test timeframes.txt IO', () => { - const io = new GTFSTimeframeIO(); - assert(io, 'timeframes.txt', GTFS_FILES.timeframes); +test('Test timeframes.txt IO', async() => { + const io = getIOFromFileName('timeframes.txt'); + assertSync(io, 'timeframes.txt', GTFS_FILES.timeframes); + const asyncIO = getAsyncIOFromFileName('timeframes.txt'); + assertAsync(asyncIO, 'timeframes.txt', GTFS_FILES.timeframes); }); -test('Test fare_media.txt IO', () => { - const io = new GTFSFareMediaIO(); - assert(io, 'fare_media.txt', GTFS_FILES.fare_media); +test('Test fare_media.txt IO', async() => { + const io = getIOFromFileName('fare_media.txt'); + assertSync(io, 'fare_media.txt', GTFS_FILES.fare_media); + const asyncIO = getAsyncIOFromFileName('fare_media.txt'); + assertAsync(asyncIO, 'fare_media.txt', GTFS_FILES.fare_media); }); -test('Test fare_products.txt IO', () => { - const io = new GTFSFareProductIO(); - assert(io, 'fare_products.txt', GTFS_FILES.fare_products); +test('Test fare_products.txt IO', async() => { + const io = getIOFromFileName('fare_products.txt'); + assertSync(io, 'fare_products.txt', GTFS_FILES.fare_products); + const asyncIO = getAsyncIOFromFileName('fare_products.txt'); + assertAsync(asyncIO, 'fare_products.txt', GTFS_FILES.fare_products); }); -test('Test fare_leg_rules.txt IO', () => { - const io = new GTFSFareLegRuleIO(); - assert(io, 'fare_leg_rules.txt', GTFS_FILES.fare_leg_rules); +test('Test fare_leg_rules.txt IO', async() => { + const io = getIOFromFileName('fare_leg_rules.txt'); + assertSync(io, 'fare_leg_rules.txt', GTFS_FILES.fare_leg_rules); + const asyncIO = getAsyncIOFromFileName('fare_leg_rules.txt'); + assertAsync(asyncIO, 'fare_leg_rules.txt', GTFS_FILES.fare_leg_rules); }); -test('Test fare_transfer_rules.txt IO', () => { - const io = new GTFSFareTransferRuleIO(); - assert(io, 'fare_transfer_rules.txt', GTFS_FILES.fare_transfer_rules); +test('Test fare_transfer_rules.txt IO', async() => { + const io = getIOFromFileName('fare_transfer_rules.txt'); + assertSync(io, 'fare_transfer_rules.txt', GTFS_FILES.fare_transfer_rules); + const asyncIO = getAsyncIOFromFileName('fare_transfer_rules.txt'); + assertAsync(asyncIO, 'fare_transfer_rules.txt', GTFS_FILES.fare_transfer_rules); }); -test('Test areas.txt IO', () => { - const io = new GTFSAreaIO(); - assert(io, 'areas.txt', GTFS_FILES.areas); +test('Test areas.txt IO', async() => { + const io = getIOFromFileName('areas.txt'); + assertSync(io, 'areas.txt', GTFS_FILES.areas); + const asyncIO = getAsyncIOFromFileName('areas.txt'); + assertAsync(asyncIO, 'areas.txt', GTFS_FILES.areas); }); -test('Test stop_areas.txt IO', () => { - const io = new GTFSStopAreaIO(); - assert(io, 'stop_areas.txt', GTFS_FILES.stop_areas); +test('Test stop_areas.txt IO', async() => { + const io = getIOFromFileName('stop_areas.txt'); + assertSync(io, 'stop_areas.txt', GTFS_FILES.stop_areas); + const asyncIO = getAsyncIOFromFileName('stop_areas.txt'); + assertAsync(asyncIO, 'stop_areas.txt', GTFS_FILES.stop_areas); }); -test('Test networks.txt IO', () => { - const io = new GTFSNetworkIO(); - assert(io, 'networks.txt', GTFS_FILES.networks); +test('Test networks.txt IO', async() => { + const io = getIOFromFileName('networks.txt'); + assertSync(io, 'networks.txt', GTFS_FILES.networks); + const asyncIO = getAsyncIOFromFileName('networks.txt'); + assertAsync(asyncIO, 'networks.txt', GTFS_FILES.networks); }); -test('Test route_networks.txt IO', () => { - const io = new GTFSRouteNetworkIO(); - assert(io, 'route_networks.txt', GTFS_FILES.route_networks); +test('Test route_networks.txt IO', async() => { + const io = getIOFromFileName('route_networks.txt'); + assertSync(io, 'route_networks.txt', GTFS_FILES.route_networks); + const asyncIO = getAsyncIOFromFileName('route_networks.txt'); + assertAsync(asyncIO, 'route_networks.txt', GTFS_FILES.route_networks); }); -test('Test shapes.txt IO', () => { - const io = new GTFSShapeIO(); - assert(io, 'shapes.txt', GTFS_FILES.shapes); +test('Test shapes.txt IO', async() => { + const io = getIOFromFileName('shapes.txt'); + assertSync(io, 'shapes.txt', GTFS_FILES.shapes); + const asyncIO = getAsyncIOFromFileName('shapes.txt'); + assertAsync(asyncIO, 'shapes.txt', GTFS_FILES.shapes); }); -test('Test frequencies.txt IO', () => { - const io = new GTFSFrequencyIO(); - assert(io, 'frequencies.txt', GTFS_FILES.frequencies); +test('Test frequencies.txt IO', async() => { + const io = getIOFromFileName('frequencies.txt'); + assertSync(io, 'frequencies.txt', GTFS_FILES.frequencies); + const asyncIO = getAsyncIOFromFileName('frequencies.txt'); + assertAsync(asyncIO, 'frequencies.txt', GTFS_FILES.frequencies); }); -test('Test transfers.txt IO', () => { - const io = new GTFSTransferIO(); - assert(io, 'transfers.txt', GTFS_FILES.transfers); +test('Test transfers.txt IO', async() => { + const io = getIOFromFileName('transfers.txt'); + assertSync(io, 'transfers.txt', GTFS_FILES.transfers); + const asyncIO = getAsyncIOFromFileName('transfers.txt'); + assertAsync(asyncIO, 'transfers.txt', GTFS_FILES.transfers); }); -test('Test pathways.txt IO', () => { - const io = new GTFSPathwayIO(); - assert(io, 'pathways.txt', GTFS_FILES.pathways); +test('Test pathways.txt IO', async() => { + const io = getIOFromFileName('pathways.txt'); + assertSync(io, 'pathways.txt', GTFS_FILES.pathways); + const asyncIO = getAsyncIOFromFileName('pathways.txt'); + assertAsync(asyncIO, 'pathways.txt', GTFS_FILES.pathways); }); -test('Test levels.txt IO', () => { - const io = new GTFSLevelIO(); - assert(io, 'levels.txt', GTFS_FILES.levels); +test('Test levels.txt IO', async() => { + const io = getIOFromFileName('levels.txt'); + assertSync(io, 'levels.txt', GTFS_FILES.levels); + const asyncIO = getAsyncIOFromFileName('levels.txt'); + assertAsync(asyncIO, 'levels.txt', GTFS_FILES.levels); }); -test('Test translations.txt IO', () => { - const io = new GTFSTranslationIO(); - assert(io, 'translations.txt', GTFS_FILES.translations); +test('Test translations.txt IO', async() => { + const io = getIOFromFileName('translations.txt'); + assertSync(io, 'translations.txt', GTFS_FILES.translations); + const asyncIO = getAsyncIOFromFileName('translations.txt'); + assertAsync(asyncIO, 'translations.txt', GTFS_FILES.translations); }); -test('Test feed_info.txt IO', () => { - const io = new GTFSFeedInfoIO(); - assert(io, 'feed_info.txt', GTFS_FILES.feed_info); +test('Test feed_info.txt IO', async() => { + const io = getIOFromFileName('feed_info.txt'); + assertSync(io, 'feed_info.txt', GTFS_FILES.feed_info); + const asyncIO = getAsyncIOFromFileName('feed_info.txt'); + assertAsync(asyncIO, 'feed_info.txt', GTFS_FILES.feed_info); }); -test('Test attributions.txt IO', () => { - const io = new GTFSAttributionIO(); - assert(io, 'attributions.txt', GTFS_FILES.attributions); +test('Test attributions.txt IO', async() => { + const io = getIOFromFileName('attributions.txt'); + assertSync(io, 'attributions.txt', GTFS_FILES.attributions); + const asyncIO = getAsyncIOFromFileName('attributions.txt'); + assertAsync(asyncIO, 'attributions.txt', GTFS_FILES.attributions); }); From 9acac0a0201afd51d131bb6debef32fff10dcad0 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 12:33:13 +0700 Subject: [PATCH 14/20] Added async feed reader --- src/index.ts | 2 +- src/io/feed-reader.ts | 310 +++++++++++++++++++++++++++++++------- src/types.ts | 2 +- tests/feed-reader.test.ts | 51 +++++-- 4 files changed, 299 insertions(+), 66 deletions(-) diff --git a/src/index.ts b/src/index.ts index d0e44d1..e4271ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { GTFSFileIO, GTFSAsyncFileIO } from './io/file'; -export { default as GTFSFeedReader } from './io/feed-reader'; +export { GTFSFeedReader } from './io/feed-reader'; export { default as GTFSFeedWriter } from './io/feed-writer'; export * from './io/feed-file'; export * from './file-info'; diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 432e539..4f6950b 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -2,13 +2,27 @@ import AdmZip from 'adm-zip'; import { existsSync as exists, openSync as openFile, - readSync as readFile + read as readAsync, + readFile, + readFile as readFileAsync, + readSync as readFileSync } from 'fs'; import { join as joinPath } from 'path'; -import { getIOFromFileName } from './feed-file'; +import { getAsyncIOFromFileName, getIOFromFileName } from './feed-file'; import { getGTFSFileInfos } from '../file-info'; import type { GTFSFileInfo, GTFSFileName } from '../file-info'; -import type { GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles, GTFSLoadedFeed } from '../types'; +import type { + GTFSAsyncFeed, + GTFSAsyncFileRecords, + GTFSAsyncIterableFeedFile, + GTFSAsyncIterableFeedFiles, + GTFSFeed, + GTFSFileContent, + GTFSFileRecords, + GTFSFileRow, + GTFSIterableFeedFiles, + GTFSLoadedFeed +} from '../types'; /** * GTFS file object to read @@ -23,35 +37,19 @@ type GTFSFile = { }; /** - * GTFS feed reader. - * Do not use constructor, instead, use the following static methods to initiate an instance: - * GTFSFeedReader.fromZip, - * GTFSFeedReader.fromDir, - * GTFSFeedReader.fromFiles + * GTFS feed reader abstract */ -export default class GTFSFeedReader { +abstract class FeedReader< + LoadedFeedType extends GTFSLoadedFeed|Promise, + FeedType extends GTFSFeed|Promise, + IterableFeedFilesType extends GTFSIterableFeedFiles|GTFSAsyncIterableFeedFiles, + RecordsType extends GTFSFileRecords|GTFSAsyncFileRecords +> { /** Zip object */ - private zip?: AdmZip = undefined; + protected zip?: AdmZip = undefined; /** File objects */ - private files?: GTFSFile[] = undefined; - - /** - * Generator of iterable chunks from a file path. - * @param filePath File path - * @returns Iterable file chunks - */ - private static *readFileChunks(filePath: string): IterableIterator { - const file = openFile(filePath, 'r'); - const bufferSize = 1024; - const buffer = Buffer.alloc(bufferSize); - - let readPos; - while ((readPos = readFile(file, buffer, 0, bufferSize, null)) !== 0) { - yield buffer.toString('utf8', 0, readPos); - } - return; - } + protected files?: GTFSFile[] = undefined; /** * Constructor, the object of this class is to be created by static methods. @@ -60,7 +58,7 @@ export default class GTFSFeedReader { * @param directoryPath Directory path * @param fileContents Array of file content objects */ - private constructor( + protected constructor( zip?: string|Buffer|ArrayBuffer, directoryPath?: string, fileContents?: GTFSFileContent[] @@ -89,34 +87,121 @@ export default class GTFSFeedReader { } /** - * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). - * @returns Iterable feed files + * Get records from an AdmZip entry. + * @param info File information + * @param entry Zip entry */ - public *getIterableFiles(): GTFSIterableFeedFiles { - for (const info of getGTFSFileInfos()) { - if (this.zip) { - const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); - if (!entry.length) continue; - const fileIO = getIOFromFileName(info.name); - yield { info, records: fileIO.read([entry[0].getData().toString()].values()) }; - } else if (this.files) { - const file = this.files.filter(f => f.info.name === info.name); - if (!file.length) continue; - const fileIO = getIOFromFileName(info.name); - if (file[0].path) { - yield { info, records: fileIO.read(GTFSFeedReader.readFileChunks(file[0].path)) }; - } else if (file[0].buffer) { - yield { info, records: fileIO.read([file[0].buffer.toString()].values()) }; - } + protected abstract getRecordsFromZipEntry(info: GTFSFileInfo, entry: AdmZip.IZipEntry): RecordsType; + /** + * Get records from file path. + * @param info File information + * @param path File path + */ + protected abstract getRecordsFromFilePath(info: GTFSFileInfo, path: string): RecordsType; + /** + * Get records from file content. + * @param info File information + * @param content File content buffer + */ + protected abstract getRecordsFromFileContent(info: GTFSFileInfo, content: Buffer): RecordsType; + + /** + * From file information, get records. + * @param info file information + * @returns Records + */ + public getRecordsFromFileInfo(info: GTFSFileInfo): RecordsType|undefined { + if (this.zip) { + const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); + if (!entry.length) return undefined; + return this.getRecordsFromZipEntry(info, entry[0]); + } else if (this.files) { + const file = this.files.filter(f => f.info.name === info.name); + if (!file.length) return undefined; + if (file[0].path) { + return this.getRecordsFromFilePath(info, file[0].path); + } else if (file[0].buffer) { + return this.getRecordsFromFileContent(info, file[0].buffer); } } - return; + return undefined; } + /** + * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). + * @returns Iterable feed files + */ + public abstract getIterableFiles(): IterableFeedFilesType; /** * Get feed object with row being file name without .txt and value being iterable records. * @returns Feed object with row being file name without .txt and value being iterable records. */ + public abstract getFeed(): FeedType; + /** + * Get feed object with row being file name without .txt and value being array of records. + * @returns Feed object with row being file name without .txt and value being array of records. + */ + public abstract loadFeed(): LoadedFeedType; +}; + +/** + * GTFS feed reader. + * Do not use constructor, instead, use the following static methods to initiate an instance: + * GTFSFeedReader.fromZip, + * GTFSFeedReader.fromDir, + * GTFSFeedReader.fromFiles + */ +export class GTFSFeedReader extends FeedReader { + /** + * Generator of iterable chunks from a file path. + * @param filePath File path + * @returns Iterable file chunks + */ + private static *readFileChunks(filePath: string): IterableIterator { + const file = openFile(filePath, 'r'); + const bufferSize = 1024; + const buffer = Buffer.alloc(bufferSize); + let leftOver = ''; + + let readPos; + while ((readPos = readFileSync(file, buffer, 0, bufferSize, null)) !== 0) { + const content = leftOver + buffer.toString('utf8', 0, readPos); + const newLineIdx = content.lastIndexOf('\n'); + yield content.slice(0, newLineIdx); + leftOver = content.slice(newLineIdx); + } + + if (leftOver) yield leftOver; + return; + } + + protected getRecordsFromZipEntry(info: GTFSFileInfo, entry: AdmZip.IZipEntry): GTFSFileRecords { + const io = getIOFromFileName(info.name); + return io.readContent(entry.getData().toString()); + } + + protected *getRecordsFromFilePath(info: GTFSFileInfo, path: string): GTFSFileRecords { + const io = getIOFromFileName(info.name); + const chunks = GTFSFeedReader.readFileChunks(path); + for (const record of io.read(chunks)) { + yield record; + } + } + + protected getRecordsFromFileContent(info: GTFSFileInfo, content: Buffer): GTFSFileRecords { + const io = getIOFromFileName(info.name); + return io.readContent(content.toString()); + } + + public *getIterableFiles(): GTFSIterableFeedFiles { + for (const info of getGTFSFileInfos()) { + const records = this.getRecordsFromFileInfo(info); + if (records === undefined) continue; + yield { info, records }; + } + return; + } + public getFeed(): GTFSFeed { const results: Partial> = { agency: [], @@ -134,10 +219,6 @@ export default class GTFSFeedReader { return results as GTFSFeed; } - /** - * Get feed object with row being file name without .txt and value being array of records. - * @returns Feed object with row being file name without .txt and value being array of records. - */ public loadFeed(): GTFSLoadedFeed { const feed = this.getFeed(); const results: Partial> = {}; @@ -176,3 +257,128 @@ export default class GTFSFeedReader { return new GTFSFeedReader(undefined, undefined, files); } }; + +export class GTFSAsyncFeedReader extends FeedReader, Promise, GTFSAsyncIterableFeedFiles, GTFSAsyncFileRecords> { + /** + * Generator of iterable chunks from a file path. + * @param filePath File path + * @returns Iterable file chunks + */ + private static async *readFileChunks(filePath: string): AsyncIterableIterator { + const file = openFile(filePath, 'r'); + const bufferSize = 1024; + const buffer = Buffer.alloc(bufferSize); + let leftOver = ''; + + const reads = (): Promise => new Promise((resolve, reject) => { + readAsync(file, buffer, 0, bufferSize, null, (err, pos) => { + if (err) return reject(err); + resolve(pos); + }); + }); + + let readPos = -1; + while (readPos !== 0) { + readPos = await reads(); + const content = leftOver + buffer.toString('utf8', 0, readPos); + const newLineIdx = content.lastIndexOf('\n'); + yield content.slice(0, newLineIdx); + leftOver = content.slice(newLineIdx); + } + + if (leftOver) yield leftOver; + return; + } + + protected getRecordsFromZipEntry(info: GTFSFileInfo, entry: AdmZip.IZipEntry): GTFSAsyncFileRecords { + const io = getAsyncIOFromFileName(info.name); + const generator = async function*() { yield entry.getData().toString(); }; + return io.read(generator()); + } + + protected getRecordsFromFilePath(info: GTFSFileInfo, path: string): GTFSAsyncFileRecords { + const io = getAsyncIOFromFileName(info.name); + return io.read(GTFSAsyncFeedReader.readFileChunks(path)); + } + + protected getRecordsFromFileContent(info: GTFSFileInfo, content: Buffer): GTFSAsyncFileRecords { + const io = getAsyncIOFromFileName(info.name); + const generator = async function*() { yield content.toString(); }; + return io.read(generator()); + } + + public async *getIterableFiles(): GTFSAsyncIterableFeedFiles { + for (const info of getGTFSFileInfos()) { + const records = this.getRecordsFromFileInfo(info); + if (records === undefined) continue; + yield { info, records }; + } + return; + } + + public async getFeed(): Promise { + const empty = async function*() {}; + const results: Partial> = { + agency: empty(), + stops: empty(), + routes: empty(), + trips: empty(), + stop_times: empty() + }; + + for await (const file of this.getIterableFiles()) { + const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; + results[key] = file.records; + } + + return results as GTFSAsyncFeed; + } + + public async loadFeed(): Promise { + const results: Partial> = { + agency: [], + stops: [], + routes: [], + trips: [], + stop_times: [] + }; + + for await (const file of this.getIterableFiles()) { + const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; + if (results[key] === undefined) results[key] = []; + + for await (const record of file.records) { + results[key]!.push(record); + } + } + + return results as GTFSLoadedFeed; + } + + /** + * Create an instance of GTFSFeedWriter from zip file. + * @param zip Zip file path or content buffer + * @returns GTFSFeedWriter instance + */ + public static fromZip(zip: string|Buffer|ArrayBuffer): GTFSAsyncFeedReader { + return new GTFSAsyncFeedReader(zip); + } + + /** + * Create an instance of GTFSFeedWriter from directory path. + * @param dirPath Path to GTFS feed directory + * @returns GTFSFeedWriter instance + */ + public static fromDir(dirPath: string): GTFSAsyncFeedReader { + return new GTFSAsyncFeedReader(undefined, dirPath); + } + + /** + * Create an instance of GTFSFeedWriter from in-memory file contents. + * @param files Feed files object + * @returns GTFSFeedWriter instance + */ + public static fromFiles(files: GTFSFileContent[]): GTFSAsyncFeedReader { + return new GTFSAsyncFeedReader(undefined, undefined, files); + } +} diff --git a/src/types.ts b/src/types.ts index 30768ca..c61b074 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,7 +58,7 @@ export type GTFSFileRow = GTFSAgency export type GTFSFileRecords = IterableIterator|RowType[]; /** GTFS async file records or loaded array */ -export type GTFSAsyncFileRecords = AsyncIterableIterator|RowType[]; +export type GTFSAsyncFileRecords = AsyncIterableIterator; /** GTFS iterable for an individual file */ export type GTFSIterableFeedFile = { diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts index 894d07b..1e74996 100644 --- a/tests/feed-reader.test.ts +++ b/tests/feed-reader.test.ts @@ -13,6 +13,7 @@ import { GTFSWheelchairAccessbility } from '../dist'; import type { GTFSFileContent, GTFSLoadedFeed } from '../dist'; +import { GTFSAsyncFeedReader } from '../dist/io/feed-reader'; const ZIP_PATH = './tests/data/gtfs.zip'; const DIR_PATH = './tests/data/gtfs'; @@ -140,7 +141,18 @@ const assert = (feed: GTFSLoadedFeed) => { expect(feed.trips[0].shape_id).toEqual('SH01'); expect(feed.trips[0].wheelchair_accessible).toEqual(GTFSWheelchairAccessbility.Accessible); expect(feed.trips[0].bikes_allowed).toEqual(GTFSTripBikesAllowed.NotAllowed); -} +}; + +const getFiles = (): GTFSFileContent[] => [ + { name: 'agency.txt', content: readFileSync(join(DIR_PATH, 'agency.txt')) }, + { name: 'calendar_dates.txt', content: readFileSync(join(DIR_PATH, 'calendar_dates.txt')) }, + { name: 'calendar.txt', content: readFileSync(join(DIR_PATH, 'calendar.txt')) }, + { name: 'routes.txt', content: readFileSync(join(DIR_PATH, 'routes.txt')) }, + { name: 'shapes.txt', content: readFileSync(join(DIR_PATH, 'shapes.txt')) }, + { name: 'stop_times.txt', content: readFileSync(join(DIR_PATH, 'stop_times.txt')) }, + { name: 'stops.txt', content: readFileSync(join(DIR_PATH, 'stops.txt')) }, + { name: 'trips.txt', content: readFileSync(join(DIR_PATH, 'trips.txt')) } +]; test('Test FeedReader: zip path', () => { const reader = GTFSFeedReader.fromZip(ZIP_PATH); @@ -148,6 +160,12 @@ test('Test FeedReader: zip path', () => { assert(feed); }); +test('Test AsyncFeedReader: zip path', async() => { + const reader = GTFSAsyncFeedReader.fromZip(ZIP_PATH); + const feed = await reader.loadFeed(); + assert(feed); +}); + test('Test FeedReader: zip content', () => { const zip = readFileSync(ZIP_PATH); const reader = GTFSFeedReader.fromZip(zip); @@ -155,24 +173,33 @@ test('Test FeedReader: zip content', () => { assert(feed); }); +test('Test AsyncFeedReader: zip content', async() => { + const zip = readFileSync(ZIP_PATH); + const reader = GTFSAsyncFeedReader.fromZip(zip); + const feed = await reader.loadFeed(); + assert(feed); +}); + test('Test FeedReader: directory path', () => { const reader = GTFSFeedReader.fromDir(DIR_PATH); const feed = reader.loadFeed(); assert(feed); }); +test('Test AsyncFeedReader: directory path', async () => { + const reader = GTFSAsyncFeedReader.fromDir(DIR_PATH); + const feed = await reader.loadFeed(); + assert(feed); +}); + test('Test FeedReader: file contents', () => { - const files: GTFSFileContent[] = [ - { name: 'agency.txt', content: readFileSync(join(DIR_PATH, 'agency.txt')) }, - { name: 'calendar_dates.txt', content: readFileSync(join(DIR_PATH, 'calendar_dates.txt')) }, - { name: 'calendar.txt', content: readFileSync(join(DIR_PATH, 'calendar.txt')) }, - { name: 'routes.txt', content: readFileSync(join(DIR_PATH, 'routes.txt')) }, - { name: 'shapes.txt', content: readFileSync(join(DIR_PATH, 'shapes.txt')) }, - { name: 'stop_times.txt', content: readFileSync(join(DIR_PATH, 'stop_times.txt')) }, - { name: 'stops.txt', content: readFileSync(join(DIR_PATH, 'stops.txt')) }, - { name: 'trips.txt', content: readFileSync(join(DIR_PATH, 'trips.txt')) } - ]; - const reader = GTFSFeedReader.fromFiles(files); + const reader = GTFSFeedReader.fromFiles(getFiles()); const feed = reader.loadFeed(); assert(feed); }); + +test('Test AsyncFeedReader: file contents', async() => { + const reader = GTFSAsyncFeedReader.fromFiles(getFiles()); + const feed = await reader.loadFeed(); + assert(feed); +}); From 356be2ff6680d09c7ff94a986d805fe0f8e2dcc1 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 14:47:28 +0700 Subject: [PATCH 15/20] Feed to their own classes. --- src/feed/base.ts | 133 ++++++++++++++++++++++++++++++ src/feed/iterable.ts | 42 ++++++++++ src/feed/loaded.ts | 27 ++++++ src/file-info.ts | 86 ++++++++++++------- src/index.ts | 7 +- src/io/feed-file.ts | 2 +- src/io/feed-reader.ts | 143 +++++++++----------------------- src/types.ts | 188 ++---------------------------------------- tsconfig.json | 2 +- 9 files changed, 309 insertions(+), 321 deletions(-) create mode 100644 src/feed/base.ts create mode 100644 src/feed/iterable.ts create mode 100644 src/feed/loaded.ts diff --git a/src/feed/base.ts b/src/feed/base.ts new file mode 100644 index 0000000..44106a0 --- /dev/null +++ b/src/feed/base.ts @@ -0,0 +1,133 @@ +import { GTFSTableName, GTFS_FILES } from '../file-info'; +import type { GTFSAgency } from '../files/agency'; +import type { GTFSArea } from '../files/area'; +import type { GTFSAttribution } from '../files/attribution'; +import type { GTFSCalendar } from '../files/calendar'; +import type { GTFSCalendarDate } from '../files/calendar-date'; +import type { GTFSFareAttribute } from '../files/fare-attribute'; +import type { GTFSFareLegRule } from '../files/fare-leg-rule'; +import type { GTFSFareMedia } from '../files/fare-media'; +import type { GTFSFareProduct } from '../files/fare-product'; +import type { GTFSFareRule } from '../files/fare-rule'; +import type { GTFSFareTransferRule } from '../files/fare-transfer-rule'; +import type { GTFSFeedInfo } from '../files/feed-info'; +import type { GTFSFrequency } from '../files/frequency'; +import type { GTFSLevel } from '../files/level'; +import type { GTFSNetwork } from '../files/network'; +import type { GTFSPathway } from '../files/pathway'; +import type { GTFSRoute } from '../files/route'; +import type { GTFSRouteNetwork } from '../files/route-network'; +import type { GTFSShape } from '../files/shape'; +import type { GTFSStop } from '../files/stop'; +import type { GTFSStopArea } from '../files/stop-area'; +import type { GTFSStopTime } from '../files/stop-time'; +import type { GTFSTimeframe } from '../files/timeframe'; +import type { GTFSTransfer } from '../files/transfer'; +import type { GTFSTranslation } from '../files/translation'; +import type { GTFSTrip } from '../files/trip'; +import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles } from '../types'; + +type RecordsGenericType = + R extends GTFSFileRow[] ? T[] : + R extends GTFSFileRecords ? GTFSFileRecords : + R extends GTFSAsyncFileRecords ? GTFSAsyncFileRecords : Iterable; + +export class GTFSFeedBase { + /** Transit agencies with service represented in this dataset. */ + public agency: RecordsGenericType; + /** Stops where vehicles pick up or drop off riders. */ + public stops: RecordsGenericType; + /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ + public routes: RecordsGenericType; + /** Trips for each route. */ + public trips: RecordsGenericType; + /** Times that a vehicle arrives at and departs from stops for each trip. */ + public stop_times: RecordsGenericType; + /** Service dates specified using a weekly schedule with start and end dates. */ + public calendar?: RecordsGenericType; + /** Exceptions for the services defined in the `calendar.txt`. */ + public calendar_dates?: RecordsGenericType; + /** Fare information for a transit agency's routes. */ + public fare_attributes?: RecordsGenericType; + /** Rules to apply fares for itineraries. */ + public fare_rules?: RecordsGenericType; + /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ + public timeframes?: RecordsGenericType; + /** To describe the fare media that can be employed to use fare products. */ + public fare_media?: RecordsGenericType; + /** To describe the different types of tickets or fares that can be purchased by riders. */ + public fare_products?: RecordsGenericType; + /** Fare rules for individual legs of travel. */ + public fare_leg_rules?: RecordsGenericType; + /** Fare rules for transfers between legs of travel. */ + public fare_transfer_rules?: RecordsGenericType; + /** Area grouping of locations. */ + public areas?: RecordsGenericType; + /** Rules to assign stops to areas. */ + public stop_areas?: RecordsGenericType; + /** Network grouping of routes. */ + public networks?: RecordsGenericType; + /** Rules to assign routes to networks. */ + public route_networks?: RecordsGenericType; + /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ + public shapes?: RecordsGenericType; + /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ + public frequencies?: RecordsGenericType; + /** Rules for making connections at transfer points between routes. */ + public transfers?: RecordsGenericType; + /** Pathways linking together locations within stations. */ + public pathways?: RecordsGenericType; + /** Levels within stations. */ + public levels?: RecordsGenericType; + /** Translations of customer-facing dataset values. */ + public translations?: RecordsGenericType; + /** Dataset metadata, including publisher, version, and expiration information. */ + public feed_info?: RecordsGenericType; + /** Dataset attributions. */ + public attributions?: RecordsGenericType; + + protected constructor(defaultValues: Partial>>, emptyValue: RecordsType) { + this.agency = defaultValues.agency as RecordsGenericType ?? emptyValue; + this.stops = defaultValues.stops as RecordsGenericType ?? emptyValue; + this.routes = defaultValues.routes as RecordsGenericType ?? emptyValue; + this.trips = defaultValues.trips as RecordsGenericType ?? emptyValue; + this.stop_times = defaultValues.stop_times as RecordsGenericType ?? emptyValue; + } + + public setTable(name: GTFSTableName, value: RecordsGenericType) { + Object.assign(this, { [name]: value }); + } + + public getTable(name: GTFSTableName): RecordsGenericType|undefined { + return this[name]! as RecordsGenericType; + } + + public *getAllTables(): GTFSIterableFeedFiles> { + if (this.agency !== undefined) yield { name: 'agency', info: GTFS_FILES.agency, records: this.agency }; + if (this.stops !== undefined) yield { name: 'stops', info: GTFS_FILES.stops, records: this.stops }; + if (this.routes !== undefined) yield { name: 'routes', info: GTFS_FILES.routes, records: this.routes }; + if (this.trips !== undefined) yield { name: 'trips', info: GTFS_FILES.trips, records: this.trips }; + if (this.stop_times !== undefined) yield { name: 'stop_times', info: GTFS_FILES.stop_times, records: this.stop_times }; + if (this.calendar !== undefined) yield { name: 'calendar', info: GTFS_FILES.calendar, records: this.calendar }; + if (this.calendar_dates !== undefined) yield { name: 'calendar_dates', info: GTFS_FILES.calendar_dates, records: this.calendar_dates }; + if (this.fare_attributes !== undefined) yield { name: 'fare_attributes', info: GTFS_FILES.fare_attributes, records: this.fare_attributes }; + if (this.fare_rules !== undefined) yield { name: 'fare_rules', info: GTFS_FILES.fare_rules, records: this.fare_rules }; + if (this.timeframes !== undefined) yield { name: 'timeframes', info: GTFS_FILES.timeframes, records: this.timeframes }; + if (this.fare_media !== undefined) yield { name: 'fare_media', info: GTFS_FILES.fare_media, records: this.fare_media }; + if (this.fare_products !== undefined) yield { name: 'fare_products', info: GTFS_FILES.fare_products, records: this.fare_products }; + if (this.fare_leg_rules !== undefined) yield { name: 'fare_leg_rules', info: GTFS_FILES.fare_leg_rules, records: this.fare_leg_rules }; + if (this.fare_transfer_rules !== undefined) yield { name: 'fare_transfer_rules', info: GTFS_FILES.fare_transfer_rules, records: this.fare_transfer_rules }; + if (this.areas !== undefined) yield { name: 'areas', info: GTFS_FILES.areas, records: this.areas }; + if (this.stop_areas !== undefined) yield { name: 'stop_areas', info: GTFS_FILES.stop_areas, records: this.stop_areas }; + if (this.networks !== undefined) yield { name: 'networks', info: GTFS_FILES.networks, records: this.networks }; + if (this.route_networks !== undefined) yield { name: 'route_networks', info: GTFS_FILES.route_networks, records: this.route_networks }; + if (this.shapes !== undefined) yield { name: 'shapes', info: GTFS_FILES.shapes, records: this.shapes }; + if (this.frequencies !== undefined) yield { name: 'frequencies', info: GTFS_FILES.frequencies, records: this.frequencies }; + if (this.transfers !== undefined) yield { name: 'transfers', info: GTFS_FILES.transfers, records: this.transfers }; + if (this.pathways !== undefined) yield { name: 'pathways', info: GTFS_FILES.pathways, records: this.pathways }; + if (this.levels !== undefined) yield { name: 'levels', info: GTFS_FILES.levels, records: this.levels }; + if (this.translations !== undefined) yield { name: 'translations', info: GTFS_FILES.translations, records: this.translations }; + if (this.feed_info !== undefined) yield { name: 'feed_info', info: GTFS_FILES.feed_info, records: this.feed_info }; + if (this.attributions !== undefined) yield { name: 'attributions', info: GTFS_FILES.attributions, records: this.attributions }; + } +}; diff --git a/src/feed/iterable.ts b/src/feed/iterable.ts new file mode 100644 index 0000000..0f0cc06 --- /dev/null +++ b/src/feed/iterable.ts @@ -0,0 +1,42 @@ +import { GTFSFeedBase } from './base'; +import { GTFSLoadedFeed } from './loaded'; +import type { GTFSTableName } from '../file-info'; +import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow } from '../types'; + +export class GTFSIterableFeed extends GTFSFeedBase { + public constructor(defaultValues: Partial> = {}) { + super(defaultValues, [].values()); + } + + public load(): GTFSLoadedFeed { + const result = new GTFSLoadedFeed(); + for (const table of this.getAllTables()) { + result.setTable(table.name, [...table.records]); + } + return result; + } +}; + +export class GTFSAsyncIterableFeed extends GTFSFeedBase { + public constructor(defaultValues: Partial> = {}) { + super(defaultValues, (async function*() {})()); + } + + public async getRecords(tableName: GTFSTableName): Promise { + const results = []; + const records = this.getTable(tableName); + if (records === undefined) return []; + for await (const record of records) { + results.push(record); + } + return results; + } + + public async load(): Promise { + const result = new GTFSLoadedFeed(); + for (const table of this.getAllTables()) { + result.setTable(table.name, await this.getRecords(table.name)); + } + return result; + } +}; diff --git a/src/feed/loaded.ts b/src/feed/loaded.ts new file mode 100644 index 0000000..ccb88d2 --- /dev/null +++ b/src/feed/loaded.ts @@ -0,0 +1,27 @@ +import { GTFSFeedBase } from './base'; +import type { GTFSFileRow } from '../types'; +import type { GTFSTableName } from '../file-info'; +import { GTFSAsyncIterableFeed, GTFSIterableFeed } from './iterable'; + +export class GTFSLoadedFeed extends GTFSFeedBase { + public constructor(defaultValues: Partial> = {}) { + super(defaultValues, (() => [])()); + } + + public getIterable(): GTFSIterableFeed { + const feed = new GTFSIterableFeed({}); + for (const table of this.getAllTables()) { + feed.setTable(table.name, table.records.values()); + } + return feed; + } + + public getAsyncIterable(): GTFSAsyncIterableFeed { + const feed = new GTFSAsyncIterableFeed({}); + for (const table of this.getAllTables()) { + const generator = async function*() { for (const record of table.records) yield record; } + feed.setTable(table.name, generator()); + } + return feed; + } +}; diff --git a/src/file-info.ts b/src/file-info.ts index c210d49..a81b4be 100644 --- a/src/file-info.ts +++ b/src/file-info.ts @@ -1,7 +1,9 @@ /** GTFS file information */ export type GTFSFileInfo = { + /** Table name (file name without .txt) */ + tableName: GTFSTableName, /** File name including .txt */ - name: string, + fileName: string, /** * Columns in file name, key being column name and value being type: * string: interpret value as string (using .toString()), @@ -15,7 +17,7 @@ export type GTFSFileInfo = { /** * GTFS file name */ -export type GTFSFileName = 'agency' +export type GTFSTableName = 'agency' | 'stops' | 'routes' | 'trips' @@ -45,9 +47,10 @@ export type GTFSFileName = 'agency' /** * GTFS files information */ -export const GTFS_FILES: Record = { +export const GTFS_FILES: Record = { agency: { - name: 'agency.txt', + tableName: 'agency', + fileName: 'agency.txt', columns: { agency_id: 'string', agency_name: 'string', @@ -60,7 +63,8 @@ export const GTFS_FILES: Record = { } }, stops: { - name: 'stops.txt', + tableName: 'stops', + fileName: 'stops.txt', columns: { stop_id: 'string', stop_code: 'string', @@ -80,7 +84,8 @@ export const GTFS_FILES: Record = { } }, routes: { - name: 'routes.txt', + tableName: 'routes', + fileName: 'routes.txt', columns: { route_id: 'string', agency_id: 'string', @@ -98,7 +103,8 @@ export const GTFS_FILES: Record = { } }, trips: { - name: 'trips.txt', + tableName: 'trips', + fileName: 'trips.txt', columns: { route_id: 'string', service_id: 'string', @@ -113,7 +119,8 @@ export const GTFS_FILES: Record = { } }, stop_times: { - name: 'stop_times.txt', + tableName: 'stop_times', + fileName: 'stop_times.txt', columns: { trip_id: 'string', arrival_time: 'string', @@ -130,7 +137,8 @@ export const GTFS_FILES: Record = { } }, calendar: { - name: 'calendar.txt', + tableName: 'calendar', + fileName: 'calendar.txt', columns: { service_id: 'string', monday: 'int', @@ -145,7 +153,8 @@ export const GTFS_FILES: Record = { } }, calendar_dates: { - name: 'calendar_dates.txt', + tableName: 'calendar_dates', + fileName: 'calendar_dates.txt', columns: { service_id: 'string', date: 'string', @@ -153,7 +162,8 @@ export const GTFS_FILES: Record = { } }, fare_attributes: { - name: 'fare_attributes.txt', + tableName: 'fare_attributes', + fileName: 'fare_attributes.txt', columns: { fare_id: 'string', price: 'float', @@ -165,7 +175,8 @@ export const GTFS_FILES: Record = { } }, fare_rules: { - name: 'fare_rules.txt', + tableName: 'fare_rules', + fileName: 'fare_rules.txt', columns: { fare_id: 'string', route_id: 'string', @@ -175,7 +186,8 @@ export const GTFS_FILES: Record = { } }, timeframes: { - name: 'timeframes.txt', + tableName: 'timeframes', + fileName: 'timeframes.txt', columns: { timeframe_group_id: 'string', start_time: 'string', @@ -184,7 +196,8 @@ export const GTFS_FILES: Record = { } }, fare_media: { - name: 'fare_media.txt', + tableName: 'fare_media', + fileName: 'fare_media.txt', columns: { fare_media_id: 'string', fare_media_name: 'string', @@ -192,7 +205,8 @@ export const GTFS_FILES: Record = { } }, fare_products: { - name: 'fare_products.txt', + tableName: 'fare_products', + fileName: 'fare_products.txt', columns: { fare_product_id: 'string', fare_product_name: 'string', @@ -202,7 +216,8 @@ export const GTFS_FILES: Record = { } }, fare_leg_rules: { - name: 'fare_leg_rules.txt', + tableName: 'fare_leg_rules', + fileName: 'fare_leg_rules.txt', columns: { leg_group_id: 'string', network_id: 'string', @@ -214,7 +229,8 @@ export const GTFS_FILES: Record = { } }, fare_transfer_rules: { - name: 'fare_transfer_rules.txt', + tableName: 'fare_transfer_rules', + fileName: 'fare_transfer_rules.txt', columns: { from_leg_group_id: 'string', to_leg_group_id: 'string', @@ -226,35 +242,40 @@ export const GTFS_FILES: Record = { } }, areas: { - name: 'areas.txt', + tableName: 'areas', + fileName: 'areas.txt', columns: { area_id: 'string', area_name: 'string' } }, stop_areas: { - name: 'stop_areas.txt', + tableName: 'stop_areas', + fileName: 'stop_areas.txt', columns: { area_id: 'string', stop_id: 'string' } }, networks: { - name: 'networks.txt', + tableName: 'networks', + fileName: 'networks.txt', columns: { network_id: 'string', network_name: 'string' } }, route_networks: { - name: 'route_networks.txt', + tableName: 'route_networks', + fileName: 'route_networks.txt', columns: { network_id: 'string', route_id: 'string' } }, shapes: { - name: 'shapes.txt', + tableName: 'shapes', + fileName: 'shapes.txt', columns: { shape_id: 'string', shape_pt_lat: 'float', @@ -264,7 +285,8 @@ export const GTFS_FILES: Record = { } }, frequencies: { - name: 'frequencies.txt', + tableName: 'frequencies', + fileName: 'frequencies.txt', columns: { trip_id: 'string', start_time: 'string', @@ -274,7 +296,8 @@ export const GTFS_FILES: Record = { } }, transfers: { - name: 'transfers.txt', + tableName: 'transfers', + fileName: 'transfers.txt', columns: { from_stop_id: 'string', to_stop_id: 'string', @@ -287,7 +310,8 @@ export const GTFS_FILES: Record = { } }, pathways: { - name: 'pathways.txt', + tableName: 'pathways', + fileName: 'pathways.txt', columns: { pathway_id: 'string', from_stop_id: 'string', @@ -304,7 +328,8 @@ export const GTFS_FILES: Record = { } }, levels: { - name: 'levels.txt', + tableName: 'levels', + fileName: 'levels.txt', columns: { level_id: 'string', level_index: 'float', @@ -312,7 +337,8 @@ export const GTFS_FILES: Record = { } }, translations: { - name: 'translations.txt', + tableName: 'translations', + fileName: 'translations.txt', columns: { table_name: 'string', field_name: 'string', @@ -324,7 +350,8 @@ export const GTFS_FILES: Record = { } }, feed_info: { - name: 'feed_info.txt', + tableName: 'feed_info', + fileName: 'feed_info.txt', columns: { feed_publisher_name: 'string', feed_publisher_url: 'string', @@ -338,7 +365,8 @@ export const GTFS_FILES: Record = { } }, attributions: { - name: 'attributions.txt', + tableName: 'attributions', + fileName: 'attributions.txt', columns: { attribution_id: 'string', agency_id: 'string', diff --git a/src/index.ts b/src/index.ts index e4271ed..17fad85 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ export { GTFSFileIO, GTFSAsyncFileIO } from './io/file'; -export { GTFSFeedReader } from './io/feed-reader'; -export { default as GTFSFeedWriter } from './io/feed-writer'; export * from './io/feed-file'; +export { GTFSFeedReader } from './io/feed-reader'; +// export { default as GTFSFeedWriter } from './io/feed-writer'; + +export * from './feed/iterable'; +export * from './feed/loaded'; export * from './file-info'; export { GTFSCalendarDateException } from './files/calendar-date'; diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index d23f717..73457aa 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -38,7 +38,7 @@ abstract class FeedFileIO { * File name including .txt. */ public get fileName(): string { - return this.fileInfo.name; + return this.fileInfo.fileName; } /** diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 4f6950b..9f83739 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -10,19 +10,17 @@ import { import { join as joinPath } from 'path'; import { getAsyncIOFromFileName, getIOFromFileName } from './feed-file'; import { getGTFSFileInfos } from '../file-info'; -import type { GTFSFileInfo, GTFSFileName } from '../file-info'; +import type { GTFSFileInfo, GTFSTableName } from '../file-info'; import type { - GTFSAsyncFeed, GTFSAsyncFileRecords, - GTFSAsyncIterableFeedFile, - GTFSAsyncIterableFeedFiles, - GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles, - GTFSLoadedFeed } from '../types'; +import { GTFSFeedBase } from '../feed/base'; +import { GTFSLoadedFeed } from '../feed/loaded'; +import { GTFSAsyncIterableFeed, GTFSIterableFeed } from '../feed/iterable'; /** * GTFS file object to read @@ -39,12 +37,7 @@ type GTFSFile = { /** * GTFS feed reader abstract */ -abstract class FeedReader< - LoadedFeedType extends GTFSLoadedFeed|Promise, - FeedType extends GTFSFeed|Promise, - IterableFeedFilesType extends GTFSIterableFeedFiles|GTFSAsyncIterableFeedFiles, - RecordsType extends GTFSFileRecords|GTFSAsyncFileRecords -> { +abstract class FeedReader|Promise>> { /** Zip object */ protected zip?: AdmZip = undefined; @@ -70,7 +63,7 @@ abstract class FeedReader< this.files = []; if (directoryPath) { for (const info of getGTFSFileInfos()) { - const path = joinPath(directoryPath, info.name); + const path = joinPath(directoryPath, info.fileName); if (!exists(path)) continue; this.files.push({ info, path }); } @@ -78,7 +71,7 @@ abstract class FeedReader< } if (fileContents) { for (const info of getGTFSFileInfos()) { - const file = fileContents.filter(f => f.name === info.name); + const file = fileContents.filter(f => f.name === info.fileName); if (!file.length) continue; this.files.push({ info, buffer: Buffer.from(file[0].content) }) } @@ -107,16 +100,17 @@ abstract class FeedReader< /** * From file information, get records. + * Undefined if file does not exist in the feed. * @param info file information * @returns Records */ - public getRecordsFromFileInfo(info: GTFSFileInfo): RecordsType|undefined { + public getRecords(info: GTFSFileInfo): RecordsType|undefined { if (this.zip) { - const entry = this.zip.getEntries().filter(entry => entry.entryName === info.name); + const entry = this.zip.getEntries().filter(entry => entry.entryName === info.fileName); if (!entry.length) return undefined; return this.getRecordsFromZipEntry(info, entry[0]); } else if (this.files) { - const file = this.files.filter(f => f.info.name === info.name); + const file = this.files.filter(f => f.info.fileName === info.fileName); if (!file.length) return undefined; if (file[0].path) { return this.getRecordsFromFilePath(info, file[0].path); @@ -127,21 +121,11 @@ abstract class FeedReader< return undefined; } - /** - * Get files existing in the feed and return their iterables (without reading them yet, depending on the initialisation). - * @returns Iterable feed files - */ - public abstract getIterableFiles(): IterableFeedFilesType; /** * Get feed object with row being file name without .txt and value being iterable records. * @returns Feed object with row being file name without .txt and value being iterable records. */ public abstract getFeed(): FeedType; - /** - * Get feed object with row being file name without .txt and value being array of records. - * @returns Feed object with row being file name without .txt and value being array of records. - */ - public abstract loadFeed(): LoadedFeedType; }; /** @@ -151,7 +135,7 @@ abstract class FeedReader< * GTFSFeedReader.fromDir, * GTFSFeedReader.fromFiles */ -export class GTFSFeedReader extends FeedReader { +export class GTFSFeedReader extends FeedReader { /** * Generator of iterable chunks from a file path. * @param filePath File path @@ -176,12 +160,12 @@ export class GTFSFeedReader extends FeedReader> = { - agency: [], - stops: [], - routes: [], - trips: [], - stop_times: [] - }; + public getFeed(): GTFSIterableFeed { + const feed = new GTFSIterableFeed(); - for (const file of this.getIterableFiles()) { - const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; - results[key] = file.records; + for (const fileInfo of getGTFSFileInfos()) { + const records = this.getRecords(fileInfo); + if (records === undefined) continue; + feed.setTable(fileInfo.tableName, records); } - return results as GTFSFeed; + return feed; } public loadFeed(): GTFSLoadedFeed { - const feed = this.getFeed(); - const results: Partial> = {}; - - for (const key of Object.keys(feed) as GTFSFileName[]) { - results[key] = [...feed[key]!]; - } - - return results as GTFSLoadedFeed; + return this.getFeed().load(); } /** @@ -258,7 +221,7 @@ export class GTFSFeedReader extends FeedReader, Promise, GTFSAsyncIterableFeedFiles, GTFSAsyncFileRecords> { +export class GTFSAsyncFeedReader extends FeedReader> { /** * Generator of iterable chunks from a file path. * @param filePath File path @@ -291,68 +254,36 @@ export class GTFSAsyncFeedReader extends FeedReader, Pro } protected getRecordsFromZipEntry(info: GTFSFileInfo, entry: AdmZip.IZipEntry): GTFSAsyncFileRecords { - const io = getAsyncIOFromFileName(info.name); + const io = getAsyncIOFromFileName(info.fileName); const generator = async function*() { yield entry.getData().toString(); }; return io.read(generator()); } protected getRecordsFromFilePath(info: GTFSFileInfo, path: string): GTFSAsyncFileRecords { - const io = getAsyncIOFromFileName(info.name); + const io = getAsyncIOFromFileName(info.fileName); return io.read(GTFSAsyncFeedReader.readFileChunks(path)); } protected getRecordsFromFileContent(info: GTFSFileInfo, content: Buffer): GTFSAsyncFileRecords { - const io = getAsyncIOFromFileName(info.name); + const io = getAsyncIOFromFileName(info.fileName); const generator = async function*() { yield content.toString(); }; return io.read(generator()); } - public async *getIterableFiles(): GTFSAsyncIterableFeedFiles { - for (const info of getGTFSFileInfos()) { - const records = this.getRecordsFromFileInfo(info); - if (records === undefined) continue; - yield { info, records }; - } - return; - } - - public async getFeed(): Promise { - const empty = async function*() {}; - const results: Partial> = { - agency: empty(), - stops: empty(), - routes: empty(), - trips: empty(), - stop_times: empty() - }; + public async getFeed(): Promise { + const feed = new GTFSAsyncIterableFeed(); - for await (const file of this.getIterableFiles()) { - const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; - results[key] = file.records; + for (const fileInfo of getGTFSFileInfos()) { + const records = this.getRecords(fileInfo); + if (records === undefined) continue; + feed.setTable(fileInfo.tableName, records); } - return results as GTFSAsyncFeed; + return feed; } public async loadFeed(): Promise { - const results: Partial> = { - agency: [], - stops: [], - routes: [], - trips: [], - stop_times: [] - }; - - for await (const file of this.getIterableFiles()) { - const key = file.info.name.slice(0, file.info.name.length - 4) as GTFSFileName; - if (results[key] === undefined) results[key] = []; - - for await (const record of file.records) { - results[key]!.push(record); - } - } - - return results as GTFSLoadedFeed; + return (await this.getFeed()).load(); } /** diff --git a/src/types.ts b/src/types.ts index c61b074..aadbe0c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,7 +24,7 @@ import type { GTFSTimeframe } from './files/timeframe'; import type { GTFSTransfer } from './files/transfer'; import type { GTFSTranslation } from './files/translation'; import type { GTFSTrip } from './files/trip'; -import type { GTFSFileInfo } from './file-info'; +import type { GTFSFileInfo, GTFSTableName } from './file-info'; /** A row record in a GTFS file */ export type GTFSFileRow = GTFSAgency @@ -55,28 +55,20 @@ export type GTFSFileRow = GTFSAgency | GTFSAttribution; /** GTFS file records */ -export type GTFSFileRecords = IterableIterator|RowType[]; +export type GTFSFileRecords = IterableIterator; /** GTFS async file records or loaded array */ export type GTFSAsyncFileRecords = AsyncIterableIterator; /** GTFS iterable for an individual file */ -export type GTFSIterableFeedFile = { +export type GTFSIterableFeedFile = { + name: GTFSTableName, info: GTFSFileInfo, - records: GTFSFileRecords -}; - -/** GTFS async iterable for an individual file */ -export type GTFSAsyncIterableFeedFile = { - info: GTFSFileInfo, - records: GTFSAsyncFileRecords + records: RecordsType }; /** GTFS Iterable feed files */ -export type GTFSIterableFeedFiles = IterableIterator; - -/** GTFS Async Iterable feed files */ -export type GTFSAsyncIterableFeedFiles = AsyncIterableIterator; +export type GTFSIterableFeedFiles = IterableIterator>; /** GTFS feed file content */ export type GTFSFileContent = { @@ -85,171 +77,3 @@ export type GTFSFileContent = { /** File content */ content: string|Buffer }; - -/** GTFS Dataset feed */ -export type GTFSFeed = { - /** Transit agencies with service represented in this dataset. */ - agency: GTFSFileRecords, - /** Stops where vehicles pick up or drop off riders. */ - stops: GTFSFileRecords, - /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ - routes: GTFSFileRecords, - /** Trips for each route. */ - trips: GTFSFileRecords, - /** Times that a vehicle arrives at and departs from stops for each trip. */ - stop_times: GTFSFileRecords, - /** Service dates specified using a weekly schedule with start and end dates. */ - calendar?: GTFSFileRecords, - /** Exceptions for the services defined in the `calendar.txt`. */ - calendar_dates?: GTFSFileRecords, - /** Fare information for a transit agency's routes. */ - fare_attributes?: GTFSFileRecords, - /** Rules to apply fares for itineraries. */ - fare_rules?: GTFSFileRecords, - /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ - timeframes?: GTFSFileRecords, - /** To describe the fare media that can be employed to use fare products. */ - fare_media?: GTFSFileRecords, - /** To describe the different types of tickets or fares that can be purchased by riders. */ - fare_products?: GTFSFileRecords, - /** Fare rules for individual legs of travel. */ - fare_leg_rules?: GTFSFileRecords, - /** Fare rules for transfers between legs of travel. */ - fare_transfer_rules?: GTFSFileRecords, - /** Area grouping of locations. */ - areas?: GTFSFileRecords, - /** Rules to assign stops to areas. */ - stop_areas?: GTFSFileRecords, - /** Network grouping of routes. */ - networks?: GTFSFileRecords, - /** Rules to assign routes to networks. */ - route_networks?: GTFSFileRecords, - /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ - shapes?: GTFSFileRecords, - /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ - frequencies?: GTFSFileRecords, - /** Rules for making connections at transfer points between routes. */ - transfers?: GTFSFileRecords, - /** Pathways linking together locations within stations. */ - pathways?: GTFSFileRecords, - /** Levels within stations. */ - levels?: GTFSFileRecords, - /** Translations of customer-facing dataset values. */ - translations?: GTFSFileRecords, - /** Dataset metadata, including publisher, version, and expiration information. */ - feed_info?: GTFSFileRecords, - /** Dataset attributions. */ - attributions?: GTFSFileRecords -}; - -/** GTFS Async Dataset feed */ -export type GTFSAsyncFeed = { - /** Transit agencies with service represented in this dataset. */ - agency: GTFSAsyncFileRecords, - /** Stops where vehicles pick up or drop off riders. */ - stops: GTFSAsyncFileRecords, - /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ - routes: GTFSAsyncFileRecords, - /** Trips for each route. */ - trips: GTFSAsyncFileRecords, - /** Times that a vehicle arrives at and departs from stops for each trip. */ - stop_times: GTFSAsyncFileRecords, - /** Service dates specified using a weekly schedule with start and end dates. */ - calendar?: GTFSAsyncFileRecords, - /** Exceptions for the services defined in the `calendar.txt`. */ - calendar_dates?: GTFSAsyncFileRecords, - /** Fare information for a transit agency's routes. */ - fare_attributes?: GTFSAsyncFileRecords, - /** Rules to apply fares for itineraries. */ - fare_rules?: GTFSAsyncFileRecords, - /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ - timeframes?: GTFSAsyncFileRecords, - /** To describe the fare media that can be employed to use fare products. */ - fare_media?: GTFSAsyncFileRecords, - /** To describe the different types of tickets or fares that can be purchased by riders. */ - fare_products?: GTFSAsyncFileRecords, - /** Fare rules for individual legs of travel. */ - fare_leg_rules?: GTFSAsyncFileRecords, - /** Fare rules for transfers between legs of travel. */ - fare_transfer_rules?: GTFSAsyncFileRecords, - /** Area grouping of locations. */ - areas?: GTFSAsyncFileRecords, - /** Rules to assign stops to areas. */ - stop_areas?: GTFSAsyncFileRecords, - /** Network grouping of routes. */ - networks?: GTFSAsyncFileRecords, - /** Rules to assign routes to networks. */ - route_networks?: GTFSAsyncFileRecords, - /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ - shapes?: GTFSAsyncFileRecords, - /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ - frequencies?: GTFSAsyncFileRecords, - /** Rules for making connections at transfer points between routes. */ - transfers?: GTFSAsyncFileRecords, - /** Pathways linking together locations within stations. */ - pathways?: GTFSAsyncFileRecords, - /** Levels within stations. */ - levels?: GTFSAsyncFileRecords, - /** Translations of customer-facing dataset values. */ - translations?: GTFSAsyncFileRecords, - /** Dataset metadata, including publisher, version, and expiration information. */ - feed_info?: GTFSAsyncFileRecords, - /** Dataset attributions. */ - attributions?: GTFSAsyncFileRecords -}; - -/** GTFS Dataset feed loaded to memory */ -export type GTFSLoadedFeed = { - /** Transit agencies with service represented in this dataset. */ - agency: GTFSAgency[], - /** Stops where vehicles pick up or drop off riders. */ - stops: GTFSStop[], - /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ - routes: GTFSRoute[], - /** Trips for each route. */ - trips: GTFSTrip[], - /** Times that a vehicle arrives at and departs from stops for each trip. */ - stop_times: GTFSStopTime[], - /** Service dates specified using a weekly schedule with start and end dates. */ - calendar?: GTFSCalendar[], - /** Exceptions for the services defined in the `calendar.txt`. */ - calendar_dates?: GTFSCalendarDate[], - /** Fare information for a transit agency's routes. */ - fare_attributes?: GTFSFareAttribute[], - /** Rules to apply fares for itineraries. */ - fare_rules?: GTFSFareRule[], - /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ - timeframes?: GTFSTimeframe[], - /** To describe the fare media that can be employed to use fare products. */ - fare_media?: GTFSFareMedia[], - /** To describe the different types of tickets or fares that can be purchased by riders. */ - fare_products?: GTFSFareProduct[], - /** Fare rules for individual legs of travel. */ - fare_leg_rules?: GTFSFareLegRule[], - /** Fare rules for transfers between legs of travel. */ - fare_transfer_rules?: GTFSFareTransferRule[], - /** Area grouping of locations. */ - areas?: GTFSArea[], - /** Rules to assign stops to areas. */ - stop_areas?: GTFSStopArea[], - /** Network grouping of routes. */ - networks?: GTFSNetwork[], - /** Rules to assign routes to networks. */ - route_networks?: GTFSRouteNetwork[], - /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ - shapes?: GTFSShape[], - /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ - frequencies?: GTFSFrequency[], - /** Rules for making connections at transfer points between routes. */ - transfers?: GTFSTransfer[], - /** Pathways linking together locations within stations. */ - pathways?: GTFSPathway[], - /** Levels within stations. */ - levels?: GTFSLevel[], - /** Translations of customer-facing dataset values. */ - translations?: GTFSTranslation[], - /** Dataset metadata, including publisher, version, and expiration information. */ - feed_info?: GTFSFeedInfo[], - /** Dataset attributions. */ - attributions?: GTFSAttribution[] -}; diff --git a/tsconfig.json b/tsconfig.json index d46df14..eeec8f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ }, "include": [ "./src/**/*.ts" - ], +, "src/io/feed-writer.ts.skip" ], "exclude": [ "./tests/*.ts" ] From 8473c74e0defb4663c8025e00266b15732e40dc1 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 15:30:44 +0700 Subject: [PATCH 16/20] Added async feed writer --- src/feed/base.ts | 1 + src/feed/iterable.ts | 9 +++ src/index.ts | 4 +- src/io/feed-writer.ts | 143 +++++++++++++++++++++++++++++--------- tests/feed-reader.test.ts | 2 +- tests/feed-writer.test.ts | 72 ++++++++++++++++++- tsconfig.json | 2 +- 7 files changed, 193 insertions(+), 40 deletions(-) diff --git a/src/feed/base.ts b/src/feed/base.ts index 44106a0..f8b11ae 100644 --- a/src/feed/base.ts +++ b/src/feed/base.ts @@ -87,6 +87,7 @@ export class GTFSFeedBase { public attributions?: RecordsGenericType; protected constructor(defaultValues: Partial>>, emptyValue: RecordsType) { + Object.assign(this, defaultValues); this.agency = defaultValues.agency as RecordsGenericType ?? emptyValue; this.stops = defaultValues.stops as RecordsGenericType ?? emptyValue; this.routes = defaultValues.routes as RecordsGenericType ?? emptyValue; diff --git a/src/feed/iterable.ts b/src/feed/iterable.ts index 0f0cc06..08f5290 100644 --- a/src/feed/iterable.ts +++ b/src/feed/iterable.ts @@ -15,6 +15,15 @@ export class GTFSIterableFeed extends GTFSFeedBase { } return result; } + + public toAsync(): GTFSAsyncIterableFeed { + const result = new GTFSAsyncIterableFeed(); + for (const table of this.getAllTables()) { + const generator = async function*() { for (const record of table.records) yield record; }; + result.setTable(table.name, generator()); + } + return result; + } }; export class GTFSAsyncIterableFeed extends GTFSFeedBase { diff --git a/src/index.ts b/src/index.ts index 17fad85..4e4ea82 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export { GTFSFileIO, GTFSAsyncFileIO } from './io/file'; export * from './io/feed-file'; -export { GTFSFeedReader } from './io/feed-reader'; -// export { default as GTFSFeedWriter } from './io/feed-writer'; +export { GTFSFeedReader, GTFSAsyncFeedReader } from './io/feed-reader'; +export { GTFSFeedWriter, GTFSAsyncFeedWriter } from './io/feed-writer'; export * from './feed/iterable'; export * from './feed/loaded'; diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index 0deea1e..243d61b 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -1,46 +1,116 @@ import AdmZip from 'adm-zip'; import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'fs'; import { join as joinPath } from 'path'; -import { getIOFromFileName } from './feed-file'; -import { GTFSFileName, GTFS_FILES } from '../file-info'; -import type { GTFSFeed, GTFSFileContent, GTFSFileRecords, GTFSIterableFeedFiles } from '../types'; +import { getAsyncIOFromFileName, getIOFromFileName } from './feed-file'; +import type { GTFSFileContent } from '../types'; +import { GTFSAsyncIterableFeed, GTFSIterableFeed } from '../feed/iterable'; +import { GTFSLoadedFeed } from '../feed/loaded'; /** * GTFS feed writer. * For static usage only. */ -export default class GTFSFeedWriter { +export class GTFSFeedWriter { private constructor() {} /** - * Generator of feed by file. + * Create a zip instance in memory. + * @param feed GTFS Feed + * @returns AdmZip instance + */ + public static createZip(feed: GTFSIterableFeed|GTFSLoadedFeed): AdmZip { + if (feed instanceof GTFSLoadedFeed) feed = feed.getIterable(); + + const zip = new AdmZip(); + for (const table of feed.getAllTables()) { + const io = getIOFromFileName(table.info.fileName); + zip.addFile( + io.fileName, + Buffer.from([...io.write(table.records)].join('')) + ); + } + return zip; + } + + /** + * Create file contents in memory. + * @param feed GTFS Feed + * @returns File contents + */ + public static createFileContents(feed: GTFSIterableFeed|GTFSLoadedFeed): GTFSFileContent[] { + if (feed instanceof GTFSLoadedFeed) feed = feed.getIterable(); + + const fileContents: GTFSFileContent[] = []; + for (const table of feed.getAllTables()) { + const io = getIOFromFileName(table.info.fileName); + fileContents.push({ + name: io.fileName, + content: [...io.write(table.records)].join('') + }); + } + return fileContents; + } + + /** + * Create a zip of GTFS feed and write to the specific path. + * @param feed GTFS Feed + * @param path Path to output zip file + */ + public static writeZip(feed: GTFSIterableFeed|GTFSLoadedFeed, path: string): void { + GTFSFeedWriter.createZip(feed).writeZip(path); + } + + /** + * Write GTFS feed to the specific directory path. * @param feed GTFS Feed - * @returns Iterable feed files + * @param path Path to output directory + * @param mkdirIfNotExists True to recursively create a directory at the path if does not exist + * @returns File names without directory path, with .txt extension. */ - private static *getIterableFiles(feed: GTFSFeed): GTFSIterableFeedFiles { - const keys = Object.keys(feed) as GTFSFileName[]; - for (const key of keys) { - const info = GTFS_FILES[key]; - if (!info) continue; - const records = feed[key] as GTFSFileRecords; - yield { info, records: Array.isArray(records) ? records.values() : records }; + public static writeDirectory(feed: GTFSIterableFeed|GTFSLoadedFeed, path: string, mkdirIfNotExists: boolean = true): string[] { + if (feed instanceof GTFSLoadedFeed) feed = feed.getIterable(); + + if (!existsSync(path) && mkdirIfNotExists) { + mkdirSync(path, { recursive: true }); } - return; + const fileNames = []; + for (const table of feed.getAllTables()) { + const io = getIOFromFileName(table.info.fileName); + const filePath = joinPath(path, io.fileName); + writeFileSync(filePath, ''); + const contents = io.write(table.records); + for (const content of contents) { + appendFileSync(filePath, content); + } + fileNames.push(io.fileName); + } + return fileNames; } +}; + +/** + * GTFS feed writer. + * For static usage only. + */ +export class GTFSAsyncFeedWriter { + private constructor() {} /** * Create a zip instance in memory. * @param feed GTFS Feed * @returns AdmZip instance */ - public static createZip(feed: GTFSFeed): AdmZip { + public static async createZip(feed: GTFSAsyncIterableFeed|GTFSIterableFeed|GTFSLoadedFeed): Promise { + if (feed instanceof GTFSLoadedFeed) feed = feed.getAsyncIterable(); + else if (feed instanceof GTFSIterableFeed) feed = feed.toAsync(); + const zip = new AdmZip(); - const files = GTFSFeedWriter.getIterableFiles(feed); - for (const file of files) { - const io = getIOFromFileName(file.info.name); - zip.addFile(io.fileName, Buffer.from( - Array.isArray(file.records) ? io.writeContent(file.records) : [...io.write(file.records)].join('') - )); + for (const table of feed.getAllTables()) { + const io = getAsyncIOFromFileName(table.info.fileName); + zip.addFile( + io.fileName, + Buffer.from(await io.writeAll(table.records)) + ); } return zip; } @@ -50,14 +120,16 @@ export default class GTFSFeedWriter { * @param feed GTFS Feed * @returns File contents */ - public static createFileContents(feed: GTFSFeed): GTFSFileContent[] { + public static async createFileContents(feed: GTFSAsyncIterableFeed|GTFSIterableFeed|GTFSLoadedFeed): Promise { + if (feed instanceof GTFSLoadedFeed) feed = feed.getAsyncIterable(); + else if (feed instanceof GTFSIterableFeed) feed = feed.toAsync(); + const fileContents: GTFSFileContent[] = []; - const files = GTFSFeedWriter.getIterableFiles(feed); - for (const file of files) { - const io = getIOFromFileName(file.info.name); + for (const table of feed.getAllTables()) { + const io = getAsyncIOFromFileName(table.info.fileName); fileContents.push({ name: io.fileName, - content: Array.isArray(file.records) ? io.writeContent(file.records) : [...io.write(file.records)].join('') + content: await io.writeAll(table.records) }); } return fileContents; @@ -68,8 +140,8 @@ export default class GTFSFeedWriter { * @param feed GTFS Feed * @param path Path to output zip file */ - public static writeZip(feed: GTFSFeed, path: string): void { - GTFSFeedWriter.createZip(feed).writeZip(path); + public static async writeZip(feed: GTFSAsyncIterableFeed|GTFSIterableFeed|GTFSLoadedFeed, path: string): Promise { + (await GTFSAsyncFeedWriter.createZip(feed)).writeZip(path); } /** @@ -79,18 +151,20 @@ export default class GTFSFeedWriter { * @param mkdirIfNotExists True to recursively create a directory at the path if does not exist * @returns File names without directory path, with .txt extension. */ - public static writeDirectory(feed: GTFSFeed, path: string, mkdirIfNotExists: boolean = true): string[] { + public static async writeDirectory(feed: GTFSAsyncIterableFeed|GTFSIterableFeed|GTFSLoadedFeed, path: string, mkdirIfNotExists: boolean = true): Promise { + if (feed instanceof GTFSLoadedFeed) feed = feed.getAsyncIterable(); + else if (feed instanceof GTFSIterableFeed) feed = feed.toAsync(); + if (!existsSync(path) && mkdirIfNotExists) { mkdirSync(path, { recursive: true }); } - const files = GTFSFeedWriter.getIterableFiles(feed); const fileNames = []; - for (const file of files) { - const io = getIOFromFileName(file.info.name); + for (const table of feed.getAllTables()) { + const io = getAsyncIOFromFileName(table.info.fileName); const filePath = joinPath(path, io.fileName); writeFileSync(filePath, ''); - const contents = Array.isArray(file.records) ? [io.writeContent(file.records)] : io.write(file.records); - for (const content of contents) { + const contents = io.write(table.records); + for await (const content of contents) { appendFileSync(filePath, content); } fileNames.push(io.fileName); @@ -98,3 +172,4 @@ export default class GTFSFeedWriter { return fileNames; } }; + diff --git a/tests/feed-reader.test.ts b/tests/feed-reader.test.ts index 1e74996..323bf18 100644 --- a/tests/feed-reader.test.ts +++ b/tests/feed-reader.test.ts @@ -4,6 +4,7 @@ import { test, expect } from 'vitest'; import { GTFSContinuousPickupDropOff, GTFSFeedReader, + GTFSAsyncFeedReader, GTFSRouteType, GTFSStopLocationType, GTFSStopTimePickupDropOff, @@ -13,7 +14,6 @@ import { GTFSWheelchairAccessbility } from '../dist'; import type { GTFSFileContent, GTFSLoadedFeed } from '../dist'; -import { GTFSAsyncFeedReader } from '../dist/io/feed-reader'; const ZIP_PATH = './tests/data/gtfs.zip'; const DIR_PATH = './tests/data/gtfs'; diff --git a/tests/feed-writer.test.ts b/tests/feed-writer.test.ts index 5aa6125..a781a34 100644 --- a/tests/feed-writer.test.ts +++ b/tests/feed-writer.test.ts @@ -3,9 +3,10 @@ import AdmZip from 'adm-zip'; import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'fs'; import { join as joinPath } from 'path'; import { + GTFSAsyncFeedWriter, GTFSContinuousPickupDropOff, - GTFSFeed, GTFSFeedWriter, + GTFSLoadedFeed, GTFSRouteType, GTFSStopTimePickupDropOff, GTFSStopTimeTimepoint, @@ -17,7 +18,7 @@ import { const OUTPUT_DIR = './tests/data/ignore'; const COMPARE_DIR = './tests/data/gtfs'; -const getTestFeed = (): GTFSFeed => ({ +const getTestFeed = (): GTFSLoadedFeed => new GTFSLoadedFeed({ agency: [{ agency_id: 'A01', agency_name: 'This Agency', @@ -110,6 +111,34 @@ test('Test FeedWriter: zip', () => { } }); +test('Test AsyncFeedWriter: zip', async() => { + const dir = joinPath(OUTPUT_DIR, 'zip'); + const path = joinPath(dir, 'gtfs.zip'); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + await GTFSAsyncFeedWriter.writeZip(getTestFeed(), path); + + expect(existsSync(path)).toBeTruthy(); + + const zip = new AdmZip(path); + const entries = zip.getEntries(); + expect(entries.length).toEqual(8); + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + expect(entries.map(x => x.entryName).every(x => files.indexOf(x) > -1)).toBeTruthy(); + + for (const entry of entries) { + expect( + entry.getData().toString().split(/\r?\n/g), + entry.entryName + ).toEqual( + readFileSync(joinPath(COMPARE_DIR, entry.entryName)).toString().split(/\r?\n/g) + ); + } +}); + test('Test FeedWriter: dir', () => { const path = joinPath(OUTPUT_DIR, 'gtfs'); if (existsSync(path)) { @@ -134,6 +163,30 @@ test('Test FeedWriter: dir', () => { } }); +test('Test AsyncFeedWriter: dir', async() => { + const path = joinPath(OUTPUT_DIR, 'gtfs'); + if (existsSync(path)) { + for (const file of readdirSync(path).filter(x => x.endsWith('.txt')).map(x => joinPath(path, x))) { + rmSync(file); + } + } + + await GTFSAsyncFeedWriter.writeDirectory(getTestFeed(), path); + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + + expect(files.every(x => existsSync(joinPath(path, x)))).toBeTruthy(); + for (const file of files) { + expect( + readFileSync(joinPath(path, file)).toString().split(/\r?\n/g), + file + ).toEqual( + readFileSync(joinPath(COMPARE_DIR, file)).toString().split(/\r?\n/g) + ); + } +}); + test('Test FeedWriter: contents', () => { const contents = GTFSFeedWriter.createFileContents(getTestFeed()); expect(contents.length).toEqual(8); @@ -148,3 +201,18 @@ test('Test FeedWriter: contents', () => { .toEqual(readFileSync(joinPath(COMPARE_DIR, content.name)).toString().split(/\r?\n/g)); } }); + +test('Test AsyncFeedWriter: contents', async() => { + const contents = await GTFSAsyncFeedWriter.createFileContents(getTestFeed()); + expect(contents.length).toEqual(8); + + const files = [ + 'agency.txt', 'calendar_dates.txt', 'calendar.txt', 'routes.txt', 'shapes.txt', 'stop_times.txt', 'stops.txt', 'trips.txt' + ]; + expect(contents.every(c => files.indexOf(c.name) > -1)).toBeTruthy(); + + for (const content of contents) { + expect(content.content.toString().split(/\r?\n/g), content.name) + .toEqual(readFileSync(joinPath(COMPARE_DIR, content.name)).toString().split(/\r?\n/g)); + } +}); diff --git a/tsconfig.json b/tsconfig.json index eeec8f1..d46df14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ }, "include": [ "./src/**/*.ts" -, "src/io/feed-writer.ts.skip" ], + ], "exclude": [ "./tests/*.ts" ] From 94feaddf725ff1de36be15d447a7eba551ffc116 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 15:48:18 +0700 Subject: [PATCH 17/20] Documented --- src/feed/base.ts | 105 +++++++++++++++++++++++++----------------- src/feed/iterable.ts | 42 +++++++++++++++-- src/feed/loaded.ts | 23 +++++++-- src/io/feed-file.ts | 6 +++ src/io/feed-reader.ts | 23 +++++---- src/io/feed-writer.ts | 2 +- src/io/file.ts | 9 +++- src/types.ts | 3 ++ 8 files changed, 151 insertions(+), 62 deletions(-) diff --git a/src/feed/base.ts b/src/feed/base.ts index f8b11ae..b32a33e 100644 --- a/src/feed/base.ts +++ b/src/feed/base.ts @@ -1,4 +1,5 @@ -import { GTFSTableName, GTFS_FILES } from '../file-info'; +import { GTFS_FILES } from '../file-info'; +import type { GTFSTableName } from '../file-info'; import type { GTFSAgency } from '../files/agency'; import type { GTFSArea } from '../files/area'; import type { GTFSAttribution } from '../files/attribution'; @@ -27,83 +28,105 @@ import type { GTFSTranslation } from '../files/translation'; import type { GTFSTrip } from '../files/trip'; import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow, GTFSIterableFeedFiles } from '../types'; -type RecordsGenericType = +/** Records Generic Type: R to be collection type, and T to be row type */ +type RT = R extends GTFSFileRow[] ? T[] : R extends GTFSFileRecords ? GTFSFileRecords : R extends GTFSAsyncFileRecords ? GTFSAsyncFileRecords : Iterable; -export class GTFSFeedBase { +/** + * Base Feed class + */ +export abstract class GTFSFeedBase { /** Transit agencies with service represented in this dataset. */ - public agency: RecordsGenericType; + public agency: RT; /** Stops where vehicles pick up or drop off riders. */ - public stops: RecordsGenericType; + public stops: RT; /** Transit routes. A route is a group of trips that are displayed to riders as a single service. */ - public routes: RecordsGenericType; + public routes: RT; /** Trips for each route. */ - public trips: RecordsGenericType; + public trips: RT; /** Times that a vehicle arrives at and departs from stops for each trip. */ - public stop_times: RecordsGenericType; + public stop_times: RT; /** Service dates specified using a weekly schedule with start and end dates. */ - public calendar?: RecordsGenericType; + public calendar?: RT; /** Exceptions for the services defined in the `calendar.txt`. */ - public calendar_dates?: RecordsGenericType; + public calendar_dates?: RT; /** Fare information for a transit agency's routes. */ - public fare_attributes?: RecordsGenericType; + public fare_attributes?: RT; /** Rules to apply fares for itineraries. */ - public fare_rules?: RecordsGenericType; + public fare_rules?: RT; /** Date and time periods to use in fare rules for fares that depend on date and time factors. */ - public timeframes?: RecordsGenericType; + public timeframes?: RT; /** To describe the fare media that can be employed to use fare products. */ - public fare_media?: RecordsGenericType; + public fare_media?: RT; /** To describe the different types of tickets or fares that can be purchased by riders. */ - public fare_products?: RecordsGenericType; + public fare_products?: RT; /** Fare rules for individual legs of travel. */ - public fare_leg_rules?: RecordsGenericType; + public fare_leg_rules?: RT; /** Fare rules for transfers between legs of travel. */ - public fare_transfer_rules?: RecordsGenericType; + public fare_transfer_rules?: RT; /** Area grouping of locations. */ - public areas?: RecordsGenericType; + public areas?: RT; /** Rules to assign stops to areas. */ - public stop_areas?: RecordsGenericType; + public stop_areas?: RT; /** Network grouping of routes. */ - public networks?: RecordsGenericType; + public networks?: RT; /** Rules to assign routes to networks. */ - public route_networks?: RecordsGenericType; + public route_networks?: RT; /** Rules for mapping vehicle travel paths, sometimes referred to as route alignments. */ - public shapes?: RecordsGenericType; + public shapes?: RT; /** Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. */ - public frequencies?: RecordsGenericType; + public frequencies?: RT; /** Rules for making connections at transfer points between routes. */ - public transfers?: RecordsGenericType; + public transfers?: RT; /** Pathways linking together locations within stations. */ - public pathways?: RecordsGenericType; + public pathways?: RT; /** Levels within stations. */ - public levels?: RecordsGenericType; + public levels?: RT; /** Translations of customer-facing dataset values. */ - public translations?: RecordsGenericType; + public translations?: RT; /** Dataset metadata, including publisher, version, and expiration information. */ - public feed_info?: RecordsGenericType; + public feed_info?: RT; /** Dataset attributions. */ - public attributions?: RecordsGenericType; + public attributions?: RT; - protected constructor(defaultValues: Partial>>, emptyValue: RecordsType) { - Object.assign(this, defaultValues); - this.agency = defaultValues.agency as RecordsGenericType ?? emptyValue; - this.stops = defaultValues.stops as RecordsGenericType ?? emptyValue; - this.routes = defaultValues.routes as RecordsGenericType ?? emptyValue; - this.trips = defaultValues.trips as RecordsGenericType ?? emptyValue; - this.stop_times = defaultValues.stop_times as RecordsGenericType ?? emptyValue; + /** + * Constructor + * @param initialValues Initial values + * @param emptyValue Default empty values of RecordsType for required table but not defined in initialValues + */ + protected constructor(initialValues: Partial>>, emptyValue: () => RecordsType) { + Object.assign(this, initialValues); + this.agency = initialValues.agency as RT ?? emptyValue; + this.stops = initialValues.stops as RT ?? emptyValue; + this.routes = initialValues.routes as RT ?? emptyValue; + this.trips = initialValues.trips as RT ?? emptyValue; + this.stop_times = initialValues.stop_times as RT ?? emptyValue; } - public setTable(name: GTFSTableName, value: RecordsGenericType) { - Object.assign(this, { [name]: value }); + /** + * Set table records. + * @param name Table name + * @param records Value + */ + public setTable(name: GTFSTableName, records: RT) { + Object.assign(this, { [name]: records }); } - public getTable(name: GTFSTableName): RecordsGenericType|undefined { - return this[name]! as RecordsGenericType; + /** + * Get table records. + * @param name Table name + * @returns RecordsType + */ + public getTable(name: GTFSTableName): RecordsType|undefined { + return this[name]! as RecordsType; } - public *getAllTables(): GTFSIterableFeedFiles> { + /** + * Get iterator for all existing tables. + */ + public *getAllTables(): GTFSIterableFeedFiles> { if (this.agency !== undefined) yield { name: 'agency', info: GTFS_FILES.agency, records: this.agency }; if (this.stops !== undefined) yield { name: 'stops', info: GTFS_FILES.stops, records: this.stops }; if (this.routes !== undefined) yield { name: 'routes', info: GTFS_FILES.routes, records: this.routes }; diff --git a/src/feed/iterable.ts b/src/feed/iterable.ts index 08f5290..583676b 100644 --- a/src/feed/iterable.ts +++ b/src/feed/iterable.ts @@ -3,11 +3,23 @@ import { GTFSLoadedFeed } from './loaded'; import type { GTFSTableName } from '../file-info'; import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow } from '../types'; +/** + * GTFS Feed whoose records are iterable iterator. + */ export class GTFSIterableFeed extends GTFSFeedBase { - public constructor(defaultValues: Partial> = {}) { - super(defaultValues, [].values()); + /** + * Constructor + * @param initialValues Initial values. + */ + public constructor(initialValues: Partial> = {}) { + super(initialValues, function*() {}); } + /** + * Load all table records into array and return GTFSLoadedFeed. + * BEWARE NOT TO CALL ON THE SAME TABLE TWICE AS THE RECORDS TYPE IS ITERATOR. + * @returns Loaded feed + */ public load(): GTFSLoadedFeed { const result = new GTFSLoadedFeed(); for (const table of this.getAllTables()) { @@ -16,6 +28,11 @@ export class GTFSIterableFeed extends GTFSFeedBase { return result; } + /** + * Convert generator of the records into asynchronous. + * BEWARE NOT TO CALL ON THE SAME TABLE TWICE AS THE RECORDS TYPE IS ITERATOR. + * @returns Async iterable feed + */ public toAsync(): GTFSAsyncIterableFeed { const result = new GTFSAsyncIterableFeed(); for (const table of this.getAllTables()) { @@ -26,11 +43,24 @@ export class GTFSIterableFeed extends GTFSFeedBase { } }; +/** + * GTFS Feed whoose records are asynchornous iterable iterator. + */ export class GTFSAsyncIterableFeed extends GTFSFeedBase { - public constructor(defaultValues: Partial> = {}) { - super(defaultValues, (async function*() {})()); + /** + * Constructor + * @param initialValues Initial values. + */ + public constructor(initialValues: Partial> = {}) { + super(initialValues, async function*() {}); } + /** + * Get records of a specific table. + * BEWARE NOT TO CALL ON THE SAME TABLE TWICE AS THE RECORDS TYPE IS ITERATOR. + * @param tableName Table name + * @returns Promise of records + */ public async getRecords(tableName: GTFSTableName): Promise { const results = []; const records = this.getTable(tableName); @@ -41,6 +71,10 @@ export class GTFSAsyncIterableFeed extends GTFSFeedBase { return results; } + /** + * Load all table records into array and return GTFSLoadedFeed. + * @returns Loaded feed + */ public async load(): Promise { const result = new GTFSLoadedFeed(); for (const table of this.getAllTables()) { diff --git a/src/feed/loaded.ts b/src/feed/loaded.ts index ccb88d2..4cfdd7c 100644 --- a/src/feed/loaded.ts +++ b/src/feed/loaded.ts @@ -1,13 +1,24 @@ import { GTFSFeedBase } from './base'; -import type { GTFSFileRow } from '../types'; -import type { GTFSTableName } from '../file-info'; import { GTFSAsyncIterableFeed, GTFSIterableFeed } from './iterable'; +import type { GTFSTableName } from '../file-info'; +import type { GTFSFileRow } from '../types'; +/** + * GTFS Feed that is loaded into memory, records type being array. + */ export class GTFSLoadedFeed extends GTFSFeedBase { - public constructor(defaultValues: Partial> = {}) { - super(defaultValues, (() => [])()); + /** + * Constructor + * @param initialValues Initial values + */ + public constructor(initialValues: Partial> = {}) { + super(initialValues, () => []); } + /** + * Convert records into generator function and return an instance of iterable feed. + * @returns Iterable feed + */ public getIterable(): GTFSIterableFeed { const feed = new GTFSIterableFeed({}); for (const table of this.getAllTables()) { @@ -16,6 +27,10 @@ export class GTFSLoadedFeed extends GTFSFeedBase { return feed; } + /** + * Convert records into asynchronous generator function and return an instance of async iterable feed. + * @returns Async iterable feed + */ public getAsyncIterable(): GTFSAsyncIterableFeed { const feed = new GTFSAsyncIterableFeed({}); for (const table of this.getAllTables()) { diff --git a/src/io/feed-file.ts b/src/io/feed-file.ts index 73457aa..2e16f90 100644 --- a/src/io/feed-file.ts +++ b/src/io/feed-file.ts @@ -30,6 +30,9 @@ import type { GTFSFeedInfo } from '../files/feed-info'; import type { GTFSAttribution } from '../files/attribution'; import { GTFSIOWriteOptions } from './types'; +/** + * IO operations of feed file. + */ abstract class FeedFileIO { /** File information */ protected fileInfo: GTFSFileInfo; @@ -100,6 +103,9 @@ export class GTFSFeedFileIO extends F } }; +/** + * Class for asynchronous IO operations on a GTFS feed file + */ export class GTFSAsyncFeedFileIO extends FeedFileIO { /** * Read lines into records. diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index 9f83739..c8f9666 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -3,24 +3,16 @@ import { existsSync as exists, openSync as openFile, read as readAsync, - readFile, - readFile as readFileAsync, readSync as readFileSync } from 'fs'; import { join as joinPath } from 'path'; import { getAsyncIOFromFileName, getIOFromFileName } from './feed-file'; import { getGTFSFileInfos } from '../file-info'; -import type { GTFSFileInfo, GTFSTableName } from '../file-info'; -import type { - GTFSAsyncFileRecords, - GTFSFileContent, - GTFSFileRecords, - GTFSFileRow, - GTFSIterableFeedFiles, -} from '../types'; import { GTFSFeedBase } from '../feed/base'; import { GTFSLoadedFeed } from '../feed/loaded'; import { GTFSAsyncIterableFeed, GTFSIterableFeed } from '../feed/iterable'; +import type { GTFSFileInfo } from '../file-info'; +import type { GTFSAsyncFileRecords, GTFSFileContent, GTFSFileRecords } from '../types'; /** * GTFS file object to read @@ -97,6 +89,10 @@ abstract class FeedReader; /** * From file information, get records. @@ -221,6 +217,13 @@ export class GTFSFeedReader extends FeedReader> { /** * Generator of iterable chunks from a file path. diff --git a/src/io/feed-writer.ts b/src/io/feed-writer.ts index 243d61b..d2b528c 100644 --- a/src/io/feed-writer.ts +++ b/src/io/feed-writer.ts @@ -2,9 +2,9 @@ import AdmZip from 'adm-zip'; import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'fs'; import { join as joinPath } from 'path'; import { getAsyncIOFromFileName, getIOFromFileName } from './feed-file'; -import type { GTFSFileContent } from '../types'; import { GTFSAsyncIterableFeed, GTFSIterableFeed } from '../feed/iterable'; import { GTFSLoadedFeed } from '../feed/loaded'; +import type { GTFSFileContent } from '../types'; /** * GTFS feed writer. diff --git a/src/io/file.ts b/src/io/file.ts index 227ec02..a58378c 100644 --- a/src/io/file.ts +++ b/src/io/file.ts @@ -1,9 +1,14 @@ import { getInitialReadChunkParams, readChunk } from './reader'; +import { getInitialWriteChunkParams, getRecordsHeader, writeRecords } from './writer'; +import type { GTFSIOWriteOptions } from './types'; import type { GTFSFileInfo } from '../file-info'; import type { GTFSAsyncFileRecords, GTFSFileRecords, GTFSFileRow } from '../types'; -import { getInitialWriteChunkParams, getRecordsHeader, writeRecords } from './writer'; -import { GTFSIOWriteOptions } from './types'; +/** + * Create or modify given options object to define default values if not provided. + * @param options Options object + * @returns Normalised writer options + */ function normaliseWriteOptions(options?: GTFSIOWriteOptions): GTFSIOWriteOptions { if (!options) options = {}; options.newline = options.newline ?? '\n'; diff --git a/src/types.ts b/src/types.ts index aadbe0c..b80390f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -62,8 +62,11 @@ export type GTFSAsyncFileRecords = As /** GTFS iterable for an individual file */ export type GTFSIterableFeedFile = { + /** File name without .txt */ name: GTFSTableName, + /** File information */ info: GTFSFileInfo, + /** Records */ records: RecordsType }; From 1e188e6915574c6f482f78998250e85a30ceef1d Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 16:22:30 +0700 Subject: [PATCH 18/20] Added doc --- README.md | 56 ++++++++++++ package-lock.json | 178 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/io/feed-reader.ts | 8 +- tests/feed-reader.test.ts | 4 +- tests/file-io.test.ts | 4 +- tests/webpack.html | 45 +++++++--- 7 files changed, 274 insertions(+), 22 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..dcca855 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# GTFS-IO + +IO Operations for GTFS dataset in node / javascript. + +## Installation + +```bash +npm i gtfs-io +``` + +## Examples + +### Load Feed + +```ts +// Synchronously +import { GTFSFeedReader } from 'gtfs-io'; +const reader = GTFSFeedReader.fromZip('gtfs.zip'); +const reader = GTFSFeedReader.fromDirectory('path/to/dir'); +const feed = reader.loadFeed(); +console.log(feed.stops[0].stop_id); +console.log(feed.trips[1].trip_id); + +// Asynchronously +import { GTFSAsyncFeedReader } from 'gtfs-io'; +const reader = GTFSAsyncFeedReader.fromZip('gtfs.zip'); +const reader = GTFSAsyncFeedReader.fromDirectory('path/to/dir'); +const feed = await reader.loadFeed(); +console.log(feed.stops[0].stop_id); +console.log(feed.trips[1].trip_id); +``` + +### Write Feed + +```ts +import { GTFSLoadedFeed } from 'gtfs-io'; +const feed = new GTFSLoadedFeed({ + agency: [{ agency_id: '...', agency_name: '...', ... }, ...], + stops: [{ stop_id: '...', ... }, { ... }, ...], + routes: [{ ... }, { ... }, ...], + trips: [{ ... }, ...], + ... +}); + +// Synchronously +import { GTFSFeedWriter } from 'gtfs-io'; +GTFSFeedWriter.writeZip(feed, 'gtfs.zip'); +GTFSFeedWriter.writeDirectory(feed, 'path/to/dir'); + +// Asynchronously +import { GTFSAsyncFeedWriter } from 'gtfs-io'; +await GTFSAsyncFeedWriter.writeZip(feed, 'gtfs.zip'); +await GTFSAsyncFeedWriter.writeDirectory(feed, 'path/to/dir'); +``` + +--- diff --git a/package-lock.json b/package-lock.json index a22dc85..0fcfc0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "stream-browserify": "^3.0.0", + "typedoc": "^0.25.4", "typescript": "^5.3.2", "util": "^0.12.5", "vitest": "^0.34.6", @@ -1018,6 +1019,12 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -1082,6 +1089,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1108,6 +1121,15 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -2226,6 +2248,12 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -2238,6 +2266,18 @@ "node": ">=12" } }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -2307,6 +2347,21 @@ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "dev": true }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -2864,6 +2919,18 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3067,6 +3134,27 @@ "node": ">=4" } }, + "node_modules/typedoc": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.4.tgz", + "integrity": "sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + } + }, "node_modules/typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", @@ -3305,6 +3393,18 @@ } } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -4160,6 +4260,12 @@ "dev": true, "requires": {} }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -4211,6 +4317,12 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4223,6 +4335,15 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -5073,6 +5194,12 @@ "get-func-name": "^2.0.1" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -5082,6 +5209,12 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -5144,6 +5277,15 @@ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "dev": true }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, "mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -5561,6 +5703,18 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "dev": true, + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5704,6 +5858,18 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "typedoc": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.4.tgz", + "integrity": "sha512-Du9ImmpBCw54bX275yJrxPVnjdIyJO/84co0/L9mwe0R3G4FSR6rQ09AlXVRvZEGMUg09+z/usc8mgygQ1aidA==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + } + }, "typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", @@ -5818,6 +5984,18 @@ "why-is-node-running": "^2.2.2" } }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 65964e0..5085704 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "stream-browserify": "^3.0.0", + "typedoc": "^0.25.4", "typescript": "^5.3.2", "util": "^0.12.5", "vitest": "^0.34.6", diff --git a/src/io/feed-reader.ts b/src/io/feed-reader.ts index c8f9666..469cb10 100644 --- a/src/io/feed-reader.ts +++ b/src/io/feed-reader.ts @@ -128,7 +128,7 @@ abstract class FeedReader { @@ -203,7 +203,7 @@ export class GTFSFeedReader extends FeedReader> { @@ -303,7 +303,7 @@ export class GTFSAsyncFeedReader extends FeedReader { }); test('Test FeedReader: directory path', () => { - const reader = GTFSFeedReader.fromDir(DIR_PATH); + const reader = GTFSFeedReader.fromDirectoy(DIR_PATH); const feed = reader.loadFeed(); assert(feed); }); test('Test AsyncFeedReader: directory path', async () => { - const reader = GTFSAsyncFeedReader.fromDir(DIR_PATH); + const reader = GTFSAsyncFeedReader.fromDirectoy(DIR_PATH); const feed = await reader.loadFeed(); assert(feed); }); diff --git a/tests/file-io.test.ts b/tests/file-io.test.ts index 9004e38..4d75156 100644 --- a/tests/file-io.test.ts +++ b/tests/file-io.test.ts @@ -87,7 +87,7 @@ describe('Test GTFSFileIO reading', () => { }); describe('Test GTFSFileIO writing', () => { - const records: GTFSFileRecords = [ + const records: GTFSTrip[] = [ { route_id: 'R01', service_id: 'S01', trip_id: 'T01', direction_id: GTFSTripDirection.OneDirection }, { route_id: 'R01', service_id: 'S02', trip_id: 'T02', trip_headsign: 'HEADSIGN' }, { route_id: 'R03', service_id: 'S01', trip_id: 'T03', trip_headsign: 'with,comma' }, @@ -120,7 +120,7 @@ describe('Test GTFSFileIO writing', () => { }); it('handles empty input', () => { - const records: GTFSFileRecords = []; + const records: GTFSRoute[] = []; const content = GTFSFileIO.writeContent(GTFS_FILES.routes, records); expect(content).toEqual( 'route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,' diff --git a/tests/webpack.html b/tests/webpack.html index 15b224f..c948ad1 100644 --- a/tests/webpack.html +++ b/tests/webpack.html @@ -14,22 +14,39 @@ var FEED = undefined; - const display = () => { + const reset = () => { document.getElementById('textarea').value = ''; - for (const file of Object.keys(FEED)) { - FEED[file] = [...FEED[file]]; - const length = FEED[file].length; - console.log(`${file}: ${length}`); - document.getElementById('textarea').value += `${file}: ${length}\n`; - } + }; + + const writeLine = line => { + document.getElementById('textarea').value += line + '\n'; + }; + + const end = () => { document.getElementById('textarea').value += 'END\n'; }; + const readFeed = async(feed) => { + reset(); + FEED = new GTFSIO.GTFSLoadedFeed(); + for (const table of feed.getAllTables()) { + const records = []; + for await (const record of table.records) { + records.push(record); + } + FEED.setTable(table.name, records); + const line = `${table.name} = ${records.length}`; + console.log(line); + writeLine(line); + } + end(); + }; + const readZip = file => { const reader = new FileReader(); - reader.onload = e => { - FEED = GTFSIO.GTFSFeedReader.fromZip(e.target.result).getFeedSync(); - display(); + reader.onload = async(e) => { + const feed = await GTFSIO.GTFSAsyncFeedReader.fromZip(e.target.result).getFeed(); + readFeed(feed); }; reader.readAsArrayBuffer(file); }; @@ -51,8 +68,8 @@ const content = await readFile(file); fileContents.push({ name: file.name, content }); } - FEED = GTFSIO.GTFSFeedReader.fromFiles(fileContents).getFeedSync(); - display(); + const feed = await GTFSIO.GTFSAsyncFeedReader.fromFiles(fileContents).getFeed(); + readFeed(feed); }; file.onchange = e => { @@ -66,9 +83,9 @@ readFiles(files); }; - document.getElementById('download').onclick = () => { + document.getElementById('download').onclick = async() => { if (!FEED) return; - const zip = GTFSIO.GTFSFeedWriter.createZip(FEED); + const zip = await GTFSIO.GTFSAsyncFeedWriter.createZip(FEED); const blob = new Blob([zip.toBuffer()], { type: 'application/x-zip-compressed' From d70cad8f2e616da2bb3868d883ac9c200db2358d Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 16:27:32 +0700 Subject: [PATCH 19/20] Added GitHub actions --- .github/workflows/pr.yml | 10 ++++++++ .github/workflows/publish.yml | 42 ++++++++++++++++++++++++++++++++ .github/workflows/unit-tests.yml | 19 +++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..7928c0d --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,10 @@ +name: Pull Request Check +on: + pull_request: + branches: + - dev/* + - master +jobs: + unit-tests: + name: Unit Tests + uses: ./.github/workflows/unit-tests.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..48936a6 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +name: Publish +on: + push: + branches: master +jobs: + unit-tests: + name: Unit Tests + uses: ./.github/workflows/unit-tests.yml + publish: + name: Publish + needs: unit-tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - name: Get package.json + id: get-package + run: echo PACKAGE=$(cat ./package.json) >> $GITHUB_OUTPUT + - name: Get package version + id: get-package-version + run: echo VERSION="${{ fromJson(steps.get-package.outputs.PACKAGE).version }}" >> $GITHUB_OUTPUT + - name: Install + run: npm ci + - name: Build & Pack + run: npm run pack + - name: Publish + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Tag + run: | + git config --global user.name "ponlawat-w" + git config --global user.email "ponlawat_w@outlook.co.th" + git tag -fa "v${{ steps.get-package-version.outputs.VERSION }}" -m "v${{ steps.get-package-version.outputs.VERSION }}" + git push --force origin v${{ steps.get-package-version.outputs.VERSION }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..fee5191 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,19 @@ +name: Unit Tests +on: workflow_call +jobs: + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Install + run: npm ci + - name: Test + run: npm run test From 8a730fefbb4d45a6efdddff36be695984aeaca10 Mon Sep 17 00:00:00 2001 From: ponlawat-w Date: Mon, 4 Dec 2023 16:27:46 +0700 Subject: [PATCH 20/20] Bumped to 0.0.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fcfc0c..465b619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gtfs-io", - "version": "0.0.0", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gtfs-io", - "version": "0.0.0", + "version": "0.0.1", "license": "ISC", "dependencies": { "adm-zip": "^0.5.10", diff --git a/package.json b/package.json index 5085704..e923747 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gtfs-io", "type": "module", - "version": "0.0.0", + "version": "0.0.1", "description": "Type definitions and IO operations for GTFS datasets", "main": "dist/index.js", "scripts": {