diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 262aa14..c4eb42e 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - node-version: [ '20.x' ] + node-version: [ '22.x' ] os: [ ubuntu-latest, macos-latest ] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c05e845..5208bbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - name: Install dependencies run: | npm ci diff --git a/biome.json b/biome.json index aec896d..8222b9c 100644 --- a/biome.json +++ b/biome.json @@ -21,9 +21,8 @@ "quoteProperties": "asNeeded", "quoteStyle": "single", "semicolons": "always", - "trailingComma": "all" - }, - "globals": ["setup", "suite", "suiteSetup", "teardown", "test"] + "trailingCommas": "all" + } }, "json": { "formatter": { @@ -38,9 +37,15 @@ "enabled": true, "rules": { "all": true, + "correctness": { + "noNodejsModules": "off" + }, "nursery": { "useImportRestrictions": "off" - } + }, + "style": { + "useFilenamingConvention": "off" + } } }, "organizeImports": { diff --git a/lib/Client.ts b/lib/Client.ts index 0bc6003..391509d 100644 --- a/lib/Client.ts +++ b/lib/Client.ts @@ -1,22 +1,22 @@ -import { ClientConfiguration } from './ClientConfiguration'; -import { ClientOptions } from './ClientOptions'; -import { EventCandidate } from './event/EventCandidate'; -import { EventContext } from './event/EventContext'; -import { getDefaultClientConfiguration } from './getDefaultClientConfiguration'; -import { StoreItem } from './handlers/StoreItem'; -import { ObserveEventsOptions } from './handlers/observeEvents/ObserveEventsOptions'; -import { observeEvents } from './handlers/observeEvents/observeEvents'; -import { ping } from './handlers/ping/ping'; -import { EventType } from './handlers/readEventTypes/EventType'; -import { readEventTypes } from './handlers/readEventTypes/readEventTypes'; -import { ReadEventsOptions } from './handlers/readEvents/ReadEventsOptions'; -import { readEvents } from './handlers/readEvents/readEvents'; -import { ReadSubjectsOptions } from './handlers/readSubjects/ReadSubjectsOptions'; -import { readSubjects } from './handlers/readSubjects/readSubjects'; -import { registerEventSchema } from './handlers/registerEventSchema/registerEventSchema'; -import { Precondition } from './handlers/writeEvents/Precondition'; -import { writeEvents } from './handlers/writeEvents/writeEvents'; -import { HttpClient } from './http/HttpClient'; +import type { ClientConfiguration } from './ClientConfiguration.js'; +import type { ClientOptions } from './ClientOptions.js'; +import type { EventCandidate } from './event/EventCandidate.js'; +import type { EventContext } from './event/EventContext.js'; +import { getDefaultClientConfiguration } from './getDefaultClientConfiguration.js'; +import type { StoreItem } from './handlers/StoreItem.js'; +import type { ObserveEventsOptions } from './handlers/observeEvents/ObserveEventsOptions.js'; +import { observeEvents } from './handlers/observeEvents/observeEvents.js'; +import { ping } from './handlers/ping/ping.js'; +import type { EventType } from './handlers/readEventTypes/EventType.js'; +import { readEventTypes } from './handlers/readEventTypes/readEventTypes.js'; +import type { ReadEventsOptions } from './handlers/readEvents/ReadEventsOptions.js'; +import { readEvents } from './handlers/readEvents/readEvents.js'; +import type { ReadSubjectsOptions } from './handlers/readSubjects/ReadSubjectsOptions.js'; +import { readSubjects } from './handlers/readSubjects/readSubjects.js'; +import { registerEventSchema } from './handlers/registerEventSchema/registerEventSchema.js'; +import type { Precondition } from './handlers/writeEvents/Precondition.js'; +import { writeEvents } from './handlers/writeEvents/writeEvents.js'; +import { HttpClient } from './http/HttpClient.js'; class Client { public readonly configuration: ClientConfiguration; @@ -62,14 +62,14 @@ class Client { } public async registerEventSchema(eventType: string, schema: string | object): Promise { - return registerEventSchema(this, eventType, schema); + return await registerEventSchema(this, eventType, schema); } public async writeEvents( eventCandidates: EventCandidate[], preconditions: Precondition[] = [], ): Promise { - return writeEvents(this, eventCandidates, preconditions); + return await writeEvents(this, eventCandidates, preconditions); } } diff --git a/lib/ClientConfiguration.ts b/lib/ClientConfiguration.ts index 8cd7e45..a88ec5e 100644 --- a/lib/ClientConfiguration.ts +++ b/lib/ClientConfiguration.ts @@ -3,7 +3,6 @@ interface ClientConfiguration { timeoutMilliseconds: number; accessToken: string; protocolVersion: string; - maxTries: number; } -export { ClientConfiguration }; +export type { ClientConfiguration }; diff --git a/lib/ClientOptions.ts b/lib/ClientOptions.ts index d6b18ab..03fd54e 100644 --- a/lib/ClientOptions.ts +++ b/lib/ClientOptions.ts @@ -2,7 +2,6 @@ interface ClientOptions { timeoutMilliseconds?: number; accessToken: string; protocolVersion?: string; - maxTries?: number; } -export { ClientOptions }; +export type { ClientOptions }; diff --git a/lib/event/Event.ts b/lib/event/Event.ts index 000d1a9..3582175 100644 --- a/lib/event/Event.ts +++ b/lib/event/Event.ts @@ -1,6 +1,6 @@ -import { UnknownObject } from '../util/UnknownObject'; -import { isObject } from '../util/isObject'; -import { EventContext } from './EventContext'; +import type { UnknownObject } from '../util/UnknownObject.js'; +import { isObject } from '../util/isObject.js'; +import { EventContext } from './EventContext.js'; class Event extends EventContext { public readonly data: Record; diff --git a/lib/event/EventCandidate.ts b/lib/event/EventCandidate.ts index 85b10f3..1d53edd 100644 --- a/lib/event/EventCandidate.ts +++ b/lib/event/EventCandidate.ts @@ -1,7 +1,7 @@ -import { UnknownObject } from '../util/UnknownObject'; -import { ValidationError } from '../util/error/ValidationError'; -import { validateSubject } from './validateSubject'; -import { validateType } from './validateType'; +import type { UnknownObject } from '../util/UnknownObject.js'; +import { ValidationError } from '../util/error/ValidationError.js'; +import { validateSubject } from './validateSubject.js'; +import { validateType } from './validateType.js'; class EventCandidate { public readonly data: Record; diff --git a/lib/event/EventContext.ts b/lib/event/EventContext.ts index fd24466..ccc6b01 100644 --- a/lib/event/EventContext.ts +++ b/lib/event/EventContext.ts @@ -1,7 +1,7 @@ -import { UnknownObject } from '../util/UnknownObject'; -import { ValidationError } from '../util/error/ValidationError'; -import { validateSubject } from './validateSubject'; -import { validateType } from './validateType'; +import type { UnknownObject } from '../util/UnknownObject.js'; +import { ValidationError } from '../util/error/ValidationError.js'; +import { validateSubject } from './validateSubject.js'; +import { validateType } from './validateType.js'; class EventContext { public readonly source: string; diff --git a/lib/event/Source.ts b/lib/event/Source.ts index 360d37b..613dbcf 100644 --- a/lib/event/Source.ts +++ b/lib/event/Source.ts @@ -1,5 +1,5 @@ -import { UnknownObject } from '../util/UnknownObject'; -import { EventCandidate } from './EventCandidate'; +import type { UnknownObject } from '../util/UnknownObject.js'; +import { EventCandidate } from './EventCandidate.js'; class Source { public readonly source: string; diff --git a/lib/event/validateSubject.ts b/lib/event/validateSubject.ts index 53a51f7..71141ae 100644 --- a/lib/event/validateSubject.ts +++ b/lib/event/validateSubject.ts @@ -1,4 +1,4 @@ -import { ValidationError } from '../util/error/ValidationError'; +import { ValidationError } from '../util/error/ValidationError.js'; const wordPattern = '[0-9A-Za-z_-]+'; const subjectPattern = new RegExp(`^/(${wordPattern}/)*(${wordPattern}/?)?$`, 'u'); diff --git a/lib/event/validateType.ts b/lib/event/validateType.ts index 1a53615..81fc90a 100644 --- a/lib/event/validateType.ts +++ b/lib/event/validateType.ts @@ -1,4 +1,4 @@ -import { ValidationError } from '../util/error/ValidationError'; +import { ValidationError } from '../util/error/ValidationError.js'; const typePattern = /^[0-9A-Za-z_-]{2,}\.(?:[0-9A-Za-z_-]+\.)+[0-9A-Za-z_-]+$/u; diff --git a/lib/getDefaultClientConfiguration.ts b/lib/getDefaultClientConfiguration.ts index ea9a25b..d19040e 100644 --- a/lib/getDefaultClientConfiguration.ts +++ b/lib/getDefaultClientConfiguration.ts @@ -1,4 +1,4 @@ -import { ClientConfiguration } from './ClientConfiguration'; +import type { ClientConfiguration } from './ClientConfiguration.js'; const getDefaultClientConfiguration = (baseUrl: string): ClientConfiguration => { return { @@ -6,7 +6,6 @@ const getDefaultClientConfiguration = (baseUrl: string): ClientConfiguration => timeoutMilliseconds: 10_000, accessToken: '', protocolVersion: '1.0.0', - maxTries: 10, }; }; diff --git a/lib/handlers/Heartbeat.ts b/lib/handlers/Heartbeat.ts index 2c8c136..e12da18 100644 --- a/lib/handlers/Heartbeat.ts +++ b/lib/handlers/Heartbeat.ts @@ -2,4 +2,4 @@ interface Heartbeat { type: 'heartbeat'; } -export { Heartbeat }; +export type { Heartbeat }; diff --git a/lib/handlers/Item.ts b/lib/handlers/Item.ts index 8a2e682..e1222f2 100644 --- a/lib/handlers/Item.ts +++ b/lib/handlers/Item.ts @@ -1,4 +1,4 @@ -import { UnknownObject } from '../util/UnknownObject'; +import type { UnknownObject } from '../util/UnknownObject.js'; interface Item { type: 'item'; @@ -8,4 +8,4 @@ interface Item { }; } -export { Item }; +export type { Item }; diff --git a/lib/handlers/StoreItem.ts b/lib/handlers/StoreItem.ts index 28276b7..50d36f7 100644 --- a/lib/handlers/StoreItem.ts +++ b/lib/handlers/StoreItem.ts @@ -1,8 +1,8 @@ -import { Event } from '../event/Event'; +import type { Event } from '../event/Event.js'; interface StoreItem { event: Event; hash: string; } -export { StoreItem }; +export type { StoreItem }; diff --git a/lib/handlers/StreamError.ts b/lib/handlers/StreamError.ts index 44e4b64..a9b87fb 100644 --- a/lib/handlers/StreamError.ts +++ b/lib/handlers/StreamError.ts @@ -5,4 +5,4 @@ interface StreamError { }; } -export { StreamError }; +export type { StreamError }; diff --git a/lib/handlers/isHeartbeat.ts b/lib/handlers/isHeartbeat.ts index eed561a..e0e867a 100644 --- a/lib/handlers/isHeartbeat.ts +++ b/lib/handlers/isHeartbeat.ts @@ -1,5 +1,5 @@ -import { isObject } from '../util/isObject'; -import { Heartbeat } from './Heartbeat'; +import { isObject } from '../util/isObject.js'; +import type { Heartbeat } from './Heartbeat.js'; const isHeartbeat = (message: unknown): message is Heartbeat => { return isObject(message) && message.type === 'heartbeat'; diff --git a/lib/handlers/isItem.ts b/lib/handlers/isItem.ts index 4a017b6..524e550 100644 --- a/lib/handlers/isItem.ts +++ b/lib/handlers/isItem.ts @@ -1,5 +1,5 @@ -import { isObject } from '../util/isObject'; -import { Item } from './Item'; +import { isObject } from '../util/isObject.js'; +import type { Item } from './Item.js'; const isItem = (message: unknown): message is Item => { if (!isObject(message) || message.type !== 'item') { diff --git a/lib/handlers/isStreamError.ts b/lib/handlers/isStreamError.ts index 5139c99..db039b9 100644 --- a/lib/handlers/isStreamError.ts +++ b/lib/handlers/isStreamError.ts @@ -1,5 +1,5 @@ -import { isObject } from '../util/isObject'; -import { StreamError } from './StreamError'; +import { isObject } from '../util/isObject.js'; +import type { StreamError } from './StreamError.js'; const isStreamError = (message: unknown): message is StreamError => { if (!isObject(message) || message.type !== 'error') { diff --git a/lib/handlers/observeEvents/ObserveEventsOptions.ts b/lib/handlers/observeEvents/ObserveEventsOptions.ts index 35562fe..962f686 100644 --- a/lib/handlers/observeEvents/ObserveEventsOptions.ts +++ b/lib/handlers/observeEvents/ObserveEventsOptions.ts @@ -1,8 +1,8 @@ -import { validateSubject } from '../../event/validateSubject'; -import { validateType } from '../../event/validateType'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { IsNonNegativeInteger } from '../../util/isNonNegativeInteger'; +import { validateSubject } from '../../event/validateSubject.js'; +import { validateType } from '../../event/validateType.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { IsNonNegativeInteger } from '../../util/isNonNegativeInteger.js'; interface ObserveEventsOptions { recursive: boolean; @@ -45,4 +45,5 @@ const validateObserveEventsOptions = (options: ObserveEventsOptions): void => { } }; -export { ObserveEventsOptions, ObserveFromLatestEvent, validateObserveEventsOptions }; +export type { ObserveEventsOptions, ObserveFromLatestEvent }; +export { validateObserveEventsOptions }; diff --git a/lib/handlers/observeEvents/observeEvents.ts b/lib/handlers/observeEvents/observeEvents.ts index b2c10ca..4575f5f 100644 --- a/lib/handlers/observeEvents/observeEvents.ts +++ b/lib/handlers/observeEvents/observeEvents.ts @@ -1,19 +1,20 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { Client } from '../../Client'; -import { Event } from '../../event/Event'; -import { validateSubject } from '../../event/validateSubject'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { InvalidParameterError } from '../../util/error/InvalidParameterError'; -import { ServerError } from '../../util/error/ServerError'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream'; -import { StoreItem } from '../StoreItem'; -import { isHeartbeat } from '../isHeartbeat'; -import { isItem } from '../isItem'; -import { isStreamError } from '../isStreamError'; -import { ObserveEventsOptions, validateObserveEventsOptions } from './ObserveEventsOptions'; +import type { Client } from '../../Client.js'; +import { Event } from '../../event/Event.js'; +import { validateSubject } from '../../event/validateSubject.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { InvalidParameterError } from '../../util/error/InvalidParameterError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream.js'; +import type { StoreItem } from '../StoreItem.js'; +import { isHeartbeat } from '../isHeartbeat.js'; +import { isItem } from '../isItem.js'; +import { isStreamError } from '../isStreamError.js'; +import { validateObserveEventsOptions } from './ObserveEventsOptions.js'; +import type { ObserveEventsOptions } from './ObserveEventsOptions.js'; const observeEvents = async function* ( client: Client, @@ -63,7 +64,7 @@ const observeEvents = async function* ( responseType: 'stream', abortController, }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/ping/ping.ts b/lib/handlers/ping/ping.ts index 57528f9..a6b0d85 100644 --- a/lib/handlers/ping/ping.ts +++ b/lib/handlers/ping/ping.ts @@ -1,8 +1,8 @@ -import { Client } from '../../Client'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { ServerError } from '../../util/error/ServerError'; -import { wrapError } from '../../util/error/wrapError'; +import type { Client } from '../../Client.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { wrapError } from '../../util/error/wrapError.js'; const ping = async (client: Client): Promise => { const response = await wrapError( @@ -12,7 +12,7 @@ const ping = async (client: Client): Promise => { responseType: 'text', withAuthorization: false, }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/readEventTypes/EventType.ts b/lib/handlers/readEventTypes/EventType.ts index 59c0db4..2a5df21 100644 --- a/lib/handlers/readEventTypes/EventType.ts +++ b/lib/handlers/readEventTypes/EventType.ts @@ -1,4 +1,4 @@ -import { isObject } from '../../util/isObject'; +import { isObject } from '../../util/isObject.js'; interface EventType { eventType: string; diff --git a/lib/handlers/readEventTypes/readEventTypes.ts b/lib/handlers/readEventTypes/readEventTypes.ts index 1c54b07..667d841 100644 --- a/lib/handlers/readEventTypes/readEventTypes.ts +++ b/lib/handlers/readEventTypes/readEventTypes.ts @@ -1,13 +1,14 @@ import { StatusCodes } from 'http-status-codes'; -import { Client } from '../../Client'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { ServerError } from '../../util/error/ServerError'; -import { wrapError } from '../../util/error/wrapError'; -import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream'; -import { isHeartbeat } from '../isHeartbeat'; -import { isStreamError } from '../isStreamError'; -import { EventType, isEventType } from './EventType'; +import type { Client } from '../../Client.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream.js'; +import { isHeartbeat } from '../isHeartbeat.js'; +import { isStreamError } from '../isStreamError.js'; +import type { EventType } from './EventType.js'; +import { isEventType } from './EventType.js'; const readEventTypes = async function* ( client: Client, @@ -21,7 +22,7 @@ const readEventTypes = async function* ( responseType: 'stream', abortController, }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/readEvents/ReadEventsOptions.ts b/lib/handlers/readEvents/ReadEventsOptions.ts index 6719be3..3498c4e 100644 --- a/lib/handlers/readEvents/ReadEventsOptions.ts +++ b/lib/handlers/readEvents/ReadEventsOptions.ts @@ -1,8 +1,8 @@ -import { validateSubject } from '../../event/validateSubject'; -import { validateType } from '../../event/validateType'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { IsNonNegativeInteger } from '../../util/isNonNegativeInteger'; +import { validateSubject } from '../../event/validateSubject.js'; +import { validateType } from '../../event/validateType.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { IsNonNegativeInteger } from '../../util/isNonNegativeInteger.js'; interface ReadEventsOptions { recursive: boolean; @@ -50,4 +50,5 @@ const validateReadEventsOptions = (options: ReadEventsOptions): void => { } }; -export { ReadEventsOptions, ReadFromLatestEvent, validateReadEventsOptions }; +export type { ReadEventsOptions, ReadFromLatestEvent }; +export { validateReadEventsOptions }; diff --git a/lib/handlers/readEvents/readEvents.ts b/lib/handlers/readEvents/readEvents.ts index f9dab2a..549db09 100644 --- a/lib/handlers/readEvents/readEvents.ts +++ b/lib/handlers/readEvents/readEvents.ts @@ -1,19 +1,20 @@ import { StatusCodes } from 'http-status-codes'; -import { Client } from '../../Client'; -import { Event } from '../../event/Event'; -import { validateSubject } from '../../event/validateSubject'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { InvalidParameterError } from '../../util/error/InvalidParameterError'; -import { ServerError } from '../../util/error/ServerError'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream'; -import { StoreItem } from '../StoreItem'; -import { isHeartbeat } from '../isHeartbeat'; -import { isItem } from '../isItem'; -import { isStreamError } from '../isStreamError'; -import { ReadEventsOptions, validateReadEventsOptions } from './ReadEventsOptions'; +import type { Client } from '../../Client.js'; +import { Event } from '../../event/Event.js'; +import { validateSubject } from '../../event/validateSubject.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { InvalidParameterError } from '../../util/error/InvalidParameterError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream.js'; +import type { StoreItem } from '../StoreItem.js'; +import { isHeartbeat } from '../isHeartbeat.js'; +import { isItem } from '../isItem.js'; +import { isStreamError } from '../isStreamError.js'; +import type { ReadEventsOptions } from './ReadEventsOptions.js'; +import { validateReadEventsOptions } from './ReadEventsOptions.js'; const readEvents = async function* ( client: Client, @@ -62,7 +63,7 @@ const readEvents = async function* ( responseType: 'stream', abortController, }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/readSubjects/ReadSubjectsOptions.ts b/lib/handlers/readSubjects/ReadSubjectsOptions.ts index 4ff3d55..544f559 100644 --- a/lib/handlers/readSubjects/ReadSubjectsOptions.ts +++ b/lib/handlers/readSubjects/ReadSubjectsOptions.ts @@ -1,4 +1,4 @@ -import { validateSubject } from '../../event/validateSubject'; +import { validateSubject } from '../../event/validateSubject.js'; interface ReadSubjectsOptions { baseSubject: string; @@ -8,4 +8,5 @@ const validateReadSubjectsOptions = (options: ReadSubjectsOptions): void => { validateSubject(options.baseSubject); }; -export { ReadSubjectsOptions, validateReadSubjectsOptions }; +export type { ReadSubjectsOptions }; +export { validateReadSubjectsOptions }; diff --git a/lib/handlers/readSubjects/Subject.ts b/lib/handlers/readSubjects/Subject.ts index 06b6933..f5fb66f 100644 --- a/lib/handlers/readSubjects/Subject.ts +++ b/lib/handlers/readSubjects/Subject.ts @@ -5,4 +5,4 @@ interface Subject { }; } -export { Subject }; +export type { Subject }; diff --git a/lib/handlers/readSubjects/isSubject.ts b/lib/handlers/readSubjects/isSubject.ts index c3476cd..11eee6e 100644 --- a/lib/handlers/readSubjects/isSubject.ts +++ b/lib/handlers/readSubjects/isSubject.ts @@ -1,5 +1,5 @@ -import { isObject } from '../../util/isObject'; -import { Subject } from './Subject'; +import { isObject } from '../../util/isObject.js'; +import type { Subject } from './Subject.js'; const isSubject = (message: unknown): message is Subject => { if (!isObject(message) || message.type !== 'subject') { diff --git a/lib/handlers/readSubjects/readSubjects.ts b/lib/handlers/readSubjects/readSubjects.ts index 9766efd..f01db3b 100644 --- a/lib/handlers/readSubjects/readSubjects.ts +++ b/lib/handlers/readSubjects/readSubjects.ts @@ -1,15 +1,16 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { Client } from '../../Client'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { InvalidParameterError } from '../../util/error/InvalidParameterError'; -import { ServerError } from '../../util/error/ServerError'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream'; -import { isStreamError } from '../isStreamError'; -import { ReadSubjectsOptions, validateReadSubjectsOptions } from './ReadSubjectsOptions'; -import { isSubject } from './isSubject'; +import type { Client } from '../../Client.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { InvalidParameterError } from '../../util/error/InvalidParameterError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import { readNdJsonStream } from '../../util/ndjson/readNdJsonStream.js'; +import { isStreamError } from '../isStreamError.js'; +import type { ReadSubjectsOptions } from './ReadSubjectsOptions.js'; +import { validateReadSubjectsOptions } from './ReadSubjectsOptions.js'; +import { isSubject } from './isSubject.js'; const readSubjects = async function* ( client: Client, @@ -47,7 +48,7 @@ const readSubjects = async function* ( responseType: 'stream', abortController, }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/registerEventSchema/registerEventSchema.ts b/lib/handlers/registerEventSchema/registerEventSchema.ts index b5dfd7d..5e577e6 100644 --- a/lib/handlers/registerEventSchema/registerEventSchema.ts +++ b/lib/handlers/registerEventSchema/registerEventSchema.ts @@ -1,12 +1,12 @@ import { StatusCodes } from 'http-status-codes'; -import { Client } from '../../Client'; -import { validateType } from '../../event/validateType'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { InvalidParameterError } from '../../util/error/InvalidParameterError'; -import { ServerError } from '../../util/error/ServerError'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; +import type { Client } from '../../Client.js'; +import { validateType } from '../../event/validateType.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { InvalidParameterError } from '../../util/error/InvalidParameterError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; const registerEventSchema = async ( client: Client, @@ -39,7 +39,7 @@ const registerEventSchema = async ( requestBody, responseType: 'text', }), - async error => { + error => { if (error instanceof CustomError) { throw error; } diff --git a/lib/handlers/writeEvents/Precondition.ts b/lib/handlers/writeEvents/Precondition.ts index 80210af..a7196a6 100644 --- a/lib/handlers/writeEvents/Precondition.ts +++ b/lib/handlers/writeEvents/Precondition.ts @@ -31,4 +31,5 @@ const isSubjectOnEventId = (payload: IsSubjectOnEventIdPrecondition): Preconditi }; }; -export { Precondition, isSubjectPristine, isSubjectOnEventId }; +export type { Precondition }; +export { isSubjectPristine, isSubjectOnEventId }; diff --git a/lib/handlers/writeEvents/writeEvents.ts b/lib/handlers/writeEvents/writeEvents.ts index 04c9ca1..6f73668 100644 --- a/lib/handlers/writeEvents/writeEvents.ts +++ b/lib/handlers/writeEvents/writeEvents.ts @@ -1,28 +1,28 @@ import { StatusCodes } from 'http-status-codes'; -import { Client } from '../../Client'; -import { EventCandidate } from '../../event/EventCandidate'; -import { EventContext } from '../../event/EventContext'; -import { CustomError } from '../../util/error/CustomError'; -import { InternalError } from '../../util/error/InternalError'; -import { InvalidParameterError } from '../../util/error/InvalidParameterError'; -import { ServerError } from '../../util/error/ServerError'; -import { ValidationError } from '../../util/error/ValidationError'; -import { wrapError } from '../../util/error/wrapError'; -import { Precondition } from './Precondition'; +import type { Client } from '../../Client.js'; +import type { EventCandidate } from '../../event/EventCandidate.js'; +import { EventContext } from '../../event/EventContext.js'; +import { CustomError } from '../../util/error/CustomError.js'; +import { InternalError } from '../../util/error/InternalError.js'; +import { InvalidParameterError } from '../../util/error/InvalidParameterError.js'; +import { ServerError } from '../../util/error/ServerError.js'; +import { ValidationError } from '../../util/error/ValidationError.js'; +import { wrapError } from '../../util/error/wrapError.js'; +import type { Precondition } from './Precondition.js'; const writeEvents = async ( client: Client, eventCandidates: EventCandidate[], preconditions: Precondition[], ): Promise => { - if (eventCandidates.length < 1) { + if (eventCandidates.length === 0) { throw new InvalidParameterError( 'eventCandidates', 'eventCandidates must contain at least one EventCandidate.', ); } for (const eventCandidate of eventCandidates) { - await wrapError( + wrapError( () => { eventCandidate.validate(); }, @@ -46,7 +46,7 @@ const writeEvents = async ( requestBody, responseType: 'json', }), - async error => { + error => { if (error instanceof CustomError) { throw error; } @@ -63,7 +63,7 @@ const writeEvents = async ( } const responseData = response.data; - return await wrapError( + return wrapError( () => responseData.map((eventContext): EventContext => EventContext.parse(eventContext)), error => { if (error instanceof ValidationError) { diff --git a/lib/http/HttpClient.ts b/lib/http/HttpClient.ts index 980436d..650429b 100644 --- a/lib/http/HttpClient.ts +++ b/lib/http/HttpClient.ts @@ -1,30 +1,29 @@ -import { Readable } from 'stream'; -import { AxiosError, AxiosResponse, CanceledError, CreateAxiosDefaults, ResponseType } from 'axios'; +import type { Readable } from 'node:stream'; +import type { AxiosResponse, CreateAxiosDefaults, ResponseType } from 'axios'; +import { AxiosError, CanceledError } from 'axios'; import axios from 'axios'; import { StatusCodes } from 'http-status-codes'; -import { Client } from '../Client'; -import { CancelationError } from '../util/error/CancelationError'; -import { ClientError } from '../util/error/ClientError'; -import { CustomError } from '../util/error/CustomError'; -import { InternalError } from '../util/error/InternalError'; -import { ServerError } from '../util/error/ServerError'; -import { RetryError } from '../util/retry/RetryError'; -import { retryWithBackoff } from '../util/retry/retryWithBackoff'; +import type { Client } from '../Client.js'; +import { CancelationError } from '../util/error/CancelationError.js'; +import { ClientError } from '../util/error/ClientError.js'; +import { CustomError } from '../util/error/CustomError.js'; +import { InternalError } from '../util/error/InternalError.js'; +import { ServerError } from '../util/error/ServerError.js'; // biome-ignore lint/style/useNamingConvention: We want to use this naming convention type ResponseDataType = TResponseType extends 'arraybuffer' ? ArrayBuffer : TResponseType extends 'blob' - ? Blob - : TResponseType extends 'document' - ? unknown - : TResponseType extends 'json' - ? unknown - : TResponseType extends 'text' - ? string - : TResponseType extends 'stream' - ? Readable - : never; + ? Blob + : TResponseType extends 'document' + ? unknown + : TResponseType extends 'json' + ? unknown + : TResponseType extends 'text' + ? string + : TResponseType extends 'stream' + ? Readable + : never; // biome-ignore lint/style/useNamingConvention: We want to use this naming convention type Response = AxiosResponse< ResponseDataType, @@ -127,32 +126,19 @@ class HttpClient { const signal = abortController.signal; try { - const response = await retryWithBackoff>( - abortController, - this.databaseClient.configuration.maxTries, - async () => { - const response = await axiosInstance.post(options.path, options.requestBody, { signal }); - if (response.status >= 500 && response.status < 600) { - return { - retry: new ServerError(`Request failed with status code '${response.status}'.`), - }; - } - - this.validateProtocolVersion(response.status, response.headers); - - if (response.status >= 400 && response.status < 500) { - throw new ClientError(`Request failed with status code '${response.status}'.`); - } - - return { return: response }; - }, - ); + const response = await axiosInstance.post(options.path, options.requestBody, { signal }); + if (response.status >= 500 && response.status < 600) { + throw new ServerError(`Request failed with status code '${response.status}'.`); + } + + this.validateProtocolVersion(response.status, response.headers); + + if (response.status >= 400 && response.status < 500) { + throw new ClientError(`Request failed with status code '${response.status}'.`); + } return response; } catch (ex) { - if (ex instanceof RetryError) { - throw new ServerError(ex.message); - } if (ex instanceof CustomError) { throw ex; } @@ -187,34 +173,22 @@ class HttpClient { const signal = abortController.signal; try { - const response = await retryWithBackoff>( - abortController, - this.databaseClient.configuration.maxTries, - async () => { - const response = await axiosInstance.get(options.path, { signal }); - - if (response.status >= 500 && response.status < 600) { - return { - retry: new ServerError(`Request failed with status code '${response.status}'.`), - }; - } - - this.validateProtocolVersion(response.status, response.headers); - - if (response.status >= 400 && response.status < 500) { - throw new ClientError(`Request failed with status code '${response.status}'.`); - } - - return { return: response }; - }, - ); + const response = await axiosInstance.get(options.path, { signal }); + + if (response.status >= 500 && response.status < 600) { + throw new ServerError(`Request failed with status code '${response.status}'.`); + } + + this.validateProtocolVersion(response.status, response.headers); + + if (response.status >= 400 && response.status < 500) { + throw new ClientError(`Request failed with status code '${response.status}'.`); + } + this.validateProtocolVersion(response.status, response.headers); return response; } catch (ex) { - if (ex instanceof RetryError) { - throw new ServerError(ex.message); - } if (ex instanceof CustomError) { throw ex; } diff --git a/lib/index.ts b/lib/index.ts index db7f1a8..3e64b67 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,21 +1,22 @@ -export { CancelationError } from './util/error/CancelationError'; -export { Client } from './Client'; -export { ClientConfiguration } from './ClientConfiguration'; -export { Event } from './event/Event'; -export { EventCandidate } from './event/EventCandidate'; -export { ReadSubjectsOptions } from './handlers/readSubjects/ReadSubjectsOptions'; -export { Source } from './event/Source'; -export { StoreItem } from './handlers/StoreItem'; -export { +// biome-ignore lint/performance/noBarrelFile: This is the main entry point for the library. +export { CancelationError } from './util/error/CancelationError.js'; +export { Client } from './Client.js'; +export type { ClientConfiguration } from './ClientConfiguration.js'; +export { Event } from './event/Event.js'; +export { EventCandidate } from './event/EventCandidate.js'; +export type { ReadSubjectsOptions } from './handlers/readSubjects/ReadSubjectsOptions.js'; +export { Source } from './event/Source.js'; +export type { StoreItem } from './handlers/StoreItem.js'; +export type { ObserveEventsOptions, ObserveFromLatestEvent, -} from './handlers/observeEvents/ObserveEventsOptions'; +} from './handlers/observeEvents/ObserveEventsOptions.js'; +export type { Precondition } from './handlers/writeEvents/Precondition.js'; export { - Precondition, isSubjectPristine, isSubjectOnEventId, -} from './handlers/writeEvents/Precondition'; -export { +} from './handlers/writeEvents/Precondition.js'; +export type { ReadEventsOptions, ReadFromLatestEvent, -} from './handlers/readEvents/ReadEventsOptions'; +} from './handlers/readEvents/ReadEventsOptions.js'; diff --git a/lib/util/UnknownObject.ts b/lib/util/UnknownObject.ts index 77e5069..2a8f7c7 100644 --- a/lib/util/UnknownObject.ts +++ b/lib/util/UnknownObject.ts @@ -1,3 +1,3 @@ type UnknownObject = Partial>; -export { UnknownObject }; +export type { UnknownObject }; diff --git a/lib/util/error/CancelationError.ts b/lib/util/error/CancelationError.ts index 1f3877f..052edf2 100644 --- a/lib/util/error/CancelationError.ts +++ b/lib/util/error/CancelationError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class CancelationError extends CustomError {} diff --git a/lib/util/error/ClientError.ts b/lib/util/error/ClientError.ts index 2fb31c1..c219dad 100644 --- a/lib/util/error/ClientError.ts +++ b/lib/util/error/ClientError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class ClientError extends CustomError { constructor(cause: string) { diff --git a/lib/util/error/InternalError.ts b/lib/util/error/InternalError.ts index 8c3f142..d77db4a 100644 --- a/lib/util/error/InternalError.ts +++ b/lib/util/error/InternalError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class InternalError extends CustomError { constructor(cause: unknown) { diff --git a/lib/util/error/InvalidParameterError.ts b/lib/util/error/InvalidParameterError.ts index e46919a..ede4def 100644 --- a/lib/util/error/InvalidParameterError.ts +++ b/lib/util/error/InvalidParameterError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class InvalidParameterError extends CustomError { constructor(parameterName: string, reason: string) { diff --git a/lib/util/error/ServerError.ts b/lib/util/error/ServerError.ts index e1816fd..c85f83f 100644 --- a/lib/util/error/ServerError.ts +++ b/lib/util/error/ServerError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class ServerError extends CustomError { constructor(cause: string) { diff --git a/lib/util/error/ValidationError.ts b/lib/util/error/ValidationError.ts index 6bb2d1c..5a0adbc 100644 --- a/lib/util/error/ValidationError.ts +++ b/lib/util/error/ValidationError.ts @@ -1,4 +1,4 @@ -import { CustomError } from './CustomError'; +import { CustomError } from './CustomError.js'; class ValidationError extends CustomError {} diff --git a/lib/util/error/wrapError.ts b/lib/util/error/wrapError.ts index f6db2b3..46531c4 100644 --- a/lib/util/error/wrapError.ts +++ b/lib/util/error/wrapError.ts @@ -1,4 +1,4 @@ -import { InternalError } from './InternalError'; +import { InternalError } from './InternalError.js'; const isPromise = (value: unknown): value is Promise => { return typeof value === 'object' && typeof (value as Record).then === 'function'; diff --git a/lib/util/isObject.ts b/lib/util/isObject.ts index 84a2f49..eeaf89d 100644 --- a/lib/util/isObject.ts +++ b/lib/util/isObject.ts @@ -1,4 +1,4 @@ -import { UnknownObject } from './UnknownObject'; +import type { UnknownObject } from './UnknownObject.js'; const isObject = (value: unknown): value is UnknownObject => { return typeof value === 'object' && !Array.isArray(value) && value !== null; diff --git a/lib/util/ndjson/LinesDecoder.ts b/lib/util/ndjson/LinesDecoder.ts index fb4ae2d..8680d66 100644 --- a/lib/util/ndjson/LinesDecoder.ts +++ b/lib/util/ndjson/LinesDecoder.ts @@ -1,9 +1,10 @@ -import { StringDecoder } from 'string_decoder'; +import { StringDecoder } from 'node:string_decoder'; class LinesDecoder { private textBuffer: string; private readonly decoder: StringDecoder; + // biome-ignore lint/correctness/noUndeclaredVariables: This is a global variable defined by Node.js. public constructor(encoding?: BufferEncoding) { this.textBuffer = ''; this.decoder = new StringDecoder(encoding); diff --git a/lib/util/ndjson/readNdJsonStream.ts b/lib/util/ndjson/readNdJsonStream.ts index eed4f4d..a2ae227 100644 --- a/lib/util/ndjson/readNdJsonStream.ts +++ b/lib/util/ndjson/readNdJsonStream.ts @@ -1,10 +1,10 @@ -import { Readable } from 'stream'; +import type { Readable } from 'node:stream'; import { CanceledError } from 'axios'; import StreamToAsyncIterator from 'stream-to-async-iterator'; -import { UnknownObject } from '../UnknownObject'; -import { CancelationError } from '../error/CancelationError'; -import { ServerError } from '../error/ServerError'; -import { LinesDecoder } from './LinesDecoder'; +import type { UnknownObject } from '../UnknownObject.js'; +import { CancelationError } from '../error/CancelationError.js'; +import { ServerError } from '../error/ServerError.js'; +import { LinesDecoder } from './LinesDecoder.js'; const readNdJsonStream = async function* ( stream: Readable, diff --git a/lib/util/retry/RetryError.ts b/lib/util/retry/RetryError.ts deleted file mode 100644 index 9800613..0000000 --- a/lib/util/retry/RetryError.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CustomError } from '../error/CustomError'; - -class RetryError extends CustomError { - private readonly errors: Error[]; - - public constructor() { - super(); - - this.errors = []; - } - - public appendError(error: Error): void { - this.errors.push(error); - } - - public get message(): string { - return `Failed operation with ${this.errors.length} errors:\n${this.errors.join('\n')}`; - } -} - -export { RetryError }; diff --git a/lib/util/retry/retryWithBackoff.ts b/lib/util/retry/retryWithBackoff.ts deleted file mode 100644 index 360a048..0000000 --- a/lib/util/retry/retryWithBackoff.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { clearTimeout } from 'timers'; -import { CancelationError } from '../error/CancelationError'; -import { RetryError } from './RetryError'; - -// biome-ignore lint/style/useNamingConvention: We want to use this return type -type RetryResult = - | { - return: TResult; - } - | { - retry: Error; - }; - -const getRandomizedDuration = ( - durationMilliseconds: number, - deviationMilliseconds: number, -): number => { - const milliseconds = - durationMilliseconds - - deviationMilliseconds + - Math.round(Math.random() * deviationMilliseconds * 2); - - return milliseconds; -}; - -// biome-ignore lint/style/useNamingConvention: We want to use this return type -const retryWithBackoff = async ( - abortController: AbortController, - tries: number, - fn: () => Promise>, -): Promise => { - if (tries < 1) { - throw new RangeError('Tries must be greater than 0.'); - } - - const retryError = new RetryError(); - - for (let triesCount = 0; triesCount < tries; triesCount++) { - // On the first iteration triesCount is 0, so the timeout is 0, and we do not wait. - const timeout = getRandomizedDuration(100, 25) * triesCount; - - await new Promise((resolve, reject) => { - if (abortController.signal.aborted) { - return reject(new CancelationError()); - } - - const handleAborted = () => { - clearTimeout(timer); - return reject(new CancelationError()); - }; - - abortController.signal.addEventListener('abort', handleAborted); - - const timer = setTimeout(() => { - abortController.signal.removeEventListener('abort', handleAborted); - return resolve(); - }, timeout); - }); - - const result = await fn(); - - if ('return' in result) { - return result.return; - } - - if ('retry' in result) { - retryError.appendError(result.retry); - } - } - - throw retryError; -}; - -const done = { return: undefined }; - -export { retryWithBackoff, done }; diff --git a/package-lock.json b/package-lock.json index d13fba3..e86919d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,184 +13,179 @@ "stream-to-async-iterator": "1.0.0" }, "devDependencies": { - "@biomejs/biome": "1.5.3", + "@biomejs/biome": "1.9.4", "@types/express": "5.0.0", - "@types/mocha": "10.0.10", + "@types/node": "22.13.10", "@types/shelljs": "0.8.15", - "assertthat": "6.5.2", "express": "4.21.2", - "mocha": "11.1.0", "shelljs": "0.9.2", - "ts-node": "10.9.2", "tsup": "8.4.0", + "tsx": "4.19.3", "typescript": "5.8.2" } }, "node_modules/@biomejs/biome": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.5.3.tgz", - "integrity": "sha512-yvZCa/g3akwTaAQ7PCwPWDCkZs3Qa5ONg/fgOUT9e6wAWsPftCjLQFPXBeGxPK30yZSSpgEmRCfpGTmVbUjGgg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", "dev": true, "hasInstallScript": true, + "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" }, "engines": { - "node": ">=14.*" + "node": ">=14.21.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.5.3", - "@biomejs/cli-darwin-x64": "1.5.3", - "@biomejs/cli-linux-arm64": "1.5.3", - "@biomejs/cli-linux-arm64-musl": "1.5.3", - "@biomejs/cli-linux-x64": "1.5.3", - "@biomejs/cli-linux-x64-musl": "1.5.3", - "@biomejs/cli-win32-arm64": "1.5.3", - "@biomejs/cli-win32-x64": "1.5.3" + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.5.3.tgz", - "integrity": "sha512-ImU7mh1HghEDyqNmxEZBoMPr8SxekkZuYcs+gynKlNW+TALQs7swkERiBLkG9NR0K1B3/2uVzlvYowXrmlW8hw==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.5.3.tgz", - "integrity": "sha512-vCdASqYnlpq/swErH7FD6nrFz0czFtK4k/iLgj0/+VmZVjineFPgevOb+Sr9vz0tk0GfdQO60bSpI74zU8M9Dw==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.5.3.tgz", - "integrity": "sha512-cupBQv0sNF1OKqBfx7EDWMSsKwRrBUZfjXawT4s6hKV6ALq7p0QzWlxr/sDmbKMLOaLQtw2Qgu/77N9rm+f9Rg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.3.tgz", - "integrity": "sha512-DYuMizUYUBYfS0IHGjDrOP1RGipqWfMGEvNEJ398zdtmCKLXaUvTimiox5dvx4X15mBK5M2m8wgWUgOP1giUpQ==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.5.3.tgz", - "integrity": "sha512-YQrSArQvcv4FYsk7Q91Yv4uuu5F8hJyORVcv3zsjCLGkjIjx2RhjYLpTL733SNL7v33GmOlZY0eFR1ko38tuUw==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.3.tgz", - "integrity": "sha512-UUHiAnlDqr2Y/LpvshBFhUYMWkl2/Jn+bi3U6jKuav0qWbbBKU/ByHgR4+NBxpKBYoCtWxhnmatfH1bpPIuZMw==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.5.3.tgz", - "integrity": "sha512-HxatYH7vf/kX9nrD+pDYuV2GI9GV8EFo6cfKkahAecTuZLPxryHx1WEfJthp5eNsE0+09STGkKIKjirP0ufaZA==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=14.*" + "node": ">=14.21.3" } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.5.3.tgz", - "integrity": "sha512-fMvbSouZEASU7mZH8SIJSANDm5OqsjgtVXlbUqxwed6BP7uuHRSs396Aqwh2+VoW8fwTpp6ybIUoC9FrzB0kyA==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT OR Apache-2.0", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=14.*" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" + "node": ">=14.21.3" } }, "node_modules/@esbuild/aix-ppc64": { @@ -762,16 +757,6 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1086,30 +1071,6 @@ "win32" ] }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -1120,12 +1081,6 @@ "@types/node": "*" } }, - "node_modules/@types/common-tags": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz", - "integrity": "sha512-20R/mDpKSPWdJs5TOpz3e7zqbeCNuMCPhV7Yndk9KU2Rbij2r5W4RzwDPkzC+2lzUqXYu9rFzTktCBnDjHuNQg==", - "dev": true - }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -1188,17 +1143,15 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true - }, "node_modules/@types/node": { - "version": "18.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", - "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", - "dev": true + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } }, "node_modules/@types/qs": { "version": "6.9.16", @@ -1248,12 +1201,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1267,36 +1214,6 @@ "node": ">= 0.6" } }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1327,52 +1244,12 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "node_modules/assertthat": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/assertthat/-/assertthat-6.5.2.tgz", - "integrity": "sha512-iNxRVPzSRNL7PArfYaP7kuJ+wMCKs7Cv2Rb+XVqtCKBMh3l0KD5o3f0lQJ0O3Lv7aD1P1WNmRHOW8S1lTe4DuQ==", - "dev": true, - "dependencies": { - "@types/common-tags": "1.8.1", - "@types/uuid": "8.3.4", - "chalk": "4.1.2", - "common-tags": "1.8.2", - "defekt": "9.1.0", - "typedescriptor": "4.0.13", - "uuid": "8.3.2" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1395,15 +1272,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -1449,12 +1317,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -1508,73 +1370,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1613,15 +1408,6 @@ "node": ">= 6" } }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/consola": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", @@ -1668,12 +1454,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1697,24 +1477,6 @@ "ms": "2.0.0" } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defekt": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/defekt/-/defekt-9.1.0.tgz", - "integrity": "sha512-Ocx7DoFAX1c7cPOacNWV/dz7Ebino2k0//0old5cxKDf3Ovyiwio9MpKLDSkgIkp6Han+PXgROmffxAKsok5nA==", - "dev": true - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1759,15 +1521,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1867,34 +1620,12 @@ "@esbuild/win32-x64": "0.25.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2089,31 +1820,6 @@ "node": ">= 0.8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -2193,11 +1899,12 @@ } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2215,16 +1922,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -2257,6 +1954,19 @@ "node": ">=6" } }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -2293,15 +2003,6 @@ "node": ">= 0.4.0" } }, - "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, - "engines": { - "node": ">=8" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -2350,15 +2051,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2416,18 +2108,6 @@ "node": ">= 0.10" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -2479,15 +2159,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -2498,18 +2169,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2540,18 +2199,6 @@ "node": ">=10" } }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/lilconfig": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", @@ -2580,55 +2227,18 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2711,19 +2321,6 @@ "node": ">= 0.6" } }, - "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -2733,123 +2330,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/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/mocha/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/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/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2883,15 +2363,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -2968,36 +2439,6 @@ "node": ">=4" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -3013,15 +2454,6 @@ "node": ">= 0.8" } }, - "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", @@ -3202,15 +2634,6 @@ ], "license": "MIT" }, - "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/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3235,18 +2658,6 @@ "node": ">= 0.8" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -3259,16 +2670,6 @@ "node": ">= 0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -3295,6 +2696,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3444,15 +2855,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -3649,18 +3051,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -3718,18 +3108,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "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", @@ -3860,58 +3238,6 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/tsup": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz", @@ -4019,6 +3345,26 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/tsx": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4032,15 +3378,6 @@ "node": ">= 0.6" } }, - "node_modules/typedescriptor": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/typedescriptor/-/typedescriptor-4.0.13.tgz", - "integrity": "sha512-k2+oyaiwBsf1YT+7NaSR/cza4ifVU+6/1sgKincN3XoEBsG33uYsEjsbHiczGrNagd3uNybgZliakOlPFOEsfg==", - "dev": true, - "dependencies": { - "defekt": "9.1.0" - } - }, "node_modules/typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -4055,6 +3392,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4073,21 +3417,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -4129,30 +3458,6 @@ "node": ">= 8" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -4177,81 +3482,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index d562d8d..2784fa6 100644 --- a/package.json +++ b/package.json @@ -29,26 +29,24 @@ "stream-to-async-iterator": "1.0.0" }, "devDependencies": { - "@biomejs/biome": "1.5.3", + "@biomejs/biome": "1.9.4", "@types/express": "5.0.0", - "@types/mocha": "10.0.10", + "@types/node": "22.13.10", "@types/shelljs": "0.8.15", - "assertthat": "6.5.2", "express": "4.21.2", - "mocha": "11.1.0", "shelljs": "0.9.2", - "ts-node": "10.9.2", "tsup": "8.4.0", + "tsx": "4.19.3", "typescript": "5.8.2" }, "scripts": { - "analyze": "npx biome ci ./**/*", + "analyze": "npx biome check ./**/*", "build": "npx tsc --noEmit && npx tsup --clean --dts --format cjs,esm --minify --out-dir=./build/ lib/index.ts", "format": "npx biome format --write ./**/*", "qa": "npm run analyze && npm run test", "test": "npm run test:unit && npm run test:integration", - "test:unit": "npx mocha --bail --require ts-node/register --recursive --ui tdd './test/unit/**/*.ts'", - "test:integration": "npx mocha --bail --require ts-node/register --recursive --ui tdd './test/integration/**/*.ts'" + "test:unit": "node --test --import tsx \"./test/unit/**/*.ts\"", + "test:integration": "node --test --import tsx \"./test/integration/**/*.ts\"" }, "repository": { "type": "git", diff --git a/test/integration/observeEventsTests.ts b/test/integration/observeEventsTests.ts index 092dada..c5f4c34 100644 --- a/test/integration/observeEventsTests.ts +++ b/test/integration/observeEventsTests.ts @@ -1,31 +1,30 @@ -import { assert } from 'assertthat'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; import { ReasonPhrases, StatusCodes } from 'http-status-codes'; -import { Client, StoreItem } from '../../lib'; -import { CancelationError } from '../../lib'; -import { Source } from '../../lib'; -import { ClientError } from '../../lib/util/error/ClientError'; -import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { Database } from '../shared/Database'; -import { newAbortControllerWithDeadline } from '../shared/abortController/newAbortControllerWithDeadline'; -import { buildDatabase } from '../shared/buildDatabase'; -import { events } from '../shared/events/events'; -import { testSource } from '../shared/events/source'; -import { startDatabase } from '../shared/startDatabase'; -import { startLocalHttpServer } from '../shared/startLocalHttpServer'; -import { stopDatabase } from '../shared/stopDatabase'; - -suite('Client.observeEvents()', function () { - this.timeout(20_000); +import type { Client, StoreItem } from '../../lib/index.js'; +import { CancelationError, Source } from '../../lib/index.js'; +import { ClientError } from '../../lib/util/error/ClientError.js'; +import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError.js'; +import { ServerError } from '../../lib/util/error/ServerError.js'; +import type { Database } from '../shared/Database.js'; +import { newAbortControllerWithDeadline } from '../shared/abortController/newAbortControllerWithDeadline.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { events } from '../shared/events/events.js'; +import { testSource } from '../shared/events/source.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { startLocalHttpServer } from '../shared/startLocalHttpServer.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; + +suite('observeEvents', { timeout: 20_000 }, () => { let database: Database; const source = new Source(testSource); const testDeadline = 10_000; - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); await database.withAuthorization.client.writeEvents([ @@ -56,33 +55,34 @@ suite('Client.observeEvents()', function () { ]); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('throws an error when trying to read from a non-reachable server.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { + await assert.rejects( + async () => { const result = client.observeEvents(new AbortController(), '/', { recursive: false }); for await (const _ of result) { // Do nothing. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: No response received.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: No response received.'); + return true; + }, + ); }); test('throws an error if the subject is invalid.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { + await assert.rejects( + async () => { const result = client.observeEvents(new AbortController(), 'applepie', { recursive: false, }); @@ -90,33 +90,33 @@ suite('Client.observeEvents()', function () { for await (const _ of result) { // Do nothing. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'subject' is invalid: Failed to validate subject: 'applepie' must be an absolute, slash-separated path.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'subject' is invalid: Failed to validate subject: 'applepie' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('supports authorization.', async (): Promise => { const client = database.withAuthorization.client; const abortController = newAbortControllerWithDeadline(testDeadline); - await assert - .that(async () => { - let observedItemsCount = 0; - const result = client.observeEvents(abortController, '/', { recursive: true }); + // Should not throw. + let observedItemsCount = 0; + const result = client.observeEvents(abortController, '/', { recursive: true }); - for await (const _data of result) { - observedItemsCount += 1; + for await (const _data of result) { + observedItemsCount += 1; - if (observedItemsCount === 4) { - return; - } - } - }) - .is.not.throwingAsync(); + if (observedItemsCount === 4) { + return; + } + } }); test('observes events from a single subject.', async (): Promise => { @@ -124,58 +124,50 @@ suite('Client.observeEvents()', function () { const abortController = newAbortControllerWithDeadline(testDeadline); const observedItems: StoreItem[] = []; - await assert - .that(async () => { - let didPushIntermediateEvent = false; - const result = client.observeEvents(abortController, '/users/registered', { - recursive: false, - }); - - for await (const item of result) { - observedItems.push(item); - if (!didPushIntermediateEvent) { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.apfelFred.type, - events.registered.apfelFred.data, - events.registered.apfelFred.traceParent, - ), - ]); - - didPushIntermediateEvent = true; - } + // Should not throw. + let didPushIntermediateEvent = false; + const result = client.observeEvents(abortController, '/users/registered', { + recursive: false, + }); - if (observedItems.length === 3) { - return; - } - } - }) - .is.not.throwingAsync(); - - assert.that(observedItems.length).is.equalTo(3); - assert.that(observedItems[0].event.source).is.equalTo(testSource); - assert.that(observedItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[0].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(observedItems[0].event.data).is.equalTo(events.registered.janeDoe.data); - assert - .that(observedItems[0].event.traceParent) - .is.equalTo(events.registered.janeDoe.traceParent); - assert.that(observedItems[1].event.source).is.equalTo(testSource); - assert.that(observedItems[1].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[1].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(observedItems[1].event.data).is.equalTo(events.registered.johnDoe.data); - assert - .that(observedItems[1].event.traceParent) - .is.equalTo(events.registered.johnDoe.traceParent); - assert.that(observedItems[2].event.source).is.equalTo(testSource); - assert.that(observedItems[2].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[2].event.type).is.equalTo(events.registered.apfelFred.type); - assert.that(observedItems[2].event.data).is.equalTo(events.registered.apfelFred.data); - assert - .that(observedItems[2].event.traceParent) - .is.equalTo(events.registered.apfelFred.traceParent); + for await (const item of result) { + observedItems.push(item); + + if (!didPushIntermediateEvent) { + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.apfelFred.type, + events.registered.apfelFred.data, + events.registered.apfelFred.traceParent, + ), + ]); + + didPushIntermediateEvent = true; + } + + if (observedItems.length === 3) { + return; + } + } + + assert.equal(observedItems.length, 3); + assert.equal(observedItems[0].event.source, testSource); + assert.equal(observedItems[0].event.subject, '/users/registered'); + assert.equal(observedItems[0].event.type, events.registered.janeDoe.type); + assert.equal(observedItems[0].event.data, events.registered.janeDoe.data); + assert.equal(observedItems[0].event.traceParent, events.registered.janeDoe.traceParent); + assert.equal(observedItems[1].event.source, testSource); + assert.equal(observedItems[1].event.subject, '/users/registered'); + assert.equal(observedItems[1].event.type, events.registered.johnDoe.type); + assert.equal(observedItems[1].event.data, events.registered.johnDoe.data); + assert.equal(observedItems[1].event.traceParent, events.registered.johnDoe.traceParent); + assert.equal(observedItems[2].event.source, testSource); + assert.equal(observedItems[2].event.subject, '/users/registered'); + assert.equal(observedItems[2].event.type, events.registered.apfelFred.type); + assert.equal(observedItems[2].event.data, events.registered.apfelFred.data); + assert.equal(observedItems[2].event.traceParent, events.registered.apfelFred.traceParent); }); test('observes events from a subject including child subjects.', async (): Promise => { @@ -183,56 +175,54 @@ suite('Client.observeEvents()', function () { const abortController = newAbortControllerWithDeadline(testDeadline); const observedItems: StoreItem[] = []; - await assert - .that(async () => { - let didPushIntermediateEvent = false; - const result = client.observeEvents(abortController, '/users', { - recursive: true, - }); - - for await (const item of result) { - observedItems.push(item); - if (!didPushIntermediateEvent) { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.apfelFred.type, - events.registered.apfelFred.data, - ), - ]); - - didPushIntermediateEvent = true; - } + // Should not throw. + let didPushIntermediateEvent = false; + const result = client.observeEvents(abortController, '/users', { + recursive: true, + }); - if (observedItems.length === 5) { - return; - } - } - }) - .is.not.throwingAsync(); - - assert.that(observedItems.length).is.equalTo(5); - assert.that(observedItems[0].event.source).is.equalTo(testSource); - assert.that(observedItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[0].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(observedItems[0].event.data).is.equalTo(events.registered.janeDoe.data); - assert.that(observedItems[1].event.source).is.equalTo(testSource); - assert.that(observedItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[1].event.type).is.equalTo(events.loggedIn.janeDoe.type); - assert.that(observedItems[1].event.data).is.equalTo(events.loggedIn.janeDoe.data); - assert.that(observedItems[2].event.source).is.equalTo(testSource); - assert.that(observedItems[2].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[2].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(observedItems[2].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(observedItems[3].event.source).is.equalTo(testSource); - assert.that(observedItems[3].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[3].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(observedItems[3].event.data).is.equalTo(events.loggedIn.johnDoe.data); - assert.that(observedItems[4].event.source).is.equalTo(testSource); - assert.that(observedItems[4].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[4].event.type).is.equalTo(events.registered.apfelFred.type); - assert.that(observedItems[4].event.data).is.equalTo(events.registered.apfelFred.data); + for await (const item of result) { + observedItems.push(item); + + if (!didPushIntermediateEvent) { + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.apfelFred.type, + events.registered.apfelFred.data, + ), + ]); + + didPushIntermediateEvent = true; + } + + if (observedItems.length === 5) { + return; + } + } + + assert.equal(observedItems.length, 5); + assert.equal(observedItems[0].event.source, testSource); + assert.equal(observedItems[0].event.subject, '/users/registered'); + assert.equal(observedItems[0].event.type, events.registered.janeDoe.type); + assert.equal(observedItems[0].event.data, events.registered.janeDoe.data); + assert.equal(observedItems[1].event.source, testSource); + assert.equal(observedItems[1].event.subject, '/users/loggedIn'); + assert.equal(observedItems[1].event.type, events.loggedIn.janeDoe.type); + assert.equal(observedItems[1].event.data, events.loggedIn.janeDoe.data); + assert.equal(observedItems[2].event.source, testSource); + assert.equal(observedItems[2].event.subject, '/users/registered'); + assert.equal(observedItems[2].event.type, events.registered.johnDoe.type); + assert.equal(observedItems[2].event.data, events.registered.johnDoe.data); + assert.equal(observedItems[3].event.source, testSource); + assert.equal(observedItems[3].event.subject, '/users/loggedIn'); + assert.equal(observedItems[3].event.type, events.loggedIn.johnDoe.type); + assert.equal(observedItems[3].event.data, events.loggedIn.johnDoe.data); + assert.equal(observedItems[4].event.source, testSource); + assert.equal(observedItems[4].event.subject, '/users/registered'); + assert.equal(observedItems[4].event.type, events.registered.apfelFred.type); + assert.equal(observedItems[4].event.data, events.registered.apfelFred.data); }); test('observes events starting from the newest event matching the given event name.', async (): Promise => { @@ -240,49 +230,46 @@ suite('Client.observeEvents()', function () { const abortController = newAbortControllerWithDeadline(testDeadline); const observedItems: StoreItem[] = []; - await assert - .that(async () => { - let didPushIntermediateEvent = false; - const result = client.observeEvents(abortController, '/users', { - recursive: true, - fromLatestEvent: { - subject: '/users/loggedIn', - type: events.loggedIn.janeDoe.type, - ifEventIsMissing: 'read-everything', - }, - }); - - for await (const item of result) { - observedItems.push(item); - - if (!didPushIntermediateEvent) { - await client.writeEvents([ - source.newEvent( - '/users/loggedIn', - events.loggedIn.apfelFred.type, - events.loggedIn.apfelFred.data, - ), - ]); - - didPushIntermediateEvent = true; - } + // Should not throw. + let didPushIntermediateEvent = false; + const result = client.observeEvents(abortController, '/users', { + recursive: true, + fromLatestEvent: { + subject: '/users/loggedIn', + type: events.loggedIn.janeDoe.type, + ifEventIsMissing: 'read-everything', + }, + }); - if (observedItems.length === 2) { - return; - } - } - }) - .is.not.throwingAsync(); - - assert.that(observedItems.length).is.equalTo(2); - assert.that(observedItems[0].event.source).is.equalTo(testSource); - assert.that(observedItems[0].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[0].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(observedItems[0].event.data).is.equalTo(events.loggedIn.johnDoe.data); - assert.that(observedItems[1].event.source).is.equalTo(testSource); - assert.that(observedItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[1].event.type).is.equalTo(events.loggedIn.apfelFred.type); - assert.that(observedItems[1].event.data).is.equalTo(events.loggedIn.apfelFred.data); + for await (const item of result) { + observedItems.push(item); + + if (!didPushIntermediateEvent) { + await client.writeEvents([ + source.newEvent( + '/users/loggedIn', + events.loggedIn.apfelFred.type, + events.loggedIn.apfelFred.data, + ), + ]); + + didPushIntermediateEvent = true; + } + + if (observedItems.length === 2) { + return; + } + } + + assert.equal(observedItems.length, 2); + assert.equal(observedItems[0].event.source, testSource); + assert.equal(observedItems[0].event.subject, '/users/loggedIn'); + assert.equal(observedItems[0].event.type, events.loggedIn.johnDoe.type); + assert.equal(observedItems[0].event.data, events.loggedIn.johnDoe.data); + assert.equal(observedItems[1].event.source, testSource); + assert.equal(observedItems[1].event.subject, '/users/loggedIn'); + assert.equal(observedItems[1].event.type, events.loggedIn.apfelFred.type); + assert.equal(observedItems[1].event.data, events.loggedIn.apfelFred.data); }); test('observes events starting from the lower bound ID.', async (): Promise => { @@ -290,49 +277,46 @@ suite('Client.observeEvents()', function () { const abortController = newAbortControllerWithDeadline(testDeadline); const observedItems: StoreItem[] = []; - await assert - .that(async () => { - let didPushIntermediateEvent = false; - const result = client.observeEvents(abortController, '/users', { - recursive: true, - lowerBoundId: '2', - }); - - for await (const item of result) { - observedItems.push(item); - - if (!didPushIntermediateEvent) { - await client.writeEvents([ - source.newEvent( - '/users/loggedIn', - events.loggedIn.apfelFred.type, - events.loggedIn.apfelFred.data, - ), - ]); - - didPushIntermediateEvent = true; - } + // Should not throw. + let didPushIntermediateEvent = false; + const result = client.observeEvents(abortController, '/users', { + recursive: true, + lowerBoundId: '2', + }); - if (observedItems.length === 3) { - return; - } - } - }) - .is.not.throwingAsync(); - - assert.that(observedItems.length).is.equalTo(3); - assert.that(observedItems[0].event.source).is.equalTo(testSource); - assert.that(observedItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(observedItems[0].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(observedItems[0].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(observedItems[1].event.source).is.equalTo(testSource); - assert.that(observedItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[1].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(observedItems[1].event.data).is.equalTo(events.loggedIn.johnDoe.data); - assert.that(observedItems[2].event.source).is.equalTo(testSource); - assert.that(observedItems[2].event.subject).is.equalTo('/users/loggedIn'); - assert.that(observedItems[2].event.type).is.equalTo(events.loggedIn.apfelFred.type); - assert.that(observedItems[2].event.data).is.equalTo(events.loggedIn.apfelFred.data); + for await (const item of result) { + observedItems.push(item); + + if (!didPushIntermediateEvent) { + await client.writeEvents([ + source.newEvent( + '/users/loggedIn', + events.loggedIn.apfelFred.type, + events.loggedIn.apfelFred.data, + ), + ]); + + didPushIntermediateEvent = true; + } + + if (observedItems.length === 3) { + return; + } + } + + assert.equal(observedItems.length, 3); + assert.equal(observedItems[0].event.source, testSource); + assert.equal(observedItems[0].event.subject, '/users/registered'); + assert.equal(observedItems[0].event.type, events.registered.johnDoe.type); + assert.equal(observedItems[0].event.data, events.registered.johnDoe.data); + assert.equal(observedItems[1].event.source, testSource); + assert.equal(observedItems[1].event.subject, '/users/loggedIn'); + assert.equal(observedItems[1].event.type, events.loggedIn.johnDoe.type); + assert.equal(observedItems[1].event.data, events.loggedIn.johnDoe.data); + assert.equal(observedItems[2].event.source, testSource); + assert.equal(observedItems[2].event.subject, '/users/loggedIn'); + assert.equal(observedItems[2].event.type, events.loggedIn.apfelFred.type); + assert.equal(observedItems[2].event.data, events.loggedIn.apfelFred.data); }); test('throws an error when the AbortController is aborted.', async (): Promise => { @@ -341,13 +325,17 @@ suite('Client.observeEvents()', function () { recursive: true, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { abortController.abort(); } - }) - .is.throwingAsync((error): boolean => error instanceof CancelationError); + }, + error => { + assert.ok(error instanceof CancelationError); + return true; + }, + ); }); test('throws an error if mutually exclusive options are used.', async (): Promise => { @@ -365,18 +353,21 @@ suite('Client.observeEvents()', function () { }, ); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _ of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.", + ); + return true; + }, + ); }); test('throws an error if the given lowerBoundId does not contain an integer', async () => { @@ -389,18 +380,21 @@ suite('Client.observeEvents()', function () { }, ); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if the given lowerBoundId does not contain a negative integer', async () => { @@ -413,18 +407,21 @@ suite('Client.observeEvents()', function () { }, ); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ObserveEventsOptions are invalid: lowerBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if an incorrect subject is used in fromLatestEvent.', async () => { @@ -441,18 +438,21 @@ suite('Client.observeEvents()', function () { }, ); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ObserveEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate subject: 'this is wrong' must be an absolute, slash-separated path.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ObserveEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate subject: 'this is wrong' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('throws an error if an incorrect type is used in fromLatestEvent.', async () => { @@ -469,25 +469,28 @@ suite('Client.observeEvents()', function () { }, ); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ObserveEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate type: 'this is wrong' must be a reverse domain name.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ObserveEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate type: 'this is wrong' must be a reverse domain name.", + ); + return true; + }, + ); }); suite('using a mock server', () => { - let stopServer: () => void; + let stopServer: () => Promise; - teardown(async () => { - stopServer(); + afterEach(async () => { + await stopServer(); }); test('throws a server error if the server responds with http 5xx on every try.', async () => { @@ -508,20 +511,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed operation with 2 errors:\n' + - "Error: Server error occurred: Request failed with status code '502'.\n" + - "Error: Server error occurred: Request failed with status code '502'.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Request failed with status code '502'.", + ); + return true; + }, + ); }); test("throws an error if the server's protocol version does not match.", async (): Promise => { @@ -543,18 +547,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === - "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", + ); + return true; + }, + ); }); test('throws a client error if the server returns a 4xx status code.', async (): Promise => { @@ -575,17 +582,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '418'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '418'.", + ); + return true; + }, + ); }); test('returns a server error if the server returns a non 200, 5xx or 4xx status code.', async (): Promise => { @@ -606,17 +617,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Unexpected response status: 202 Accepted.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Unexpected response status: 202 Accepted.', + ); + return true; + }, + ); }); test("throws a server error if the server sends a stream item that can't be unmarshalled.", async (): Promise => { @@ -636,17 +651,18 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Failed to read response.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: Failed to read response.'); + return true; + }, + ); }); test('throws a server error if the server sends a stream item with unsupported type.', async (): Promise => { @@ -666,18 +682,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to observe events, an unexpected stream item was received: \'{"type":":clown:"}\'.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Failed to observe events, an unexpected stream item was received: \'{"type":":clown:"}\'.', + ); + return true; + }, + ); }); test('throws a server error if the server sends a an error item through the ndjson stream.', async (): Promise => { @@ -697,17 +716,18 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: not enough JUICE 😩.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: not enough JUICE 😩.'); + return true; + }, + ); }); test("throws a server error if the server sends a an error item through the ndjson stream, but the error can't be unmarshalled.", async (): Promise => { @@ -727,18 +747,21 @@ suite('Client.observeEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to observe events, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', - ); + }, + error => { + assert(error instanceof ServerError); + assert( + error.message, + 'Server error occurred: Failed to observe events, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', + ); + return true; + }, + ); }); }); }); diff --git a/test/integration/pingTests.ts b/test/integration/pingTests.ts index 2e6add3..e12fbc7 100644 --- a/test/integration/pingTests.ts +++ b/test/integration/pingTests.ts @@ -1,58 +1,56 @@ -import { assert } from 'assertthat'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; import { StatusCodes } from 'http-status-codes'; -import { Client } from '../../lib'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { startDatabase } from '../shared/startDatabase'; -import { startLocalHttpServer } from '../shared/startLocalHttpServer'; -import { stopDatabase } from '../shared/stopDatabase'; +import type { Client } from '../../lib/index.js'; +import { ServerError } from '../../lib/util/error/ServerError.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { startLocalHttpServer } from '../shared/startLocalHttpServer.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; -suite('Client.ping()', function () { - this.timeout(20_000); +suite('ping', { timeout: 20_000 }, () => { let database: Database; - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('throws an error if the server is not reachable.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.ping(); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: No response received.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: No response received.'); + return true; + }, + ); }); test('does not throw an error if EventSourcingDB is reachable.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.ping(); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.ping(); }); suite('with a mock server', () => { - let stopServer: () => void; + let stopServer: () => Promise; - teardown(async () => { - stopServer(); + afterEach(async () => { + await stopServer(); }); test('throws an error if the server responds with an unexpected status code.', async (): Promise => { @@ -64,18 +62,19 @@ suite('Client.ping()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.ping(); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed operation with 2 errors:\n' + - "Error: Server error occurred: Request failed with status code '502'.\n" + - "Error: Server error occurred: Request failed with status code '502'.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Request failed with status code '502'.", + ); + return true; + }, + ); }); test("throws an error if the server's response body is not 'OK'.", async (): Promise => { @@ -87,15 +86,16 @@ suite('Client.ping()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.ping(); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Received unexpected response.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: Received unexpected response.'); + return true; + }, + ); }); }); }); diff --git a/test/integration/readEventTypesTests.ts b/test/integration/readEventTypesTests.ts index 6fe3454..ca74b14 100644 --- a/test/integration/readEventTypesTests.ts +++ b/test/integration/readEventTypesTests.ts @@ -1,26 +1,26 @@ -import { assert } from 'assertthat'; -import { EventCandidate } from '../../lib'; -import { EventType } from '../../lib/handlers/readEventTypes/EventType'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { testSource } from '../shared/events/source'; -import { startDatabase } from '../shared/startDatabase'; -import { stopDatabase } from '../shared/stopDatabase'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; +import type { EventType } from '../../lib/handlers/readEventTypes/EventType.js'; +import { EventCandidate } from '../../lib/index.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { testSource } from '../shared/events/source.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; -suite('Client.readEventTypes()', function () { - this.timeout(20_000); +suite('readEventTypes', { timeout: 20_000 }, () => { let database: Database; - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('Reads all event types of existing events, as well as all event types with registered schemas.', async () => { @@ -38,15 +38,15 @@ suite('Client.readEventTypes()', function () { const expectedEventTypes: EventType[] = [ { - eventType: 'com.foo.bar', + eventType: 'com.bar.baz', isPhantom: false, }, { - eventType: 'com.bar.baz', + eventType: 'com.baz.leml', isPhantom: false, }, { - eventType: 'com.baz.leml', + eventType: 'com.foo.bar', isPhantom: false, }, { @@ -70,7 +70,7 @@ suite('Client.readEventTypes()', function () { observedEventTypes.push(observedEventType); } - assert.that(observedEventTypes).is.containingAllOf(expectedEventTypes); - assert.that(observedEventTypes.length).is.equalTo(expectedEventTypes.length); + assert.deepEqual(observedEventTypes, expectedEventTypes); + assert.equal(observedEventTypes.length, expectedEventTypes.length); }); }); diff --git a/test/integration/readEventsTests.ts b/test/integration/readEventsTests.ts index a41ecfb..a234b02 100644 --- a/test/integration/readEventsTests.ts +++ b/test/integration/readEventsTests.ts @@ -1,29 +1,28 @@ -import { assert } from 'assertthat'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; import { ReasonPhrases, StatusCodes } from 'http-status-codes'; -import { Client, StoreItem } from '../../lib'; -import { Source } from '../../lib'; -import { CancelationError } from '../../lib'; -import { ClientError } from '../../lib/util/error/ClientError'; -import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { events } from '../shared/events/events'; -import { testSource } from '../shared/events/source'; -import { startDatabase } from '../shared/startDatabase'; -import { startLocalHttpServer } from '../shared/startLocalHttpServer'; -import { stopDatabase } from '../shared/stopDatabase'; - -suite('Client.readEvents()', function () { - this.timeout(20_000); +import type { Client, StoreItem } from '../../lib/index.js'; +import { CancelationError, Source } from '../../lib/index.js'; +import { ClientError } from '../../lib/util/error/ClientError.js'; +import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError.js'; +import { ServerError } from '../../lib/util/error/ServerError.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { events } from '../shared/events/events.js'; +import { testSource } from '../shared/events/source.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { startLocalHttpServer } from '../shared/startLocalHttpServer.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; + +suite('readEvents', { timeout: 20_000 }, () => { let database: Database; const source = new Source(testSource); - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); await database.withAuthorization.client.writeEvents([ @@ -54,36 +53,31 @@ suite('Client.readEvents()', function () { ]); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('throws an error when trying to read from a non-reachable server.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { - const result = client.readEvents(new AbortController(), '/', { recursive: false }); + await assert.rejects(async () => { + const result = client.readEvents(new AbortController(), '/', { recursive: false }); - for await (const _ of result) { - // Do nothing. - } - }) - .is.throwingAsync(); + for await (const _ of result) { + // Do nothing. + } + }); }); test('supports authorization.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - const result = client.readEvents(new AbortController(), '/', { recursive: false }); + // Should not throw. + const result = client.readEvents(new AbortController(), '/', { recursive: false }); - for await (const _ of result) { - // Do nothing. - } - }) - .is.not.throwingAsync(); + for await (const _ of result) { + // Do nothing. + } }); test('reads events from a single subject.', async (): Promise => { @@ -98,17 +92,19 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(2); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[0].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.registered.janeDoe.data); - assert.that(readItems[0].event.traceParent).is.equalTo(events.registered.janeDoe.traceParent); - assert.that(readItems[1].event.source).is.equalTo(testSource); - assert.that(readItems[1].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[1].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(readItems[1].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(readItems[1].event.traceParent).is.equalTo(events.registered.johnDoe.traceParent); + assert.equal(readItems.length, 2); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/registered'); + assert.equal(readItems[0].event.type, events.registered.janeDoe.type); + assert.deepEqual(readItems[0].event.data, events.registered.janeDoe.data); + assert.equal(readItems[0].event.traceParent, events.registered.janeDoe.traceParent); + + assert.equal(readItems[1].event.source, testSource); + assert.equal(readItems[1].event.subject, '/users/registered'); + assert.equal(readItems[1].event.type, events.registered.johnDoe.type); + assert.deepEqual(readItems[1].event.data, events.registered.johnDoe.data); + assert.equal(readItems[1].event.traceParent, events.registered.johnDoe.traceParent); }); test('reads events from a subject including child subjects.', async (): Promise => { @@ -121,23 +117,27 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(4); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[0].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.registered.janeDoe.data); - assert.that(readItems[1].event.source).is.equalTo(testSource); - assert.that(readItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(readItems[1].event.type).is.equalTo(events.loggedIn.janeDoe.type); - assert.that(readItems[1].event.data).is.equalTo(events.loggedIn.janeDoe.data); - assert.that(readItems[2].event.source).is.equalTo(testSource); - assert.that(readItems[2].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[2].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(readItems[2].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(readItems[3].event.source).is.equalTo(testSource); - assert.that(readItems[3].event.subject).is.equalTo('/users/loggedIn'); - assert.that(readItems[3].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(readItems[3].event.data).is.equalTo(events.loggedIn.johnDoe.data); + assert.equal(readItems.length, 4); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/registered'); + assert.equal(readItems[0].event.type, events.registered.janeDoe.type); + assert.deepEqual(readItems[0].event.data, events.registered.janeDoe.data); + + assert.equal(readItems[1].event.source, testSource); + assert.equal(readItems[1].event.subject, '/users/loggedIn'); + assert.equal(readItems[1].event.type, events.loggedIn.janeDoe.type); + assert.deepEqual(readItems[1].event.data, events.loggedIn.janeDoe.data); + + assert.equal(readItems[2].event.source, testSource); + assert.equal(readItems[2].event.subject, '/users/registered'); + assert.equal(readItems[2].event.type, events.registered.johnDoe.type); + assert.deepEqual(readItems[2].event.data, events.registered.johnDoe.data); + + assert.equal(readItems[3].event.source, testSource); + assert.equal(readItems[3].event.subject, '/users/loggedIn'); + assert.equal(readItems[3].event.type, events.loggedIn.johnDoe.type); + assert.deepEqual(readItems[3].event.data, events.loggedIn.johnDoe.data); }); test('reads the events in antichronological order.', async (): Promise => { @@ -152,15 +152,17 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(2); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[0].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(readItems[1].event.source).is.equalTo(testSource); - assert.that(readItems[1].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[1].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(readItems[1].event.data).is.equalTo(events.registered.janeDoe.data); + assert.equal(readItems.length, 2); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/registered'); + assert.equal(readItems[0].event.type, events.registered.johnDoe.type); + assert.deepEqual(readItems[0].event.data, events.registered.johnDoe.data); + + assert.equal(readItems[1].event.source, testSource); + assert.equal(readItems[1].event.subject, '/users/registered'); + assert.equal(readItems[1].event.type, events.registered.janeDoe.type); + assert.deepEqual(readItems[1].event.data, events.registered.janeDoe.data); }); test('reads events starting from the latest event matching the given event name.', async (): Promise => { @@ -178,11 +180,12 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(1); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/loggedIn'); - assert.that(readItems[0].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.loggedIn.johnDoe.data); + assert.equal(readItems.length, 1); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/loggedIn'); + assert.equal(readItems[0].event.type, events.loggedIn.johnDoe.type); + assert.deepEqual(readItems[0].event.data, events.loggedIn.johnDoe.data); }); test('reads events starting from the lower bound ID.', async (): Promise => { @@ -196,15 +199,17 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(2); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[0].event.type).is.equalTo(events.registered.johnDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.registered.johnDoe.data); - assert.that(readItems[1].event.source).is.equalTo(testSource); - assert.that(readItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(readItems[1].event.type).is.equalTo(events.loggedIn.johnDoe.type); - assert.that(readItems[1].event.data).is.equalTo(events.loggedIn.johnDoe.data); + assert.equal(readItems.length, 2); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/registered'); + assert.equal(readItems[0].event.type, events.registered.johnDoe.type); + assert.deepEqual(readItems[0].event.data, events.registered.johnDoe.data); + + assert.equal(readItems[1].event.source, testSource); + assert.equal(readItems[1].event.subject, '/users/loggedIn'); + assert.equal(readItems[1].event.type, events.loggedIn.johnDoe.type); + assert.deepEqual(readItems[1].event.data, events.loggedIn.johnDoe.data); }); test('reads events up to the upper bound ID.', async (): Promise => { @@ -218,15 +223,17 @@ suite('Client.readEvents()', function () { readItems.push(item); } - assert.that(readItems.length).is.equalTo(2); - assert.that(readItems[0].event.source).is.equalTo(testSource); - assert.that(readItems[0].event.subject).is.equalTo('/users/registered'); - assert.that(readItems[0].event.type).is.equalTo(events.registered.janeDoe.type); - assert.that(readItems[0].event.data).is.equalTo(events.registered.janeDoe.data); - assert.that(readItems[1].event.source).is.equalTo(testSource); - assert.that(readItems[1].event.subject).is.equalTo('/users/loggedIn'); - assert.that(readItems[1].event.type).is.equalTo(events.loggedIn.janeDoe.type); - assert.that(readItems[1].event.data).is.equalTo(events.loggedIn.janeDoe.data); + assert.equal(readItems.length, 2); + + assert.equal(readItems[0].event.source, testSource); + assert.equal(readItems[0].event.subject, '/users/registered'); + assert.equal(readItems[0].event.type, events.registered.janeDoe.type); + assert.deepEqual(readItems[0].event.data, events.registered.janeDoe.data); + + assert.equal(readItems[1].event.source, testSource); + assert.equal(readItems[1].event.subject, '/users/loggedIn'); + assert.equal(readItems[1].event.type, events.loggedIn.janeDoe.type); + assert.deepEqual(readItems[1].event.data, events.loggedIn.janeDoe.data); }); test('throws an error when the AbortController is aborted.', async (): Promise => { @@ -235,13 +242,17 @@ suite('Client.readEvents()', function () { recursive: true, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { abortController.abort(); } - }) - .is.throwingAsync((error): boolean => error instanceof CancelationError); + }, + error => { + assert.ok(error instanceof CancelationError); + return true; + }, + ); }); test('throws an error if mutually exclusive options are used.', async (): Promise => { @@ -255,15 +266,17 @@ suite('Client.readEvents()', function () { lowerBoundId: '2', }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.", - ); + }, + { + message: + "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.", + }, + ); }); test('throws an error if the subject is invalid.', async (): Promise => { @@ -271,18 +284,21 @@ suite('Client.readEvents()', function () { recursive: true, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'subject' is invalid: Failed to validate subject: 'invalid' must be an absolute, slash-separated path.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'subject' is invalid: Failed to validate subject: 'invalid' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('throws an error if the given lowerBoundID does not contain an integer.', async (): Promise => { @@ -291,18 +307,21 @@ suite('Client.readEvents()', function () { lowerBoundId: 'invalid', }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if the given lowerBoundID contains an integer that is negative.', async (): Promise => { @@ -311,18 +330,21 @@ suite('Client.readEvents()', function () { lowerBoundId: '-1', }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: lowerBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if the given upperBoundID does not contain an integer.', async (): Promise => { @@ -331,18 +353,21 @@ suite('Client.readEvents()', function () { upperBoundId: 'invalid', }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: upperBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: upperBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if the given upperBoundID contains an integer that is negative.', async (): Promise => { @@ -351,18 +376,21 @@ suite('Client.readEvents()', function () { upperBoundId: '-1', }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: upperBoundId must be 0 or greater.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: upperBoundId must be 0 or greater.", + ); + return true; + }, + ); }); test('throws an error if an incorrect subject is used in ReadFromLatestEvent.', async (): Promise => { @@ -375,18 +403,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate subject: 'invalid' must be an absolute, slash-separated path.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate subject: 'invalid' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('throws an error if an incorrect type is used in ReadFromLatestEvent.', async (): Promise => { @@ -399,25 +430,28 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'options' is invalid: ReadEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate type: 'invalid' must be a reverse domain name.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'options' is invalid: ReadEventsOptions are invalid: Failed to validate 'fromLatestEvent': Failed to validate type: 'invalid' must be a reverse domain name.", + ); + return true; + }, + ); }); suite('using a mock server', () => { - let stopServer: () => void; + let stopServer: () => Promise; - teardown(async () => { - stopServer(); + afterEach(async () => { + await stopServer(); }); test('throws a sever error if the server responds with HTTP 5xx on every try.', async (): Promise => { @@ -438,20 +472,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed operation with 2 errors:\n' + - "Error: Server error occurred: Request failed with status code '502'.\n" + - "Error: Server error occurred: Request failed with status code '502'.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Request failed with status code '502'.", + ); + return true; + }, + ); }); test("throws an error if the server's protocol version does not match.", async (): Promise => { @@ -473,18 +508,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === - "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", + ); + return true; + }, + ); }); test('throws a client error if the server returns a 4xx status code.', async (): Promise => { @@ -505,17 +543,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '418'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '418'.", + ); + return true; + }, + ); }); test('throws a server error if the server returns a non 200, 5xx or 4xx status code.', async (): Promise => { @@ -536,17 +578,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Unexpected response status: 202 Accepted.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Unexpected response status: 202 Accepted.', + ); + return true; + }, + ); }); test("throws a server error if the server sends a stream item that can't be unmarshalled.", async (): Promise => { @@ -566,17 +612,18 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Failed to read response.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: Failed to read response.'); + return true; + }, + ); }); test('throws a server error if the server sends a stream item with unsupported type.', async (): Promise => { @@ -596,18 +643,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to read events, an unexpected stream item was received: \'{"type":":clown:"}\'.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Failed to read events, an unexpected stream item was received: \'{"type":":clown:"}\'.', + ); + return true; + }, + ); }); test('throws a server error if the server sends a an error item through the ndjson stream.', async (): Promise => { @@ -627,17 +677,18 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: not enough JUICE 😩.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: not enough JUICE 😩.'); + return true; + }, + ); }); test("throws a server error if the server sends a an error item through the ndjson stream, but the error can't be unmarshalled.", async (): Promise => { @@ -657,18 +708,21 @@ suite('Client.readEvents()', function () { }, }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to read events, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Failed to read events, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', + ); + return true; + }, + ); }); }); }); diff --git a/test/integration/readSubjectsTests.ts b/test/integration/readSubjectsTests.ts index 441004a..1a6be99 100644 --- a/test/integration/readSubjectsTests.ts +++ b/test/integration/readSubjectsTests.ts @@ -1,59 +1,53 @@ -import { assert } from 'assertthat'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; import { ReasonPhrases, StatusCodes } from 'http-status-codes'; -import { Client, EventCandidate } from '../../lib'; -import { CancelationError } from '../../lib'; -import { ClientError } from '../../lib/util/error/ClientError'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { events } from '../shared/events/events'; -import { testSource } from '../shared/events/source'; -import { startDatabase } from '../shared/startDatabase'; -import { startLocalHttpServer } from '../shared/startLocalHttpServer'; -import { stopDatabase } from '../shared/stopDatabase'; - -suite('Client.readSubjects()', function () { - this.timeout(20_000); +import type { Client } from '../../lib/index.js'; +import { CancelationError, EventCandidate } from '../../lib/index.js'; +import { ClientError } from '../../lib/util/error/ClientError.js'; +import { ServerError } from '../../lib/util/error/ServerError.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { events } from '../shared/events/events.js'; +import { testSource } from '../shared/events/source.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { startLocalHttpServer } from '../shared/startLocalHttpServer.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; + +suite('readSubjects', { timeout: 20_000 }, () => { let database: Database; - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('throws an error when trying to read from a non-reachable server.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { - const readSubjectsResult = client.readSubjects(new AbortController(), { baseSubject: '/' }); + await assert.rejects(async () => { + const readSubjectsResult = client.readSubjects(new AbortController(), { baseSubject: '/' }); - for await (const _ of readSubjectsResult) { - // Intentionally left blank. - } - }) - .is.throwingAsync(); + for await (const _ of readSubjectsResult) { + // Intentionally left blank. + } + }); }); test('supports authorization.', async (): Promise => { const client = database.withAuthorization.client; + // Should not throw. + const readSubjectsResult = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { - const readSubjectsResult = client.readSubjects(new AbortController(), { baseSubject: '/' }); - - for await (const _ of readSubjectsResult) { - // Intentionally left blank. - } - }) - .is.not.throwingAsync(); + for await (const _ of readSubjectsResult) { + // Intentionally left blank. + } }); test('reads all subjects starting from /.', async (): Promise => { @@ -71,7 +65,7 @@ suite('Client.readSubjects()', function () { actualSubjects.push(subject); } - assert.that(actualSubjects).is.equalTo(['/', '/foo']); + assert.deepEqual(actualSubjects, ['/', '/foo']); }); test('reads all subjects starting from the given base subject.', async (): Promise => { @@ -89,7 +83,7 @@ suite('Client.readSubjects()', function () { actualSubjects.push(subject); } - assert.that(actualSubjects).is.equalTo(['/foo', '/foo/bar']); + assert.deepEqual(actualSubjects, ['/foo', '/foo/bar']); }); test('throws an error when the AbortController is aborted.', async (): Promise => { @@ -97,39 +91,45 @@ suite('Client.readSubjects()', function () { const abortController = new AbortController(); - await assert - .that(async () => { + await assert.rejects( + async () => { const readSubjectsResult = client.readSubjects(abortController, { baseSubject: '/' }); abortController.abort(); for await (const _ of readSubjectsResult) { // Intentionally left blank. } - }) - .is.throwingAsync((error): boolean => error instanceof CancelationError); + }, + error => { + assert.ok(error instanceof CancelationError); + return true; + }, + ); }); test('throws an error if the base subject is malformed.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { + await assert.rejects( + async () => { const readSubjectsResult = client.readSubjects(new AbortController(), { baseSubject: '' }); for await (const _ of readSubjectsResult) { // Intentionally left blank. } - }) - .is.throwingAsync( - "Parameter 'options' is invalid: Failed to validate subject: '' must be an absolute, slash-separated path.", - ); + }, + { + message: + "Parameter 'options' is invalid: Failed to validate subject: '' must be an absolute, slash-separated path.", + }, + ); }); suite('with a mock server', () => { - let stopServer: () => void; + let stopServer: () => Promise; - teardown(async () => { - stopServer(); + afterEach(async () => { + await stopServer(); }); test('throws a server error if the server responds with http 5xx on every try.', async () => { @@ -143,20 +143,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed operation with 2 errors:\n' + - "Error: Server error occurred: Request failed with status code '502'.\n" + - "Error: Server error occurred: Request failed with status code '502'.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Request failed with status code '502'.", + ); + return true; + }, + ); }); test("throws an error if the server's protocol version does not match.", async (): Promise => { @@ -171,18 +172,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === - "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", + ); + return true; + }, + ); }); test('throws a client error if the server returns a 4xx status code.', async (): Promise => { @@ -196,17 +200,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '418'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '418'.", + ); + return true; + }, + ); }); test('returns a server error if the server returns a non 200, 5xx or 4xx status code.', async (): Promise => { @@ -220,17 +228,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Unexpected response status: 202 Accepted.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Unexpected response status: 202 Accepted.', + ); + return true; + }, + ); }); test("throws a server error if the server sends a stream item that can't be unmarshalled.", async (): Promise => { @@ -243,17 +255,18 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Failed to read response.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: Failed to read response.'); + return true; + }, + ); }); test('throws a server error if the server sends a stream item with unsupported type.', async (): Promise => { @@ -266,18 +279,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to read subjects, an unexpected stream item was received: \'{"type":":clown:"}\'.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Failed to read subjects, an unexpected stream item was received: \'{"type":":clown:"}\'.', + ); + return true; + }, + ); }); test('throws a server error if the server sends a an error item through the ndjson stream.', async (): Promise => { @@ -290,17 +306,18 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: not enough JUICE 😩.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: not enough JUICE 😩.'); + return true; + }, + ); }); test("throws a server error if the server sends a an error item through the ndjson stream, but the error can't be unmarshalled.", async (): Promise => { @@ -313,18 +330,21 @@ suite('Client.readSubjects()', function () { const result = client.readSubjects(new AbortController(), { baseSubject: '/' }); - await assert - .that(async () => { + await assert.rejects( + async () => { for await (const _item of result) { // Intentionally left blank. } - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed to read subjects, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Failed to read subjects, an unexpected stream item was received: \'{"type":"error","payload":42}\'.', + ); + return true; + }, + ); }); }); }); diff --git a/test/integration/registerEventSchemaTests.ts b/test/integration/registerEventSchemaTests.ts index ec8ae13..a527146 100644 --- a/test/integration/registerEventSchemaTests.ts +++ b/test/integration/registerEventSchemaTests.ts @@ -1,28 +1,28 @@ -import { assert } from 'assertthat'; -import { EventCandidate } from '../../lib'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { testSource } from '../shared/events/source'; -import { startDatabase } from '../shared/startDatabase'; -import { stopDatabase } from '../shared/stopDatabase'; - -suite('Client.registerEventSchema()', function () { - this.timeout(20_000); +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; +import { EventCandidate } from '../../lib/index.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { testSource } from '../shared/events/source.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; + +suite('registerEventSchema', { timeout: 20_000 }, () => { let database: Database; - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); - test("Registers the new schema if it doesn't conflict with existing events.", async (): Promise => { + test("registers the new schema if it doesn't conflict with existing events.", async (): Promise => { const client = database.withAuthorization.client; await client.writeEvents([new EventCandidate(testSource, '/eppes', 'com.ekht.ekht', {})]); @@ -30,64 +30,54 @@ suite('Client.registerEventSchema()', function () { await client.registerEventSchema('com.ekht.ekht', { type: 'object' }); }); - test('Rejects the request if at least one of the existing events conflicts with the schema.', async (): Promise => { + test('rejects the request if at least one of the existing events conflicts with the schema.', async (): Promise => { const client = database.withAuthorization.client; await client.writeEvents([ new EventCandidate(testSource, '/eppes', 'com.gornisht.ekht', { oy: 'gevalt' }), ]); - await assert - .that(async () => { - await client.registerEventSchema('com.gornisht.ekht', { - type: 'object', - additionalProperties: false, - }); - }) - .is.throwingAsync(); + await assert.rejects(async () => { + await client.registerEventSchema('com.gornisht.ekht', { + type: 'object', + additionalProperties: false, + }); + }); }); - test('Rejects the request if the schema already exists.', async (): Promise => { + test('rejects the request if the schema already exists.', async (): Promise => { const client = database.withAuthorization.client; await client.writeEvents([new EventCandidate(testSource, '/eppes', 'com.ekht.ekht', {})]); await client.registerEventSchema('com.ekht.ekht', { type: 'object' }); - await assert - .that(async () => { - await client.registerEventSchema('com.ekht.ekht', { type: 'object' }); - }) - .is.throwingAsync(); + await assert.rejects(async () => { + await client.registerEventSchema('com.ekht.ekht', { type: 'object' }); + }); }); - test('Rejects the request if the given schema is not valid JSON.', async (): Promise => { + test('rejects the request if the given schema is not valid JSON.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.registerEventSchema('com.ekht.ekht', '{"type":'); - }) - .is.throwingAsync(); + await assert.rejects(async () => { + await client.registerEventSchema('com.ekht.ekht', '{"type":'); + }); }); - test('Rejects the request if the given schema is not valid JSONSchema.', async (): Promise => { + test('rejects the request if the given schema is not valid JSONSchema.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.registerEventSchema('com.ekht.ekht', { type: 'object', properties: 'banana' }); - }) - .is.throwingAsync(); + await assert.rejects(async () => { + await client.registerEventSchema('com.ekht.ekht', { type: 'object', properties: 'banana' }); + }); }); - test('Rejects the request if the given schema does not specify an object at the top level.', async (): Promise => { + test('rejects the request if the given schema does not specify an object at the top level.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.registerEventSchema('com.ekht.ekht', { type: 'array' }); - }) - .is.throwingAsync(); + await assert.rejects(async () => { + await client.registerEventSchema('com.ekht.ekht', { type: 'array' }); + }); }); }); diff --git a/test/integration/writeEventsTests.ts b/test/integration/writeEventsTests.ts index 65e12bd..0466f6b 100644 --- a/test/integration/writeEventsTests.ts +++ b/test/integration/writeEventsTests.ts @@ -1,40 +1,41 @@ -import { assert } from 'assertthat'; +import assert from 'node:assert/strict'; +import { afterEach, before, beforeEach, suite, test } from 'node:test'; import { ReasonPhrases, StatusCodes } from 'http-status-codes'; -import { Client, Source, StoreItem, isSubjectOnEventId, isSubjectPristine } from '../../lib'; -import { ClientError } from '../../lib/util/error/ClientError'; -import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { Database } from '../shared/Database'; -import { buildDatabase } from '../shared/buildDatabase'; -import { events } from '../shared/events/events'; -import { testSource } from '../shared/events/source'; -import { prefixEventType } from '../shared/events/type'; -import { startDatabase } from '../shared/startDatabase'; -import { startLocalHttpServer } from '../shared/startLocalHttpServer'; -import { stopDatabase } from '../shared/stopDatabase'; - -suite('Client.writeEvents()', function () { - this.timeout(20_000); +import { Source, isSubjectOnEventId, isSubjectPristine } from '../../lib/index.js'; +import type { Client, StoreItem } from '../../lib/index.js'; +import { ClientError } from '../../lib/util/error/ClientError.js'; +import { InvalidParameterError } from '../../lib/util/error/InvalidParameterError.js'; +import { ServerError } from '../../lib/util/error/ServerError.js'; +import type { Database } from '../shared/Database.js'; +import { buildDatabase } from '../shared/buildDatabase.js'; +import { events } from '../shared/events/events.js'; +import { testSource } from '../shared/events/source.js'; +import { prefixEventType } from '../shared/events/type.js'; +import { startDatabase } from '../shared/startDatabase.js'; +import { startLocalHttpServer } from '../shared/startLocalHttpServer.js'; +import { stopDatabase } from '../shared/stopDatabase.js'; + +suite('writeEvents', { timeout: 20_000 }, () => { let database: Database; const source = new Source(testSource); - suiteSetup(async () => { + before(() => { buildDatabase('test/shared/docker/eventsourcingdb'); }); - setup(async () => { + beforeEach(async () => { database = await startDatabase(); }); - teardown(async () => { - await stopDatabase(database); + afterEach(() => { + stopDatabase(database); }); test('throws an error when trying to write to a non-reachable server.', async (): Promise => { const client = database.withInvalidUrl.client; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents([ source.newEvent( '/foobar', @@ -42,114 +43,109 @@ suite('Client.writeEvents()', function () { events.registered.janeDoe.data, ), ]); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: No response received.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server error occurred: No response received.'); + return true; + }, + ); }); test('throws an error if no candidates are passed.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents([]); - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'eventCandidates' is invalid: eventCandidates must contain at least one EventCandidate.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'eventCandidates' is invalid: eventCandidates must contain at least one EventCandidate.", + ); + return true; + }, + ); }); test('throws an error if a candidate subject is malformed.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents([ source.newEvent('foobar', events.registered.janeDoe.type, events.registered.janeDoe.data), ]); - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'eventCandidates' is invalid: Failed to validate subject: 'foobar' must be an absolute, slash-separated path.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'eventCandidates' is invalid: Failed to validate subject: 'foobar' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('throws an error if a candidate type is malformed.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents([ source.newEvent('/foobar', 'haram', events.registered.janeDoe.data), ]); - }) - .is.throwingAsync( - error => - error instanceof InvalidParameterError && - error.message === - "Parameter 'eventCandidates' is invalid: Failed to validate type: 'haram' must be a reverse domain name.", - ); + }, + error => { + assert.ok(error instanceof InvalidParameterError); + assert.equal( + error.message, + "Parameter 'eventCandidates' is invalid: Failed to validate type: 'haram' must be a reverse domain name.", + ); + return true; + }, + ); }); - test.skip('throws an error if a precondition uses an invalid source.', async (): Promise => { - assert.that(true).is.false(); - }); + test('throws an error if a precondition uses an invalid source.'); test('supports authorization.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/foobar', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent('/foobar', events.registered.janeDoe.type, events.registered.janeDoe.data), + ]); }); test('writes a single event.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/foobar', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - '00-eb0e08452e7ee4b0d3b8b30987c37951-c31bc0a7013beab8-00', - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent( + '/foobar', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + '00-eb0e08452e7ee4b0d3b8b30987c37951-c31bc0a7013beab8-00', + ), + ]); }); test('returns the written event metadata.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + ]); const writtenEventsMetadata = await client.writeEvents([ source.newEvent( @@ -164,36 +160,33 @@ suite('Client.writeEvents()', function () { ), ]); - assert.that(writtenEventsMetadata.length).is.equalTo(2); - assert.that(writtenEventsMetadata[0].source).is.equalTo(testSource); - assert.that(writtenEventsMetadata[0].type).is.equalTo(prefixEventType('registered')); - assert.that(writtenEventsMetadata[0].subject).is.equalTo('/users/registered'); - assert.that(writtenEventsMetadata[0].id).is.equalTo('1'); - assert.that(writtenEventsMetadata[1].source).is.equalTo(testSource); - assert.that(writtenEventsMetadata[1].type).is.equalTo(prefixEventType('loggedIn')); - assert.that(writtenEventsMetadata[1].subject).is.equalTo('/users/loggedIn'); - assert.that(writtenEventsMetadata[1].id).is.equalTo('2'); + assert.equal(writtenEventsMetadata.length, 2); + assert.equal(writtenEventsMetadata[0].source, testSource); + assert.equal(writtenEventsMetadata[0].type, prefixEventType('registered')); + assert.equal(writtenEventsMetadata[0].subject, '/users/registered'); + assert.equal(writtenEventsMetadata[0].id, '1'); + assert.equal(writtenEventsMetadata[1].source, testSource); + assert.equal(writtenEventsMetadata[1].type, prefixEventType('loggedIn')); + assert.equal(writtenEventsMetadata[1].subject, '/users/loggedIn'); + assert.equal(writtenEventsMetadata[1].id, '2'); }); test('writes multiple events.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - source.newEvent( - '/users/registered', - events.registered.johnDoe.type, - events.registered.johnDoe.data, - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + source.newEvent( + '/users/registered', + events.registered.johnDoe.type, + events.registered.johnDoe.data, + ), + ]); }); test('throws an error if any of the given events does not validate against the schema.', async (): Promise => { @@ -204,55 +197,52 @@ suite('Client.writeEvents()', function () { additionalProperties: false, }); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents([ source.newEvent('/users/registered', 'com.knackige.wuerstchen', { oh: 'no' }), ]); - }) - .is.throwingAsync("Client error occurred: Request failed with status code '409'."); + }, + { + message: "Client error occurred: Request failed with status code '409'.", + }, + ); }); suite('when using the isSubjectPristine precondition', (): void => { test('writes the events if the subject is pristine.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents( - [ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - ], - [isSubjectPristine({ subject: '/users/registered' })], - ); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents( + [ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + ], + [isSubjectPristine({ subject: '/users/registered' })], + ); }); test('throws an error if the subject is not pristine.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents( - [ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - ], - [isSubjectPristine({ subject: '/users/registered' })], - ); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents( + [ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + ], + [isSubjectPristine({ subject: '/users/registered' })], + ); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents( [ source.newEvent( @@ -263,12 +253,16 @@ suite('Client.writeEvents()', function () { ], [isSubjectPristine({ subject: '/users/registered' })], ); - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '409'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '409'.", + ); + return true; + }, + ); }); }); @@ -276,22 +270,19 @@ suite('Client.writeEvents()', function () { test('writes the events if the last event of the subject has the given event ID.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - source.newEvent( - '/users/registered', - events.registered.johnDoe.type, - events.registered.johnDoe.data, - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + source.newEvent( + '/users/registered', + events.registered.johnDoe.type, + events.registered.johnDoe.data, + ), + ]); const readEventsResult = database.withAuthorization.client.readEvents( new AbortController(), @@ -305,46 +296,40 @@ suite('Client.writeEvents()', function () { } const lastEventId = readItems[readItems.length - 1].event.id; - await assert - .that(async () => { - await client.writeEvents( - [ - source.newEvent( - '/users/registered', - events.registered.apfelFred.type, - events.registered.apfelFred.data, - ), - ], - [isSubjectOnEventId({ subject: '/users/registered', eventId: lastEventId })], - ); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents( + [ + source.newEvent( + '/users/registered', + events.registered.apfelFred.type, + events.registered.apfelFred.data, + ), + ], + [isSubjectOnEventId({ subject: '/users/registered', eventId: lastEventId })], + ); }); test('throws an error if the last event of the subject does not have the given event ID.', async (): Promise => { const client = database.withAuthorization.client; - await assert - .that(async () => { - await client.writeEvents([ - source.newEvent( - '/users/registered', - events.registered.janeDoe.type, - events.registered.janeDoe.data, - ), - source.newEvent( - '/users/registered', - events.registered.johnDoe.type, - events.registered.johnDoe.data, - ), - ]); - }) - .is.not.throwingAsync(); + // Should not throw. + await client.writeEvents([ + source.newEvent( + '/users/registered', + events.registered.janeDoe.type, + events.registered.janeDoe.data, + ), + source.newEvent( + '/users/registered', + events.registered.johnDoe.type, + events.registered.johnDoe.data, + ), + ]); const lastEventId = '1337'; - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents( [ source.newEvent( @@ -355,20 +340,24 @@ suite('Client.writeEvents()', function () { ], [isSubjectOnEventId({ subject: '/users/registered', eventId: lastEventId })], ); - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '409'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '409'.", + ); + return true; + }, + ); }); }); suite('using mocked HTTP server', (): void => { - let stopServer: () => void; + let stopServer: () => Promise; - teardown(async () => { - stopServer(); + afterEach(async () => { + await stopServer(); }); const events = [source.newEvent('/', 'com.foobar.baz', {})]; @@ -382,18 +371,19 @@ suite('Client.writeEvents()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents(events); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - 'Server error occurred: Failed operation with 2 errors:\n' + - "Error: Server error occurred: Request failed with status code '502'.\n" + - "Error: Server error occurred: Request failed with status code '502'.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Request failed with status code '502'.", + ); + return true; + }, + ); }); test("throws an error if the server's protocol version does not match.", async () => { @@ -406,16 +396,19 @@ suite('Client.writeEvents()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents(events); - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === - "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Protocol version mismatch, server '0.0.0', client '1.0.0.'", + ); + return true; + }, + ); }); test('throws a client error if the server returns a 4xx status code.', async () => { @@ -427,15 +420,19 @@ suite('Client.writeEvents()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents(events); - }) - .is.throwingAsync( - error => - error instanceof ClientError && - error.message === "Client error occurred: Request failed with status code '418'.", - ); + }, + error => { + assert.ok(error instanceof ClientError); + assert.equal( + error.message, + "Client error occurred: Request failed with status code '418'.", + ); + return true; + }, + ); }); test('returns a server error if the server returns a non 200, 5xx or 4xx status code.', async () => { @@ -447,15 +444,19 @@ suite('Client.writeEvents()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents(events); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === 'Server error occurred: Unexpected response status: 202 Accepted.', - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + 'Server error occurred: Unexpected response status: 202 Accepted.', + ); + return true; + }, + ); }); test("throws a server error if the server's response can't be parsed.", async () => { @@ -466,16 +467,19 @@ suite('Client.writeEvents()', function () { }); })); - await assert - .that(async () => { + await assert.rejects( + async () => { await client.writeEvents(events); - }) - .is.throwingAsync( - error => - error instanceof ServerError && - error.message === - "Server error occurred: Failed to parse response 'utter garbage' to array.", - ); + }, + error => { + assert.ok(error instanceof ServerError); + assert.equal( + error.message, + "Server error occurred: Failed to parse response 'utter garbage' to array.", + ); + return true; + }, + ); }); }); }); diff --git a/test/shared/ContainerizedTestingDatabase.ts b/test/shared/ContainerizedTestingDatabase.ts index f9b74a2..8daab17 100644 --- a/test/shared/ContainerizedTestingDatabase.ts +++ b/test/shared/ContainerizedTestingDatabase.ts @@ -1,9 +1,8 @@ -import { Client } from '../../lib'; -import { ClientOptions } from '../../lib/ClientOptions'; -import { ServerError } from '../../lib/util/error/ServerError'; -import { done, retryWithBackoff } from '../../lib/util/retry/retryWithBackoff'; -import { Container } from './docker/Container'; -import { Image } from './docker/Image'; +import { setTimeout } from 'node:timers/promises'; +import type { ClientOptions } from '../../lib/ClientOptions.js'; +import { Client } from '../../lib/index.js'; +import type { Container } from './docker/Container.js'; +import type { Image } from './docker/Image.js'; class ContainerizedTestingDatabase { private readonly command: string; @@ -46,19 +45,15 @@ class ContainerizedTestingDatabase { const baseUrl = `http://127.0.0.1:${exposedPort}`; const client = new Client(baseUrl, options); - await retryWithBackoff(new AbortController(), 10, async () => { + for (let i = 0; i < 10; i++) { try { await client.ping(); - } catch (ex: unknown) { - if (ex instanceof ServerError) { - return { retry: ex }; - } - - throw ex; + break; + } catch { + // We intentionally ignore server error exceptions since this is expected to fail the first few times. } - - return done; - }); + await setTimeout(1_000); + } return { container, diff --git a/test/shared/Database.ts b/test/shared/Database.ts index bf83281..032ca3a 100644 --- a/test/shared/Database.ts +++ b/test/shared/Database.ts @@ -1,9 +1,9 @@ -import { ContainerizedTestingDatabase } from './ContainerizedTestingDatabase'; -import { TestingDatabase } from './TestingDatabase'; +import type { ContainerizedTestingDatabase } from './ContainerizedTestingDatabase.js'; +import type { TestingDatabase } from './TestingDatabase.js'; interface Database { withAuthorization: ContainerizedTestingDatabase; withInvalidUrl: TestingDatabase; } -export { Database }; +export type { Database }; diff --git a/test/shared/TestingDatabase.ts b/test/shared/TestingDatabase.ts index 7c9524f..59698ea 100644 --- a/test/shared/TestingDatabase.ts +++ b/test/shared/TestingDatabase.ts @@ -1,4 +1,4 @@ -import { Client } from '../../lib'; +import type { Client } from '../../lib/index.js'; class TestingDatabase { public readonly client: Client; diff --git a/test/shared/buildDatabase.ts b/test/shared/buildDatabase.ts index 8de9463..208e0a2 100644 --- a/test/shared/buildDatabase.ts +++ b/test/shared/buildDatabase.ts @@ -1,4 +1,4 @@ -import { Image } from './docker/Image'; +import { Image } from './docker/Image.js'; const buildDatabase = (dockerfileDirectory: string): void => { const image = new Image('eventsourcingdb', 'latest'); diff --git a/test/shared/docker/Image.ts b/test/shared/docker/Image.ts index de6aa73..44497ef 100644 --- a/test/shared/docker/Image.ts +++ b/test/shared/docker/Image.ts @@ -1,5 +1,5 @@ import { exec } from 'shelljs'; -import { Container } from './Container'; +import { Container } from './Container.js'; class Image { private readonly name: string; diff --git a/test/shared/events/events.ts b/test/shared/events/events.ts index f02ebf4..89ea95d 100644 --- a/test/shared/events/events.ts +++ b/test/shared/events/events.ts @@ -1,4 +1,4 @@ -import { prefixEventType } from './type'; +import { prefixEventType } from './type.js'; const registeredEventType = prefixEventType('registered'); const loggedInEventType = prefixEventType('loggedIn'); diff --git a/test/shared/startDatabase.ts b/test/shared/startDatabase.ts index 7f4d9a3..722996f 100644 --- a/test/shared/startDatabase.ts +++ b/test/shared/startDatabase.ts @@ -1,9 +1,9 @@ -import { randomUUID } from 'crypto'; -import { Client } from '../../lib'; -import { ContainerizedTestingDatabase } from './ContainerizedTestingDatabase'; -import { Database } from './Database'; -import { TestingDatabase } from './TestingDatabase'; -import { Image } from './docker/Image'; +import { randomUUID } from 'node:crypto'; +import { Client } from '../../lib/index.js'; +import { ContainerizedTestingDatabase } from './ContainerizedTestingDatabase.js'; +import type { Database } from './Database.js'; +import { TestingDatabase } from './TestingDatabase.js'; +import { Image } from './docker/Image.js'; const startDatabase = async (): Promise => { const image = new Image('eventsourcingdb', 'latest'); diff --git a/test/shared/startLocalHttpServer.ts b/test/shared/startLocalHttpServer.ts index c2fc4c8..8c0fa1a 100644 --- a/test/shared/startLocalHttpServer.ts +++ b/test/shared/startLocalHttpServer.ts @@ -1,6 +1,6 @@ -import * as http from 'http'; +import type http from 'node:http'; import express from 'express'; -import { Client } from '../../lib'; +import { Client } from '../../lib/index.js'; const startLocalHttpServer = async ( attachHandlers: (app: express.Express) => void, @@ -25,7 +25,6 @@ const startLocalHttpServer = async ( } const client = new Client(`http://localhost:${address.port}`, { - maxTries: 2, accessToken: 'irrelevant', }); const stopServer = async () => { diff --git a/test/shared/stopDatabase.ts b/test/shared/stopDatabase.ts index 915202a..19e4304 100644 --- a/test/shared/stopDatabase.ts +++ b/test/shared/stopDatabase.ts @@ -1,6 +1,6 @@ -import { Database } from './Database'; +import type { Database } from './Database.js'; -const stopDatabase = async (database: Database): Promise => { +const stopDatabase = (database: Database): void => { database.withAuthorization.stop(); }; diff --git a/test/unit/event/EventContextTests.ts b/test/unit/event/EventContextTests.ts index 2190860..79b2661 100644 --- a/test/unit/event/EventContextTests.ts +++ b/test/unit/event/EventContextTests.ts @@ -1,11 +1,12 @@ -import { assert } from 'assertthat'; -import { EventContext } from '../../../lib/event/EventContext'; -import { ValidationError } from '../../../lib/util/error/ValidationError'; -import { events } from '../../shared/events/events'; -import { testSource } from '../../shared/events/source'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { EventContext } from '../../../lib/event/EventContext.js'; +import { ValidationError } from '../../../lib/util/error/ValidationError.js'; +import { events } from '../../shared/events/events.js'; +import { testSource } from '../../shared/events/source.js'; suite('EventContext', () => { - suite('.parse()', () => { + suite('parse', () => { test('returns an EventContext for a valid object.', () => { const toParse = { source: testSource, @@ -18,11 +19,8 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { - EventContext.parse(toParse); - }) - .is.not.throwing(); + // Should not throw. + EventContext.parse(toParse); }); test('throws an error for invalid source.', () => { @@ -37,15 +35,16 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse source '42' to string." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse source '42' to string."); + return true; + }, + ); }); test('throws an error for invalid subject.', () => { @@ -60,16 +59,19 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === - "Failed to validate subject: 'this/is/invalid' must be an absolute, slash-separated path." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + error.message, + "Failed to validate subject: 'this/is/invalid' must be an absolute, slash-separated path.", + ); + return true; + }, + ); }); test('throws an error for invalid type.', () => { @@ -84,16 +86,19 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === - "Failed to validate type: 'a.this.is.invalid' must be a reverse domain name." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + error.message, + "Failed to validate type: 'a.this.is.invalid' must be a reverse domain name.", + ); + return true; + }, + ); }); test('throws an error for invalid specversion.', () => { @@ -108,15 +113,16 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse specVersion '1' to string." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse specVersion '1' to string."); + return true; + }, + ); }); test('throws an error for invalid id.', () => { @@ -131,15 +137,16 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse id '[object Object]' to string." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse id '[object Object]' to string."); + return true; + }, + ); }); test('throws an error for invalid time.', () => { @@ -154,15 +161,16 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse time 'not a date' to Date." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse time 'not a date' to Date."); + return true; + }, + ); }); test('throws an error for invalid dataContentType.', () => { @@ -177,15 +185,16 @@ suite('EventContext', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse dataContentType 'undefined' to string." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse dataContentType 'undefined' to string."); + return true; + }, + ); }); test('throws an error for invalid dataContentType.', () => { @@ -200,15 +209,16 @@ suite('EventContext', () => { predecessorhash: null, }; - assert - .that(() => { + assert.throws( + () => { EventContext.parse(toParse); - }) - .is.throwing( - error => - error.message === "Failed to parse predecessorHash 'null' to string." && - error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal(error.message, "Failed to parse predecessorHash 'null' to string."); + return true; + }, + ); }); }); }); diff --git a/test/unit/event/EventTests.ts b/test/unit/event/EventTests.ts index 7960410..e37a7a0 100644 --- a/test/unit/event/EventTests.ts +++ b/test/unit/event/EventTests.ts @@ -1,10 +1,11 @@ -import { assert } from 'assertthat'; -import { Event } from '../../../lib'; -import { events } from '../../shared/events/events'; -import { testSource } from '../../shared/events/source'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { Event } from '../../../lib/index.js'; +import { events } from '../../shared/events/events.js'; +import { testSource } from '../../shared/events/source.js'; suite('Event', () => { - suite('.parse()', () => { + suite('parse', () => { test('returns an Event for a valid object.', () => { const toParse = { source: testSource, @@ -18,11 +19,8 @@ suite('Event', () => { data: { someKey: 'some-data' }, }; - assert - .that(() => { - Event.parse(toParse); - }) - .is.not.throwing(); + // Should not throw. + Event.parse(toParse); }); test('throws an error for missing data.', () => { @@ -37,11 +35,14 @@ suite('Event', () => { predecessorhash: 'some-predecessor-hash', }; - assert - .that(() => { + assert.throws( + () => { Event.parse(toParse); - }) - .is.throwing("Failed to parse data 'undefined' to object."); + }, + { + message: "Failed to parse data 'undefined' to object.", + }, + ); }); test('throws an error for invalid data.', () => { @@ -57,11 +58,14 @@ suite('Event', () => { data: 42, }; - assert - .that(() => { + assert.throws( + () => { Event.parse(toParse); - }) - .is.throwing("Failed to parse data '42' to object."); + }, + { + message: "Failed to parse data '42' to object.", + }, + ); }); }); }); diff --git a/test/unit/event/validateSubjectTests.ts b/test/unit/event/validateSubjectTests.ts index 9b1342a..e51f365 100644 --- a/test/unit/event/validateSubjectTests.ts +++ b/test/unit/event/validateSubjectTests.ts @@ -1,48 +1,75 @@ -import { assert } from 'assertthat'; -import { validateSubject } from '../../../lib/event/validateSubject'; -import { ValidationError } from '../../../lib/util/error/ValidationError'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { validateSubject } from '../../../lib/event/validateSubject.js'; +import { ValidationError } from '../../../lib/util/error/ValidationError.js'; -suite('validateSubject()', () => { +suite('validateSubject', () => { test('returns without throwing on a valid subject.', () => { - assert - .that(() => { - validateSubject('/this/is/valid'); - }) - .is.not.throwing(); + // Should not throw. + validateSubject('/this/is/valid'); }); test('contains the invalid subject in the error message in case of throwing.', () => { - assert - .that(() => { + assert.throws( + () => { validateSubject('invalidExampleSubject'); - }) - .is.throwing( - error => - error.message.includes('invalidExampleSubject') && error instanceof ValidationError, - ); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.ok(error.message.includes('invalidExampleSubject')); + return true; + }, + ); }); - test('is throwing if the subject is not an absolute slash separated path.', () => { - assert - .that(() => { - validateSubject('invalidExampleSubject'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws if the subject is not an absolute slash separated path.', () => { + const subject = 'invalidExampleSubject'; + assert.throws( + () => { + validateSubject(subject); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate subject: '${subject}' must be an absolute, slash-separated path.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing if the subject is a relative path.', () => { - assert - .that(() => { - validateSubject('this/is/invalid'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws if the subject is a relative path.', () => { + const subject = 'this/is/invalid'; + assert.throws( + () => { + validateSubject(subject); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate subject: '${subject}' must be an absolute, slash-separated path.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing if the subject has invalid characters.', () => { - assert - .that(() => { - validateSubject('/user/günter/registered'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws if the subject has invalid characters.', () => { + const subject = '/user/günter/registered'; + assert.throws( + () => { + validateSubject(subject); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate subject: '${subject}' must be an absolute, slash-separated path.`, + error.message, + ); + return true; + }, + ); }); }); diff --git a/test/unit/event/validateTypeTests.ts b/test/unit/event/validateTypeTests.ts index c051276..9330489 100644 --- a/test/unit/event/validateTypeTests.ts +++ b/test/unit/event/validateTypeTests.ts @@ -1,63 +1,113 @@ -import { assert } from 'assertthat'; -import { validateType } from '../../../lib/event/validateType'; -import { ValidationError } from '../../../lib/util/error/ValidationError'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { validateType } from '../../../lib/event/validateType.js'; +import { ValidationError } from '../../../lib/util/error/ValidationError.js'; -suite('validateType()', () => { - test('returns without throwing on a valid type.', async () => { - assert - .that(() => { - validateType('com.example.exampleType'); - }) - .is.not.throwing(); +suite('validateType', () => { + test('returns without throwing on a valid type.', () => { + // Should not throw. + validateType('com.example.exampleType'); }); test('contains the invalid type in the error message in case of throwing an error.', () => { - assert - .that(() => { - validateType('invalidExampleType'); - }) - .is.throwing( - error => error.message.includes('invalidExampleType') && error instanceof ValidationError, - ); + const eventType = 'invalidExampleType'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing an error if the type is not a reverse domain name.', () => { - assert - .that(() => { - validateType('invalidExampleType'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws an error if the type is not a reverse domain name.', () => { + const eventType = 'invalidExampleType'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); - test("is throwing an error if the separator is not not a '.'.", () => { - assert - .that(() => { - validateType('com:example:exampleType'); - }) - .is.throwing(error => error instanceof ValidationError); + test("throws an error if the separator is not not a '.'.", () => { + const eventType = 'com:example:exampleType'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing an error if the reverse domain has less than 3 segments.', () => { - assert - .that(() => { - validateType('com.example'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws an error if the reverse domain has less than 3 segments.', () => { + const eventType = 'com.example'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing an error if the type has invalid characters.', () => { - assert - .that(() => { - validateType('com.example.apfel-günter.registered'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws an error if the type has invalid characters.', () => { + const eventType = 'com.example.apfel-günter.registered'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); - test('is throwing an error if the tld of the reverse domain has less than 1 character.', () => { - assert - .that(() => { - validateType('a.example.exampleType'); - }) - .is.throwing(error => error instanceof ValidationError); + test('throws an error if the tld of the reverse domain has less than 1 character.', () => { + const eventType = 'a.example.exampleType'; + assert.throws( + () => { + validateType(eventType); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + `Failed to validate type: '${eventType}' must be a reverse domain name.`, + error.message, + ); + return true; + }, + ); }); }); diff --git a/test/unit/handlers/isHeartbeatTests.ts b/test/unit/handlers/isHeartbeatTests.ts index c3e07ee..573594a 100644 --- a/test/unit/handlers/isHeartbeatTests.ts +++ b/test/unit/handlers/isHeartbeatTests.ts @@ -1,35 +1,31 @@ -import { assert } from 'assertthat'; -import { isHeartbeat } from '../../../lib/handlers/isHeartbeat'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { isHeartbeat } from '../../../lib/handlers/isHeartbeat.js'; -suite('isHeartbeat()', () => { +suite('isHeartbeat', () => { test('returns true for a heartbeat object.', () => { - assert - .that( - isHeartbeat({ - type: 'heartbeat', - }), - ) - .is.true(); + assert.ok( + isHeartbeat({ + type: 'heartbeat', + }), + ); }); test('ignores additional attributes.', () => { - assert - .that( - isHeartbeat({ - type: 'heartbeat', - additional: 'attribute', - }), - ) - .is.true(); + assert.ok( + isHeartbeat({ + type: 'heartbeat', + additional: 'attribute', + }), + ); }); test('returns false for a non heartbeat object.', () => { - assert - .that( - isHeartbeat({ - type: 'not-a-heartbeat', - }), - ) - .is.false(); + assert.equal( + isHeartbeat({ + type: 'not-a-heartbeat', + }), + false, + ); }); }); diff --git a/test/unit/handlers/isItemTests.ts b/test/unit/handlers/isItemTests.ts index acbdc6f..1da939d 100644 --- a/test/unit/handlers/isItemTests.ts +++ b/test/unit/handlers/isItemTests.ts @@ -1,68 +1,64 @@ -import { assert } from 'assertthat'; -import { isItem } from '../../../lib/handlers/isItem'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { isItem } from '../../../lib/handlers/isItem.js'; -suite('isItem()', () => { +suite('isItem', () => { test('returns true for a item object.', () => { - assert - .that( - isItem({ - type: 'item', - payload: { - event: {}, - hash: 'some-hash', - }, - }), - ) - .is.true(); + assert.ok( + isItem({ + type: 'item', + payload: { + event: {}, + hash: 'some-hash', + }, + }), + ); }); test('ignores additional attributes.', () => { - assert - .that( - isItem({ - type: 'item', - payload: { - event: {}, - hash: 'some-hash', - }, - additional: 'attribute', - }), - ) - .is.true(); + assert.ok( + isItem({ + type: 'item', + payload: { + event: {}, + hash: 'some-hash', + }, + additional: 'attribute', + }), + ); }); test('returns false for a missing payload.', () => { - assert - .that( - isItem({ - type: 'item', - }), - ) - .is.false(); + assert.equal( + isItem({ + type: 'item', + }), + false, + ); }); test('returns false for a invalid payload.', () => { - assert - .that( - isItem({ - type: 'item', - payload: {}, - }), - ) - .is.false(); + assert.equal( + isItem({ + type: 'item', + payload: { + event: {}, + }, + }), + false, + ); }); test('returns false for a non item object.', () => { - assert - .that( - isItem({ - type: 'not-an-item', - payload: { - event: {}, - hash: 'some-hash', - }, - }), - ) - .is.false(); + assert.equal( + isItem({ + type: 'not-an-item', + payload: { + event: {}, + hash: 'some-hash', + }, + }), + false, + ); }); }); diff --git a/test/unit/handlers/isStreamErrorTests.ts b/test/unit/handlers/isStreamErrorTests.ts index 159f4dd..78a48b1 100644 --- a/test/unit/handlers/isStreamErrorTests.ts +++ b/test/unit/handlers/isStreamErrorTests.ts @@ -1,65 +1,59 @@ -import { assert } from 'assertthat'; -import { isStreamError } from '../../../lib/handlers/isStreamError'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { isStreamError } from '../../../lib/handlers/isStreamError.js'; -suite('isStreamError()', () => { +suite('isStreamError', () => { test('returns true for a error object.', () => { - assert - .that( - isStreamError({ - type: 'error', - payload: { - error: 'some-error', - }, - }), - ) - .is.true(); + assert.ok( + isStreamError({ + type: 'error', + payload: { + error: 'some-error', + }, + }), + ); }); test('ignores additional attributes.', () => { - assert - .that( - isStreamError({ - type: 'error', - payload: { - error: 'some-error', - }, - additional: 'attribute', - }), - ) - .is.true(); + assert.ok( + isStreamError({ + type: 'error', + payload: { + error: 'some-error', + }, + additional: 'attribute', + }), + ); }); test('returns false for a missing payload', () => { - assert - .that( - isStreamError({ - type: 'error', - }), - ) - .is.false(); + assert.equal( + isStreamError({ + type: 'error', + }), + false, + ); }); test('returns false for a invalid payload', () => { - assert - .that( - isStreamError({ - type: 'error', - payload: {}, - }), - ) - .is.false(); + assert.equal( + isStreamError({ + type: 'error', + payload: {}, + }), + false, + ); }); test('returns false for a non error object', () => { - assert - .that( - isStreamError({ - type: 'not-an-error', - payload: { - error: 'some-error', - }, - }), - ) - .is.false(); + assert.equal( + isStreamError({ + type: 'not-an-error', + payload: { + error: 'some-error', + }, + }), + false, + ); }); }); diff --git a/test/unit/handlers/observeEvents/ObserveEventsOptions.ts b/test/unit/handlers/observeEvents/ObserveEventsOptions.ts index 0607dc0..3032bfe 100644 --- a/test/unit/handlers/observeEvents/ObserveEventsOptions.ts +++ b/test/unit/handlers/observeEvents/ObserveEventsOptions.ts @@ -1,21 +1,19 @@ -import { assert } from 'assertthat'; -import { validateObserveEventsOptions } from '../../../../lib/handlers/observeEvents/ObserveEventsOptions'; -import { ValidationError } from '../../../../lib/util/error/ValidationError'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { validateObserveEventsOptions } from '../../../../lib/handlers/observeEvents/ObserveEventsOptions.js'; +import { ValidationError } from '../../../../lib/util/error/ValidationError.js'; suite('validateObserveEventOptions', () => { test('returns for a valid options object.', () => { - assert - .that(() => - validateObserveEventsOptions({ - recursive: false, - }), - ) - .is.not.throwing(); + // Should not throw. + validateObserveEventsOptions({ + recursive: false, + }); }); test('throws an error if the both lowerBoundId and fromLatestEvent were given.', () => { - assert - .that(() => + assert.throws( + () => { validateObserveEventsOptions({ recursive: false, lowerBoundId: '1', @@ -24,13 +22,16 @@ suite('validateObserveEventOptions', () => { type: 'some-type', ifEventIsMissing: 'wait-for-event', }, - }), - ) - .is.throwing( - error => - error.message === - 'ObserveEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.' && - error instanceof ValidationError, - ); + }); + }, + error => { + assert.ok(error instanceof ValidationError); + assert.equal( + 'ObserveEventsOptions are invalid: lowerBoundId and fromLatestEvent are mutually exclusive.', + error.message, + ); + return true; + }, + ); }); }); diff --git a/test/unit/utils/isObjectTests.ts b/test/unit/utils/isObjectTests.ts index 36412c3..e25981c 100644 --- a/test/unit/utils/isObjectTests.ts +++ b/test/unit/utils/isObjectTests.ts @@ -1,17 +1,19 @@ -import { assert } from 'assertthat'; -import { isObject } from '../../../lib/util/isObject'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { isObject } from '../../../lib/util/isObject.js'; -suite('isObject()', (): void => { - test('returns false if the given value is not an object.', async (): Promise => { - assert.that(isObject(1)).is.false(); - assert.that(isObject('')).is.false(); - assert.that(isObject([])).is.false(); - assert.that(isObject(null)).is.false(); - assert.that(isObject(undefined)).is.false(); - assert.that(isObject(true)).is.false(); +suite('isObject', (): void => { + test('returns false if the given value is not an object.', (): void => { + assert.equal(isObject(1), false); + assert.equal(isObject(''), false); + assert.equal(isObject([]), false); + assert.equal(isObject(null), false); + assert.equal(isObject(undefined), false); + assert.equal(isObject(true), false); }); - test('returns true if the given value is an object.', async (): Promise => { - assert.that(isObject({})).is.true(); - assert.that(isObject(new Map())).is.true(); + + test('returns true if the given value is an object.', (): void => { + assert.ok(isObject({})); + assert.ok(isObject(new Map())); }); }); diff --git a/test/unit/utils/ndjson/LinesDecoderTests.ts b/test/unit/utils/ndjson/LinesDecoderTests.ts index 794e25f..d24e025 100644 --- a/test/unit/utils/ndjson/LinesDecoderTests.ts +++ b/test/unit/utils/ndjson/LinesDecoderTests.ts @@ -1,24 +1,25 @@ -import { assert } from 'assertthat'; -import { LinesDecoder } from '../../../../lib/util/ndjson/LinesDecoder'; +import assert from 'node:assert/strict'; +import { suite, test } from 'node:test'; +import { LinesDecoder } from '../../../../lib/util/ndjson/LinesDecoder.js'; suite('LinesDecoder', (): void => { - test('returns all completed lines on write().', async (): Promise => { + test('returns all completed lines on write().', (): void => { const decoder = new LinesDecoder(); const actualLines = decoder.write(Buffer.from('hello\nworld\nfoobar')); - assert.that(actualLines).is.equalTo(['hello', 'world']); + assert.deepEqual(actualLines, ['hello', 'world']); }); - test('buffers incomplete lines and returns them on write() as soon as they are completed.', async (): Promise => { + test('buffers incomplete lines and returns them on write() as soon as they are completed.', (): void => { const decoder = new LinesDecoder(); let actualLines = decoder.write(Buffer.from('incomplete')); - assert.that(actualLines).is.equalTo([]); + assert.deepEqual(actualLines, []); actualLines = decoder.write(Buffer.from(' line\n')); - assert.that(actualLines).is.equalTo(['incomplete line']); + assert.deepEqual(actualLines, ['incomplete line']); }); }); diff --git a/test/unit/utils/ndjson/readNdJsonStream.ts b/test/unit/utils/ndjson/readNdJsonStream.ts index eb7e8a1..be246a3 100644 --- a/test/unit/utils/ndjson/readNdJsonStream.ts +++ b/test/unit/utils/ndjson/readNdJsonStream.ts @@ -1,7 +1,8 @@ -import { Readable } from 'stream'; -import { assert } from 'assertthat'; -import { UnknownObject } from '../../../../lib/util/UnknownObject'; -import { readNdJsonStream } from '../../../../lib/util/ndjson/readNdJsonStream'; +import assert from 'node:assert/strict'; +import { Readable } from 'node:stream'; +import { suite, test } from 'node:test'; +import type { UnknownObject } from '../../../../lib/util/UnknownObject.js'; +import { readNdJsonStream } from '../../../../lib/util/ndjson/readNdJsonStream.js'; suite('readNdJsonStream', (): void => { test('returns an async generator that yields parsed json objects.', async (): Promise => { @@ -14,6 +15,6 @@ suite('readNdJsonStream', (): void => { actualMessages.push(message); } - assert.that(actualMessages).is.equalTo([{ foo: 'bar' }, { bar: 'baz' }]); + assert.deepEqual(actualMessages, [{ foo: 'bar' }, { bar: 'baz' }]); }); }); diff --git a/test/unit/utils/retry/retryWithBackoffTests.ts b/test/unit/utils/retry/retryWithBackoffTests.ts deleted file mode 100644 index fb63716..0000000 --- a/test/unit/utils/retry/retryWithBackoffTests.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { assert } from 'assertthat'; -import { CancelationError } from '../../../../lib'; -import { RetryError } from '../../../../lib/util/retry/RetryError'; -import { done, retryWithBackoff } from '../../../../lib/util/retry/retryWithBackoff'; - -suite('retryWithBackoff', (): void => { - test('returns immediately if no error occurs.', async (): Promise => { - let count = 0; - const maxTries = 3; - - await assert - .that(async () => { - await retryWithBackoff(new AbortController(), maxTries, async () => { - count += 1; - - return done; - }); - }) - .is.not.throwingAsync(); - - assert.that(count).is.equalTo(1); - }); - - test('throws a RetryError if an error occurs during all tries.', async (): Promise => { - let count = 0; - const maxTries = 3; - - await assert - .that(async () => { - await retryWithBackoff(new AbortController(), maxTries, async () => { - count += 1; - - return { retry: new Error(`Error no. ${count}`) }; - }); - }) - .is.throwingAsync( - 'Failed operation with 3 errors:\n' + - 'Error: Error no. 1\n' + - 'Error: Error no. 2\n' + - 'Error: Error no. 3', - ); - - assert.that(count).is.equalTo(maxTries); - }); - - test('returns when no error occurs anymore.', async (): Promise => { - let count = 0; - const maxTries = 5; - const successfulTry = 3; - - await assert - .that(async () => { - await retryWithBackoff(new AbortController(), maxTries, async () => { - count += 1; - - if (count !== successfulTry) { - return { retry: new Error(`Error no. ${count}`) }; - } - - return done; - }); - }) - .is.not.throwingAsync(); - - assert.that(count).is.equalTo(successfulTry); - }); - - test('returns immediately when the AbortController is canceled.', async (): Promise => { - let count = 0; - const maxTries = 5; - const cancelingTry = 3; - - const abortController = new AbortController(); - - await assert - .that(async () => { - await retryWithBackoff(abortController, maxTries, async () => { - count += 1; - - if (count === cancelingTry) { - abortController.abort(); - } - - return { retry: new Error(`Error no. ${count}`) }; - }); - }) - .is.throwingAsync(error => error instanceof CancelationError); - - assert.that(count).is.equalTo(cancelingTry); - }); - - test('aborts the retries if an error is thrown.', async () => { - const maxTries = 5; - const abortController = new AbortController(); - - await assert - .that(async () => { - await retryWithBackoff(abortController, maxTries, async () => { - throw new Error('Abort the retries.'); - }); - }) - .is.throwingAsync(error => !(error instanceof RetryError)); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 01bbb6b..4611f30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,15 @@ { - "compilerOptions": { - "baseUrl": ".", - "declaration": true, - "esModuleInterop": true, - "lib": [ "ES2022" ], - "module": "CommonJS", - "outDir": "build", - "resolveJsonModule": true, - "strict": true, - "target": "ES2022" - }, - "include": [ - "./**/*.ts" - ], - "exclude": [ - "./build" - ] + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "esModuleInterop": true, + "lib": ["ES2022"], + "module": "CommonJS", + "outDir": "build", + "resolveJsonModule": true, + "strict": true, + "target": "ES2022" + }, + "include": ["./**/*.ts"], + "exclude": ["./build"] }