Add initial PIS route

This commit is contained in:
2026-02-21 00:04:25 +00:00
parent 35bfa52531
commit 4059082416
7 changed files with 111 additions and 5 deletions

View File

@@ -2,7 +2,16 @@
"name": "owlboard-ts",
"version": "3.0.0",
"description": "TypeScript API Library to interact with the OwlBoard API (> v3.0.0)",
"main": "index.js",
"main": "./dist/index.js",
"type": "module",
"sideEffects": false,
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},

5
src/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export * from './lib/base.js';
export * from './lib/client.js';
export * from './lib/errors.js';
export * from './modules/pis.js';

46
src/lib/base.ts Normal file
View File

@@ -0,0 +1,46 @@
import { ApiError } from "./errors.js";
import type { ApiErrorCode } from "./errors.js";
import type { ApiEnvelope } from "@owlboard/api-schema-types";
export interface ApiResult<T> {
data: T;
producedAt: Date;
}
export class BaseClient {
constructor(
protected readonly baseUrl: string,
protected readonly apiKey?: string
) {}
/**
* Handles the Envelope logic
*/
public async request<T>(path: string, options: RequestInit = {}): Promise<ApiResult<T>> {
const url = `${this.baseUrl}${path}`;
const headers = new Headers(options.headers);
headers.set('Content-Type', 'application/json');
if (this.apiKey) headers.set('X-OWL-KEY', this.apiKey);
const response = await fetch(url, { ...options, headers });
if (!response.ok && !response.headers.get('content-type')?.includes('application/json')) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Parse raw envelope
const envelope = (await response.json()) as ApiEnvelope.Envelope;
if (envelope.e !== undefined && envelope.e !== null) {
throw new ApiError(envelope.e || {}, response.status);
}
return {
data: envelope.d as T,
// Convert seconds to milliseconds before parsing date
producedAt: new Date(Number(envelope.t) * 1000)
};
}
}

12
src/lib/client.ts Normal file
View File

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

17
src/lib/errors.ts Normal file
View File

@@ -0,0 +1,17 @@
import type { ApiEnvelope } from "@owlboard/api-schema-types";
type RawErrorObject = NonNullable<ApiEnvelope.Envelope["e"]>;
export type ApiErrorCode = NonNullable<RawErrorObject['code']>
export class ApiError extends Error {
public readonly code: ApiErrorCode;
public readonly status: number;
constructor(errorObj: RawErrorObject, status: number) {
super(errorObj.msg || 'An unknown error occurred');
this.name = 'ApiError';
this.status = status;
// cast fallback to ensure it satisfies union
this.code = errorObj.code || ("SERVER" as ApiErrorCode);
}
}

14
src/modules/pis.ts Normal file
View File

@@ -0,0 +1,14 @@
import type { ApiPisObject } from '@owlboard/api-schema-types';
import type { BaseClient, ApiResult } from '../lib/base.js';
export class PisModule {
constructor(private client: BaseClient) {}
async getByStartEndCrs(startCrs: string, endCrs: string): Promise<ApiResult<ApiPisObject.PisObjects>> {
const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`;
return this.client.request<ApiPisObject.PisObjects>(path, {
method: 'GET',
});
}
}

View File

@@ -2,13 +2,15 @@
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"baseUrl": ".",
"types": [],
// For nodejs:
// "lib": ["esnext"],
@@ -40,5 +42,6 @@
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
},
"include": ["src/**/*"]
}