bmlt-query-client 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(wc -l /Users/pjoyce/workspace/git/bmlt-enabled/bmlt-query-client/src/**/*.ts /Users/pjoyce/workspace/git/bmlt-enabled/bmlt-query-client/test/*.ts)"
4
+ "Bash(npm view:*)",
5
+ "Bash(npm ls *)",
6
+ "Bash(curl -s https://raw.githubusercontent.com/qmhc/unplugin-dts/main/README.md)",
7
+ "Bash(curl -s https://raw.githubusercontent.com/qmhc/unplugin-dts/main/docs/en/migration-v4-to-v5.md)",
8
+ "Bash(npm run *)",
9
+ "Bash(npm test *)",
10
+ "Bash(tar -xzf bmlt-query-client-1.4.0.tgz)",
11
+ "Bash(grep -nA 12 '\"scripts\"' /Users/patrick/workspace/git/bmlt-enabled/bmlt-query-client/package.json)",
12
+ "Bash(grep -nA 3 \"interface Format\" /Users/patrick/workspace/git/bmlt-enabled/crumb-widget/node_modules/bmlt-query-client/dist/app.d.ts)",
13
+ "Bash(grep -nA 30 \"^function makeMeeting\\\\|^function rawMeeting\\\\|^function rawFormat\\\\|^function makeFormat\\\\|^ function makeGeoMeeting\\\\|^ function makeFormat\" src/tests/unit/format.test.ts src/tests/unit/data.svelte.test.ts src/tests/unit/Controls.svelte.test.ts)",
14
+ "Bash(grep -nA 30 \"^function makeMeeting\" src/tests/unit/MapView.svelte.test.ts src/tests/unit/MeetingDetail.svelte.test.ts src/tests/unit/MeetingList.svelte.test.ts)"
5
15
  ]
6
- },
7
- "disabledMcpjsonServers": [
8
- "laravel-boost"
9
- ]
16
+ }
10
17
  }
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.0] - 2026-09-13
10
+
11
+ ### Added
12
+
13
+ - Virtual-meeting ordering params on `SearchResultsParams`, mirroring the BMLT aggregator's timezone-aware "starting soonest" support: `sort_results_by_next_start` (order by each meeting's next occurrence as an absolute instant), `next_start_grace_minutes` (keep recently-started meetings sorting high), and `target_time_zone` (evaluate weekday/time-of-day filters in the reader's IANA zone). All three are aggregator-mode only and ignored on ordinary root servers.
14
+ - Fluent builder helpers `MeetingQueryBuilder.sortByNextStart(graceMinutes?)` and `.targetTimeZone(tz)`, plus `QuickSearch.virtualSoonest(tz?)` (virtual + hybrid, ordered soonest-first).
15
+
16
+ ## [1.4.2] - 2026-05-19
17
+
18
+ ### Fixed
19
+
20
+ - `countUniqueGroups` now matches crouton's long-standing definition: groups are identified by `(service_body, normalized meeting name, location)` — physical coordinates for in-person/hybrid (`venue_type !== 2`), virtual link + additional info for virtual (`venue_type === 2`). The previous algorithm keyed only on `(service_body, name)`, which under-counted whenever two distinct home groups happened to share a name but met at different locations (real-world example: MCANA returned 12 groups vs. crouton's 17 for the same 30 meetings). Bug; widget headers and downstream callers now agree with crouton.
21
+
9
22
  ## [1.4.1] - 2026-05-15
10
23
 
11
24
  ### Fixed
@@ -92,7 +105,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
92
105
  - Zero external runtime dependencies — p-queue and p-retry are bundled via Vite.
93
106
  - ES module only; native `fetch` API; TypeScript declarations included.
94
107
 
95
- [Unreleased]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.4.1...HEAD
108
+ [Unreleased]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.4.2...HEAD
109
+ [1.4.2]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.4.1...v1.4.2
96
110
  [1.4.1]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.4.0...v1.4.1
97
111
  [1.4.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.3.0...v1.4.0
98
112
  [1.3.0]: https://github.com/bmlt-enabled/bmlt-query-client/compare/v1.2.0...v1.3.0
package/CLAUDE.md ADDED
@@ -0,0 +1,39 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ TypeScript client library for querying BMLT (Basic Meeting List Tool) servers with built-in Nominatim geocoding. Published to NPM as `bmlt-query-client`. ESM-only, targets Node.js 20+ and modern browsers (ES2020).
8
+
9
+ ## Commands
10
+
11
+ - **Build:** `npm run build` (Vite → `dist/app.js` single ES module + type declarations)
12
+ - **Test:** `npm test` (Vitest, runs integration tests against live server `https://latest.aws.bmlt.app/main_server`)
13
+ - **Lint:** `npm run lint`
14
+ - **Format:** `npm run format` (Prettier) / `npm run format:check`
15
+ - **Type check:** `npm run type-check` (tsc --noEmit)
16
+ - **Clean:** `npm run clean` (removes dist/)
17
+
18
+ Tests hit a real BMLT server and Nominatim, so they require network access and have 15-second timeouts.
19
+
20
+ ## Architecture
21
+
22
+ **Entry point:** `src/index.ts` — re-exports everything from the modules below.
23
+
24
+ **Core modules:**
25
+
26
+ - `src/client/bmlt-client.ts` — **BmltClient** class. Main API client wrapping all BMLT endpoints (searchMeetings, getFormats, getServiceBodies, getChanges, getFieldKeys, getFieldValues, getNAWSDump, getServerInfo, getCoverageArea). Also provides geocoded search methods (searchMeetingsByAddress, searchMeetingsByCoordinates) and reverse geocoding.
27
+ - `src/client/query-builder.ts` — **MeetingQueryBuilder** (fluent/chainable query API) and **QuickSearch** (convenience presets like today(), weekend(), virtual()). QueryBuilder holds a reference to BmltClient and delegates execution.
28
+ - `src/services/geocoding.ts` — **GeocodingService** wrapping Nominatim OpenStreetMap API with rate limiting (p-queue) and retry logic (p-retry).
29
+ - `src/utils/url-builder.ts` — URL construction and parameter validation for BMLT API calls.
30
+ - `src/utils/errors.ts` — **BmltQueryError** custom error class with BmltErrorType enum, plus ErrorFactory, ErrorHandler, and RetryHandler utilities.
31
+ - `src/types/` — TypeScript interfaces and enums: BmltDataFormat, BmltEndpoint, Weekday (1-7, Sun-Sat), VenueType (1-3), SortKey, Language, request/response interfaces.
32
+
33
+ **Build output:** Vite bundles all dependencies (including p-queue, p-retry) into a single `dist/app.js` with no externals, plus `dist/app.d.ts` type declarations.
34
+
35
+ ## Code Style
36
+
37
+ - Strict TypeScript (`strict: true` in tsconfig)
38
+ - Prettier: single quotes, trailing commas (es5), 100 char print width, 2-space indent, no semicolons optional (semi: true)
39
+ - ESLint: prefer-const, no-var enforced
package/README.md CHANGED
@@ -183,6 +183,27 @@ const todaysVirtualMeetings = await quickSearch
183
183
  .execute();
184
184
  ```
185
185
 
186
+ ### Virtual Meetings ("starting soonest")
187
+
188
+ On an aggregator (`aggregator_mode_enabled`), the server can order virtual/hybrid
189
+ meetings by whichever starts soonest across every timezone — handy for a
190
+ "what can I join right now?" list. `QuickSearch.virtualSoonest(tz)` wires this up:
191
+
192
+ ```javascript
193
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // e.g. 'America/New_York'
194
+
195
+ const result = await new QuickSearch(client)
196
+ .virtualSoonest(tz) // venue_types=[2,3] + sort_results_by_next_start + target_time_zone
197
+ .sortByNextStart(15) // optional: keep meetings that started ≤15 min ago near the top
198
+ .executeWithFormats();
199
+ ```
200
+
201
+ These ordering params are **aggregator-mode only** — ordinary root servers ignore
202
+ `sort_results_by_next_start`, `next_start_grace_minutes`, and `target_time_zone`,
203
+ returning the venue-filtered meetings in their usual order. `target_time_zone`
204
+ affects only filtering/ordering; each meeting's `start_time`/`weekday_tinyint`
205
+ still come back in its own local time, so convert client-side for display.
206
+
186
207
  ### Raw Query
187
208
 
188
209
  When you need to pass a BMLT query string exactly as-is — including parameters like `meeting_key_value[]` that match multiple values — use `rawQuery`:
package/dist/app.d.ts CHANGED
@@ -304,9 +304,15 @@ export declare interface Coordinates {
304
304
  /**
305
305
  * Count unique groups across a list of meetings.
306
306
  *
307
- * A "group" is identified by the combination of service body and meeting name
308
- * (case-insensitive, trimmed), so multiple weekly meetings of the same group
309
- * count once.
307
+ * A "group" is identified by the combination of service body, meeting name
308
+ * (case-insensitive, trimmed), and meeting location physical coordinates
309
+ * for in-person/hybrid (venue_type !== 2), or the virtual meeting link plus
310
+ * additional info for virtual (venue_type === 2). Multiple weekly meetings of
311
+ * the same group count once; two distinct groups that happen to share a name
312
+ * but meet at different locations count separately. Matches the long-standing
313
+ * "group_id" definition used by crouton.
314
+ *
315
+ * Meetings missing a service body or name are skipped.
310
316
  */
311
317
  export declare function countUniqueGroups(meetings: Meeting[]): number;
312
318
 
@@ -791,6 +797,19 @@ export declare class MeetingQueryBuilder {
791
797
  * Sort by distance (requires geographic search)
792
798
  */
793
799
  sortByDistance(): this;
800
+ /**
801
+ * Order results by each meeting's next upcoming start as an absolute instant
802
+ * ("starting soonest" across timezones). Optionally pass a grace window in
803
+ * minutes so recently-started meetings still sort near the top.
804
+ * Aggregator-mode only — ignored on ordinary root servers.
805
+ */
806
+ sortByNextStart(graceMinutes?: number): this;
807
+ /**
808
+ * Evaluate weekday/time-of-day filters against each meeting's next occurrence
809
+ * in the given IANA time zone (e.g. `'America/New_York'`).
810
+ * Aggregator-mode only — ignored on ordinary root servers.
811
+ */
812
+ targetTimeZone(tz: string): this;
794
813
  /**
795
814
  * Set pagination
796
815
  */
@@ -937,6 +956,14 @@ export declare class QuickSearch {
937
956
  * Search for in-person meetings only
938
957
  */
939
958
  inPerson(): MeetingQueryBuilder;
959
+ /**
960
+ * Find virtual + hybrid meetings ordered by which starts soonest across
961
+ * timezones. Pass the reader's IANA time zone (e.g. from
962
+ * `Intl.DateTimeFormat().resolvedOptions().timeZone`) to reckon any further
963
+ * weekday/time filters in their local zone. Aggregator-mode only — on
964
+ * ordinary root servers the ordering params are ignored.
965
+ */
966
+ virtualSoonest(tz?: string): MeetingQueryBuilder;
940
967
  /**
941
968
  * Search by meeting name or location
942
969
  */
@@ -1020,6 +1047,26 @@ export declare interface SearchResultsParams extends BaseSearchParams {
1020
1047
  geo_width_km?: number;
1021
1048
  /** Sort results by distance when using geographic search */
1022
1049
  sort_results_by_distance?: boolean;
1050
+ /**
1051
+ * Order results by each meeting's next upcoming start as an absolute instant
1052
+ * (timezone-aware "starting soonest"), built for virtual meetings spanning
1053
+ * timezones. Aggregator-mode only — ignored on ordinary root servers.
1054
+ */
1055
+ sort_results_by_next_start?: boolean;
1056
+ /**
1057
+ * Minutes (0–1440) to shift the "now" reference backward when using
1058
+ * {@link sort_results_by_next_start}, so recently-started / in-progress
1059
+ * meetings still sort near the top. Aggregator-mode only.
1060
+ */
1061
+ next_start_grace_minutes?: number;
1062
+ /**
1063
+ * IANA time zone (e.g. `'America/New_York'`) the reader is in. When set, the
1064
+ * `weekdays` and start/end time-of-day filters are evaluated against each
1065
+ * meeting's next occurrence converted into this zone. Aggregator-mode only —
1066
+ * ignored on ordinary root servers. Note: this does not change the returned
1067
+ * `start_time`/`weekday_tinyint`, which remain the meeting's native values.
1068
+ */
1069
+ target_time_zone?: string;
1023
1070
  /** Search for specific field value */
1024
1071
  meeting_key?: string;
1025
1072
  /** The value to search for; pass an array to match any of multiple values */