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

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
@@ -1,261 +1,280 @@
1
1
  # @tileflow/core
2
2
 
3
- Typed Tileflow config helpers and the MapLibre style compiler.
3
+ Typed Tileflow authoring primitives and the deterministic Tileflow Streets compiler.
4
4
 
5
5
  ```ts
6
- import {defineTileflow, labels, osm, poi, roads} from '@tileflow/core';
6
+ import {defineTileflow, labels, poi, roads, streets, water, zoom} from '@tileflow/core';
7
7
 
8
8
  export default defineTileflow({
9
- icons: {
10
- brand: {
11
- sprite: 'https://cdn.example.com/tileflow/brand/sprite',
12
- },
13
- },
14
9
  themes: {
15
- light: {
10
+ editorial: {
11
+ extends: 'light',
16
12
  colors: {
17
- background: '#F8F7F7',
18
- land: '#F4F2ED',
19
- water: '#8ED6E8',
20
- park: '#C3F1D5',
21
- building: '#EEF0F2',
22
- road: '#FFFFFF',
23
- roadMajor: '#F5D58A',
24
- roadCasing: '#DDE0E3',
25
- boundary: '#C9CED3',
26
- text: '#566371',
27
- textMuted: '#8A98A8',
28
- textHalo: '#FFFFFF',
29
- },
30
- modules: {
31
- labels: {
32
- road: '#7A8794',
33
- water: '#4B8FA8',
34
- },
35
- poi: {
36
- food: '#D97706',
37
- transit: '#2563EB',
38
- },
39
- roads: {
40
- motorway: '#F7C56F',
41
- },
42
- },
43
- typography: {
44
- font: 'Inter',
45
- roads: {weight: 'bold'},
13
+ background: '#E4DFD4',
14
+ land: '#F5F2EB',
15
+ water: '#A7CED7',
16
+ park: '#C6D8B7',
17
+ building: '#D6CFC3',
18
+ road: '#FCFBF8',
19
+ roadMajor: '#EBCB8F',
20
+ roadCasing: '#BBB1A1',
21
+ boundary: '#999184',
22
+ text: '#252B2D',
23
+ textMuted: '#626866',
24
+ textHalo: '#FBF9F4',
46
25
  },
26
+ typography: {font: 'Noto Sans', places: {weight: 'bold'}},
47
27
  },
48
28
  },
49
29
  maps: {
50
30
  madrid: {
51
- basemap: osm({version: '2026-06-07'}),
52
- icons: 'brand',
53
- theme: 'light',
54
- modules: [
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({
31
+ basemap: streets(),
32
+ theme: 'editorial',
33
+ modules: {
34
+ water: water({bodies: {fill: {opacity: 0.95}}}),
35
+ roads: roads({
63
36
  detail: 'streets',
64
- hierarchy: 'clear',
65
- oneWayMarkers: true,
66
- outline: 'subtle',
67
- weight: 'regular',
68
- widthScale: {motorway: 1.15, minor: 0.9},
37
+ hierarchy: 'strong',
38
+ classes: {
39
+ primary: {
40
+ surface: {
41
+ fill: {
42
+ color: '#E4A85B',
43
+ width: zoom.linear([
44
+ [7, 0.6],
45
+ [16, 8],
46
+ ]),
47
+ },
48
+ },
49
+ },
50
+ },
69
51
  }),
70
- ],
71
- layers: {
72
- 'highway-primary': {
73
- paint: {
74
- 'line-opacity': 0.9,
52
+ labels: labels({
53
+ language: 'local',
54
+ places: 'all',
55
+ roads: 'streets',
56
+ styles: {
57
+ places: {city: {text: {size: 18, haloWidth: 1.5}}},
75
58
  },
76
- },
77
- },
78
- view: {
79
- center: [-3.7038, 40.4168],
80
- zoom: 12,
59
+ }),
60
+ poi: poi({categories: ['food', 'culture', 'transit'], color: 'category'}),
81
61
  },
62
+ view: {center: [-3.7038, 40.4168], pitch: 35, zoom: 12},
82
63
  },
83
64
  },
84
65
  });
85
66
  ```
86
67
 
87
- ## Capture scenes
68
+ ## Authoring model
88
69
 
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.
70
+ `streets()` is the only built-in basemap. It identifies a versioned cartographic recipe and does
71
+ not inherit or patch another style. The compiler resolves the complete Streets design and asks
72
+ each domain module to create its own MapLibre layers. A shared graph then determines layer order.
91
73
 
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
- });
74
+ ```mermaid
75
+ flowchart LR
76
+ A["Human or agent edits config"] --> B["Streets defaults + theme + keyed modules"]
77
+ B --> C["Domain compilers"]
78
+ C --> D["Ordered Streets layers"]
79
+ D --> E["Validated MapLibre Style JSON"]
115
80
  ```
116
81
 
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.
82
+ Modules are keyed by domain, so object order never controls rendering and a domain can appear only
83
+ once. Omit a module to keep the complete Streets default. Use `enabled: false` to remove a domain
84
+ deliberately. The public domains are `land`, `water`, `roads`, `buildings`, `boundaries`, `labels`,
85
+ `poi`, `aeroways`, and `transit`.
123
86
 
124
- ## Style contract
87
+ Every styling module supports semantic shortcuts and exact semantic targets. Style values accept
88
+ constants, raw MapLibre expressions through `expression(...)`, and zoom functions through
89
+ `zoom.step(...)`, `zoom.linear(...)`, or `zoom.exponential(...)`. Exact controls address stable
90
+ concepts such as `roads.classes.primary.surface.fill`, not renderer layer IDs.
125
91
 
126
- Tileflow keeps these levels separate:
92
+ ## Shared visual primitives
127
93
 
128
- - `basemap: osm()` chooses the source defaults, glyphs, sprites, and OSM Bright layer template.
129
- - `theme.colors` contains human-scale base tokens such as `water`, `park`, `road`, `roadMajor`, `roadCasing`, `text`, and `textHalo`.
130
- - `theme.modules.<name>` contains advanced, module-specific tokens. These are granular: setting `theme.modules.roads.rail` does not recolor primary roads.
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.
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.
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.
136
- - `layers` is the raw MapLibre escape hatch for exact layer patches.
94
+ The same small visual language is reused across every geographic domain. This keeps an agent from
95
+ having to learn different spellings for the same MapLibre behavior:
137
96
 
138
- Missing colors preserve the basemap default. Legacy aliases such as `accent`, `canvas`, `greenspace`, `fontFamily`, and `fonts` are accepted for compatibility but are not the recommended API.
97
+ - `BackgroundStyle`: color, opacity, pattern, visibility, and zoom range.
98
+ - `FillStyle`: color, opacity, pattern, antialiasing, visibility, and zoom range.
99
+ - `LineStyle`: color, width, opacity, dash, blur, gap, offset, pattern, caps, joins, and zoom range.
100
+ - `LineHatchStyle`: repeated diagonal detail with color, opacity, spacing, size, angle, and zoom
101
+ range for a road structure.
102
+ - `TextStyle`: field, font, weight, fallbacks, size, color, halo, spacing metrics, transform,
103
+ collision policy, rotation, fixed/variable anchoring, line constraints, and zoom range.
104
+ - `IconStyle`: image, size, color, halo, rotation/alignment, collision policy, and zoom range.
105
+ - `CircleStyle`: radius, fill/stroke appearance, blur, opacity, pitch behavior, and zoom range.
106
+ - `ExtrusionStyle`: color, height, base, opacity, pattern, vertical gradient, and zoom range.
139
107
 
140
- ## Roads module
108
+ Compounds express common cartographic structures: `AreaStyle` has `fill` and `outline`,
109
+ `LineStackStyle` has `shadow`, `casing`, and `fill`, and `SymbolStyle` combines placement with
110
+ optional `text`, `icon`, and `marker`. Geographic selection remains owned by semantic modules;
111
+ ordinary visual styles intentionally do not accept raw source filters.
141
112
 
142
- Control each independent road-network decision directly:
113
+ A `SymbolStyle` root zoom range governs its text/icon layer and is inherited by its marker layer.
114
+ Because a marker is materialized as a separate circle layer, an explicit marker range may refine
115
+ that inherited default. Text and icon ranges must agree because MapLibre renders them together.
116
+
117
+ The road module distinguishes the path family semantically:
143
118
 
144
119
  ```ts
145
120
  roads({
146
- detail: 'streets',
147
- hierarchy: 'strong',
148
- weight: 'regular',
149
- outline: 'strong',
150
- oneWayMarkers: true,
151
- widthScale: {motorway: 1.15, primary: 1.05, minor: 0.9},
152
- extras: {
153
- paths: false,
154
- rail: false,
155
- ferry: false,
121
+ extras: {paths: true},
122
+ areas: {
123
+ pedestrian: {
124
+ fill: {color: '#F1F3F5'},
125
+ outline: {color: '#D5DCE3', width: 1},
126
+ },
127
+ },
128
+ classes: {
129
+ primary: {
130
+ tunnel: {
131
+ casing: {color: '#8EA3B8', width: 10},
132
+ fill: {color: '#F5F8FA', width: 8},
133
+ hatch: {color: '#8EA3B8', opacity: 0.25, spacing: 10},
134
+ },
135
+ },
136
+ pedestrian: {surface: {fill: {color: '#F5F6F7', width: 6}}},
137
+ footway: {surface: {fill: {color: '#D9DEE3', width: 1.2}}},
138
+ cycleway: {surface: {fill: {color: '#9ED8C5', width: 1.5}}},
139
+ steps: {surface: {fill: {color: '#C7CED6', dash: [1, 1], width: 1.4}}},
140
+ pathway: {surface: {fill: {color: '#E8ECEF', width: 1.2}}},
141
+ },
142
+ modifiers: {
143
+ expressway: {widthScale: 1.06},
144
+ ramp: {widthScale: 0.7},
145
+ unpaved: {surface: {fill: {color: '#E6DFD3', dash: [2, 1]}}},
146
+ construction: {surface: {fill: {dash: [2, 1], opacity: 0.7}}},
147
+ indoor: {surface: {fill: {dash: [1, 1], opacity: 0.4}}},
148
+ },
149
+ restrictions: {
150
+ access: {surface: {fill: {dash: [1.5, 1], opacity: 0.55}}},
151
+ toll: {surface: {casing: {color: '#C5B7D8'}}},
152
+ },
153
+ serviceTypes: {
154
+ driveway: {widthScale: 0.75},
155
+ parkingAisle: {widthScale: 0.6},
156
156
  },
157
157
  });
158
158
  ```
159
159
 
160
- `detail` is progressive: `none` hides roads; `highways` shows motorway and trunk roads;
161
- `major` adds primary, secondary, and tertiary roads; `streets` adds minor streets; and `all`
162
- adds service roads and tracks. `hierarchy` accepts `subtle`, `clear`, or `strong`; `weight`
163
- accepts `thin`, `regular`, or `bold`; and `outline` accepts `none`, `subtle`, or `strong`.
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.
167
-
168
- Without options, `roads()` uses `detail: 'streets'`, `hierarchy: 'clear'`,
169
- `weight: 'regular'`, `outline: 'subtle'`, and all extras disabled. Named visual recipes belong
170
- in examples rather than the runtime API, so the resolved behavior is always visible in config.
160
+ These targets are non-overlapping translations of the OpenMapTiles road class and subclass. The
161
+ same names are available under `labels().roadClasses` and `labels().styles.roads`. Surface, tunnel,
162
+ and bridge phases can be controlled independently for each target; each structure has
163
+ `shadow`/`casing`/`fill` and an optional `hatch`. Hatch marks inherit the resolved fill width when
164
+ `size` is omitted and never participate in label collision. No raw source filter or generated layer
165
+ ID is needed.
171
166
 
172
- Colors never belong in `roads()`. Put common colors in `theme.colors` and granular road colors in `theme.modules.roads`. Road-label detail remains a `labels({roads: ...})` concern.
167
+ `classes.pedestrian` styles line-like pedestrian ways. Polygon pedestrian plazas are a distinct
168
+ geometry and use `areas.pedestrian`, including optional fill opacity, outline color, or sprite
169
+ pattern. This prevents plazas from degrading into a thin polygon outline.
173
170
 
174
- ## Labels module
171
+ Road treatments refine a class without creating another class or exposing source fields.
172
+ `construction`, `expressway`, `indoor`, `official`, `ramp`, and `unpaved` live under `modifiers`;
173
+ general, bicycle, foot, horse, and toll restrictions live under `restrictions`; `alley`,
174
+ `crossover`, `driveway`, `parkingAisle`, and `yard` live under `serviceTypes`; and exact
175
+ mountain-bike difficulty values live under `mountainBike`. Treatments reuse
176
+ surface/tunnel/bridge and casing/fill/shadow paint controls, plus a relative `widthScale`. The
177
+ compiler emits feature-driven paint expressions inside the existing stable class layers rather
178
+ than multiplying every possible combination.
175
179
 
176
- Control label families independently without a preset:
180
+ Road names, shields, and motorway junctions remain label concerns:
177
181
 
178
182
  ```ts
179
183
  labels({
180
- language: 'auto',
181
- places: 'major',
182
- roads: 'major',
183
- roadClasses: ['motorway', 'trunk', 'primary', 'secondary'],
184
- water: 'major',
184
+ roads: 'all',
185
+ shields: 'all',
186
+ junctions: true,
187
+ styles: {
188
+ shields: {
189
+ default: {text: {color: '#405264', haloColor: '#fff', haloWidth: 2, weight: 'bold'}},
190
+ networks: {
191
+ 'network-value': {text: {color: '#B43A35'}},
192
+ },
193
+ },
194
+ junctions: {text: {color: '#405264', haloColor: '#fff', haloWidth: 2}},
195
+ },
185
196
  });
186
197
  ```
187
198
 
188
- `places` and `water` accept `none`, `major`, or `all`. Road labels follow the road hierarchy and
189
- accept `none`, `highways`, `major`, `streets`, or `all`: `highways` includes motorway and trunk
190
- labels, `major` adds primary through tertiary, `streets` adds minor streets, and `all` adds service
191
- roads and tracks. Path labels are eligible at `all` only when path geometry is visible; when
192
- `roads()` is present, enable it with `roads({extras: {paths: true}})`.
199
+ POI `density`, `labels`, and `icons` are independent policies. Density bounds eligible feature
200
+ ranks, label detail controls text ranks, icon detail controls icon ranks, and
201
+ `placement.coupleIconAndLabel` deliberately keeps only features eligible for both. Without
202
+ coupling, icon and label layers collide normally but can be styled and zoomed independently.
203
+ The `balanced` policy keeps a practical cross-category candidate set from an overscaled
204
+ OpenMapTiles source tile; MapLibre collision placement still decides which candidates fit the
205
+ current viewport. Use a category style's inclusive `maxRank` when different feature families need
206
+ different candidate ceilings; the explicit category value replaces the density/label/icon preset
207
+ ceiling for that category without exposing a raw layer ID or filter.
193
208
 
194
- Detail values select which feature classes may be labeled; they do not force every name onto the
195
- map. Labels keep the template's natural zoom ranges and MapLibre collision handling. Road labels
196
- are also clamped to the classes currently visible through `roads({detail})`, so the effective result
197
- is the intersection of both modules. `language` accepts `auto`, `local`, or a language code such as
198
- `en` or `es`.
209
+ Theme tokens provide shared color and typography defaults. A module-level exact style wins over a
210
+ theme token for that target. Precedence is deterministic:
199
211
 
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.
212
+ 1. Streets recipe defaults.
213
+ 2. Selected theme and inherited named theme.
214
+ 3. Explicit keyed module fields.
215
+ 4. Ordered raw overrides.
202
216
 
203
- Without options, `labels()` uses `language: 'auto'` and `major` for all three label families.
204
- Label colors remain in `theme.modules.labels`; font family and weight remain in `typography`.
217
+ Raw `addLayer`, `patchLayer`, `moveLayer`, and `removeLayer` operations are the final MapLibre escape
218
+ hatch. They are ordered, target generated `streets-*` IDs explicitly, and fail when a target or
219
+ anchor does not exist. They should be used for one-off MapLibre behavior, not ordinary design.
205
220
 
206
- ## POI module
221
+ ## Data is separate from design
207
222
 
208
- POI category policy is resolved once for filtering, icon selection, label color, and collision
209
- priority:
223
+ Omitting `data` selects Tileflow World at the revision pinned by this SDK version:
210
224
 
211
225
  ```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
- });
226
+ {
227
+ basemap: streets();
228
+ }
221
229
  ```
222
230
 
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.
231
+ Pin the official data deliberately, or use another OpenMapTiles-compatible vector source:
228
232
 
229
- ## Tile-source revision
233
+ ```ts
234
+ import {openMapTiles, tileflowWorld, vectorTiles} from '@tileflow/core';
230
235
 
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.
236
+ const official = tileflowWorld({revision: '2026-06-07'});
237
+ const external = vectorTiles({
238
+ url: 'https://tiles.example.com/tiles.json',
239
+ attribution: '© Example © OpenStreetMap contributors',
240
+ schema: openMapTiles(),
241
+ });
242
+ ```
243
+
244
+ The basemap controls how data is drawn; `data` controls where compatible features come from. The
245
+ compiler performs no network request. `openMapTiles({layers, fields})` can bind renamed
246
+ source-layers or properties while preserving the versioned semantic contract. External browser
247
+ credentials do not belong in public Style JSON; supply them through the framework's
248
+ `transformRequest` integration.
235
249
 
236
- ## Public API
250
+ ## Capture scenes
237
251
 
238
- Application config, compilation, runtime resolution, and capture-scene APIs come from the package
239
- root:
252
+ Commit named scenes beside maps. Scenes affect visual evidence, not style or manifest identity.
240
253
 
241
254
  ```ts
242
- import {
243
- createStyle,
244
- defineTileflow,
245
- labels,
246
- osm,
247
- poi,
248
- roads,
249
- styleOverride,
250
- validateConfig,
251
- } from '@tileflow/core';
255
+ export default defineTileflow({
256
+ maps: {madrid: {basemap: streets()}},
257
+ scenes: {
258
+ 'madrid-desktop': {
259
+ map: 'madrid',
260
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
261
+ viewport: {width: 1280, height: 800, dpr: 1},
262
+ },
263
+ },
264
+ });
252
265
  ```
253
266
 
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.
267
+ Use `tileflow dev --scene madrid-desktop` for live review and `tileflow capture madrid-desktop`
268
+ for exact pixels and a schema-version-2 receipt containing the Streets, data, style, renderer, and
269
+ image identities.
270
+
271
+ ## Public API and browser subpath
272
+
273
+ Config, data, modules, compilation, validation, runtime resolution, and capture-scene APIs come
274
+ from the package root. Legacy basemap factories, `renderer`, top-level `tiles`/`tileset`, module
275
+ arrays, and raw `layers` are not part of this API.
257
276
 
258
- Framework adapters can import the browser-only lifecycle kernel explicitly:
277
+ Framework adapters import the browser-only lifecycle kernel explicitly:
259
278
 
260
279
  ```ts
261
280
  import {
@@ -266,13 +285,30 @@ import {
266
285
  } from '@tileflow/core/browser';
267
286
  ```
268
287
 
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).
288
+ The browser entry is not re-exported from the root. It is SSR-safe to import, has no MapLibre
289
+ runtime dependency, and reads no browser global during module evaluation. See the
290
+ [framework browser runtime contract](../../docs/contracts/framework-browser-runtime.md) and the
291
+ [cartographic authoring contract](../../docs/contracts/cartographic-authoring.md).
292
+
293
+ ## Hosted session authorization
294
+
295
+ The framework adapters use `createTileflowSessionController` to obtain one short-lived session
296
+ grant before eligible hosted style, tile, font, or sprite requests. Concurrent resources share the
297
+ same preflight, grants are attached only to reviewed HTTP(S) origins, and an expired unused session
298
+ rotates once when the server requests it. A controller also rotates after six hours or 10,000
299
+ eligible requests. The preflight has a 10-second client timeout by default; direct controller users
300
+ can set `grantTimeoutMs` from 1 to 120,000 milliseconds.
301
+
302
+ `analytics.enabled: false` disables only the optional analytics beacon. It does not disable this
303
+ server-owned commercial authorization. User `transformRequest` callbacks still run first; Tileflow
304
+ then decorates only the resulting eligible URL and preserves the other request options.
274
305
 
275
306
  Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
276
307
  details and can change during alpha releases.
277
308
 
278
- Docs: https://tileflow.dev/docs
309
+ Streets build, preview, capture, and deploy flows supply a built-in POI sprite with Google Places
310
+ glyphs inside Tileflow circular markers. The markers use Google's category colors, a white rim, and
311
+ a compact shadow. A project-local icon source or explicit hosted sprite replaces that catalog;
312
+ build and deploy replace local sources with generated sprite URLs without exposing source paths.
313
+ Calling the pure core style compiler without asset preparation remains text-only unless the map
314
+ provides `icons` or `sprite` explicitly.
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as TileflowAnalytics } from './runtime-BMZ_tuba.js';
1
+ import { T as TileflowAnalytics, a as TileflowSessionController } from './runtime-Dd_xGAXq.js';
2
2
  import 'zod';
3
3
 
4
4
  type TileflowMapReadinessState = 'error' | 'idle' | 'loading';
@@ -35,6 +35,7 @@ type TileflowMarkerController<TMap, TDefinition, TMarker> = {
35
35
  replace: (map: TMap, definitions: readonly TDefinition[]) => void;
36
36
  };
37
37
  declare function createTileflowSessionStarter(options: {
38
+ getSessionId?: () => string;
38
39
  sessionId: string;
39
40
  source: string;
40
41
  startSession?: TileflowSessionSender;
@@ -55,6 +56,7 @@ declare function createTileflowTransformRequest<TRequest extends TileflowTransfo
55
56
  always: true;
56
57
  asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
57
58
  getAnalytics: () => TileflowAnalytics | undefined;
59
+ sessionController?: TileflowSessionController;
58
60
  sessionId: string;
59
61
  transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
60
62
  }): TileflowTransformRequest<TRequest, TResourceType>;
@@ -62,6 +64,7 @@ declare function createTileflowTransformRequest<TRequest extends TileflowTransfo
62
64
  always?: boolean;
63
65
  asyncAnalyticsTiming?: TileflowAsyncAnalyticsTiming;
64
66
  getAnalytics: () => TileflowAnalytics | undefined;
67
+ sessionController?: TileflowSessionController;
65
68
  sessionId: string;
66
69
  transformRequest?: TileflowUserTransformRequest<TRequest, TResourceType>;
67
70
  }): TileflowTransformRequest<TRequest, TResourceType> | undefined;
package/dist/browser.js CHANGED
@@ -26,7 +26,7 @@ function startTileflowSession(analytics, input) {
26
26
  if (!analytics || analytics.enabled === false || !input.mapId || typeof navigator === "undefined") {
27
27
  return;
28
28
  }
29
- const endpoint = analytics.endpoint ?? `${getTileflowAnalyticsApiUrl(analytics)}/v1/sessions/start`;
29
+ const endpoint = analytics.endpoint ?? `${getTileflowAnalyticsApiUrl(analytics)}/v1/sessions/analytics`;
30
30
  const payload = JSON.stringify({
31
31
  mapId: input.mapId,
32
32
  metadata: analytics.metadata,
@@ -49,7 +49,7 @@ function startTileflowSession(analytics, input) {
49
49
  },
50
50
  keepalive: true,
51
51
  method: "POST"
52
- });
52
+ }).catch(() => void 0);
53
53
  }
54
54
  function getTileflowAnalyticsApiUrl(analytics) {
55
55
  if (analytics.apiUrl) {
@@ -94,17 +94,18 @@ function createTileflowSessionStarter(options) {
94
94
  },
95
95
  start(analytics, styleId) {
96
96
  const mapId = analytics?.mapId;
97
+ const sessionId = options.getSessionId?.() ?? options.sessionId;
97
98
  if (!mapId) {
98
99
  return false;
99
100
  }
100
- const key = `${options.sessionId}:${mapId}:${styleId ?? ""}`;
101
+ const key = `${sessionId}:${mapId}:${styleId ?? ""}`;
101
102
  if (starts.has(key)) {
102
103
  return false;
103
104
  }
104
105
  starts.add(key);
105
106
  send(analytics, {
106
107
  mapId,
107
- sessionId: options.sessionId,
108
+ sessionId,
108
109
  source: options.source,
109
110
  styleId
110
111
  });
@@ -216,7 +217,8 @@ function attachTileflowMapLifecycle(options) {
216
217
  function createTileflowTransformRequest(options) {
217
218
  if (!options.always && !options.transformRequest) {
218
219
  const analytics = options.getAnalytics();
219
- if (!analytics || analytics.enabled === false) {
220
+ const requiresCommercialSession = Boolean(options.sessionController && analytics?.mapId);
221
+ if (!requiresCommercialSession && (!analytics || analytics.enabled === false)) {
220
222
  return void 0;
221
223
  }
222
224
  }
@@ -226,19 +228,21 @@ function createTileflowTransformRequest(options) {
226
228
  const analyticsAtRequest = asyncAnalyticsTiming === "request" ? options.getAnalytics() : void 0;
227
229
  if (isPromiseLike(request)) {
228
230
  return request.then(
229
- (resolvedRequest) => applyTileflowAnalyticsRequest(
231
+ (resolvedRequest) => applyTileflowRequest(
230
232
  url,
231
233
  resolvedRequest,
232
234
  asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
233
- options.sessionId
235
+ options.sessionId,
236
+ options.sessionController
234
237
  ) ?? resolvedRequest
235
238
  );
236
239
  }
237
- return applyTileflowAnalyticsRequest(
240
+ return applyTileflowRequest(
238
241
  url,
239
242
  request,
240
243
  asyncAnalyticsTiming === "resolution" ? options.getAnalytics() : analyticsAtRequest,
241
- options.sessionId
244
+ options.sessionId,
245
+ options.sessionController
242
246
  );
243
247
  };
244
248
  }
@@ -272,10 +276,23 @@ function createTileflowMarkerController(options) {
272
276
  }
273
277
  };
274
278
  }
275
- function applyTileflowAnalyticsRequest(url, request, analytics, sessionId) {
276
- const nextUrl = resolveTileflowAnalyticsRequestUrl(request?.url ?? url, analytics, sessionId);
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) {
277
294
  if (!nextUrl) {
278
- return request;
295
+ return request ?? (ensureRequest ? { url: originalUrl } : void 0);
279
296
  }
280
297
  return request ? { ...request, url: nextUrl } : { url: nextUrl };
281
298
  }