@tileflow/core 0.1.0-alpha.14 → 0.1.0-alpha.17

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.
package/README.md CHANGED
@@ -42,18 +42,31 @@ export default defineTileflow({
42
42
  },
43
43
  typography: {
44
44
  font: 'Inter',
45
+ roads: {weight: 'bold'},
45
46
  },
46
47
  },
47
48
  },
48
49
  maps: {
49
50
  madrid: {
50
- basemap: osm(),
51
+ basemap: osm({version: '2026-06-07'}),
51
52
  icons: 'brand',
52
53
  theme: 'light',
53
54
  modules: [
54
- labels({roads: 'major'}),
55
- poi({categories: ['food', 'coffee', 'culture', 'transit'], icons: 'essential'}),
56
- roads({detail: 'streets', hierarchy: 'clear', outline: 'subtle', weight: 'regular'}),
55
+ labels({roads: 'major', roadClasses: ['motorway', 'trunk', 'primary']}),
56
+ poi({
57
+ categories: ['food', 'coffee', 'culture', 'transit'],
58
+ color: 'category',
59
+ density: 'balanced',
60
+ icons: 'essential',
61
+ }),
62
+ roads({
63
+ detail: 'streets',
64
+ hierarchy: 'clear',
65
+ oneWayMarkers: true,
66
+ outline: 'subtle',
67
+ weight: 'regular',
68
+ widthScale: {motorway: 1.15, minor: 0.9},
69
+ }),
57
70
  ],
58
71
  layers: {
59
72
  'highway-primary': {
@@ -71,6 +84,43 @@ export default defineTileflow({
71
84
  });
72
85
  ```
73
86
 
87
+ ## Capture scenes
88
+
89
+ Commit named visual scenes beside the maps they exercise. Scenes are strict, bounded test inputs;
90
+ they do not change style or manifest identity.
91
+
92
+ ```ts
93
+ export default defineTileflow({
94
+ maps: {
95
+ madrid: {basemap: osm(), theme: 'light'},
96
+ },
97
+ scenes: {
98
+ 'madrid-desktop': {
99
+ map: 'madrid',
100
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
101
+ viewport: {width: 1280, height: 800, dpr: 1},
102
+ },
103
+ 'madrid-product': {
104
+ map: 'madrid',
105
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
106
+ viewport: {width: 390, height: 844, dpr: 2},
107
+ target: {
108
+ kind: 'application',
109
+ path: '/maps/madrid',
110
+ captureId: 'product-map',
111
+ },
112
+ },
113
+ },
114
+ });
115
+ ```
116
+
117
+ The default target renders autonomously with no server. An application target uses the app's
118
+ normal loopback development server and selects exactly one wrapper by `captureId`, explicit
119
+ selector, or map name. Cameras accept `center` or `bounds`; viewports are 64–4096 CSS pixels at DPR
120
+ 1 or 2 within the public physical-pixel budget. Standalone capture applies the camera; an
121
+ application route must render it itself because capture does not mutate application component
122
+ state. Use `tileflow capture <scene>` or the `@tileflow/capture` Node package to render them.
123
+
74
124
  ## Style contract
75
125
 
76
126
  Tileflow keeps these levels separate:
@@ -80,6 +130,8 @@ Tileflow keeps these levels separate:
80
130
  - `theme.modules.<name>` contains advanced, module-specific tokens. These are granular: setting `theme.modules.roads.rail` does not recolor primary roads.
81
131
  - `maps.<name>.icons` can point at a reusable root icon set, a hosted MapLibre sprite URL, or a local icon folder for Vite/build output.
82
132
  - `maps.<name>.modules` enables semantic behavior such as `labels()`, `poi()`, and `roads()`. Road colors remain in `theme.modules.roads`; `roads()` controls detail, hierarchy, weight, outline, and optional transport extras.
133
+ - `typography` sets a global font/weight and optional `places`, `roads`, `water`, and `poi` overrides. The hosted Noto Sans endpoint guarantees `regular` and `bold`; custom glyph endpoints may provide the other typed weights.
134
+ - `buildingStyle` controls generated building fill opacity, outline opacity/width, and a height threshold that selects the theme's low/high-rise colors. Generated landuse and landcover layers consume their corresponding module colors.
83
135
  - `maps.<name>.renderer` can be `auto`, `osm-bright`, or `generated`. `auto` preserves the default template when compatible; `generated` always uses Tileflow's generated layers; `osm-bright` throws if the map config cannot be rendered with the template.
84
136
  - `layers` is the raw MapLibre escape hatch for exact layer patches.
85
137
 
@@ -95,6 +147,8 @@ roads({
95
147
  hierarchy: 'strong',
96
148
  weight: 'regular',
97
149
  outline: 'strong',
150
+ oneWayMarkers: true,
151
+ widthScale: {motorway: 1.15, primary: 1.05, minor: 0.9},
98
152
  extras: {
99
153
  paths: false,
100
154
  rail: false,
@@ -108,6 +162,8 @@ roads({
108
162
  adds service roads and tracks. `hierarchy` accepts `subtle`, `clear`, or `strong`; `weight`
109
163
  accepts `thin`, `regular`, or `bold`; and `outline` accepts `none`, `subtle`, or `strong`.
110
164
  `extras.paths`, `extras.rail`, and `extras.ferry` are independent and default to `false`.
165
+ `widthScale` accepts bounded multipliers for selected road classes, and `oneWayMarkers` adds
166
+ direction markers to eligible one-way roads.
111
167
 
112
168
  Without options, `roads()` uses `detail: 'streets'`, `hierarchy: 'clear'`,
113
169
  `weight: 'regular'`, `outline: 'subtle'`, and all extras disabled. Named visual recipes belong
@@ -124,6 +180,7 @@ labels({
124
180
  language: 'auto',
125
181
  places: 'major',
126
182
  roads: 'major',
183
+ roadClasses: ['motorway', 'trunk', 'primary', 'secondary'],
127
184
  water: 'major',
128
185
  });
129
186
  ```
@@ -140,12 +197,46 @@ are also clamped to the classes currently visible through `roads({detail})`, so
140
197
  is the intersection of both modules. `language` accepts `auto`, `local`, or a language code such as
141
198
  `en` or `es`.
142
199
 
200
+ `roadClasses` is an optional explicit eligibility list. It is intersected with both the label
201
+ detail and visible `roads()` classes, so it cannot label a road family that the map has hidden.
202
+
143
203
  Without options, `labels()` uses `language: 'auto'` and `major` for all three label families.
144
204
  Label colors remain in `theme.modules.labels`; font family and weight remain in `typography`.
145
205
 
206
+ ## POI module
207
+
208
+ POI category policy is resolved once for filtering, icon selection, label color, and collision
209
+ priority:
210
+
211
+ ```ts
212
+ poi({
213
+ categories: ['food', 'culture', 'lodging'],
214
+ classMapping: {culture: ['planetarium']},
215
+ color: 'category',
216
+ density: 'balanced',
217
+ icons: 'full',
218
+ labels: 'full',
219
+ placement: {coupleIconAndLabel: true, iconPadding: 4, textPadding: 8},
220
+ });
221
+ ```
222
+
223
+ Built-in mappings inspect both `class` and `subclass`; `classMapping` adds source-specific values
224
+ without removing the defaults. `color: 'category'` uses `theme.modules.poi` colors end to end.
225
+ Named density is opt-in and adds bounded category-aware rank gates plus stable sort priority;
226
+ omitting it preserves legacy layer behavior. Raw source rank expressions remain available through
227
+ `styleOverride` when a particular dataset needs a bespoke rule.
228
+
229
+ ## Tile-source revision
230
+
231
+ Set `version` on `osm({...})`, `maps.<name>.tiles`, or a named project tileset to pin an immutable
232
+ archive identifier. Map-level `tiles.version` wins over basemap and named-tileset defaults. Tileflow
233
+ adds it as `archiveVersion` to the TileJSON URL and records it as
234
+ `metadata['tileflow:tilesetVersion']`; it never infers stability from an unversioned response.
235
+
146
236
  ## Public API
147
237
 
148
- Use the package root only:
238
+ Application config, compilation, runtime resolution, and capture-scene APIs come from the package
239
+ root:
149
240
 
150
241
  ```ts
151
242
  import {
@@ -160,6 +251,28 @@ import {
160
251
  } from '@tileflow/core';
161
252
  ```
162
253
 
163
- Subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation details and can change during alpha releases.
254
+ Capture scene schemas, types, normalization, limits, and schema version are also exported from the
255
+ package root. See the [local visual capture contract](https://tileflow.dev/docs/agent-workflow) for
256
+ the CLI workflow and readiness boundary.
257
+
258
+ Framework adapters can import the browser-only lifecycle kernel explicitly:
259
+
260
+ ```ts
261
+ import {
262
+ attachTileflowMapLifecycle,
263
+ createTileflowMarkerController,
264
+ createTileflowSessionStarter,
265
+ createTileflowTransformRequest,
266
+ } from '@tileflow/core/browser';
267
+ ```
268
+
269
+ The browser subpath is not re-exported from the root. It is safe to import during SSR, does not
270
+ depend on MapLibre, and reads no browser globals during module evaluation. Adapter authors inject
271
+ their frame scheduler plus MapLibre event and marker operations after mount. The package contract
272
+ is documented in
273
+ [`docs/contracts/framework-browser-runtime.md`](../../docs/contracts/framework-browser-runtime.md).
274
+
275
+ Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
276
+ details and can change during alpha releases.
164
277
 
165
278
  Docs: https://tileflow.dev/docs
@@ -0,0 +1,74 @@
1
+ import { T as TileflowAnalytics } from './runtime-BMZ_tuba.js';
2
+ import 'zod';
3
+
4
+ type TileflowMapReadinessState = 'error' | 'idle' | 'loading';
5
+ type TileflowMapLifecycleEvent = 'dataloading' | 'error' | 'idle' | 'load' | 'styledataloading';
6
+ type TileflowFrameScheduler<TFrame> = {
7
+ cancelFrame: (frame: TFrame) => void;
8
+ requestFrame: (callback: () => void) => TFrame;
9
+ };
10
+ type TileflowMapLifecycleSubscriber<TMap> = (map: TMap, event: TileflowMapLifecycleEvent, listener: () => void) => () => void;
11
+ type TileflowSessionSender = (analytics: TileflowAnalytics | undefined, input: {
12
+ mapId?: string;
13
+ sessionId: string;
14
+ source: string;
15
+ styleId?: string;
16
+ }) => void;
17
+ type TileflowSessionStarter = {
18
+ clear: () => void;
19
+ start: (analytics: TileflowAnalytics | undefined, styleId?: string) => boolean;
20
+ };
21
+ type TileflowMapLifecycleAttachment = {
22
+ dispose: () => void;
23
+ invalidate: (state?: TileflowMapReadinessState) => void;
24
+ };
25
+ type TileflowTransformRequestParameters = {
26
+ url: string;
27
+ };
28
+ type TileflowComposedRequest<TRequest extends TileflowTransformRequestParameters> = Pick<TRequest, 'url'> & Partial<Omit<TRequest, 'url'>>;
29
+ type TileflowTransformRequest<TRequest extends TileflowTransformRequestParameters, TResourceType> = (url: string, resourceType?: TResourceType) => Promise<TileflowComposedRequest<TRequest>> | TileflowComposedRequest<TRequest> | undefined;
30
+ type TileflowUserTransformRequest<TRequest extends TileflowTransformRequestParameters, TResourceType> = (url: string, resourceType?: TResourceType) => Promise<TRequest> | TRequest | undefined;
31
+ type TileflowAsyncAnalyticsTiming = 'request' | 'resolution';
32
+ type TileflowMarkerController<TMap, TDefinition, TMarker> = {
33
+ clear: () => void;
34
+ dispose: () => void;
35
+ replace: (map: TMap, definitions: readonly TDefinition[]) => void;
36
+ };
37
+ declare function createTileflowSessionStarter(options: {
38
+ sessionId: string;
39
+ source: string;
40
+ startSession?: TileflowSessionSender;
41
+ }): TileflowSessionStarter;
42
+ declare function attachTileflowMapLifecycle<TMap, TFrame>(options: {
43
+ getSession?: () => {
44
+ analytics: TileflowAnalytics | undefined;
45
+ styleId?: string;
46
+ };
47
+ map: TMap;
48
+ onLoad?: (map: TMap) => void;
49
+ scheduler: TileflowFrameScheduler<TFrame>;
50
+ sessionStarter?: TileflowSessionStarter;
51
+ setState: (state: TileflowMapReadinessState) => void;
52
+ subscribe: TileflowMapLifecycleSubscriber<TMap>;
53
+ }): TileflowMapLifecycleAttachment;
54
+ declare function createTileflowTransformRequest<TRequest extends TileflowTransformRequestParameters = TileflowTransformRequestParameters, TResourceType = unknown>(options: {
55
+ always: true;
56
+ asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
57
+ getAnalytics: () => TileflowAnalytics | undefined;
58
+ sessionId: string;
59
+ transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
60
+ }): TileflowTransformRequest<TRequest, TResourceType>;
61
+ declare function createTileflowTransformRequest<TRequest extends TileflowTransformRequestParameters = TileflowTransformRequestParameters, TResourceType = unknown>(options: {
62
+ always?: boolean;
63
+ asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
64
+ getAnalytics: () => TileflowAnalytics | undefined;
65
+ sessionId: string;
66
+ transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
67
+ }): TileflowTransformRequest<TRequest, TResourceType> | undefined;
68
+ declare function createTileflowMarkerController<TMap, TDefinition, TMarker>(options: {
69
+ attach: (marker: TMarker, map: TMap, definition: TDefinition) => void;
70
+ create: (definition: TDefinition) => TMarker;
71
+ remove: (marker: TMarker) => void;
72
+ }): TileflowMarkerController<TMap, TDefinition, TMarker>;
73
+
74
+ export { type TileflowAsyncAnalyticsTiming, type TileflowComposedRequest, type TileflowFrameScheduler, type TileflowMapLifecycleAttachment, type TileflowMapLifecycleEvent, type TileflowMapLifecycleSubscriber, type TileflowMapReadinessState, type TileflowMarkerController, type TileflowSessionSender, type TileflowSessionStarter, type TileflowTransformRequest, type TileflowTransformRequestParameters, type TileflowUserTransformRequest, attachTileflowMapLifecycle, createTileflowMarkerController, createTileflowSessionStarter, createTileflowTransformRequest };
@@ -0,0 +1,320 @@
1
+ // src/runtime.ts
2
+ var maxStaticImagePixels = 1280 * 1280;
3
+ var manifestCache = new globalThis.Map();
4
+ function normalizeTileflowUrl(value) {
5
+ return value.replace(/\/+$/, "");
6
+ }
7
+ function resolveTileflowAnalyticsRequestUrl(url, analytics, sessionId) {
8
+ if (!analytics || analytics.enabled === false) {
9
+ return void 0;
10
+ }
11
+ const apiUrl = getTileflowAnalyticsApiUrl(analytics);
12
+ if (!apiUrl || !isTileflowTileUrl(url, apiUrl)) {
13
+ return void 0;
14
+ }
15
+ const nextUrl = new URL(url);
16
+ nextUrl.searchParams.set("session", sessionId);
17
+ if (analytics.mapId) {
18
+ nextUrl.searchParams.set("map", analytics.mapId);
19
+ }
20
+ if (analytics.styleId) {
21
+ nextUrl.searchParams.set("styleId", analytics.styleId);
22
+ }
23
+ return nextUrl.toString();
24
+ }
25
+ function startTileflowSession(analytics, input) {
26
+ if (!analytics || analytics.enabled === false || !input.mapId || typeof navigator === "undefined") {
27
+ return;
28
+ }
29
+ const endpoint = analytics.endpoint ?? `${getTileflowAnalyticsApiUrl(analytics)}/v1/sessions/start`;
30
+ const payload = JSON.stringify({
31
+ mapId: input.mapId,
32
+ metadata: analytics.metadata,
33
+ referrer: typeof document === "undefined" ? void 0 : document.referrer || "",
34
+ sdkVersion: analytics.sdkVersion,
35
+ sessionId: input.sessionId,
36
+ source: analytics.source ?? input.source,
37
+ styleId: input.styleId,
38
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
39
+ });
40
+ const blob = new Blob([payload], { type: "application/json" });
41
+ if (navigator.sendBeacon?.(endpoint, blob)) {
42
+ return;
43
+ }
44
+ void fetch(endpoint, {
45
+ body: payload,
46
+ credentials: "omit",
47
+ headers: {
48
+ "Content-Type": "application/json"
49
+ },
50
+ keepalive: true,
51
+ method: "POST"
52
+ });
53
+ }
54
+ function getTileflowAnalyticsApiUrl(analytics) {
55
+ if (analytics.apiUrl) {
56
+ return normalizeTileflowUrl(analytics.apiUrl);
57
+ }
58
+ if (analytics.endpoint) {
59
+ try {
60
+ const url = new URL(analytics.endpoint, getTileflowLocationOrigin());
61
+ return url.origin;
62
+ } catch {
63
+ return "https://api.tileflow.dev";
64
+ }
65
+ }
66
+ return "https://api.tileflow.dev";
67
+ }
68
+ function isTileflowTileUrl(url, apiUrl) {
69
+ try {
70
+ const parsedUrl = new URL(url);
71
+ const parsedApiUrl = new URL(apiUrl);
72
+ return parsedUrl.origin === parsedApiUrl.origin && isTilePath(parsedUrl);
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function isTilePath(url) {
78
+ return url.pathname.startsWith("/tiles/") || url.pathname.startsWith("/v1/tiles/");
79
+ }
80
+ function getTileflowLocationOrigin() {
81
+ if (typeof window === "undefined") {
82
+ return "http://localhost";
83
+ }
84
+ return window.location.origin;
85
+ }
86
+
87
+ // src/browser.ts
88
+ function createTileflowSessionStarter(options) {
89
+ const starts = /* @__PURE__ */ new Set();
90
+ const send = options.startSession ?? startTileflowSession;
91
+ return {
92
+ clear() {
93
+ starts.clear();
94
+ },
95
+ start(analytics, styleId) {
96
+ const mapId = analytics?.mapId;
97
+ if (!mapId) {
98
+ return false;
99
+ }
100
+ const key = `${options.sessionId}:${mapId}:${styleId ?? ""}`;
101
+ if (starts.has(key)) {
102
+ return false;
103
+ }
104
+ starts.add(key);
105
+ send(analytics, {
106
+ mapId,
107
+ sessionId: options.sessionId,
108
+ source: options.source,
109
+ styleId
110
+ });
111
+ return true;
112
+ }
113
+ };
114
+ }
115
+ function attachTileflowMapLifecycle(options) {
116
+ let disposed = false;
117
+ let generation = 0;
118
+ const frames = /* @__PURE__ */ new Set();
119
+ const unsubscribers = [];
120
+ const cancelFrames = () => {
121
+ for (const frame of frames) {
122
+ options.scheduler.cancelFrame(frame);
123
+ }
124
+ frames.clear();
125
+ };
126
+ const invalidate = (state) => {
127
+ generation += 1;
128
+ cancelFrames();
129
+ if (state) {
130
+ options.setState(state);
131
+ }
132
+ };
133
+ const scheduleFrame = (callback) => {
134
+ const frame = options.scheduler.requestFrame(() => {
135
+ frames.delete(frame);
136
+ callback();
137
+ });
138
+ frames.add(frame);
139
+ };
140
+ const handleLoad = () => {
141
+ if (disposed) {
142
+ return;
143
+ }
144
+ options.onLoad?.(options.map);
145
+ const session = options.getSession?.();
146
+ if (session) {
147
+ options.sessionStarter?.start(session.analytics, session.styleId);
148
+ }
149
+ };
150
+ const handleLoading = () => {
151
+ if (!disposed) {
152
+ invalidate("loading");
153
+ }
154
+ };
155
+ const handleIdle = () => {
156
+ if (disposed) {
157
+ return;
158
+ }
159
+ generation += 1;
160
+ cancelFrames();
161
+ const run = generation;
162
+ scheduleFrame(() => {
163
+ if (disposed || generation !== run) {
164
+ return;
165
+ }
166
+ scheduleFrame(() => {
167
+ if (!disposed && generation === run) {
168
+ options.setState("idle");
169
+ }
170
+ });
171
+ });
172
+ };
173
+ const handleError = () => {
174
+ if (!disposed) {
175
+ invalidate("error");
176
+ }
177
+ };
178
+ invalidate("loading");
179
+ try {
180
+ for (const [event, listener] of [
181
+ ["load", handleLoad],
182
+ ["dataloading", handleLoading],
183
+ ["styledataloading", handleLoading],
184
+ ["idle", handleIdle],
185
+ ["error", handleError]
186
+ ]) {
187
+ unsubscribers.push(options.subscribe(options.map, event, listener));
188
+ }
189
+ } catch (error) {
190
+ disposed = true;
191
+ generation += 1;
192
+ cancelFrames();
193
+ try {
194
+ disposeFunctions(unsubscribers);
195
+ } catch {
196
+ }
197
+ throw error;
198
+ }
199
+ return {
200
+ dispose() {
201
+ if (disposed) {
202
+ return;
203
+ }
204
+ disposed = true;
205
+ generation += 1;
206
+ cancelFrames();
207
+ disposeFunctions(unsubscribers);
208
+ },
209
+ invalidate(state) {
210
+ if (!disposed) {
211
+ invalidate(state);
212
+ }
213
+ }
214
+ };
215
+ }
216
+ function createTileflowTransformRequest(options) {
217
+ if (!options.always && !options.transformRequest) {
218
+ const analytics = options.getAnalytics();
219
+ if (!analytics || analytics.enabled === false) {
220
+ return void 0;
221
+ }
222
+ }
223
+ const asyncAnalyticsTiming = options.asyncAnalyticsTiming ?? "request";
224
+ return (url, resourceType) => {
225
+ const request = options.transformRequest?.(url, resourceType);
226
+ const analyticsAtRequest = asyncAnalyticsTiming === "request" ? options.getAnalytics() : void 0;
227
+ if (isPromiseLike(request)) {
228
+ return request.then(
229
+ (resolvedRequest) => applyTileflowAnalyticsRequest(
230
+ url,
231
+ resolvedRequest,
232
+ asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
233
+ options.sessionId
234
+ ) ?? resolvedRequest
235
+ );
236
+ }
237
+ return applyTileflowAnalyticsRequest(
238
+ url,
239
+ request,
240
+ asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
241
+ options.sessionId
242
+ );
243
+ };
244
+ }
245
+ function createTileflowMarkerController(options) {
246
+ let markers = [];
247
+ const clear = () => {
248
+ const previousMarkers = markers;
249
+ markers = [];
250
+ removeMarkers(previousMarkers, options.remove);
251
+ };
252
+ return {
253
+ clear,
254
+ dispose: clear,
255
+ replace(map, definitions) {
256
+ clear();
257
+ const nextMarkers = [];
258
+ try {
259
+ for (const definition of definitions) {
260
+ const marker = options.create(definition);
261
+ nextMarkers.push(marker);
262
+ options.attach(marker, map, definition);
263
+ }
264
+ } catch (error) {
265
+ try {
266
+ removeMarkers(nextMarkers, options.remove);
267
+ } catch {
268
+ }
269
+ throw error;
270
+ }
271
+ markers = nextMarkers;
272
+ }
273
+ };
274
+ }
275
+ function applyTileflowAnalyticsRequest(url, request, analytics, sessionId) {
276
+ const nextUrl = resolveTileflowAnalyticsRequestUrl(request?.url ?? url, analytics, sessionId);
277
+ if (!nextUrl) {
278
+ return request;
279
+ }
280
+ return request ? { ...request, url: nextUrl } : { url: nextUrl };
281
+ }
282
+ function disposeFunctions(disposers) {
283
+ let firstError;
284
+ let failed = false;
285
+ for (const dispose of disposers.splice(0).reverse()) {
286
+ try {
287
+ dispose();
288
+ } catch (error) {
289
+ if (!failed) firstError = error;
290
+ failed = true;
291
+ }
292
+ }
293
+ if (failed) {
294
+ throw firstError;
295
+ }
296
+ }
297
+ function isPromiseLike(value) {
298
+ return Boolean(value && typeof value.then === "function");
299
+ }
300
+ function removeMarkers(markers, remove) {
301
+ let firstError;
302
+ let failed = false;
303
+ for (const marker of markers) {
304
+ try {
305
+ remove(marker);
306
+ } catch (error) {
307
+ if (!failed) firstError = error;
308
+ failed = true;
309
+ }
310
+ }
311
+ if (failed) {
312
+ throw firstError;
313
+ }
314
+ }
315
+ export {
316
+ attachTileflowMapLifecycle,
317
+ createTileflowMarkerController,
318
+ createTileflowSessionStarter,
319
+ createTileflowTransformRequest
320
+ };