Skip to content

Commit a3dc475

Browse files
committed
actual types
1 parent 5fa819a commit a3dc475

6 files changed

Lines changed: 120 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
## [1.4.0] - 2026-05-15
10+
11+
### Added
12+
13+
- Response transforms applied inside `searchMeetings`, `searchMeetingsByAddress`, `searchMeetingsByCoordinates`, `searchMeetingsWithFormats`, `getServerInfo`, and `getCoverageArea`. BMLT serializes numeric fields as quoted strings; the client now coerces them to real `number` values so consumers get idiomatic types without needing to parse manually.
14+
15+
### Changed
16+
17+
- **Breaking:** `Meeting` numeric fields are now typed and returned as `number` (were `string` in 1.3.0): `weekday_tinyint`, `venue_type`, `published`, `latitude`, `longitude`.
18+
- **Breaking:** `Meeting.distance_in_km` and `Meeting.distance_in_miles` are `number | undefined` (were `string | undefined`); the transform maps the empty strings BMLT returns for non-geographic searches to `undefined`.
19+
- **Breaking:** `ServerInfo` numeric fields are now `number` (were `string`): `versionInt`, `centerLongitude`, `centerLatitude`, `centerZoom`, `semanticAdmin`, `changesPerMeeting`.
20+
- **Breaking:** `CoverageArea` corner fields are now `number` (were `string`): `nw_corner_longitude`, `nw_corner_latitude`, `se_corner_longitude`, `se_corner_latitude`.
21+
922
## [1.3.0] - 2026-05-15
1023

1124
### Added
@@ -72,7 +85,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7285
- Zero external runtime dependencies — p-queue and p-retry are bundled via Vite.
7386
- ES module only; native `fetch` API; TypeScript declarations included.
7487

75-
[Unreleased]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.3.0...HEAD
88+
[Unreleased]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.4.0...HEAD
89+
[1.4.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.3.0...v1.4.0
7690
[1.3.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.2.0...v1.3.0
7791
[1.2.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.1.0...v1.2.0
7892
[1.1.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.0.6...v1.1.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bmlt-query-client",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "A TypeScript client for querying BMLT (Basic Meeting List Tool) servers with built-in geocoding support",
55
"type": "module",
66
"main": "dist/app.js",

src/client/bmlt-client.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
validateRadius,
3636
} from '../utils/url-builder';
3737
import { ErrorHandler } from '../utils/errors';
38+
import { transformMeeting, transformServerInfo, transformCoverageArea } from '../utils/transforms';
3839

3940
export interface BmltClientOptions {
4041
/** Server URL */
@@ -181,7 +182,12 @@ export class BmltClient {
181182
*/
182183
async searchMeetings(params: SearchResultsParams = {}): Promise<Meeting[]> {
183184
const { format = this.defaultFormat, ...searchParams } = params;
184-
return this.makeRequest<Meeting[]>(BmltEndpoint.GET_SEARCH_RESULTS, searchParams, format);
185+
const result = await this.makeRequest<unknown[]>(
186+
BmltEndpoint.GET_SEARCH_RESULTS,
187+
searchParams,
188+
format
189+
);
190+
return result.map(transformMeeting);
185191
}
186192

187193
/**
@@ -197,11 +203,15 @@ export class BmltClient {
197203
params: Omit<SearchResultsParams, 'get_used_formats' | 'get_formats_only'> = {}
198204
): Promise<MeetingsWithFormats> {
199205
const { format = this.defaultFormat, ...searchParams } = params;
200-
return this.makeRequest<MeetingsWithFormats>(
206+
const result = await this.makeRequest<{ meetings: unknown[]; formats: Format[] }>(
201207
BmltEndpoint.GET_SEARCH_RESULTS,
202208
{ ...searchParams, get_used_formats: true },
203209
format
204210
);
211+
return {
212+
meetings: result.meetings.map(transformMeeting),
213+
formats: result.formats,
214+
};
205215
}
206216

207217
/**
@@ -319,16 +329,16 @@ export class BmltClient {
319329
* Get server information
320330
*/
321331
async getServerInfo(): Promise<ServerInfo> {
322-
const result = await this.makeRequest<ServerInfo[]>(BmltEndpoint.GET_SERVER_INFO);
323-
return result[0];
332+
const result = await this.makeRequest<unknown[]>(BmltEndpoint.GET_SERVER_INFO);
333+
return transformServerInfo(result[0]);
324334
}
325335

326336
/**
327337
* Get server coverage area
328338
*/
329339
async getCoverageArea(): Promise<CoverageArea> {
330-
const result = await this.makeRequest<CoverageArea[]>(BmltEndpoint.GET_COVERAGE_AREA);
331-
return result[0];
340+
const result = await this.makeRequest<unknown[]>(BmltEndpoint.GET_COVERAGE_AREA);
341+
return transformCoverageArea(result[0]);
332342
}
333343

334344
/**

src/types/responses.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export interface Meeting {
88
/** Meeting ID */
99
id_bigint: string;
1010

11-
/** Weekday (1-7) as a numeric string */
12-
weekday_tinyint: string;
11+
/** Weekday (1=Sunday, 7=Saturday) */
12+
weekday_tinyint: number;
1313

14-
/** Venue type (1=In-person, 2=Virtual, 3=Hybrid) as a numeric string */
15-
venue_type: string;
14+
/** Venue type (1=In-person, 2=Virtual, 3=Hybrid) */
15+
venue_type: number;
1616

1717
/** Start time (24-hour format HH:MM:SS) */
1818
start_time: string;
@@ -56,14 +56,14 @@ export interface Meeting {
5656
/** Location nation */
5757
location_nation?: string;
5858

59-
/** Latitude as a numeric string */
60-
latitude: string;
59+
/** Latitude */
60+
latitude: number;
6161

62-
/** Longitude as a numeric string */
63-
longitude: string;
62+
/** Longitude */
63+
longitude: number;
6464

65-
/** Published status (0=unpublished, 1=published) as a numeric string */
66-
published: string;
65+
/** Published status (0=unpublished, 1=published) */
66+
published: number;
6767

6868
/** Email contact */
6969
email_contact?: string;
@@ -131,11 +131,11 @@ export interface Meeting {
131131
/** Root server URI (for aggregator mode) */
132132
root_server_uri?: string;
133133

134-
/** Distance from search point in km as a numeric string (when using geographic search; may be empty string otherwise) */
135-
distance_in_km?: string;
134+
/** Distance from search point in km (undefined when not a geographic search) */
135+
distance_in_km?: number;
136136

137-
/** Distance from search point in miles as a numeric string (when using geographic search; may be empty string otherwise) */
138-
distance_in_miles?: string;
137+
/** Distance from search point in miles (undefined when not a geographic search) */
138+
distance_in_miles?: number;
139139
}
140140

141141
export interface Format {
@@ -238,23 +238,23 @@ export interface ServerInfo {
238238
/** Server version (e.g. "4.2.0") */
239239
version: string;
240240

241-
/** Server version as integer string (e.g. "4002000") */
242-
versionInt: string;
241+
/** Server version as integer (e.g. 4002000) */
242+
versionInt: number;
243243

244244
/** Supported languages, comma-separated (e.g. "da,de,el,en,es") */
245245
langs: string;
246246

247247
/** Native language code (e.g. "en") */
248248
nativeLang: string;
249249

250-
/** Map center longitude as a numeric string */
251-
centerLongitude: string;
250+
/** Map center longitude */
251+
centerLongitude: number;
252252

253-
/** Map center latitude as a numeric string */
254-
centerLatitude: string;
253+
/** Map center latitude */
254+
centerLatitude: number;
255255

256-
/** Default map zoom level as a numeric string */
257-
centerZoom: string;
256+
/** Default map zoom level */
257+
centerZoom: number;
258258

259259
/** Default meeting duration (HH:MM:SS) */
260260
defaultDuration: string;
@@ -268,11 +268,11 @@ export interface ServerInfo {
268268
/** Distance units ("mi" or "km") */
269269
distanceUnits: string;
270270

271-
/** Semantic admin enabled flag as a numeric string ("0" or "1") */
272-
semanticAdmin: string;
271+
/** Semantic admin enabled flag (0 or 1) */
272+
semanticAdmin: number;
273273

274-
/** Number of changes tracked per meeting as a numeric string */
275-
changesPerMeeting: string;
274+
/** Number of changes tracked per meeting */
275+
changesPerMeeting: number;
276276

277277
/** Comma-separated list of states/provinces with meetings */
278278
meeting_states_and_provinces: string;
@@ -315,17 +315,17 @@ export interface ServerInfo {
315315
}
316316

317317
export interface CoverageArea {
318-
/** Northwest corner longitude as a numeric string */
319-
nw_corner_longitude: string;
318+
/** Northwest corner longitude */
319+
nw_corner_longitude: number;
320320

321-
/** Northwest corner latitude as a numeric string */
322-
nw_corner_latitude: string;
321+
/** Northwest corner latitude */
322+
nw_corner_latitude: number;
323323

324-
/** Southeast corner longitude as a numeric string */
325-
se_corner_longitude: string;
324+
/** Southeast corner longitude */
325+
se_corner_longitude: number;
326326

327-
/** Southeast corner latitude as a numeric string */
328-
se_corner_latitude: string;
327+
/** Southeast corner latitude */
328+
se_corner_latitude: number;
329329
}
330330

331331
export interface FieldKey {

src/utils/transforms.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Response transforms — BMLT serializes numeric fields as quoted strings.
3+
* These converters give consumers idiomatic typed numbers.
4+
*/
5+
6+
import type { Meeting, ServerInfo, CoverageArea } from '../types/responses';
7+
8+
const num = (v: unknown): number => (typeof v === 'number' ? v : parseFloat(v as string));
9+
10+
const optNum = (v: unknown): number | undefined => {
11+
if (v === undefined || v === null || v === '') return undefined;
12+
return num(v);
13+
};
14+
15+
export function transformMeeting(raw: unknown): Meeting {
16+
const r = raw as Record<string, unknown>;
17+
return {
18+
...(r as unknown as Meeting),
19+
weekday_tinyint: num(r.weekday_tinyint),
20+
venue_type: num(r.venue_type),
21+
published: num(r.published),
22+
latitude: num(r.latitude),
23+
longitude: num(r.longitude),
24+
distance_in_km: optNum(r.distance_in_km),
25+
distance_in_miles: optNum(r.distance_in_miles),
26+
};
27+
}
28+
29+
export function transformServerInfo(raw: unknown): ServerInfo {
30+
const r = raw as Record<string, unknown>;
31+
return {
32+
...(r as unknown as ServerInfo),
33+
versionInt: num(r.versionInt),
34+
centerLongitude: num(r.centerLongitude),
35+
centerLatitude: num(r.centerLatitude),
36+
centerZoom: num(r.centerZoom),
37+
semanticAdmin: num(r.semanticAdmin),
38+
changesPerMeeting: num(r.changesPerMeeting),
39+
};
40+
}
41+
42+
export function transformCoverageArea(raw: unknown): CoverageArea {
43+
const r = raw as Record<string, unknown>;
44+
return {
45+
nw_corner_longitude: num(r.nw_corner_longitude),
46+
nw_corner_latitude: num(r.nw_corner_latitude),
47+
se_corner_longitude: num(r.se_corner_longitude),
48+
se_corner_latitude: num(r.se_corner_latitude),
49+
};
50+
}

test/basic.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('BMLT Query Client - Basic Tests', () => {
6565

6666
expect(Array.isArray(virtualMeetings)).toBe(true);
6767
virtualMeetings.forEach(meeting => {
68-
expect(parseInt(meeting.venue_type.toString())).toBe(VenueType.VIRTUAL);
68+
expect(meeting.venue_type).toBe(VenueType.VIRTUAL);
6969
});
7070
});
7171

@@ -77,7 +77,7 @@ describe('BMLT Query Client - Basic Tests', () => {
7777

7878
expect(Array.isArray(mondayMeetings)).toBe(true);
7979
mondayMeetings.forEach(meeting => {
80-
expect(parseInt(meeting.weekday_tinyint.toString())).toBe(Weekday.MONDAY);
80+
expect(meeting.weekday_tinyint).toBe(Weekday.MONDAY);
8181
});
8282
});
8383

@@ -101,7 +101,7 @@ describe('BMLT Query Client - Basic Tests', () => {
101101
expect(Array.isArray(meetings)).toBe(true);
102102
meetings.forEach(meeting => {
103103
expect(meeting.distance_in_miles).toBeDefined();
104-
expect(parseFloat(meeting.distance_in_miles?.toString() || '0')).toBeLessThanOrEqual(2);
104+
expect(meeting.distance_in_miles ?? 0).toBeLessThanOrEqual(2);
105105
});
106106
});
107107

@@ -116,7 +116,7 @@ describe('BMLT Query Client - Basic Tests', () => {
116116
expect(Array.isArray(meetings)).toBe(true);
117117
meetings.forEach(meeting => {
118118
expect(meeting.distance_in_miles).toBeDefined();
119-
expect(parseFloat(meeting.distance_in_miles?.toString() || '0')).toBeLessThanOrEqual(1);
119+
expect(meeting.distance_in_miles ?? 0).toBeLessThanOrEqual(1);
120120
});
121121
});
122122

0 commit comments

Comments
 (0)