@tileflow/core 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
@@ -235,7 +235,8 @@ adds it as `archiveVersion` to the TileJSON URL and records it as
235
235
 
236
236
  ## Public API
237
237
 
238
- Use the package root only:
238
+ Application config, compilation, runtime resolution, and capture-scene APIs come from the package
239
+ root:
239
240
 
240
241
  ```ts
241
242
  import {
@@ -254,6 +255,37 @@ Capture scene schemas, types, normalization, limits, and schema version are also
254
255
  package root. See the [local visual capture contract](https://tileflow.dev/docs/agent-workflow) for
255
256
  the CLI workflow and readiness boundary.
256
257
 
257
- Subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation details and can change during alpha releases.
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
+ ## Hosted session authorization
276
+
277
+ The framework adapters use `createTileflowSessionController` to obtain one short-lived session
278
+ grant before eligible hosted style, tile, font, or sprite requests. Concurrent resources share the
279
+ same preflight, grants are attached only to reviewed HTTP(S) origins, and an expired unused session
280
+ rotates once when the server requests it. A controller also rotates after six hours or 10,000
281
+ eligible requests. The preflight has a 10-second client timeout by default; direct controller users
282
+ can set `grantTimeoutMs` from 1 to 120,000 milliseconds.
283
+
284
+ `analytics.enabled: false` disables only the optional analytics beacon. It does not disable this
285
+ server-owned commercial authorization. User `transformRequest` callbacks still run first; Tileflow
286
+ then decorates only the resulting eligible URL and preserves the other request options.
287
+
288
+ Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
289
+ details and can change during alpha releases.
258
290
 
259
291
  Docs: https://tileflow.dev/docs
@@ -0,0 +1,77 @@
1
+ import { T as TileflowAnalytics, a as TileflowSessionController } from './runtime-Bg--GMWM.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
+ getSessionId?: () => string;
39
+ sessionId: string;
40
+ source: string;
41
+ startSession?: TileflowSessionSender;
42
+ }): TileflowSessionStarter;
43
+ declare function attachTileflowMapLifecycle<TMap, TFrame>(options: {
44
+ getSession?: () => {
45
+ analytics: TileflowAnalytics | undefined;
46
+ styleId?: string;
47
+ };
48
+ map: TMap;
49
+ onLoad?: (map: TMap) => void;
50
+ scheduler: TileflowFrameScheduler<TFrame>;
51
+ sessionStarter?: TileflowSessionStarter;
52
+ setState: (state: TileflowMapReadinessState) => void;
53
+ subscribe: TileflowMapLifecycleSubscriber<TMap>;
54
+ }): TileflowMapLifecycleAttachment;
55
+ declare function createTileflowTransformRequest<TRequest extends TileflowTransformRequestParameters = TileflowTransformRequestParameters, TResourceType = unknown>(options: {
56
+ always: true;
57
+ asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
58
+ getAnalytics: () => TileflowAnalytics | undefined;
59
+ sessionController?: TileflowSessionController;
60
+ sessionId: string;
61
+ transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
62
+ }): TileflowTransformRequest<TRequest, TResourceType>;
63
+ declare function createTileflowTransformRequest<TRequest extends TileflowTransformRequestParameters = TileflowTransformRequestParameters, TResourceType = unknown>(options: {
64
+ always?: boolean;
65
+ asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
66
+ getAnalytics: () => TileflowAnalytics | undefined;
67
+ sessionController?: TileflowSessionController;
68
+ sessionId: string;
69
+ transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
70
+ }): TileflowTransformRequest<TRequest, TResourceType> | undefined;
71
+ declare function createTileflowMarkerController<TMap, TDefinition, TMarker>(options: {
72
+ attach: (marker: TMarker, map: TMap, definition: TDefinition) => void;
73
+ create: (definition: TDefinition) => TMarker;
74
+ remove: (marker: TMarker) => void;
75
+ }): TileflowMarkerController<TMap, TDefinition, TMarker>;
76
+
77
+ 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,337 @@
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/analytics`;
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
+ }).catch(() => void 0);
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
+ const sessionId = options.getSessionId?.() ?? options.sessionId;
98
+ if (!mapId) {
99
+ return false;
100
+ }
101
+ const key = `${sessionId}:${mapId}:${styleId ?? ""}`;
102
+ if (starts.has(key)) {
103
+ return false;
104
+ }
105
+ starts.add(key);
106
+ send(analytics, {
107
+ mapId,
108
+ sessionId,
109
+ source: options.source,
110
+ styleId
111
+ });
112
+ return true;
113
+ }
114
+ };
115
+ }
116
+ function attachTileflowMapLifecycle(options) {
117
+ let disposed = false;
118
+ let generation = 0;
119
+ const frames = /* @__PURE__ */ new Set();
120
+ const unsubscribers = [];
121
+ const cancelFrames = () => {
122
+ for (const frame of frames) {
123
+ options.scheduler.cancelFrame(frame);
124
+ }
125
+ frames.clear();
126
+ };
127
+ const invalidate = (state) => {
128
+ generation += 1;
129
+ cancelFrames();
130
+ if (state) {
131
+ options.setState(state);
132
+ }
133
+ };
134
+ const scheduleFrame = (callback) => {
135
+ const frame = options.scheduler.requestFrame(() => {
136
+ frames.delete(frame);
137
+ callback();
138
+ });
139
+ frames.add(frame);
140
+ };
141
+ const handleLoad = () => {
142
+ if (disposed) {
143
+ return;
144
+ }
145
+ options.onLoad?.(options.map);
146
+ const session = options.getSession?.();
147
+ if (session) {
148
+ options.sessionStarter?.start(session.analytics, session.styleId);
149
+ }
150
+ };
151
+ const handleLoading = () => {
152
+ if (!disposed) {
153
+ invalidate("loading");
154
+ }
155
+ };
156
+ const handleIdle = () => {
157
+ if (disposed) {
158
+ return;
159
+ }
160
+ generation += 1;
161
+ cancelFrames();
162
+ const run = generation;
163
+ scheduleFrame(() => {
164
+ if (disposed || generation !== run) {
165
+ return;
166
+ }
167
+ scheduleFrame(() => {
168
+ if (!disposed && generation === run) {
169
+ options.setState("idle");
170
+ }
171
+ });
172
+ });
173
+ };
174
+ const handleError = () => {
175
+ if (!disposed) {
176
+ invalidate("error");
177
+ }
178
+ };
179
+ invalidate("loading");
180
+ try {
181
+ for (const [event, listener] of [
182
+ ["load", handleLoad],
183
+ ["dataloading", handleLoading],
184
+ ["styledataloading", handleLoading],
185
+ ["idle", handleIdle],
186
+ ["error", handleError]
187
+ ]) {
188
+ unsubscribers.push(options.subscribe(options.map, event, listener));
189
+ }
190
+ } catch (error) {
191
+ disposed = true;
192
+ generation += 1;
193
+ cancelFrames();
194
+ try {
195
+ disposeFunctions(unsubscribers);
196
+ } catch {
197
+ }
198
+ throw error;
199
+ }
200
+ return {
201
+ dispose() {
202
+ if (disposed) {
203
+ return;
204
+ }
205
+ disposed = true;
206
+ generation += 1;
207
+ cancelFrames();
208
+ disposeFunctions(unsubscribers);
209
+ },
210
+ invalidate(state) {
211
+ if (!disposed) {
212
+ invalidate(state);
213
+ }
214
+ }
215
+ };
216
+ }
217
+ function createTileflowTransformRequest(options) {
218
+ if (!options.always && !options.transformRequest) {
219
+ const analytics = options.getAnalytics();
220
+ const requiresCommercialSession = Boolean(options.sessionController && analytics?.mapId);
221
+ if (!requiresCommercialSession && (!analytics || analytics.enabled === false)) {
222
+ return void 0;
223
+ }
224
+ }
225
+ const asyncAnalyticsTiming = options.asyncAnalyticsTiming ?? "request";
226
+ return (url, resourceType) => {
227
+ const request = options.transformRequest?.(url, resourceType);
228
+ const analyticsAtRequest = asyncAnalyticsTiming === "request" ? options.getAnalytics() : void 0;
229
+ if (isPromiseLike(request)) {
230
+ return request.then(
231
+ (resolvedRequest) => applyTileflowRequest(
232
+ url,
233
+ resolvedRequest,
234
+ asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
235
+ options.sessionId,
236
+ options.sessionController
237
+ ) ?? resolvedRequest
238
+ );
239
+ }
240
+ return applyTileflowRequest(
241
+ url,
242
+ request,
243
+ asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
244
+ options.sessionId,
245
+ options.sessionController
246
+ );
247
+ };
248
+ }
249
+ function createTileflowMarkerController(options) {
250
+ let markers = [];
251
+ const clear = () => {
252
+ const previousMarkers = markers;
253
+ markers = [];
254
+ removeMarkers(previousMarkers, options.remove);
255
+ };
256
+ return {
257
+ clear,
258
+ dispose: clear,
259
+ replace(map, definitions) {
260
+ clear();
261
+ const nextMarkers = [];
262
+ try {
263
+ for (const definition of definitions) {
264
+ const marker = options.create(definition);
265
+ nextMarkers.push(marker);
266
+ options.attach(marker, map, definition);
267
+ }
268
+ } catch (error) {
269
+ try {
270
+ removeMarkers(nextMarkers, options.remove);
271
+ } catch {
272
+ }
273
+ throw error;
274
+ }
275
+ markers = nextMarkers;
276
+ }
277
+ };
278
+ }
279
+ function applyTileflowRequest(url, request, analytics, sessionId, sessionController) {
280
+ const requestUrl = request?.url ?? url;
281
+ if (sessionController) {
282
+ return sessionController.resolveRequestUrl(requestUrl, analytics).then(
283
+ (nextUrl) => composeTileflowRequest(url, request, nextUrl, true) ?? { url }
284
+ );
285
+ }
286
+ return composeTileflowRequest(
287
+ url,
288
+ request,
289
+ resolveTileflowAnalyticsRequestUrl(requestUrl, analytics, sessionId),
290
+ false
291
+ );
292
+ }
293
+ function composeTileflowRequest(originalUrl, request, nextUrl, ensureRequest) {
294
+ if (!nextUrl) {
295
+ return request ?? (ensureRequest ? { url: originalUrl } : void 0);
296
+ }
297
+ return request ? { ...request, url: nextUrl } : { url: nextUrl };
298
+ }
299
+ function disposeFunctions(disposers) {
300
+ let firstError;
301
+ let failed = false;
302
+ for (const dispose of disposers.splice(0).reverse()) {
303
+ try {
304
+ dispose();
305
+ } catch (error) {
306
+ if (!failed) firstError = error;
307
+ failed = true;
308
+ }
309
+ }
310
+ if (failed) {
311
+ throw firstError;
312
+ }
313
+ }
314
+ function isPromiseLike(value) {
315
+ return Boolean(value && typeof value.then === "function");
316
+ }
317
+ function removeMarkers(markers, remove) {
318
+ let firstError;
319
+ let failed = false;
320
+ for (const marker of markers) {
321
+ try {
322
+ remove(marker);
323
+ } catch (error) {
324
+ if (!failed) firstError = error;
325
+ failed = true;
326
+ }
327
+ }
328
+ if (failed) {
329
+ throw firstError;
330
+ }
331
+ }
332
+ export {
333
+ attachTileflowMapLifecycle,
334
+ createTileflowMarkerController,
335
+ createTileflowSessionStarter,
336
+ createTileflowTransformRequest
337
+ };