Compare commits

...

31 Commits

Author SHA1 Message Date
935dc271a2 Add getByRid to TrainsModule
All checks were successful
Publish Package / build-and-publish (push) Successful in 13s
2026-05-03 00:51:03 +01:00
ed2f8527d6 Implement use of a custom fetch fn 2026-05-02 10:09:18 +01:00
50c2e5f427 Ensure that the ApiErrorCode type is exported
All checks were successful
Publish Package / build-and-publish (push) Successful in 11s
2026-04-28 17:52:45 +01:00
eb8bee65d9 Fix module res?
All checks were successful
Publish Package / build-and-publish (push) Successful in 7s
2026-04-27 23:48:08 +01:00
fc4c8745a6 Fix the date validator, was checking indexes, not values!
All checks were successful
Publish Package / build-and-publish (push) Successful in 8s
2026-04-27 23:39:36 +01:00
0474973588 Fix path to train module
All checks were successful
Publish Package / build-and-publish (push) Successful in 8s
2026-04-27 23:31:43 +01:00
7f2cb2c413 Fix path to helpers.js
All checks were successful
Publish Package / build-and-publish (push) Successful in 8s
2026-04-27 23:29:31 +01:00
3c81b5225f Add trains endpoint handler
All checks were successful
Publish Package / build-and-publish (push) Successful in 8s
2026-04-27 22:28:55 +01:00
d10dabf604 Adjust generateGeohash from static to standard method to support singleton approach in Svelte without additonal import/exports
All checks were successful
Publish Package / build-and-publish (push) Successful in 3s
2026-03-30 22:57:57 +01:00
26886f8a7d Fix pathing to stationDate.js file
All checks were successful
Publish Package / build-and-publish (push) Successful in 3s
2026-03-30 22:52:09 +01:00
46dcf65de6 Ensure StationDataModule is exported
All checks were successful
Publish Package / build-and-publish (push) Successful in 2s
2026-03-30 22:49:53 +01:00
d160ad87f9 Ensure staitionData module is available in Client
All checks were successful
Publish Package / build-and-publish (push) Successful in 2s
2026-03-30 22:11:33 +01:00
fc74e933d6 Add StationDataModule, implementing getNearestStations & a Geohash generator method
All checks were successful
Publish Package / build-and-publish (push) Successful in 6s
2026-03-30 21:53:01 +01:00
ad355fe15e Re-export API Schema types
All checks were successful
Publish Package / build-and-publish (push) Successful in 4s
2026-03-25 10:22:34 +00:00
4dd8bd1108 Fix import paths
All checks were successful
Publish Package / build-and-publish (push) Successful in 3s
2026-03-24 12:40:07 +00:00
e2f4433042 Add LocationFilterModule
All checks were successful
Publish Package / build-and-publish (push) Successful in 7s
2026-03-24 00:53:29 +00:00
7775abbbca Adjust tagging strategy
All checks were successful
Publish Package / build-and-publish (push) Successful in 7s
2026-03-19 20:04:21 +00:00
f340c57ffd Ensure dynamic versioning for NPM pubslishing
Some checks failed
Publish Package / build-and-publish (push) Failing after 5s
2026-03-19 19:55:47 +00:00
6c6a603cd8 Ensure all paths are relative
Some checks failed
Publish Package / build-and-publish (push) Failing after 7s
2026-03-19 19:51:41 +00:00
35167f1853 And again...
All checks were successful
Publish Package / build-and-publish (push) Successful in 7s
2026-03-19 10:52:46 +00:00
815ae473db Try again
Some checks failed
Publish Package / build-and-publish (push) Failing after 6s
2026-03-19 10:49:43 +00:00
ff3052f3b0 Fix casing in NPM configuration for build/push job
Some checks failed
Publish Package / build-and-publish (push) Failing after 5s
2026-03-19 10:46:12 +00:00
0985e40ead Attempt another registry push pattern
Some checks failed
Publish Package / build-and-publish (push) Failing after 7s
2026-03-19 10:40:10 +00:00
23afe71752 Attempt force auth
Some checks failed
Publish Package / build-and-publish (push) Failing after 6s
2026-03-18 21:08:00 +00:00
a77d661797 Update package name
Some checks failed
Publish Package / build-and-publish (push) Failing after 7s
2026-03-18 21:06:06 +00:00
bf46067a0c Update publish action
Some checks failed
Publish Package / build-and-publish (push) Failing after 5s
2026-03-18 21:03:27 +00:00
ed4bbf2327 Add auto-publishing
Some checks failed
Publish Package / build-and-publish (push) Failing after 11s
2026-03-18 21:01:22 +00:00
16bb7d3510 Add data normalization and validation. Add validation error error type.
Add lookup by code method to pis class.
2026-02-21 19:28:26 +00:00
edca2698a7 Add manual test for PIS endpoint 2026-02-21 11:17:01 +00:00
4fd95189ce Fic declatation of baseUrl to ensure it includes /api/v3 whether provided or not. 2026-02-21 00:18:44 +00:00
4f13309cdc Tidy up package.json 2026-02-21 00:10:41 +00:00
16 changed files with 2279 additions and 24 deletions

View File

@@ -0,0 +1,37 @@
name: Publish Package
on:
push:
tags:
- "v*"
jobs:
build-and-publish:
runs-on: ubuntu-22.04
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 20
registry-url: 'https://git.fjla.uk/api/packages/owlboard/npm'
scope: '@OwlBoard'
- name: Install Dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to Gitea Repo
run: |
VERSION=${GITHUB_REF_NAME#v}
npm version $VERSION --no-git-tag-version
npm config set //git.fjla.uk/api/packages/owlboard/npm/:_authToken ${{ secrets.REPO_TOKEN }}
npm config set //git.fjla.uk/api/packages/OwlBoard/npm/:_authToken ${{ secrets.REPO_TOKEN }}
npm publish --registry=https://git.fjla.uk

1804
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,14 @@
{ {
"name": "owlboard-ts", "name": "@owlboard/owlboard-ts",
"version": "3.0.0", "version": "3.0.0",
"description": "TypeScript API Library to interact with the OwlBoard API (> v3.0.0)", "description": "TypeScript API Library to interact with the OwlBoard API (> v3.0.0)",
"main": "./dist/index.js", "main": "./dist/index.js",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"files": [
"dist"
],
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -13,7 +16,9 @@
} }
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"test-pis": "NODE_TLS_REJECT_UNAUTHORIZED=0 tsx tests/manual/pis.ts"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,8 +29,14 @@
], ],
"author": "Frederick Boniface", "author": "Frederick Boniface",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": {
"@owlboard/api-schema-types": "^3.0.3-alpha8",
"install": "^0.13.0",
"latlon-geohash": "^2.0.0",
"npm": "^11.13.0"
},
"devDependencies": { "devDependencies": {
"@owlboard/api-schema-types": "^3.0.1-alpha3", "@types/latlon-geohash": "^2.0.4",
"@types/node": "^25.3.0", "@types/node": "^25.3.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsx": "^4.21.0", "tsx": "^4.21.0",

11
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import { OwlBoardClient } from "./lib/client";
declare global {
namespace App {
interface Locals {
api: OwlBoardClient;
}
}
}
export {};

View File

@@ -1,5 +1,11 @@
export * from './lib/base.js'; export { OwlBoardClient } from './lib/client.js';
export * from './lib/client.js'; export { ValidationError, ApiError, type ApiErrorCode } from './lib/errors.js';
export * from './lib/errors.js';
export * from './modules/pis.js'; // Re-export API Schema types
export type * from '@owlboard/api-schema-types'
// Useful exports for Type Hinting
export { PisModule } from './modules/pis.js';
export { LocationFilterModule } from './modules/locationFilter.js';
export { StationDataModule } from './modules/stationData.js';
export { TrainsModule } from './modules/trains.js';

View File

@@ -8,22 +8,43 @@ export interface ApiResult<T> {
} }
export class BaseClient { export class BaseClient {
protected readonly baseUrl: string;
constructor( constructor(
protected readonly baseUrl: string, baseUrl: string,
protected readonly apiKey?: string protected readonly apiKey?: string
) {} ) {
const root = baseUrl.replace(/\/$/, '');
this.baseUrl = root.endsWith('/api/v3')
? root
: `${root}/api/v3`;
}
public async ping(): Promise<boolean> {
try {
await fetch(this.baseUrl, {
method: 'HEAD',
signal: AbortSignal.timeout(3000)
});
return true
} catch (err) {
return false;
}
}
/** /**
* Handles the Envelope logic * Handles the Envelope logic
* @param fetcher - Optional custom fetch instance (e.g SvelteKit load fetch)
*/ */
public async request<T>(path: string, options: RequestInit = {}): Promise<ApiResult<T>> { public async request<T>(path: string, options: RequestInit = {}, fetcher: typeof fetch = fetch): Promise<ApiResult<T>> {
const url = `${this.baseUrl}${path}`; const url = `${this.baseUrl}${path}`;
console.debug(`[API DEBUG] Calling: ${url}`);
const headers = new Headers(options.headers); const headers = new Headers(options.headers);
headers.set('Content-Type', 'application/json'); headers.set('Content-Type', 'application/json');
if (this.apiKey) headers.set('X-OWL-KEY', this.apiKey); if (this.apiKey) headers.set('X-OWL-KEY', this.apiKey);
const response = await fetch(url, { ...options, headers }); const response = await fetcher(url, { ...options, headers });
if (!response.ok && !response.headers.get('content-type')?.includes('application/json')) { if (!response.ok && !response.headers.get('content-type')?.includes('application/json')) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`); throw new Error(`HTTP ${response.status}: ${response.statusText}`);

View File

@@ -1,12 +1,21 @@
import { BaseClient } from "./base.js"; import { BaseClient } from "./base.js";
import { PisModule } from "../modules/pis.js"; import { PisModule } from "../modules/pis.js";
import { LocationFilterModule } from "../modules/locationFilter.js";
import { StationDataModule } from "../modules/stationData.js";
import { TrainsModule } from "../modules/trains.js";
export class OwlBoardClient extends BaseClient { export class OwlBoardClient extends BaseClient {
public readonly pis: PisModule; public readonly pis: PisModule;
public readonly locationFilter: LocationFilterModule;
public readonly stationData: StationDataModule;
public readonly trains: TrainsModule;
constructor(baseUrl: string, apiKey?: string) { constructor(baseUrl: string, apiKey?: string) {
super(baseUrl, apiKey); super(baseUrl, apiKey);
this.pis = new PisModule(this); this.pis = new PisModule(this);
this.locationFilter = new LocationFilterModule(this);
this.stationData = new StationDataModule(this);
this.trains = new TrainsModule(this);
} }
} }

View File

@@ -14,4 +14,11 @@ export class ApiError extends Error {
// cast fallback to ensure it satisfies union // cast fallback to ensure it satisfies union
this.code = errorObj.code || ("SERVER" as ApiErrorCode); this.code = errorObj.code || ("SERVER" as ApiErrorCode);
} }
}
export class ValidationError extends Error {
constructor(public field: string, public reason: string) {
super(`validation failed for ${field}: ${reason}`);
this.name = "VALIDATIONERROR";
}
} }

20
src/lib/helpers.ts Normal file
View File

@@ -0,0 +1,20 @@
/**
* Normalises input to YYYY-MM-DD string
* - Strings are passed through as is
* - Date objects are converted to UK time
*/
export const ensureDateString = (input: Date | string): string => {
if (typeof input === 'string') return input.trim();
const d = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
timeZone: 'Europe/London'
}).formatToParts(input);
const part = (type: string) => d.find(p => p.type === type)?.value;
return `${part('year')}-${part('month')}-${part('day')}`
}

143
src/lib/validation.ts Normal file
View File

@@ -0,0 +1,143 @@
/**
* Checks if a character code is a digit
*/
const isDigit = (c: number) => c >= 48 && c <= 57;
/**
* Checks if a character code is an upper case letter
*/
const isUpper = (c: number) => c >= 65 && c <= 90;
/**
* Checks if a string is a valid CRS (Syntactically Only)
* using byte level checking for max performance
* @param CRS The CRS Code to be validated
*/
export const IsValidCrs = (CRS: string): boolean => {
if (CRS.length !== 3) return false;
for (let i = 0; i < 3; i++) {
const char = CRS.charCodeAt(i);
if (!(
(char >= 65 && char <=90) ||
(char >= 97 && char <=122)
)) return false;
}
return true;
}
/**
* Checks if a string is a valid RID, with 15-16 numeric characters
* @param rid The RID to validate
* @returns True/False - Whether valid or not
*/
export const IsValidRid = (rid: string): boolean => {
if (rid.length > 16 || rid.length < 15) {
return false
}
for (let i = 0; i < rid.length; i++) {
const char = rid.charCodeAt(i);
if (!isDigit(char)) {
return false
}
}
return true
}
/**
* Checks if a string is a valid TIPLOC (Syntactically Only)
* using byte level checking for max performance
* @param TIPLOC The TIPLOC to be validated
*/
export const IsValidTiploc = (TIPLOC: string): boolean => {
const l = TIPLOC.length;
if (l === 0 || l > 7) return false;
for (let i = 0; i > l; i++) {
const char = TIPLOC.charCodeAt(i);
if (!(
(char >= 48 && char <= 57) ||
(char >= 65 && char <=90) ||
(char >= 97 && char <=122)
)) return false;
}
return true;
}
/**
* Checks if a string is a valid PIS Code (Syntactically Only)
* using byte level checking for max performance
* ONLY GWR (4-digit) CODES SUPPORTED
* @param PIS The PIS Code to be validated
*/
export const IsValidPis = (PIS: string): boolean => {
if (PIS.length !== 4) return false;
for (let i = 0; i < 4; i++) {
const char = PIS.charCodeAt(i);
if (!(char >= 48 && char <= 57)) return false;
}
return true;
}
/**
* Checks if a string is the correct format for a Headcode
* USes byte level checking for max performance
* @param Headcode The headcode to be validated
*/
export const IsValidHeadcode = (Headcode: string): boolean => {
if (Headcode.length !== 4) return false;
const c0 = Headcode.charCodeAt(0);
const c1 = Headcode.charCodeAt(1);
const c2 = Headcode.charCodeAt(2);
const c3 = Headcode.charCodeAt(3);
return isDigit(c0) && isUpper(c1) && isDigit(c2) && isDigit(c3)
}
/**
* Checks if a string is the correct format for a TOC Code
*/
export const IsValidToc = (toc: string): boolean => {
if (toc.length !== 2) {
return false;
}
const c0 = toc.charCodeAt(0);
const c1 = toc.charCodeAt(1);
return isUpper(c0) && isUpper(c1);
}
/**
* Checks if a string is the correct format, and is a valid date
*/
export const IsValidDateStr = (s: string): boolean => {
if (s.length !== 10) return false;
if (s[4] !== '-' || s[7] !== '-') return false;
for (let i = 0; i < 10; i++) {
if (i === 4 || i === 7) continue;
const c = s.charCodeAt(i);
if (!isDigit(c)) return false;
}
const y = parseInt(s.substring(0, 4), 10);
const m = parseInt(s.substring(5, 7), 10) - 1; // JS months are 0-11
const d = parseInt(s.substring(8, 10), 10);
const date = new Date(y, m, d);
return (
date.getFullYear() === y &&
date.getMonth() === m &&
date.getDate() === d
);
}
/**
* Validates Geohash string against standard b32 alphabet
* (Syntactically Only validation)
* @param hash The geohash to be validated
* @param maxLen Defaults to 6 - which is enforced by the server, should not need changing
*/
export const IsValidGeoHash = (hash: string, maxLen: number = 6): boolean => {
const geoHashRegex = new RegExp(`^[0-9bcdefghjkmnpqrstuvwxyz]{1,${maxLen}}$`, 'i');
return geoHashRegex.test(hash);
}

View File

@@ -0,0 +1,16 @@
import { ApiLocationFilter } from "@owlboard/api-schema-types";
import type { BaseClient, ApiResult } from '../lib/base.js';
export class LocationFilterModule {
constructor(private client: BaseClient) {}
async getLocationFilterData(customFetch?: typeof fetch): Promise<ApiResult<ApiLocationFilter.LocationFilterObject[]>> {
const path = '/locationFilter/data';
return this.client.request<ApiLocationFilter.LocationFilterObject[]>(
path,
{method: "GET"},
customFetch,
);
}
}

View File

@@ -1,14 +1,35 @@
import type { ApiPisObject } from '@owlboard/api-schema-types'; import { ApiPisObject } from '@owlboard/api-schema-types';
import type { BaseClient, ApiResult } from '../lib/base.js'; import type { BaseClient, ApiResult } from '../lib/base.js';
import { IsValidCrs, IsValidPis } from '../lib/validation.js';
import { ValidationError } from '../lib/errors.js';
export class PisModule { export class PisModule {
constructor(private client: BaseClient) {} constructor(private client: BaseClient) {}
async getByStartEndCrs(startCrs: string, endCrs: string): Promise<ApiResult<ApiPisObject.PisObjects>> { async getByStartEndCrs(startCrs: string, endCrs: string, customFetch?: typeof fetch): Promise<ApiResult<ApiPisObject.PisObjects[]>> {
if (!IsValidCrs(startCrs)) {
throw new ValidationError("startCrs", "Invalid CRS Format")
}
if (!IsValidCrs(endCrs)) {
throw new ValidationError("endCrs", "Invalid CRS Format")
}
const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`; const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`;
return this.client.request<ApiPisObject.PisObjects>(path, { return this.client.request<ApiPisObject.PisObjects[]>(path, {
method: 'GET', method: 'GET',
}); }, customFetch);
}
async getByCode(code: string, customFetch?: typeof fetch): Promise<ApiResult<ApiPisObject.PisObjects[]>> {
if (!IsValidPis(code)) {
throw new ValidationError("code", "Invalid PIS Code Format")
}
const path = `/pis/code/${encodeURIComponent(code)}`;
return this.client.request<ApiPisObject.PisObjects[]>(path, {
method: 'GET',
}, customFetch)
} }
} }

View File

@@ -0,0 +1,37 @@
import { ApiStationsNearestStations } from '@owlboard/api-schema-types';
import type { BaseClient, ApiResult } from '../lib/base.js';
import { IsValidGeoHash } from '../lib/validation.js';
import { ValidationError } from '../lib/errors.js';
import Geohash from 'latlon-geohash';
export class StationDataModule {
constructor(private client: BaseClient) {}
/**
* Generates a 6-char Geohash for the given co-ordinates
* @param lt Latitude
* @param ln Longitude
* @returns Geohash as string
*/
generateGeohash(lt: number, ln: number): string {
return Geohash.encode(lt, ln, 6);
}
/**
*
* @param geohash Geohash as string (up to six characters), generate using this.generateGeohash()
* @returns Nearest Stations API Response (CRS, Name)
*/
async getNearestStations(geohash: string, customFetch?: typeof fetch): Promise<ApiResult<ApiStationsNearestStations.StationsNearestStations[]>> {
if (!IsValidGeoHash(geohash)) {
throw new ValidationError("hash", "Invalid Geohash requested");
}
const path = `/stationData/nearest/${geohash}`;
return this.client.request<ApiStationsNearestStations.StationsNearestStations[]>(path, {
method: 'GET',
}, customFetch)
}
// getStationSate(crs: string){}
}

45
src/modules/trains.ts Normal file
View File

@@ -0,0 +1,45 @@
import { ApiTrainsTrainByHeadcode, ApiTrainsTrainDetails } from '@owlboard/api-schema-types';
import type { BaseClient, ApiResult } from '../lib/base.js';
import { IsValidHeadcode, IsValidRid, IsValidToc, IsValidDateStr } from '../lib/validation.js';
import { ensureDateString } from '../lib/helpers.js';
import { ValidationError } from '../lib/errors.js';
export class TrainsModule {
constructor(private client: BaseClient) { }
async getByHeadcode(headcode: string, date: string | Date = new Date, toc: string = "", customFetch?: typeof fetch): Promise<ApiResult<ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]>> {
if (!IsValidHeadcode(headcode)) {
throw new ValidationError("headcode", "Invalid headcode format")
}
if (toc !== "" && !IsValidToc(toc)) {
throw new ValidationError("TOC", "Invalid TOC Format, needs to be two characters");
}
const dateStr = ensureDateString(date);
if (!IsValidDateStr(dateStr)) {
throw new ValidationError("Date", "Invalid date format, need YYYY-MM-DD");
}
const searchParams = new URLSearchParams();
searchParams.append('d', dateStr);
searchParams.append('h', headcode);
if (toc) searchParams.append('t', toc);
const path = `/trains?${searchParams.toString()}`;
return this.client.request<ApiTrainsTrainByHeadcode.TrainByHeadcodeResponse[]>(path, {
method: 'GET',
}, customFetch);
}
async getByRid(rid: string, customFetch?: typeof fetch): Promise<ApiResult<ApiTrainsTrainDetails.TrainDetailsResponse>> {
if (!IsValidRid(rid)) {
throw new ValidationError("RID", "Invalid RID format, must be 15-16 numerals")
}
const path = `/train/${rid}`
return this.client.request<ApiTrainsTrainDetails.TrainDetailsResponse>(path, {
method: 'GET',
}, customFetch);
}
}

80
tests/manual/pis.ts Normal file
View File

@@ -0,0 +1,80 @@
import { OwlBoardClient } from '../../src/lib/client';
import { ApiError, ValidationError } from '../../src/lib/errors';
console.log("Manual PIS Tests are running")
const TEST_SERVER: string = "https://owlboard.rke2-gw.svc.fjla.net"
console.log(`Using TEST_SERVER: ${TEST_SERVER}`)
const api = new OwlBoardClient(TEST_SERVER);
async function main() {
// Test server reachability
console.log("Testing server reachability")
if (!(await api.ping())) {
console.error('Server not reachable. Check it is running and is reachable at the provided address')
process.exit(1);
}
console.log("Server is reachable. Running tests...")
// Run code here.
{
// Test PIS Route
const start = 'bri';
const end = 'svb';
console.log(`Querying PIS Route: ${start} > ${end}...`);
try {
const {data, producedAt} = await api.pis.getByStartEndCrs(start, end);
console.log('\n---Response Success---');
console.log(`Data Produced At: ${producedAt.toLocaleTimeString()}`);
console.log(`Number of entries in response: ${data.length}`);
console.log(JSON.stringify(data));
} catch (err) {
if (err instanceof ApiError) {
console.error('\n--- API Error ---');
console.error(`Code: ${err.code}`);
console.error(`Msg: ${err.message}`);
console.error(`Status: ${err.status}`);
} else if (err instanceof ValidationError) {
console.error('\n--- Local Validation Error ---');
console.error(`Error Field: ${err.field}`);
console.error(`Message: ${err.reason}`);
console.error(`Request not sent`);
} else {
console.error(`Unknown Error: ${err}`)
}
}
}
// Test PIS Code
{
const code = 5001;
console.log(`Querying PIS Code: ${code}`);
try {
const {data, producedAt} = await api.pis.getByCode(code.toString());
console.log("\n--- Response Success ---");
console.log(`Data produced at: ${producedAt.toLocaleString()}`);
console.log(`Number of results: ${data.length}`);
console.log(JSON.stringify(data));
} catch (err) {
if (err instanceof ApiError) {
console.error('\n--- API Error ---');
console.error(`Code: ${err.code}`);
console.error(`Msg: ${err.message}`);
console.error(`Status: ${err.status}`);
} else if (err instanceof ValidationError) {
console.error('\n--- Local Validation Error ---');
console.error(`Error Field: ${err.field}`);
console.error(`Message: ${err.reason}`);
console.error(`Request not sent`);
} else {
console.error(`Unknown Error: ${err}`)
}
}
}
};
main();

View File

@@ -7,9 +7,9 @@
// Environment Settings // Environment Settings
// See also https://aka.ms/tsconfig/module // See also https://aka.ms/tsconfig/module
"module": "esnext", "module": "nodenext",
"target": "esnext", "target": "esnext",
"moduleResolution": "bundler", "moduleResolution": "nodenext",
"baseUrl": ".", "baseUrl": ".",
"types": [], "types": [],
// For nodejs: // For nodejs:
@@ -21,6 +21,7 @@
"sourceMap": true, "sourceMap": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"composite": true,
// Stricter Typechecking Options // Stricter Typechecking Options
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,