Skip to content

Commit 94b28eb

Browse files
committed
optionally support type-safe Tags and Key
1 parent 08f8766 commit 94b28eb

7 files changed

Lines changed: 54 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- Optionally support type-safe `Tags`. `Tags` is currently defined as `Record<string, string>`. If you want additional type-safety, you can specify the keys are the allowed. See the docs for more info.
11+
1012
## 2.6.1 (2025-05-19)
1113

1214
- Fix configuration issue which broke the type-definitions in the previous release

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ All methods return promises. Examples requests and responses are available for a
9696
- [`getApiCapabilities()`](./examples/getApiCapabilities.md)
9797
- [`getMapData`](./examples/getMapData.md)
9898
- [Using the Development Server](./examples/dev-server.md)
99+
- [Additional type-safety for Keys/Tags](./examples/type-safe-tags.md)
99100
- Authentication (browser only, not available in NodeJS)
100101
- `login`
101102
- `logout`

examples/type-safe-tags.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
See the JSDoc comment for `Key` in [src/types/general.ts](../src/types/general.ts)

src/api/changesets/uploadChangeset.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface UploadChunkInfo {
2222
* @returns the changeset number
2323
*/
2424
export async function uploadChangeset(
25-
tags: { [key: string]: string },
25+
tags: Tags,
2626
diff: OsmChange,
2727
options?: FetchOptions & {
2828
/**

src/types/changesets.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { OsmFeature } from "./features";
2+
import type { Tags } from "./general";
23

34
export type ChangesetComment = {
45
id: number;
@@ -35,9 +36,7 @@ export type Changeset = {
3536
max_lon: number;
3637
uid: number;
3738
user: string;
38-
tags: {
39-
[key: string]: string;
40-
};
39+
tags: Tags;
4140
/** the `discussion` attribute is only included in the `getChangeset` API */
4241
discussion?: ChangesetComment[];
4342
};

src/types/features.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Tags } from "./general";
2+
13
export type OsmFeatureType = "node" | "way" | "relation";
24

35
/** these attributes exist on nodes, ways, and relations */
@@ -11,9 +13,7 @@ export type OsmBaseFeature = {
1113
version: number;
1214
uid: number;
1315

14-
tags?: {
15-
[key: string]: string;
16-
};
16+
tags?: Tags;
1717

1818
/** if false, it means the feature has been deleted */
1919
visible?: false;

src/types/general.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,50 @@
1+
declare global {
2+
namespace OsmApi {
3+
/**
4+
* use this interface to get additional typesafety, see the documentation
5+
* on {@link Key} for more info.
6+
*/
7+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- intentional
8+
interface Keys {}
9+
}
10+
}
11+
12+
/**
13+
* By default, this library defines {@link Tags} to be `Record<string, string>`. This
14+
* means that you don't get any typesafety for OSM keys/tags.
15+
*
16+
* There are two methods to make this more strict:
17+
*
18+
* 1. enable TypeScript's `noPropertyAccessFromIndexSignature` along with eslint's `dot-notation`.
19+
* This is not perfect, but it will force you to use `tags[KEY]` instead of `tags.key` which
20+
* makes the hardcoded keys more visible.
21+
*
22+
* 2. Declare a string union for every permitted osm key. For example:
23+
* ```ts
24+
* declare global {
25+
* namespace OsmApi {
26+
* interface Keys {
27+
* keys: 'amenity' | 'highway';
28+
* }
29+
* }
30+
* }
31+
* export {};
32+
* ```
33+
*
34+
* Regardless of what method you use, it also makes sense to enable TypeScript's
35+
* `noUncheckedIndexedAccess` option.
36+
*/
37+
export type Key = OsmApi.Keys extends { keys: string }
38+
? OsmApi.Keys["keys"]
39+
: string;
40+
41+
export type Tags = OsmApi.Keys extends { keys: string }
42+
? Partial<Record<Key, string>>
43+
: Record<string, string>;
44+
145
export type BBox = readonly [
246
minLng: number,
347
minLat: number,
448
maxLng: number,
549
maxLat: number,
650
];
7-
8-
export type Tags = Record<string, string>;

0 commit comments

Comments
 (0)