@tileflow/react 0.1.0-alpha.16 → 0.1.0-alpha.18

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
@@ -38,7 +38,7 @@ manifest:
38
38
  Or bypass the manifest and load a hosted style directly:
39
39
 
40
40
  ```tsx
41
- <Map styleUrl="https://api.tileflow.dev/v1/styles/acme/madrid.json" />
41
+ <Map styleUrl="https://api.tileflow.dev/maps/map_1234567890abcdef/style.json" />
42
42
  ```
43
43
 
44
44
  For simple hosted static images, use image mode:
@@ -55,6 +55,35 @@ the production image behavior locally.
55
55
  For full static scenes with bounds, overlays, and server-side precache, use
56
56
  `StaticMap` from `@tileflow/react/static` or helpers from `@tileflow/static`.
57
57
 
58
+ ```tsx
59
+ import {StaticMap} from '@tileflow/react/static';
60
+ import {useState} from 'react';
61
+ import {createStaticMapIdempotencyKey} from '@tileflow/static';
62
+
63
+ export function Preview() {
64
+ const [idempotencyKey] = useState(createStaticMapIdempotencyKey);
65
+
66
+ return (
67
+ <StaticMap
68
+ map="madrid"
69
+ camera={{type: 'center', center: [-3.7038, 40.4168], zoom: 12}}
70
+ size={{width: 1200, height: 800}}
71
+ createUrl="/api/static-maps"
72
+ idempotencyKey={idempotencyKey}
73
+ />
74
+ );
75
+ }
76
+ ```
77
+
78
+ `idempotencyKey` is required in create mode. Keep one key for one intentional create action and
79
+ reuse it across retries and remounts. Concurrent React consumers share work only when URL,
80
+ normalized scene, and idempotency key all identify the same operation; unmounting the final
81
+ consumer aborts its in-flight request.
82
+
83
+ Hosted interactive maps automatically preflight a short-lived commercial session grant before
84
+ eligible resources. Setting `analytics={{enabled: false}}` disables the optional beacon only; it
85
+ does not remove hosted authorization or override a user `mapOptions.transformRequest` callback.
86
+
58
87
  ## Headless capture readiness
59
88
 
60
89
  Use `captureId` to disambiguate multiple maps with the same configured name:
package/dist/index.js CHANGED
@@ -10,20 +10,24 @@ import {
10
10
  } from "react";
11
11
  import maplibregl from "maplibre-gl";
12
12
  import {
13
- createTileflowSessionId,
13
+ createTileflowSessionController,
14
14
  defaultTileflowManifestUrl,
15
15
  loadTileflowManifest,
16
16
  mergeTileflowAnalytics,
17
17
  normalizeTileflowCaptureId,
18
18
  normalizeTileflowStaticImageSize,
19
- resolveTileflowAnalyticsRequestUrl,
20
19
  resolveTileflowManifestMap,
21
20
  resolveTileflowMapMode,
22
21
  resolveTileflowRuntimeStyle,
23
22
  resolveTileflowStaticImageUrl,
24
- shouldLoadTileflowManifest,
25
- startTileflowSession
23
+ shouldLoadTileflowManifest
26
24
  } from "@tileflow/core";
25
+ import {
26
+ attachTileflowMapLifecycle,
27
+ createTileflowMarkerController,
28
+ createTileflowSessionStarter,
29
+ createTileflowTransformRequest
30
+ } from "@tileflow/core/browser";
27
31
  import { jsx } from "react/jsx-runtime";
28
32
  var defaultCenter = [0, 20];
29
33
  var defaultMarkers = [];
@@ -55,7 +59,6 @@ function Map({
55
59
  const containerRef = useRef(null);
56
60
  const imageRef = useRef(null);
57
61
  const mapRef = useRef(null);
58
- const markerInstancesRef = useRef([]);
59
62
  const stableMapOptions = useStableMapOptions(mapOptions);
60
63
  const resolvedCenter = useStableMapOptionValue(
61
64
  center ?? stableMapOptions?.center ?? defaultCenter
@@ -68,9 +71,24 @@ function Map({
68
71
  const onLoadRef = useRef(onLoad);
69
72
  const mapNameRef = useRef(mapName);
70
73
  const resolvedAnalyticsRef = useRef(void 0);
71
- const sessionStartsRef = useRef(/* @__PURE__ */ new Set());
72
74
  const readinessRunRef = useRef(0);
73
- const sessionId = useMemo(() => createTileflowSessionId(), []);
75
+ const markerController = useMemo(
76
+ () => createTileflowMarkerController({
77
+ attach(markerInstance, map, marker) {
78
+ markerInstance.setLngLat(marker.coordinates).addTo(map);
79
+ markerInstance.getElement().title = marker.label ?? marker.id;
80
+ },
81
+ create(marker) {
82
+ return new maplibregl.Marker({
83
+ color: marker.color ?? "#C6A15B"
84
+ });
85
+ },
86
+ remove(markerInstance) {
87
+ markerInstance.remove();
88
+ }
89
+ }),
90
+ []
91
+ );
74
92
  const [manifestMap, setManifestMap] = useState(null);
75
93
  const [imageSize, setImageSize] = useState(null);
76
94
  const [captureState, setCaptureState] = useState("loading");
@@ -165,14 +183,6 @@ function Map({
165
183
  }) : void 0),
166
184
  [imageSize, imageUrl, isImageMode, manifestMap, resolvedZoom, staticImageCenter]
167
185
  );
168
- const transformRequest = useMemo(
169
- () => createTransformRequest(
170
- () => resolvedAnalyticsRef.current,
171
- sessionId,
172
- stableMapOptions?.transformRequest
173
- ),
174
- [sessionId, stableMapOptions?.transformRequest]
175
- );
176
186
  useEffect(() => {
177
187
  if (!isImageMode || imageUrl || !containerRef.current) {
178
188
  setImageSize(null);
@@ -199,25 +209,22 @@ function Map({
199
209
  if (!containerRef.current || !runtimeStyle) {
200
210
  return;
201
211
  }
202
- const setLoading = () => {
203
- readinessRunRef.current += 1;
204
- setCaptureState("loading");
205
- };
206
- const setError = () => {
207
- readinessRunRef.current += 1;
208
- setCaptureState("error");
209
- };
210
- const setIdleAfterFrames = () => {
211
- const run = ++readinessRunRef.current;
212
- requestAnimationFrame(() => {
213
- requestAnimationFrame(() => {
214
- if (readinessRunRef.current === run && mapRef.current === map) {
215
- setCaptureState("idle");
216
- }
217
- });
218
- });
219
- };
220
- setLoading();
212
+ readinessRunRef.current += 1;
213
+ setCaptureState("loading");
214
+ const session = createTileflowSessionController({ source: "react" });
215
+ const sessionStarter = createTileflowSessionStarter({
216
+ getSessionId: () => session.sessionId,
217
+ sessionId: session.sessionId,
218
+ source: "react"
219
+ });
220
+ const transformRequest = createTileflowTransformRequest({
221
+ always: true,
222
+ asyncAnalyticsTiming: "resolution",
223
+ getAnalytics: () => resolvedAnalyticsRef.current,
224
+ sessionController: session,
225
+ sessionId: session.sessionId,
226
+ transformRequest: stableMapOptions?.transformRequest ?? void 0
227
+ });
221
228
  const map = new maplibregl.Map({
222
229
  ...stableMapOptions,
223
230
  container: containerRef.current,
@@ -237,36 +244,50 @@ function Map({
237
244
  map.resize();
238
245
  });
239
246
  resizeObserver.observe(containerRef.current);
240
- map.on("load", () => {
241
- const analyticsForLoad = resolvedAnalyticsRef.current;
242
- const styleId = analyticsForLoad?.styleId ?? (typeof runtimeStyle.style === "string" ? runtimeStyle.style : mapNameRef.current);
243
- onLoadRef.current?.(map);
244
- if (analyticsForLoad?.mapId) {
245
- const sessionKey = `${sessionId}:${analyticsForLoad.mapId}:${styleId ?? ""}`;
246
- if (!sessionStartsRef.current.has(sessionKey)) {
247
- sessionStartsRef.current.add(sessionKey);
248
- startTileflowSession(analyticsForLoad, {
249
- mapId: analyticsForLoad.mapId,
250
- sessionId,
251
- source: "react",
252
- styleId
253
- });
254
- }
247
+ const lifecycle = attachTileflowMapLifecycle({
248
+ getSession: () => {
249
+ const analyticsForLoad = resolvedAnalyticsRef.current;
250
+ return {
251
+ analytics: analyticsForLoad,
252
+ styleId: analyticsForLoad?.styleId ?? (typeof runtimeStyle.style === "string" ? runtimeStyle.style : mapNameRef.current)
253
+ };
254
+ },
255
+ map,
256
+ onLoad: (loadedMap) => {
257
+ onLoadRef.current?.(loadedMap);
258
+ },
259
+ scheduler: {
260
+ cancelFrame: (frame) => cancelAnimationFrame(frame),
261
+ requestFrame: (callback) => requestAnimationFrame(callback)
262
+ },
263
+ sessionStarter,
264
+ setState: setCaptureState,
265
+ subscribe: (subscribedMap, event, listener) => {
266
+ const subscription = subscribedMap.on(event, listener);
267
+ return () => subscription.unsubscribe();
255
268
  }
256
269
  });
257
- map.on("dataloading", setLoading);
258
- map.on("styledataloading", setLoading);
259
- map.on("idle", setIdleAfterFrames);
260
- map.on("error", setError);
261
270
  return () => {
262
271
  readinessRunRef.current += 1;
263
- resizeObserver.disconnect();
264
- clearMarkers(markerInstancesRef.current);
265
- markerInstancesRef.current = [];
266
- map.remove();
267
- mapRef.current = null;
272
+ try {
273
+ lifecycle.dispose();
274
+ } finally {
275
+ try {
276
+ resizeObserver.disconnect();
277
+ } finally {
278
+ try {
279
+ markerController.clear();
280
+ } finally {
281
+ try {
282
+ map.remove();
283
+ } finally {
284
+ if (mapRef.current === map) mapRef.current = null;
285
+ }
286
+ }
287
+ }
288
+ }
268
289
  };
269
- }, [resolvedInteractive, runtimeStyle, sessionId, stableMapOptions, transformRequest]);
290
+ }, [markerController, resolvedInteractive, runtimeStyle, stableMapOptions]);
270
291
  useLayoutEffect(() => {
271
292
  if (!isImageMode) return;
272
293
  readinessRunRef.current += 1;
@@ -291,23 +312,15 @@ function Map({
291
312
  }
292
313
  useEffect(() => {
293
314
  const map = mapRef.current;
294
- clearMarkers(markerInstancesRef.current);
295
- markerInstancesRef.current = [];
315
+ markerController.clear();
296
316
  if (!map) {
297
317
  return;
298
318
  }
299
- markerInstancesRef.current = markers.map((marker) => {
300
- const markerInstance = new maplibregl.Marker({
301
- color: marker.color ?? "#C6A15B"
302
- }).setLngLat(marker.coordinates).addTo(map);
303
- markerInstance.getElement().title = marker.label ?? marker.id;
304
- return markerInstance;
305
- });
319
+ markerController.replace(map, markers);
306
320
  return () => {
307
- clearMarkers(markerInstancesRef.current);
308
- markerInstancesRef.current = [];
321
+ markerController.clear();
309
322
  };
310
- }, [markers, runtimeStyle]);
323
+ }, [markerController, markers, runtimeStyle]);
311
324
  const frameStyle = {
312
325
  height,
313
326
  minHeight: 240,
@@ -361,31 +374,6 @@ function Map({
361
374
  }
362
375
  );
363
376
  }
364
- function createTransformRequest(getAnalytics, sessionId, userTransformRequest) {
365
- return (url, resourceType) => {
366
- const request = userTransformRequest?.(url, resourceType);
367
- if (isPromiseLike(request)) {
368
- return request.then(
369
- (resolvedRequest) => applyTileflowAnalyticsRequest(url, resolvedRequest, getAnalytics(), sessionId) ?? resolvedRequest
370
- );
371
- }
372
- return applyTileflowAnalyticsRequest(url, request, getAnalytics(), sessionId);
373
- };
374
- }
375
- function applyTileflowAnalyticsRequest(url, request, analytics, sessionId) {
376
- const requestUrl = request?.url ?? url;
377
- const nextUrl = resolveTileflowAnalyticsRequestUrl(requestUrl, analytics, sessionId);
378
- if (!nextUrl) {
379
- return request;
380
- }
381
- return request ? { ...request, url: nextUrl } : { url: nextUrl };
382
- }
383
- function isPromiseLike(value) {
384
- return Boolean(value && typeof value.then === "function");
385
- }
386
- function clearMarkers(markerInstances) {
387
- markerInstances.forEach((marker) => marker.remove());
388
- }
389
377
  function useStableMapOptions(options) {
390
378
  return useStableMapOptionValue(options);
391
379
  }
package/dist/static.d.ts CHANGED
@@ -16,13 +16,15 @@ type StaticMapBaseProps = StaticSceneInput & {
16
16
  };
17
17
  type StaticMapImageProps = {
18
18
  createUrl?: never;
19
+ idempotencyKey?: never;
19
20
  imageUrl: string;
20
21
  };
21
22
  type StaticMapCreateProps = {
22
23
  createUrl: string;
24
+ idempotencyKey: string;
23
25
  imageUrl?: never;
24
26
  };
25
27
  type StaticMapProps = StaticMapBaseProps & (StaticMapCreateProps | StaticMapImageProps);
26
- declare function StaticMap({ alt, camera, className, captureId, createUrl, imageStyle, imageUrl, keepPreviousImage, loading, map, onError, onImageLoad, onReady, overlays, size, style, }: StaticMapProps): react.JSX.Element;
28
+ declare function StaticMap({ alt, camera, className, captureId, createUrl, imageStyle, imageUrl, idempotencyKey, keepPreviousImage, loading, map, onError, onImageLoad, onReady, overlays, size, style, }: StaticMapProps): react.JSX.Element;
27
29
 
28
30
  export { StaticMap, type StaticMapProps };
package/dist/static.js CHANGED
@@ -9,8 +9,181 @@ import {
9
9
  useState
10
10
  } from "react";
11
11
  import { normalizeTileflowCaptureId } from "@tileflow/core";
12
- import { jsx } from "react/jsx-runtime";
12
+ import {
13
+ validateStaticMapIdempotencyKey
14
+ } from "@tileflow/static";
15
+
16
+ // src/static-map-request.ts
17
+ import {
18
+ staticMapProcessingResultSchema,
19
+ staticMapReadyResultSchema
20
+ } from "@tileflow/static";
13
21
  var inFlightRequests = /* @__PURE__ */ new Map();
22
+ function createStaticMapRequestKey(input) {
23
+ return stableStringify(input);
24
+ }
25
+ async function resolveStaticMap(input) {
26
+ throwIfAborted(input.signal);
27
+ let entry = inFlightRequests.get(input.requestKey);
28
+ if (!entry) {
29
+ const controller = new AbortController();
30
+ const promise = requestStaticMapUntilReady({ ...input, signal: controller.signal });
31
+ const created = {
32
+ controller,
33
+ consumers: 0,
34
+ promise,
35
+ settled: false
36
+ };
37
+ const settle = () => {
38
+ created.settled = true;
39
+ if (inFlightRequests.get(input.requestKey) === created) {
40
+ inFlightRequests.delete(input.requestKey);
41
+ }
42
+ };
43
+ void promise.then(settle, settle);
44
+ inFlightRequests.set(input.requestKey, created);
45
+ entry = created;
46
+ }
47
+ entry.consumers += 1;
48
+ try {
49
+ return await raceWithAbort(entry.promise, input.signal);
50
+ } finally {
51
+ entry.consumers -= 1;
52
+ if (entry.consumers === 0 && !entry.settled) {
53
+ if (inFlightRequests.get(input.requestKey) === entry) {
54
+ inFlightRequests.delete(input.requestKey);
55
+ }
56
+ entry.controller.abort();
57
+ }
58
+ }
59
+ }
60
+ async function requestStaticMapUntilReady(input) {
61
+ const maxWaitMs = input.maxWaitMs ?? 3e4;
62
+ if (!Number.isSafeInteger(maxWaitMs) || maxWaitMs < 1 || maxWaitMs > 12e4) {
63
+ throw new Error("Static map maxWaitMs must be an integer from 1 to 120000");
64
+ }
65
+ return runWithinBudget(maxWaitMs, input.signal, async (signal) => {
66
+ const body = JSON.stringify(input.scene);
67
+ const fetcher = input.fetch ?? fetch;
68
+ let operationId = null;
69
+ while (true) {
70
+ const response = await fetcher(input.createUrl, {
71
+ body,
72
+ headers: {
73
+ "Content-Type": "application/json",
74
+ "Idempotency-Key": input.idempotencyKey
75
+ },
76
+ method: "POST",
77
+ signal
78
+ });
79
+ throwIfAborted(signal);
80
+ if (!response.ok) throw new Error(`Static map endpoint failed: ${response.status}`);
81
+ let value;
82
+ try {
83
+ value = await response.json();
84
+ } catch {
85
+ throw new Error("Static map endpoint returned invalid JSON");
86
+ }
87
+ throwIfAborted(signal);
88
+ if (response.status !== 202) {
89
+ const ready = staticMapReadyResultSchema.safeParse(value);
90
+ if (!ready.success) throw new Error("Static map endpoint returned an invalid response");
91
+ assertStaticMapOperationIdentity(operationId, ready.data.operationId);
92
+ return ready.data;
93
+ }
94
+ const pending = staticMapProcessingResultSchema.safeParse(value);
95
+ if (!pending.success) throw new Error("Static map endpoint returned an invalid response");
96
+ assertStaticMapOperationIdentity(operationId, pending.data.operationId);
97
+ operationId ??= pending.data.operationId;
98
+ await delay(pending.data.retryAfterMs, signal);
99
+ }
100
+ });
101
+ }
102
+ function stableStringify(value) {
103
+ return JSON.stringify(sortJson(value));
104
+ }
105
+ function assertStaticMapOperationIdentity(expected, actual) {
106
+ if (expected !== null && actual !== expected) {
107
+ throw new Error("Static map endpoint changed operation identity while polling");
108
+ }
109
+ }
110
+ async function runWithinBudget(maxWaitMs, externalSignal, operation) {
111
+ const controller = new AbortController();
112
+ const timeoutError = new Error(`Static map request timed out after ${maxWaitMs}ms`);
113
+ const abortFromConsumer = () => controller.abort(externalSignal?.reason);
114
+ if (externalSignal?.aborted) abortFromConsumer();
115
+ else externalSignal?.addEventListener("abort", abortFromConsumer, { once: true });
116
+ const timeout = setTimeout(() => controller.abort(timeoutError), maxWaitMs);
117
+ try {
118
+ throwIfAborted(controller.signal);
119
+ const pending = operation(controller.signal);
120
+ return await raceWithAbort(pending, controller.signal);
121
+ } finally {
122
+ clearTimeout(timeout);
123
+ externalSignal?.removeEventListener("abort", abortFromConsumer);
124
+ }
125
+ }
126
+ function delay(milliseconds, signal) {
127
+ if (milliseconds === 0) {
128
+ throwIfAborted(signal);
129
+ return Promise.resolve();
130
+ }
131
+ return new Promise((resolve, reject) => {
132
+ const timeout = setTimeout(() => {
133
+ signal.removeEventListener("abort", onAbort);
134
+ resolve();
135
+ }, milliseconds);
136
+ const onAbort = () => {
137
+ clearTimeout(timeout);
138
+ reject(abortReason(signal));
139
+ };
140
+ if (signal.aborted) onAbort();
141
+ else signal.addEventListener("abort", onAbort, { once: true });
142
+ });
143
+ }
144
+ function raceWithAbort(operation, signal) {
145
+ if (!signal) return operation;
146
+ if (signal.aborted) return Promise.reject(abortReason(signal));
147
+ return new Promise((resolve, reject) => {
148
+ const onAbort = () => {
149
+ cleanup();
150
+ reject(abortReason(signal));
151
+ };
152
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
153
+ signal.addEventListener("abort", onAbort, { once: true });
154
+ operation.then(
155
+ (value) => {
156
+ cleanup();
157
+ resolve(value);
158
+ },
159
+ (error) => {
160
+ cleanup();
161
+ reject(error);
162
+ }
163
+ );
164
+ });
165
+ }
166
+ function throwIfAborted(signal) {
167
+ if (signal?.aborted) throw abortReason(signal);
168
+ }
169
+ function abortReason(signal) {
170
+ if (signal.reason instanceof Error) return signal.reason;
171
+ const error = new Error("Static map request was aborted");
172
+ error.name = "AbortError";
173
+ return error;
174
+ }
175
+ function sortJson(value) {
176
+ if (Array.isArray(value)) return value.map(sortJson);
177
+ if (value && typeof value === "object") {
178
+ return Object.fromEntries(
179
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)])
180
+ );
181
+ }
182
+ return value;
183
+ }
184
+
185
+ // src/static-map.tsx
186
+ import { jsx } from "react/jsx-runtime";
14
187
  function StaticMap({
15
188
  alt = "",
16
189
  camera,
@@ -19,6 +192,7 @@ function StaticMap({
19
192
  createUrl,
20
193
  imageStyle,
21
194
  imageUrl,
195
+ idempotencyKey,
22
196
  keepPreviousImage = false,
23
197
  loading = "lazy",
24
198
  map,
@@ -43,7 +217,7 @@ function StaticMap({
43
217
  () => stableStringify({ camera, map, overlays, size }),
44
218
  [camera, map, overlays, size]
45
219
  );
46
- const requestKey = imageUrl ? `image:${imageUrl}` : `create:${createUrl}:${sceneKey}`;
220
+ const intentKey = imageUrl ? `image:${imageUrl}` : `create:${createUrl}:${idempotencyKey ?? ""}:${sceneKey}`;
47
221
  useLayoutEffect(() => {
48
222
  imageLoadRunRef.current += 1;
49
223
  setCaptureState("loading");
@@ -52,7 +226,7 @@ function StaticMap({
52
226
  if (image.naturalWidth === 0) setCaptureState("error");
53
227
  else void markImageReady(image);
54
228
  }
55
- }, [requestKey]);
229
+ }, [intentKey]);
56
230
  async function markImageReady(image) {
57
231
  const runId = ++imageLoadRunRef.current;
58
232
  try {
@@ -81,14 +255,31 @@ function StaticMap({
81
255
  if (!createUrl) {
82
256
  return;
83
257
  }
258
+ const validation = validateStaticMapIdempotencyKey(idempotencyKey ?? "");
259
+ if (!validation.ok) {
260
+ const keyError = new Error(`Invalid Static Maps idempotency key: ${validation.error}`);
261
+ if (!keepPreviousImage) setResult(null);
262
+ setError(keyError);
263
+ imageLoadRunRef.current += 1;
264
+ setCaptureState("error");
265
+ onErrorRef.current?.(keyError);
266
+ return;
267
+ }
84
268
  if (!keepPreviousImage) {
85
269
  setResult(null);
86
270
  }
87
271
  setError(null);
272
+ const requestController = new AbortController();
88
273
  resolveStaticMap({
89
274
  createUrl,
90
- requestKey,
91
- scene: JSON.parse(sceneKey)
275
+ idempotencyKey: validation.key,
276
+ requestKey: createStaticMapRequestKey({
277
+ createUrl,
278
+ idempotencyKey: validation.key,
279
+ sceneKey
280
+ }),
281
+ scene: JSON.parse(sceneKey),
282
+ signal: requestController.signal
92
283
  }).then((nextResult) => {
93
284
  if (cancelled) {
94
285
  return;
@@ -111,8 +302,9 @@ function StaticMap({
111
302
  });
112
303
  return () => {
113
304
  cancelled = true;
305
+ requestController.abort();
114
306
  };
115
- }, [createUrl, imageUrl, keepPreviousImage, requestKey, sceneKey]);
307
+ }, [createUrl, idempotencyKey, imageUrl, intentKey, keepPreviousImage, sceneKey]);
116
308
  return /* @__PURE__ */ jsx(
117
309
  "div",
118
310
  {
@@ -159,50 +351,17 @@ function StaticMap({
159
351
  }
160
352
  );
161
353
  }
162
- async function resolveStaticMap(input) {
163
- const existing = inFlightRequests.get(input.requestKey);
164
- if (existing) {
165
- return existing;
166
- }
167
- const promise = fetch(input.createUrl, {
168
- body: JSON.stringify(input.scene),
169
- headers: {
170
- "Content-Type": "application/json"
171
- },
172
- method: "POST"
173
- }).then(async (response) => {
174
- if (!response.ok) {
175
- throw new Error(`Static map endpoint failed: ${response.status}`);
176
- }
177
- return await response.json();
178
- }).finally(() => {
179
- inFlightRequests.delete(input.requestKey);
180
- });
181
- inFlightRequests.set(input.requestKey, promise);
182
- return promise;
183
- }
184
354
  function imageResult(imageUrl) {
185
355
  return {
186
356
  cached: true,
187
357
  hash: "",
188
358
  imageUrl,
189
- status: "ready"
359
+ operationId: null,
360
+ remainingUnits: null,
361
+ status: "ready",
362
+ unitCost: 0
190
363
  };
191
364
  }
192
- function stableStringify(value) {
193
- return JSON.stringify(sortJson(value));
194
- }
195
- function sortJson(value) {
196
- if (Array.isArray(value)) {
197
- return value.map(sortJson);
198
- }
199
- if (value && typeof value === "object") {
200
- return Object.fromEntries(
201
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)])
202
- );
203
- }
204
- return value;
205
- }
206
365
  export {
207
366
  StaticMap
208
367
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/react",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "description": "React components for rendering Tileflow maps with MapLibre.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -41,8 +41,8 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@tileflow/core": "0.1.0-alpha.16",
45
- "@tileflow/static": "0.1.0-alpha.16"
44
+ "@tileflow/core": ">=0.1.0-alpha.18 <0.1.0-beta.0",
45
+ "@tileflow/static": ">=0.1.0-alpha.17 <0.1.0-beta.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "maplibre-gl": ">=5",