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

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',
});
}
}