@chelone/core 0.6.0 → 0.7.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.
Files changed (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -1,39 +1,38 @@
1
+ // src/geo/map-style.ts
2
+ /** MapLibre has no `{s}` support, so a template with one becomes three URLs. */
1
3
  function expandTileUrl(url) {
2
- if (!url.includes("{s}")) return [url];
3
- return ["a", "b", "c"].map((host) => url.replaceAll("{s}", host));
4
+ if (!url.includes("{s}")) return [url];
5
+ return [
6
+ "a",
7
+ "b",
8
+ "c"
9
+ ].map((host) => url.replaceAll("{s}", host));
4
10
  }
5
- function rasterTileStyle({
6
- tileUrl,
7
- attribution,
8
- maxZoom = 19
9
- }) {
10
- if (!tileUrl) throw new TypeError("rasterTileStyle: tileUrl is required");
11
- if (!attribution) {
12
- throw new TypeError("rasterTileStyle: attribution is required");
13
- }
14
- return {
15
- version: 8,
16
- sources: {
17
- basemap: {
18
- type: "raster",
19
- tiles: expandTileUrl(tileUrl),
20
- tileSize: 256,
21
- maxzoom: maxZoom,
22
- attribution
23
- }
24
- },
25
- layers: [{ id: "basemap", type: "raster", source: "basemap" }]
26
- };
11
+ function rasterTileStyle({ tileUrl, attribution, maxZoom = 19 }) {
12
+ if (!tileUrl) throw new TypeError("rasterTileStyle: tileUrl is required");
13
+ if (!attribution) throw new TypeError("rasterTileStyle: attribution is required");
14
+ return {
15
+ version: 8,
16
+ sources: { basemap: {
17
+ type: "raster",
18
+ tiles: expandTileUrl(tileUrl),
19
+ tileSize: 256,
20
+ maxzoom: maxZoom,
21
+ attribution
22
+ } },
23
+ layers: [{
24
+ id: "basemap",
25
+ type: "raster",
26
+ source: "basemap"
27
+ }]
28
+ };
27
29
  }
30
+ /** `[lat, lng]` (what components take) to MapLibre's `[lng, lat]`. */
28
31
  function latLngToLngLat(latLng) {
29
- return [latLng[1], latLng[0]];
32
+ return [latLng[1], latLng[0]];
30
33
  }
34
+ /** MapLibre's `[lng, lat]` to `[lat, lng]`. */
31
35
  function lngLatToLatLng(lngLat) {
32
- return [lngLat[1], lngLat[0]];
36
+ return [lngLat[1], lngLat[0]];
33
37
  }
34
- export {
35
- expandTileUrl,
36
- latLngToLngLat,
37
- lngLatToLatLng,
38
- rasterTileStyle
39
- };
38
+ export { expandTileUrl, latLngToLngLat, lngLatToLatLng, rasterTileStyle };
@@ -1,47 +1,50 @@
1
1
  import { defineMessages } from "../i18n/messages.js";
2
- const mapMessages = defineMessages({
3
- "chelone.map.load_failed": "The map could not be loaded",
4
- "chelone.map.no_coordinates": "No coordinates",
5
- "chelone.map.no_geometry": "No geometry",
6
- "chelone.map.shape_not_shown": "Part of this shape cannot be shown on the map",
7
- "chelone.map.snap_failed": "The line could not be snapped to roads",
8
- "chelone.map.toolbar": "Map tools",
9
- "chelone.map.tool.draw_point": "Draw a point",
10
- "chelone.map.tool.draw_line": "Draw a line",
11
- "chelone.map.tool.draw_polygon": "Draw a polygon",
12
- "chelone.map.tool.draw_rectangle": "Draw a rectangle",
13
- "chelone.map.tool.draw_circle": "Draw a circle",
14
- "chelone.map.tool.edit": "Edit",
15
- "chelone.map.tool.move": "Move",
16
- "chelone.map.tool.delete": "Delete",
17
- "chelone.map.tool.cut": "Cut a hole",
18
- "chelone.map.search_placeholder": "Search for a place",
19
- "chelone.map.search_button": "Search",
20
- "chelone.map.searching": "Searching...",
21
- "chelone.map.no_results": "No results",
22
- "chelone.map.search_failed": "The search failed, try again shortly",
23
- "chelone.map.simplify.tolerance": "Tolerance: {value}",
24
- "chelone.map.simplify.default": "Default",
25
- "chelone.map.simplify.high": "High",
26
- "chelone.map.osm.subtract": "Subtract OpenStreetMap areas",
27
- "chelone.map.osm.add": "Add OpenStreetMap areas",
28
- "chelone.map.osm.no_sources": "No OpenStreetMap sources configured",
29
- "chelone.map.osm.no_polygon": "Draw a polygon first",
30
- "chelone.map.osm.no_match": "No matching OpenStreetMap area found",
31
- "chelone.map.osm.covered": "The polygon is already covered",
32
- "chelone.map.osm.subtracted": "Subtracted {area}",
33
- "chelone.map.osm.added": "Added {area}",
34
- "chelone.map.osm.would_split": "The result has more than one piece, so the polygon was left as it was",
35
- "chelone.map.osm.union_empty": "The union is empty",
36
- "chelone.map.osm.error": "OpenStreetMap request failed",
37
- "chelone.map.osm.rate_limited": "OpenStreetMap is rate limiting requests, try again shortly",
38
- "chelone.validation.latitude": "Must be a latitude between -90 and 90",
39
- "chelone.validation.longitude": "Must be a longitude between -180 and 180",
40
- "chelone.validation.minVertices": "Must have at least {min} vertices",
41
- "chelone.validation.maxVertices": "Must have {max} vertices or fewer",
42
- "chelone.validation.minArea": "Area {area} is below the minimum of {min}",
43
- "chelone.validation.selfIntersection": "Must not cross itself"
2
+ // src/geo/messages.ts
3
+ /**
4
+ * English text for the map add-on. Spread next to `defaultMessages`:
5
+ * `{ ...defaultMessages, ...mapMessages }`. Shares no key with it.
6
+ */
7
+ var mapMessages = defineMessages({
8
+ "chelone.map.load_failed": "The map could not be loaded",
9
+ "chelone.map.no_coordinates": "No coordinates",
10
+ "chelone.map.no_geometry": "No geometry",
11
+ "chelone.map.shape_not_shown": "Part of this shape cannot be shown on the map",
12
+ "chelone.map.snap_failed": "The line could not be snapped to roads",
13
+ "chelone.map.toolbar": "Map tools",
14
+ "chelone.map.tool.draw_point": "Draw a point",
15
+ "chelone.map.tool.draw_line": "Draw a line",
16
+ "chelone.map.tool.draw_polygon": "Draw a polygon",
17
+ "chelone.map.tool.draw_rectangle": "Draw a rectangle",
18
+ "chelone.map.tool.draw_circle": "Draw a circle",
19
+ "chelone.map.tool.edit": "Edit",
20
+ "chelone.map.tool.move": "Move",
21
+ "chelone.map.tool.delete": "Delete",
22
+ "chelone.map.tool.cut": "Cut a hole",
23
+ "chelone.map.search_placeholder": "Search for a place",
24
+ "chelone.map.search_button": "Search",
25
+ "chelone.map.searching": "Searching...",
26
+ "chelone.map.no_results": "No results",
27
+ "chelone.map.search_failed": "The search failed, try again shortly",
28
+ "chelone.map.simplify.tolerance": "Tolerance: {value}",
29
+ "chelone.map.simplify.default": "Default",
30
+ "chelone.map.simplify.high": "High",
31
+ "chelone.map.osm.subtract": "Subtract OpenStreetMap areas",
32
+ "chelone.map.osm.add": "Add OpenStreetMap areas",
33
+ "chelone.map.osm.no_sources": "No OpenStreetMap sources configured",
34
+ "chelone.map.osm.no_polygon": "Draw a polygon first",
35
+ "chelone.map.osm.no_match": "No matching OpenStreetMap area found",
36
+ "chelone.map.osm.covered": "The polygon is already covered",
37
+ "chelone.map.osm.subtracted": "Subtracted {area}",
38
+ "chelone.map.osm.added": "Added {area}",
39
+ "chelone.map.osm.would_split": "The result has more than one piece, so the polygon was left as it was",
40
+ "chelone.map.osm.union_empty": "The union is empty",
41
+ "chelone.map.osm.error": "OpenStreetMap request failed",
42
+ "chelone.map.osm.rate_limited": "OpenStreetMap is rate limiting requests, try again shortly",
43
+ "chelone.validation.latitude": "Must be a latitude between -90 and 90",
44
+ "chelone.validation.longitude": "Must be a longitude between -180 and 180",
45
+ "chelone.validation.minVertices": "Must have at least {min} vertices",
46
+ "chelone.validation.maxVertices": "Must have {max} vertices or fewer",
47
+ "chelone.validation.minArea": "Area {area} is below the minimum of {min}",
48
+ "chelone.validation.selfIntersection": "Must not cross itself"
44
49
  });
45
- export {
46
- mapMessages
47
- };
50
+ export { mapMessages };
@@ -1,87 +1,95 @@
1
+ import { isRecord } from "../guards.js";
1
2
  import { createThrottle } from "./throttle.js";
3
+ // src/geo/nominatim.ts
4
+ /** `Number(null)` and `Number("")` are 0; a missing coordinate must not be. */
2
5
  function toNumber(value) {
3
- if (typeof value === "number") return value;
4
- if (typeof value === "string" && value.trim() !== "") return Number(value);
5
- return Number.NaN;
6
+ if (typeof value === "number") return value;
7
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
8
+ return NaN;
6
9
  }
7
10
  function toResult(hit) {
8
- if (typeof hit.display_name !== "string") return null;
9
- const lat = toNumber(hit.lat);
10
- const lng = toNumber(hit.lon);
11
- if (!Number.isFinite(lat) || !Number.isFinite(lng)) return null;
12
- const result = { displayName: hit.display_name, lat, lng };
13
- if (Array.isArray(hit.boundingbox) && hit.boundingbox.length === 4) {
14
- const [minLat, maxLat, minLon, maxLon] = hit.boundingbox.map(toNumber);
15
- const bbox = [minLon, minLat, maxLon, maxLat];
16
- if (bbox.every(Number.isFinite)) result.bbox = bbox;
17
- }
18
- if (typeof hit.type === "string") result.type = hit.type;
19
- result.raw = hit;
20
- return result;
11
+ if (typeof hit.display_name !== "string") return null;
12
+ const lat = toNumber(hit.lat);
13
+ const lng = toNumber(hit.lon);
14
+ if (!Number.isFinite(lat) || !Number.isFinite(lng)) return null;
15
+ const result = {
16
+ displayName: hit.display_name,
17
+ lat,
18
+ lng
19
+ };
20
+ if (Array.isArray(hit.boundingbox) && hit.boundingbox.length === 4) {
21
+ const [minLat = NaN, maxLat = NaN, minLon = NaN, maxLon = NaN] = hit.boundingbox.map(toNumber);
22
+ const bbox = [
23
+ minLon,
24
+ minLat,
25
+ maxLon,
26
+ maxLat
27
+ ];
28
+ if (bbox.every(Number.isFinite)) result.bbox = bbox;
29
+ }
30
+ if (typeof hit.type === "string") result.type = hit.type;
31
+ result.raw = hit;
32
+ return result;
21
33
  }
22
34
  function createNominatimProvider(options) {
23
- const { endpoint } = options;
24
- if (typeof endpoint !== "string" || endpoint.trim() === "") {
25
- throw new TypeError(
26
- 'createNominatimProvider: the "endpoint" option is required'
27
- );
28
- }
29
- const base = endpoint.trim().replace(/\/+$/, "");
30
- const language = options.language ?? "en";
31
- const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
32
- const throttle = createThrottle({
33
- minIntervalMs: options.minIntervalMs ?? 1e3,
34
- now: options.now,
35
- sleep: options.sleep
36
- });
37
- const get = async (path, params, signal) => {
38
- await throttle.wait(signal);
39
- return doFetch(`${base}/${path}?${params}`, {
40
- headers: { "Accept-Language": language },
41
- signal
42
- });
43
- };
44
- return {
45
- name: "nominatim",
46
- cacheKey: `nominatim|${base}|${language}`,
47
- typeahead: options.typeahead ?? false,
48
- async search(query, searchOptions = {}) {
49
- const { signal, limit = 10, countryCodes, viewBox } = searchOptions;
50
- const params = new URLSearchParams({
51
- q: query,
52
- format: "json",
53
- limit: String(limit)
54
- });
55
- if (countryCodes?.length) {
56
- params.set("countrycodes", countryCodes.join(","));
57
- }
58
- if (viewBox) params.set("viewbox", viewBox.join(","));
59
- const response = await get("search", params, signal);
60
- if (!response.ok) throw new Error(`Nominatim search ${response.status}`);
61
- const hits = await response.json();
62
- if (!Array.isArray(hits)) return [];
63
- return hits.flatMap((hit) => toResult(hit) ?? []);
64
- },
65
- async reverse(lat, lng, reverseOptions = {}) {
66
- const { signal } = reverseOptions;
67
- const params = new URLSearchParams({
68
- lat: String(lat),
69
- lon: String(lng),
70
- format: "json"
71
- });
72
- try {
73
- const response = await get("reverse", params, signal);
74
- if (!response.ok) return null;
75
- const body = await response.json();
76
- if (!body || body.error) return null;
77
- return toResult(body);
78
- } catch (error) {
79
- if (signal?.aborted) throw error;
80
- return null;
81
- }
82
- }
83
- };
35
+ const { endpoint } = options;
36
+ if (typeof endpoint !== "string" || endpoint.trim() === "") throw new TypeError("createNominatimProvider: the \"endpoint\" option is required");
37
+ const base = endpoint.trim().replace(/\/+$/, "");
38
+ const language = options.language ?? "en";
39
+ const doFetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
40
+ const throttle = createThrottle({
41
+ minIntervalMs: options.minIntervalMs ?? 1e3,
42
+ now: options.now,
43
+ sleep: options.sleep
44
+ });
45
+ const get = async (path, params, signal) => {
46
+ await throttle.wait(signal);
47
+ return doFetch(`${base}/${path}?${params}`, {
48
+ headers: { "Accept-Language": language },
49
+ signal
50
+ });
51
+ };
52
+ return {
53
+ name: "nominatim",
54
+ cacheKey: `nominatim|${base}|${language}`,
55
+ typeahead: options.typeahead ?? false,
56
+ async search(query, searchOptions = {}) {
57
+ const { signal, limit = 10, countryCodes, viewBox } = searchOptions;
58
+ const params = new URLSearchParams({
59
+ q: query,
60
+ format: "json",
61
+ limit: String(limit)
62
+ });
63
+ if (countryCodes?.length) params.set("countrycodes", countryCodes.join(","));
64
+ if (viewBox) params.set("viewbox", viewBox.join(","));
65
+ const response = await get("search", params, signal);
66
+ if (!response.ok) throw new Error(`Nominatim search ${response.status}`);
67
+ const hits = await response.json();
68
+ if (!Array.isArray(hits)) return [];
69
+ return hits.flatMap((hit) => {
70
+ if (!isRecord(hit)) return [];
71
+ const result = toResult(hit);
72
+ return result ? [result] : [];
73
+ });
74
+ },
75
+ async reverse(lat, lng, reverseOptions = {}) {
76
+ const { signal } = reverseOptions;
77
+ const params = new URLSearchParams({
78
+ lat: String(lat),
79
+ lon: String(lng),
80
+ format: "json"
81
+ });
82
+ try {
83
+ const response = await get("reverse", params, signal);
84
+ if (!response.ok) return null;
85
+ const body = await response.json();
86
+ if (!isRecord(body) || body.error) return null;
87
+ return toResult(body);
88
+ } catch (error) {
89
+ if (signal?.aborted) throw error;
90
+ return null;
91
+ }
92
+ }
93
+ };
84
94
  }
85
- export {
86
- createNominatimProvider
87
- };
95
+ export { createNominatimProvider };
@@ -1,4 +1,5 @@
1
- import type { GeoBBox } from "./geojson.js";
1
+ import type { JsonObject } from "../guards.js";
2
+ import type { LngLatBox } from "./bounds.js";
2
3
  import type { OsmTagFilter, OsmTagInput } from "./osm-tags.js";
3
4
  export type OsmElementType = "way" | "relation";
4
5
  export interface OsmPreset {
@@ -74,7 +75,7 @@ export type OsmPresetName = keyof typeof OSM_PRESETS;
74
75
  * The first of `names` with a filter that accepts the feature's tags, so an
75
76
  * adapter can read its `bufferLinesMeters`; `null` when none does.
76
77
  */
77
- export declare function findPresetForTags(tags: Readonly<Record<string, unknown>>, names: readonly OsmPresetName[]): OsmPreset | null;
78
+ export declare function findPresetForTags(tags: Readonly<JsonObject>, names: readonly OsmPresetName[]): OsmPreset | null;
78
79
  export interface OverpassQuerySources {
79
80
  presets?: readonly OsmPresetName[];
80
81
  tags?: readonly OsmTagInput[];
@@ -83,4 +84,4 @@ export interface OverpassQuerySources {
83
84
  * Overpass QL for every feature matching a preset or a tag inside `bbox`
84
85
  * (`[west, south, east, north]`, written south, west, north, east).
85
86
  */
86
- export declare function buildOverpassQuery(sources: OverpassQuerySources, bbox: GeoBBox): string;
87
+ export declare function buildOverpassQuery(sources: OverpassQuerySources, bbox: LngLatBox): string;
@@ -1,98 +1,120 @@
1
1
  import { tagToFilter } from "./osm-tags.js";
2
- const anyOf = (key) => ({ filters: [{ key, any: true }] });
3
- const OSM_PRESETS = {
4
- water: {
5
- filters: [
6
- { key: "natural", values: ["water", "bay", "strait"] },
7
- { key: "waterway", any: true }
8
- ]
9
- },
10
- buildings: anyOf("building"),
11
- forest: {
12
- filters: [
13
- { key: "natural", values: ["wood", "forest"] },
14
- { key: "landuse", value: "forest" }
15
- ]
16
- },
17
- roads: {
18
- filters: [
19
- { key: "highway", values: ["motorway", "trunk", "primary", "secondary"] }
20
- ],
21
- bufferLinesMeters: 15,
22
- elementTypes: ["way"]
23
- },
24
- natural: anyOf("natural"),
25
- building: anyOf("building"),
26
- landuse: anyOf("landuse"),
27
- amenity: anyOf("amenity"),
28
- leisure: anyOf("leisure"),
29
- boundary: anyOf("boundary"),
30
- place: anyOf("place"),
31
- man_made: anyOf("man_made"),
32
- shop: anyOf("shop"),
33
- tourism: anyOf("tourism"),
34
- historic: anyOf("historic"),
35
- highway: anyOf("highway"),
36
- railway: { ...anyOf("railway"), bufferLinesMeters: 5, elementTypes: ["way"] },
37
- waterway: { ...anyOf("waterway"), bufferLinesMeters: 5 },
38
- barrier: { ...anyOf("barrier"), bufferLinesMeters: 3 },
39
- power: { ...anyOf("power"), bufferLinesMeters: 3 }
2
+ // src/geo/osm-presets.ts
3
+ var anyOf = (key) => ({ filters: [{
4
+ key,
5
+ any: true
6
+ }] });
7
+ var OSM_PRESETS = {
8
+ water: { filters: [{
9
+ key: "natural",
10
+ values: [
11
+ "water",
12
+ "bay",
13
+ "strait"
14
+ ]
15
+ }, {
16
+ key: "waterway",
17
+ any: true
18
+ }] },
19
+ buildings: anyOf("building"),
20
+ forest: { filters: [{
21
+ key: "natural",
22
+ values: ["wood", "forest"]
23
+ }, {
24
+ key: "landuse",
25
+ value: "forest"
26
+ }] },
27
+ roads: {
28
+ filters: [{
29
+ key: "highway",
30
+ values: [
31
+ "motorway",
32
+ "trunk",
33
+ "primary",
34
+ "secondary"
35
+ ]
36
+ }],
37
+ bufferLinesMeters: 15,
38
+ elementTypes: ["way"]
39
+ },
40
+ natural: anyOf("natural"),
41
+ building: anyOf("building"),
42
+ landuse: anyOf("landuse"),
43
+ amenity: anyOf("amenity"),
44
+ leisure: anyOf("leisure"),
45
+ boundary: anyOf("boundary"),
46
+ place: anyOf("place"),
47
+ man_made: anyOf("man_made"),
48
+ shop: anyOf("shop"),
49
+ tourism: anyOf("tourism"),
50
+ historic: anyOf("historic"),
51
+ highway: anyOf("highway"),
52
+ railway: {
53
+ ...anyOf("railway"),
54
+ bufferLinesMeters: 5,
55
+ elementTypes: ["way"]
56
+ },
57
+ waterway: {
58
+ ...anyOf("waterway"),
59
+ bufferLinesMeters: 5
60
+ },
61
+ barrier: {
62
+ ...anyOf("barrier"),
63
+ bufferLinesMeters: 3
64
+ },
65
+ power: {
66
+ ...anyOf("power"),
67
+ bufferLinesMeters: 3
68
+ }
40
69
  };
41
70
  function accepts(filter, tags) {
42
- const raw = tags[filter.key];
43
- if (raw === void 0 || raw === null) return false;
44
- if ("any" in filter) return true;
45
- const actual = String(raw);
46
- return "values" in filter ? filter.values.includes(actual) : filter.value === actual;
71
+ const raw = tags[filter.key];
72
+ if (raw === void 0 || raw === null) return false;
73
+ if ("any" in filter) return true;
74
+ const actual = String(raw);
75
+ return "values" in filter ? filter.values.includes(actual) : filter.value === actual;
47
76
  }
77
+ /**
78
+ * The first of `names` with a filter that accepts the feature's tags, so an
79
+ * adapter can read its `bufferLinesMeters`; `null` when none does.
80
+ */
48
81
  function findPresetForTags(tags, names) {
49
- for (const name of names) {
50
- const preset = OSM_PRESETS[name];
51
- if (preset.filters.some((filter) => accepts(filter, tags))) return preset;
52
- }
53
- return null;
82
+ for (const name of names) {
83
+ const preset = OSM_PRESETS[name];
84
+ if (preset.filters.some((filter) => accepts(filter, tags))) return preset;
85
+ }
86
+ return null;
54
87
  }
88
+ /** Escapes for an Overpass string; a newline cannot be escaped, so it throws. */
55
89
  function quote(text) {
56
- if (/[\r\n]/.test(text)) {
57
- throw new TypeError("An OSM tag cannot contain a newline");
58
- }
59
- return `"${text.replace(/[\\"]/g, "\\$&")}"`;
90
+ if (/[\r\n]/.test(text)) throw new TypeError("An OSM tag cannot contain a newline");
91
+ return `"${text.replace(/[\\"]/g, "\\$&")}"`;
60
92
  }
61
- const regexLiteral = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
93
+ /** A regex alternative must match its text literally. */
94
+ var regexLiteral = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62
95
  function filterToSelector(filter) {
63
- const key = quote(filter.key);
64
- if ("any" in filter) return `[${key}]`;
65
- if ("values" in filter) {
66
- const alternatives = filter.values.map(regexLiteral).join("|");
67
- return `[${key}~${quote(`^(${alternatives})$`)}]`;
68
- }
69
- return `[${key}=${quote(filter.value)}]`;
96
+ const key = quote(filter.key);
97
+ if ("any" in filter) return `[${key}]`;
98
+ if ("values" in filter) return `[${key}~${quote(`^(${filter.values.map(regexLiteral).join("|")})$`)}]`;
99
+ return `[${key}=${quote(filter.value)}]`;
70
100
  }
101
+ /**
102
+ * Overpass QL for every feature matching a preset or a tag inside `bbox`
103
+ * (`[west, south, east, north]`, written south, west, north, east).
104
+ */
71
105
  function buildOverpassQuery(sources, bbox) {
72
- const [west, south, east, north] = bbox;
73
- const area = `(${south},${west},${north},${east})`;
74
- const clauses = [];
75
- const add = (filter, types) => {
76
- const selector = filterToSelector(filter);
77
- for (const type of types) clauses.push(` ${type}${selector}${area};`);
78
- };
79
- for (const name of sources.presets ?? []) {
80
- const preset = OSM_PRESETS[name];
81
- for (const filter of preset.filters) {
82
- add(filter, preset.elementTypes ?? ["way", "relation"]);
83
- }
84
- }
85
- for (const tag of sources.tags ?? [])
86
- add(tagToFilter(tag), ["way", "relation"]);
87
- const union = clauses.length ? `(
88
- ${clauses.join("\n")}
89
- );` : "();";
90
- return `[out:json][timeout:25];
91
- ${union}
92
- out geom;`;
106
+ const [west, south, east, north] = bbox;
107
+ const area = `(${south},${west},${north},${east})`;
108
+ const clauses = [];
109
+ const add = (filter, types) => {
110
+ const selector = filterToSelector(filter);
111
+ for (const type of types) clauses.push(` ${type}${selector}${area};`);
112
+ };
113
+ for (const name of sources.presets ?? []) {
114
+ const preset = OSM_PRESETS[name];
115
+ for (const filter of preset.filters) add(filter, preset.elementTypes ?? ["way", "relation"]);
116
+ }
117
+ for (const tag of sources.tags ?? []) add(tagToFilter(tag), ["way", "relation"]);
118
+ return `[out:json][timeout:25];\n${clauses.length ? `(\n${clauses.join("\n")}\n);` : "();"}\nout geom;`;
93
119
  }
94
- export {
95
- OSM_PRESETS,
96
- buildOverpassQuery,
97
- findPresetForTags
98
- };
120
+ export { OSM_PRESETS, buildOverpassQuery, findPresetForTags };
@@ -1,14 +1,16 @@
1
+ // src/geo/osm-tags.ts
2
+ /** `natural=water` is `{ key, value }`; `building=*` matches any value. */
1
3
  function tagToFilter(tag) {
2
- const separator = tag.indexOf("=");
3
- const key = separator < 0 ? "" : tag.slice(0, separator).trim();
4
- const value = separator < 0 ? "" : tag.slice(separator + 1).trim();
5
- if (!key || !value) {
6
- throw new TypeError(
7
- `Invalid OSM tag "${tag}": expected "key=value" or "key=*"`
8
- );
9
- }
10
- return value === "*" ? { key, any: true } : { key, value };
4
+ const separator = tag.indexOf("=");
5
+ const key = separator < 0 ? "" : tag.slice(0, separator).trim();
6
+ const value = separator < 0 ? "" : tag.slice(separator + 1).trim();
7
+ if (!key || !value) throw new TypeError(`Invalid OSM tag "${tag}": expected "key=value" or "key=*"`);
8
+ return value === "*" ? {
9
+ key,
10
+ any: true
11
+ } : {
12
+ key,
13
+ value
14
+ };
11
15
  }
12
- export {
13
- tagToFilter
14
- };
16
+ export { tagToFilter };