@tileflow/core 0.1.0-alpha.16 → 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
@@ -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,24 @@ 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
+ Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
276
+ details and can change during alpha releases.
258
277
 
259
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
+ };