@tileflow/static 0.1.0-alpha.20 → 0.1.0-alpha.22
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 +102 -12
- package/dist/chunk-EMCKSFZV.js +477 -0
- package/dist/{chunk-LGEYA6FQ.js → chunk-EPOYQNNT.js} +22 -6
- package/dist/{chunk-QCR76BUO.js → chunk-MSQ4OLKA.js} +74 -4
- package/dist/{chunk-YUG3PBK2.js → chunk-NM2JSFUF.js} +63 -9
- package/dist/chunk-TQMKOTBE.js +287 -0
- package/dist/client.d.ts +94 -5
- package/dist/client.js +11 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +45 -5
- package/dist/manifest.d.ts +579 -8
- package/dist/manifest.js +25 -3
- package/dist/overlays.d.ts +5 -3
- package/dist/overlays.js +2 -2
- package/dist/{scene-contract-DzsUeJ_V.d.ts → scene-contract-OMIOisSB.d.ts} +66 -3
- package/dist/scene.d.ts +200 -5
- package/dist/scene.js +12 -2
- package/package.json +1 -1
- package/dist/chunk-HKYA7ZIU.js +0 -79
- package/dist/chunk-ROBEGDDY.js +0 -63
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
StaticMapRequestError,
|
|
3
|
+
staticMapRequestErrorResponseSchema,
|
|
2
4
|
validateStaticScene
|
|
3
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EMCKSFZV.js";
|
|
4
6
|
import {
|
|
5
7
|
isSafeHttpUrl,
|
|
8
|
+
jsonByteLength,
|
|
6
9
|
stableStringify
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NM2JSFUF.js";
|
|
8
11
|
|
|
9
12
|
// src/client.ts
|
|
10
13
|
import { z } from "zod";
|
|
11
14
|
var maxStaticMapResponseBytes = 64 * 1024;
|
|
12
15
|
var staticMapIdempotencyKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
16
|
+
var STATIC_MAP_RESULT_V2_MEDIA_TYPE = "application/vnd.tileflow.static-map-result+json;version=2";
|
|
13
17
|
var preparedStaticMapRequestBodies = /* @__PURE__ */ new WeakMap();
|
|
14
18
|
var staticMapReadyResultSchema = z.object({
|
|
15
19
|
cached: z.boolean(),
|
|
@@ -20,6 +24,39 @@ var staticMapReadyResultSchema = z.object({
|
|
|
20
24
|
status: z.literal("ready"),
|
|
21
25
|
unitCost: z.literal(15)
|
|
22
26
|
}).strict();
|
|
27
|
+
var safeAttributionTextSchema = z.string().min(1).max(16384).refine(hasNoControlCharacters, { message: "Attribution text contains control characters" });
|
|
28
|
+
var staticMapAttributionEntrySchema = z.object({
|
|
29
|
+
authority: z.enum(["platform-notice", "team-declared"]),
|
|
30
|
+
links: z.array(
|
|
31
|
+
z.object({
|
|
32
|
+
label: safeAttributionTextSchema,
|
|
33
|
+
url: z.string().trim().url().max(2048).refine(isSafeHttpUrl, {
|
|
34
|
+
message: "Expected an http(s) URL without credentials or a fragment"
|
|
35
|
+
})
|
|
36
|
+
}).strict()
|
|
37
|
+
).max(8),
|
|
38
|
+
text: safeAttributionTextSchema
|
|
39
|
+
}).strict();
|
|
40
|
+
var staticMapAttributionResultSchema = z.object({
|
|
41
|
+
entries: z.array(staticMapAttributionEntrySchema).max(18),
|
|
42
|
+
mode: z.enum(["embedded", "external"]),
|
|
43
|
+
position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).nullable()
|
|
44
|
+
}).strict().superRefine((result, context) => {
|
|
45
|
+
if (result.mode === "external" && result.position !== null || result.mode === "embedded" && result.position === null) {
|
|
46
|
+
context.addIssue({
|
|
47
|
+
code: "custom",
|
|
48
|
+
message: "Attribution position must match its mode",
|
|
49
|
+
path: ["position"]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (jsonByteLength(result) > 32 * 1024) {
|
|
53
|
+
context.addIssue({ code: "custom", message: "Attribution result exceeds 32 KiB" });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
var staticMapHostedResultSchema = staticMapReadyResultSchema.extend({
|
|
57
|
+
attribution: staticMapAttributionResultSchema,
|
|
58
|
+
resultVersion: z.literal(2)
|
|
59
|
+
}).strict();
|
|
23
60
|
var staticMapProcessingResultSchema = z.object({
|
|
24
61
|
operationId: z.string().min(20).max(68),
|
|
25
62
|
retryAfterMs: z.number().int().min(0).max(5e3),
|
|
@@ -44,6 +81,10 @@ function createStaticMapIdempotencyKey() {
|
|
|
44
81
|
function prepareStaticMapRequest(scene) {
|
|
45
82
|
const validation = validateStaticScene(scene);
|
|
46
83
|
if (!validation.ok) {
|
|
84
|
+
if (isStaticMapRequestFailure(validation)) {
|
|
85
|
+
const { ok: _ok, ...response } = validation;
|
|
86
|
+
throw new StaticMapRequestError(response);
|
|
87
|
+
}
|
|
47
88
|
throw new Error(`Invalid Tileflow static scene: ${validation.error}`);
|
|
48
89
|
}
|
|
49
90
|
const prepared = Object.freeze({
|
|
@@ -86,6 +127,7 @@ async function requestStaticMapUntilReady(request, options) {
|
|
|
86
127
|
const fetcher = options.fetch ?? fetch;
|
|
87
128
|
const createUrl = normalizeStaticMapEndpointUrl(options.createUrl);
|
|
88
129
|
const headers = {
|
|
130
|
+
Accept: STATIC_MAP_RESULT_V2_MEDIA_TYPE,
|
|
89
131
|
"Content-Type": "application/json",
|
|
90
132
|
"Idempotency-Key": idempotency.key
|
|
91
133
|
};
|
|
@@ -103,14 +145,17 @@ async function requestStaticMapUntilReady(request, options) {
|
|
|
103
145
|
});
|
|
104
146
|
throwIfAborted(signal);
|
|
105
147
|
if (!response.ok) {
|
|
106
|
-
await discardBoundedResponse(response, 8 * 1024);
|
|
148
|
+
const requestError = response.status === 422 ? await readStaticMapRequestErrorResponse(response, 8 * 1024) : await discardBoundedResponse(response, 8 * 1024);
|
|
107
149
|
throwIfAborted(signal);
|
|
150
|
+
if (requestError) {
|
|
151
|
+
throw new StaticMapRequestError(requestError, response.status);
|
|
152
|
+
}
|
|
108
153
|
throw new Error(`Tileflow static map failed (${response.status}).`);
|
|
109
154
|
}
|
|
110
155
|
const json = await readJsonResponse(response);
|
|
111
156
|
throwIfAborted(signal);
|
|
112
157
|
if (response.status !== 202) {
|
|
113
|
-
const parsed =
|
|
158
|
+
const parsed = staticMapHostedResultSchema.safeParse(json);
|
|
114
159
|
if (!parsed.success) {
|
|
115
160
|
throw new Error(
|
|
116
161
|
`Tileflow static map returned an invalid response: ${parsed.error.message}`
|
|
@@ -131,6 +176,12 @@ async function requestStaticMapUntilReady(request, options) {
|
|
|
131
176
|
}
|
|
132
177
|
});
|
|
133
178
|
}
|
|
179
|
+
function hasNoControlCharacters(value) {
|
|
180
|
+
return !Array.from(value).some((character) => {
|
|
181
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
182
|
+
return codePoint <= 31 || codePoint === 127;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
134
185
|
function boundedClientDuration(value, fallback, minimum, maximum, name) {
|
|
135
186
|
const resolved = value ?? fallback;
|
|
136
187
|
if (!Number.isSafeInteger(resolved) || resolved < minimum || resolved > maximum) {
|
|
@@ -148,11 +199,26 @@ async function readJsonResponse(response) {
|
|
|
148
199
|
throw new Error("Tileflow static map returned an invalid response: expected JSON");
|
|
149
200
|
}
|
|
150
201
|
}
|
|
202
|
+
async function readStaticMapRequestErrorResponse(response, maximumBytes) {
|
|
203
|
+
try {
|
|
204
|
+
const source = await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
|
|
205
|
+
const parsed = staticMapRequestErrorResponseSchema.safeParse(JSON.parse(source));
|
|
206
|
+
return parsed.success ? parsed.data : null;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
151
211
|
async function discardBoundedResponse(response, maximumBytes) {
|
|
152
212
|
try {
|
|
153
213
|
await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
|
|
154
214
|
} catch {
|
|
155
215
|
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function isStaticMapRequestFailure(value) {
|
|
219
|
+
if (!("code" in value)) return false;
|
|
220
|
+
const { ok: _ok, ...response } = value;
|
|
221
|
+
return staticMapRequestErrorResponseSchema.safeParse(response).success;
|
|
156
222
|
}
|
|
157
223
|
async function readBoundedResponseText(response, maximumBytes, displayLimit) {
|
|
158
224
|
if (!response.body) return "";
|
|
@@ -293,7 +359,11 @@ function hasControlCharacter(value) {
|
|
|
293
359
|
}
|
|
294
360
|
|
|
295
361
|
export {
|
|
362
|
+
STATIC_MAP_RESULT_V2_MEDIA_TYPE,
|
|
296
363
|
staticMapReadyResultSchema,
|
|
364
|
+
staticMapAttributionEntrySchema,
|
|
365
|
+
staticMapAttributionResultSchema,
|
|
366
|
+
staticMapHostedResultSchema,
|
|
297
367
|
staticMapProcessingResultSchema,
|
|
298
368
|
validateStaticMapIdempotencyKey,
|
|
299
369
|
createStaticMapIdempotencyKey,
|
|
@@ -49,17 +49,24 @@ function base64Url(bytes) {
|
|
|
49
49
|
// src/scene-contract.ts
|
|
50
50
|
import { z } from "zod";
|
|
51
51
|
var staticSceneSchemaVersion = 1;
|
|
52
|
+
var MAX_OVERLAY_LATITUDE = 85.051129;
|
|
52
53
|
var staticSceneLimits = {
|
|
53
|
-
maxDimension:
|
|
54
|
+
maxDimension: 2048,
|
|
54
55
|
maxGeoJsonBytes: 96e3,
|
|
55
56
|
maxOverlays: 24,
|
|
56
57
|
maxPathCoordinates: 2e3,
|
|
57
|
-
maxPhysicalPixels:
|
|
58
|
+
maxPhysicalPixels: 2048 * 2048 * 4,
|
|
58
59
|
minDimension: 64
|
|
59
60
|
};
|
|
60
61
|
var coordinateSchema = z.tuple([z.number().finite(), z.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
|
|
61
62
|
message: "Expected [lng, lat] within world bounds"
|
|
62
63
|
});
|
|
64
|
+
var overlayCoordinateSchema = coordinateSchema.refine(
|
|
65
|
+
([, latitude]) => Math.abs(latitude) <= MAX_OVERLAY_LATITUDE,
|
|
66
|
+
{
|
|
67
|
+
message: `Static overlay latitude must be within \xB1${MAX_OVERLAY_LATITUDE}\xB0`
|
|
68
|
+
}
|
|
69
|
+
);
|
|
63
70
|
var colorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {
|
|
64
71
|
message: "Expected a hex color like #C6A15B"
|
|
65
72
|
});
|
|
@@ -72,12 +79,32 @@ var portableIdSchema = z.string().min(1).max(64).regex(/^[a-z][a-z0-9-]{0,63}$/u
|
|
|
72
79
|
var concreteThemeSchema = portableIdSchema.refine((value) => value !== "system", {
|
|
73
80
|
message: 'Static maps require a concrete theme; "system" is browser-only'
|
|
74
81
|
});
|
|
82
|
+
var staticMapFormatSchema = z.enum(["png", "jpeg", "webp"]);
|
|
83
|
+
var staticAttributionPositionSchema = z.enum([
|
|
84
|
+
"auto",
|
|
85
|
+
"top-left",
|
|
86
|
+
"top-right",
|
|
87
|
+
"bottom-left",
|
|
88
|
+
"bottom-right"
|
|
89
|
+
]);
|
|
90
|
+
var staticAttributionRequestSchema = z.union([
|
|
91
|
+
z.object({
|
|
92
|
+
mode: z.literal("embedded").optional(),
|
|
93
|
+
position: staticAttributionPositionSchema.optional()
|
|
94
|
+
}).strict(),
|
|
95
|
+
z.object({
|
|
96
|
+
mode: z.literal("external")
|
|
97
|
+
}).strict()
|
|
98
|
+
]);
|
|
75
99
|
var sizeSchema = z.object({
|
|
76
|
-
dpr: z.literal(1).optional(),
|
|
100
|
+
dpr: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
77
101
|
height: z.number().int().min(staticSceneLimits.minDimension).max(staticSceneLimits.maxDimension),
|
|
78
102
|
width: z.number().int().min(staticSceneLimits.minDimension).max(staticSceneLimits.maxDimension)
|
|
79
103
|
}).refine(
|
|
80
|
-
(size) =>
|
|
104
|
+
(size) => {
|
|
105
|
+
const dpr = size.dpr ?? 1;
|
|
106
|
+
return size.width * size.height * dpr * dpr <= staticSceneLimits.maxPhysicalPixels;
|
|
107
|
+
},
|
|
81
108
|
{
|
|
82
109
|
message: "Static map exceeds the maximum render pixel budget"
|
|
83
110
|
}
|
|
@@ -99,9 +126,30 @@ var boundsCameraSchema = z.object({
|
|
|
99
126
|
}).refine((camera) => camera.bounds[3] > camera.bounds[1], {
|
|
100
127
|
message: "Bounds north must be greater than south"
|
|
101
128
|
});
|
|
129
|
+
var staticPaddingSchema = z.object({
|
|
130
|
+
bottom: z.number().int().nonnegative(),
|
|
131
|
+
left: z.number().int().nonnegative(),
|
|
132
|
+
right: z.number().int().nonnegative(),
|
|
133
|
+
top: z.number().int().nonnegative()
|
|
134
|
+
}).strict();
|
|
135
|
+
var autoPaddingInputSchema = z.union([
|
|
136
|
+
z.number().int().nonnegative(),
|
|
137
|
+
z.object({
|
|
138
|
+
bottom: z.number().int().nonnegative().optional(),
|
|
139
|
+
left: z.number().int().nonnegative().optional(),
|
|
140
|
+
right: z.number().int().nonnegative().optional(),
|
|
141
|
+
top: z.number().int().nonnegative().optional()
|
|
142
|
+
}).strict()
|
|
143
|
+
]);
|
|
144
|
+
var autoCameraSchema = z.object({
|
|
145
|
+
bearing: z.number().finite().min(-180).max(180).optional(),
|
|
146
|
+
maxZoom: z.number().finite().min(0).max(22).optional(),
|
|
147
|
+
padding: autoPaddingInputSchema.optional(),
|
|
148
|
+
type: z.literal("auto")
|
|
149
|
+
});
|
|
102
150
|
var lineOverlaySchema = z.object({
|
|
103
151
|
color: colorSchema.default("#C6A15B"),
|
|
104
|
-
coordinates: z.array(
|
|
152
|
+
coordinates: z.array(overlayCoordinateSchema).min(2).max(staticSceneLimits.maxPathCoordinates),
|
|
105
153
|
id: z.string().trim().min(1).max(64).optional(),
|
|
106
154
|
opacity: z.number().finite().min(0).max(1).default(1),
|
|
107
155
|
type: z.literal("line"),
|
|
@@ -109,7 +157,7 @@ var lineOverlaySchema = z.object({
|
|
|
109
157
|
});
|
|
110
158
|
var circleOverlaySchema = z.object({
|
|
111
159
|
color: colorSchema.default("#C6A15B"),
|
|
112
|
-
coordinate:
|
|
160
|
+
coordinate: overlayCoordinateSchema,
|
|
113
161
|
id: z.string().trim().min(1).max(64).optional(),
|
|
114
162
|
opacity: z.number().finite().min(0).max(1).default(1),
|
|
115
163
|
radius: z.number().finite().min(1).max(64).default(6),
|
|
@@ -119,14 +167,14 @@ var circleOverlaySchema = z.object({
|
|
|
119
167
|
});
|
|
120
168
|
var markerOverlaySchema = z.object({
|
|
121
169
|
color: colorSchema.default("#C6A15B"),
|
|
122
|
-
coordinate:
|
|
170
|
+
coordinate: overlayCoordinateSchema,
|
|
123
171
|
id: z.string().trim().min(1).max(64).optional(),
|
|
124
172
|
radius: z.number().finite().min(2).max(64).default(8),
|
|
125
173
|
strokeColor: colorSchema.default("#ffffff"),
|
|
126
174
|
strokeWidth: z.number().finite().min(0).max(16).default(2),
|
|
127
175
|
type: z.literal("marker")
|
|
128
176
|
});
|
|
129
|
-
var polygonRingSchema = z.array(
|
|
177
|
+
var polygonRingSchema = z.array(overlayCoordinateSchema).min(4).max(staticSceneLimits.maxPathCoordinates).refine(
|
|
130
178
|
(ring) => {
|
|
131
179
|
const first = ring[0];
|
|
132
180
|
const last = ring[ring.length - 1];
|
|
@@ -150,7 +198,9 @@ var staticOverlaySchema = z.discriminatedUnion("type", [
|
|
|
150
198
|
polygonOverlaySchema
|
|
151
199
|
]);
|
|
152
200
|
var staticSceneSchema = z.object({
|
|
153
|
-
|
|
201
|
+
attribution: staticAttributionRequestSchema.optional(),
|
|
202
|
+
camera: z.discriminatedUnion("type", [centerCameraSchema, boundsCameraSchema, autoCameraSchema]),
|
|
203
|
+
format: staticMapFormatSchema.optional(),
|
|
154
204
|
map: portableIdSchema,
|
|
155
205
|
overlays: z.array(staticOverlaySchema).max(staticSceneLimits.maxOverlays).default([]),
|
|
156
206
|
size: sizeSchema,
|
|
@@ -190,7 +240,11 @@ export {
|
|
|
190
240
|
hashStableValue,
|
|
191
241
|
isSafeHttpUrl,
|
|
192
242
|
staticSceneSchemaVersion,
|
|
243
|
+
MAX_OVERLAY_LATITUDE,
|
|
193
244
|
staticSceneLimits,
|
|
245
|
+
staticAttributionPositionSchema,
|
|
246
|
+
staticAttributionRequestSchema,
|
|
247
|
+
staticPaddingSchema,
|
|
194
248
|
staticOverlaySchema,
|
|
195
249
|
staticSceneSchema,
|
|
196
250
|
normalizeStaticOverlay,
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeStaticAutoFit,
|
|
3
|
+
normalizeStaticScene,
|
|
4
|
+
staticAutoFitPlanSchema,
|
|
5
|
+
validateStaticScene
|
|
6
|
+
} from "./chunk-EMCKSFZV.js";
|
|
7
|
+
import {
|
|
8
|
+
hashStableValue,
|
|
9
|
+
isSafeHttpUrl,
|
|
10
|
+
jsonByteLength,
|
|
11
|
+
stableStringify,
|
|
12
|
+
staticSceneSchema,
|
|
13
|
+
stripUndefined
|
|
14
|
+
} from "./chunk-NM2JSFUF.js";
|
|
15
|
+
|
|
16
|
+
// src/manifest.ts
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
var staticRendererSchemaVersionV1 = 1;
|
|
19
|
+
var staticRendererSchemaVersion = 2;
|
|
20
|
+
var staticAttributionPlanSchemaVersion = 1;
|
|
21
|
+
var staticAttributionLimits = {
|
|
22
|
+
maxBytes: 32 * 1024,
|
|
23
|
+
maxEntries: 18,
|
|
24
|
+
maxLinksPerEntry: 8,
|
|
25
|
+
maxTextLength: 16384,
|
|
26
|
+
maxUrlLength: 2048
|
|
27
|
+
};
|
|
28
|
+
var sha256HexSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
29
|
+
var sourceIdSchema = z.string().min(1).max(128);
|
|
30
|
+
var boundedIdentitySchema = z.string().min(1).max(512).refine(hasNoControlCharacters, {
|
|
31
|
+
message: "Expected no control characters"
|
|
32
|
+
});
|
|
33
|
+
var staticResolvedAttributionPositionSchema = z.enum([
|
|
34
|
+
"top-left",
|
|
35
|
+
"top-right",
|
|
36
|
+
"bottom-left",
|
|
37
|
+
"bottom-right"
|
|
38
|
+
]);
|
|
39
|
+
var staticAttributionSegmentSchema = z.discriminatedUnion("kind", [
|
|
40
|
+
z.object({
|
|
41
|
+
kind: z.literal("text"),
|
|
42
|
+
text: z.string().min(1).max(staticAttributionLimits.maxTextLength).refine(hasNoControlCharacters, { message: "Attribution text contains control characters" })
|
|
43
|
+
}).strict(),
|
|
44
|
+
z.object({
|
|
45
|
+
kind: z.literal("link"),
|
|
46
|
+
label: z.string().min(1).max(staticAttributionLimits.maxTextLength).refine(hasNoControlCharacters, {
|
|
47
|
+
message: "Attribution link label contains control characters"
|
|
48
|
+
}),
|
|
49
|
+
url: z.string().trim().url().max(staticAttributionLimits.maxUrlLength).refine(isSafeHttpUrl, {
|
|
50
|
+
message: "Expected an http(s) URL without credentials or a fragment"
|
|
51
|
+
})
|
|
52
|
+
}).strict()
|
|
53
|
+
]);
|
|
54
|
+
var attributionSourceSchema = z.object({
|
|
55
|
+
sourceId: sourceIdSchema,
|
|
56
|
+
sourceSelectionIdentity: boundedIdentitySchema
|
|
57
|
+
}).strict();
|
|
58
|
+
var attributionSourcesSchema = z.array(attributionSourceSchema).min(1).max(18).refine(isUniqueSortedSources, {
|
|
59
|
+
message: "Attribution sources must be unique and sorted"
|
|
60
|
+
});
|
|
61
|
+
var staticAttributionProvenanceSchema = z.discriminatedUnion("authority", [
|
|
62
|
+
z.object({
|
|
63
|
+
authority: z.literal("platform-notice"),
|
|
64
|
+
noticeId: boundedIdentitySchema,
|
|
65
|
+
noticeSha256: sha256HexSchema,
|
|
66
|
+
resource: z.enum(["terrain", "world"]),
|
|
67
|
+
sources: attributionSourcesSchema
|
|
68
|
+
}).strict(),
|
|
69
|
+
z.object({
|
|
70
|
+
authority: z.literal("team-declared"),
|
|
71
|
+
sources: z.array(
|
|
72
|
+
attributionSourceSchema.extend({
|
|
73
|
+
mapRevision: boundedIdentitySchema,
|
|
74
|
+
resourceId: boundedIdentitySchema
|
|
75
|
+
}).strict()
|
|
76
|
+
).min(1).max(18).refine(isUniqueSortedSources, {
|
|
77
|
+
message: "Attribution sources must be unique and sorted"
|
|
78
|
+
})
|
|
79
|
+
}).strict()
|
|
80
|
+
]);
|
|
81
|
+
var staticAttributionEntrySchema = z.object({
|
|
82
|
+
authority: z.enum(["platform-notice", "team-declared"]),
|
|
83
|
+
provenance: staticAttributionProvenanceSchema,
|
|
84
|
+
segments: z.array(staticAttributionSegmentSchema).min(1).max(17)
|
|
85
|
+
}).strict().superRefine((entry, context) => {
|
|
86
|
+
if (entry.authority !== entry.provenance.authority) {
|
|
87
|
+
context.addIssue({
|
|
88
|
+
code: "custom",
|
|
89
|
+
message: "Attribution entry authority must match its provenance",
|
|
90
|
+
path: ["authority"]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const links = entry.segments.filter((segment) => segment.kind === "link").length;
|
|
94
|
+
if (links > staticAttributionLimits.maxLinksPerEntry) {
|
|
95
|
+
context.addIssue({
|
|
96
|
+
code: "custom",
|
|
97
|
+
message: `Attribution entries can contain at most ${staticAttributionLimits.maxLinksPerEntry} links`,
|
|
98
|
+
path: ["segments"]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
var staticAttributionPlanSchemaV1 = z.object({
|
|
103
|
+
entries: z.array(staticAttributionEntrySchema).max(staticAttributionLimits.maxEntries),
|
|
104
|
+
mode: z.enum(["embedded", "external"]),
|
|
105
|
+
position: z.union([z.literal("auto"), staticResolvedAttributionPositionSchema, z.null()]),
|
|
106
|
+
schemaVersion: z.literal(staticAttributionPlanSchemaVersion)
|
|
107
|
+
}).strict().superRefine((plan, context) => {
|
|
108
|
+
if (plan.mode === "external" && plan.position !== null || plan.mode === "embedded" && plan.position === null) {
|
|
109
|
+
context.addIssue({
|
|
110
|
+
code: "custom",
|
|
111
|
+
message: "Attribution position must match its mode",
|
|
112
|
+
path: ["position"]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (jsonByteLength(plan) > staticAttributionLimits.maxBytes) {
|
|
116
|
+
context.addIssue({
|
|
117
|
+
code: "custom",
|
|
118
|
+
message: `Attribution plan exceeds ${staticAttributionLimits.maxBytes} bytes`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
var staticRenderManifestBaseShape = {
|
|
123
|
+
autoFit: staticAutoFitPlanSchema.optional(),
|
|
124
|
+
mapId: z.string().trim().min(1).max(128),
|
|
125
|
+
rendererVersion: z.string().trim().min(1).max(64),
|
|
126
|
+
styleId: z.string().trim().min(1).max(128).optional(),
|
|
127
|
+
styleRevision: z.string().trim().min(1).max(128),
|
|
128
|
+
styleUrl: z.string().trim().url().max(512).refine(isSafeHttpUrl, {
|
|
129
|
+
message: "Expected an http(s) style URL without credentials or a fragment"
|
|
130
|
+
})
|
|
131
|
+
};
|
|
132
|
+
var staticSceneSchemaV1 = staticSceneSchema.omit({ attribution: true });
|
|
133
|
+
var staticRenderManifestV1Schema = z.object({
|
|
134
|
+
...staticRenderManifestBaseShape,
|
|
135
|
+
schemaVersion: z.literal(staticRendererSchemaVersionV1),
|
|
136
|
+
scene: staticSceneSchemaV1
|
|
137
|
+
});
|
|
138
|
+
var staticRenderManifestV2Schema = z.object({
|
|
139
|
+
...staticRenderManifestBaseShape,
|
|
140
|
+
attribution: staticAttributionPlanSchemaV1,
|
|
141
|
+
schemaVersion: z.literal(staticRendererSchemaVersion),
|
|
142
|
+
scene: staticSceneSchema
|
|
143
|
+
}).strict();
|
|
144
|
+
var staticRenderManifestSchema = z.discriminatedUnion("schemaVersion", [
|
|
145
|
+
staticRenderManifestV1Schema,
|
|
146
|
+
staticRenderManifestV2Schema
|
|
147
|
+
]);
|
|
148
|
+
function validateStaticRenderManifest(input) {
|
|
149
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
150
|
+
return { error: "manifest: Expected an object", ok: false };
|
|
151
|
+
}
|
|
152
|
+
const schemaVersion = input.schemaVersion;
|
|
153
|
+
if (schemaVersion !== staticRendererSchemaVersionV1 && schemaVersion !== staticRendererSchemaVersion) {
|
|
154
|
+
return { error: "manifest.schemaVersion: Unsupported renderer schema version", ok: false };
|
|
155
|
+
}
|
|
156
|
+
const rawScene = input.scene;
|
|
157
|
+
const sceneValidation = validateVersionedScene(rawScene, schemaVersion);
|
|
158
|
+
if (!sceneValidation.ok) return sceneValidation;
|
|
159
|
+
const schema = schemaVersion === staticRendererSchemaVersionV1 ? staticRenderManifestV1Schema : staticRenderManifestV2Schema;
|
|
160
|
+
const parsed = schema.safeParse({
|
|
161
|
+
...input,
|
|
162
|
+
scene: sceneValidation.scene
|
|
163
|
+
});
|
|
164
|
+
if (!parsed.success) return zodFailure(parsed.error);
|
|
165
|
+
const autoFit = expectedAutoFit(sceneValidation.scene);
|
|
166
|
+
if (autoFit === void 0 !== (parsed.data.autoFit === void 0) || autoFit !== void 0 && stableStringify(autoFit) !== stableStringify(parsed.data.autoFit)) {
|
|
167
|
+
return { error: "manifest.autoFit: Expected the exact plan derived from scene", ok: false };
|
|
168
|
+
}
|
|
169
|
+
if (schemaVersion === staticRendererSchemaVersion && (!("attribution" in parsed.data) || !attributionMatchesScene(parsed.data.attribution, sceneValidation.scene))) {
|
|
170
|
+
return {
|
|
171
|
+
error: "manifest.attribution: Expected the exact request resolved from scene",
|
|
172
|
+
ok: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
manifest: {
|
|
177
|
+
...parsed.data,
|
|
178
|
+
...autoFit === void 0 ? {} : { autoFit },
|
|
179
|
+
scene: sceneValidation.scene
|
|
180
|
+
},
|
|
181
|
+
ok: true
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function createRenderManifestV1(input) {
|
|
185
|
+
const rawScene = staticSceneSchemaV1.parse(input.scene);
|
|
186
|
+
const scene = normalizeStaticScene(rawScene);
|
|
187
|
+
const autoFit = expectedAutoFit(scene);
|
|
188
|
+
return staticRenderManifestV1Schema.parse(
|
|
189
|
+
stripUndefined({
|
|
190
|
+
autoFit,
|
|
191
|
+
mapId: input.mapId,
|
|
192
|
+
rendererVersion: input.rendererVersion,
|
|
193
|
+
schemaVersion: staticRendererSchemaVersionV1,
|
|
194
|
+
scene,
|
|
195
|
+
styleId: input.styleId,
|
|
196
|
+
styleRevision: input.styleRevision,
|
|
197
|
+
styleUrl: input.styleUrl
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
var createRenderManifest = createRenderManifestV1;
|
|
202
|
+
function createRenderManifestV2(input) {
|
|
203
|
+
const scene = normalizeStaticScene(input.scene);
|
|
204
|
+
const autoFit = expectedAutoFit(scene);
|
|
205
|
+
return staticRenderManifestV2Schema.parse(
|
|
206
|
+
stripUndefined({
|
|
207
|
+
attribution: input.attribution,
|
|
208
|
+
autoFit,
|
|
209
|
+
mapId: input.mapId,
|
|
210
|
+
rendererVersion: input.rendererVersion,
|
|
211
|
+
schemaVersion: staticRendererSchemaVersion,
|
|
212
|
+
scene,
|
|
213
|
+
styleId: input.styleId,
|
|
214
|
+
styleRevision: input.styleRevision,
|
|
215
|
+
styleUrl: input.styleUrl
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
async function hashRenderManifest(manifest) {
|
|
220
|
+
const validation = validateStaticRenderManifest(manifest);
|
|
221
|
+
if (!validation.ok) throw new Error(`Invalid Tileflow render manifest: ${validation.error}`);
|
|
222
|
+
return hashStableValue(validation.manifest);
|
|
223
|
+
}
|
|
224
|
+
async function hashStaticSceneRequest(scene) {
|
|
225
|
+
const validation = validateStaticScene(scene);
|
|
226
|
+
if (!validation.ok) throw new Error(`Invalid Tileflow static scene: ${validation.error}`);
|
|
227
|
+
return hashStableValue(validation.scene);
|
|
228
|
+
}
|
|
229
|
+
function validateVersionedScene(input, version) {
|
|
230
|
+
if (version === 1 && input && typeof input === "object" && !Array.isArray(input)) {
|
|
231
|
+
const { attribution: _attribution, ...legacy } = input;
|
|
232
|
+
return validateStaticScene(legacy);
|
|
233
|
+
}
|
|
234
|
+
return validateStaticScene(input);
|
|
235
|
+
}
|
|
236
|
+
function attributionMatchesScene(plan, scene) {
|
|
237
|
+
const request = scene.attribution;
|
|
238
|
+
const expectedMode = request?.mode === "external" ? "external" : "embedded";
|
|
239
|
+
const expectedPosition = expectedMode === "external" ? null : request && "position" in request ? request.position ?? "auto" : "auto";
|
|
240
|
+
return plan.mode === expectedMode && plan.position === expectedPosition;
|
|
241
|
+
}
|
|
242
|
+
function expectedAutoFit(scene) {
|
|
243
|
+
if (scene.camera.type !== "auto") return void 0;
|
|
244
|
+
const analysis = analyzeStaticAutoFit(scene);
|
|
245
|
+
if (!analysis.ok) throw new Error(`Invalid Tileflow auto-fit scene: ${analysis.error}`);
|
|
246
|
+
return analysis.plan;
|
|
247
|
+
}
|
|
248
|
+
function hasNoControlCharacters(value) {
|
|
249
|
+
return !Array.from(value).some((character) => {
|
|
250
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
251
|
+
return codePoint <= 31 || codePoint === 127;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function isUniqueSortedSources(values) {
|
|
255
|
+
return values.every(
|
|
256
|
+
(value, index) => index === 0 || compareCodeUnits(values[index - 1].sourceId, value.sourceId) < 0
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
function compareCodeUnits(left, right) {
|
|
260
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
261
|
+
}
|
|
262
|
+
function zodFailure(error) {
|
|
263
|
+
return {
|
|
264
|
+
error: error.issues.map((issue) => `${issue.path.join(".") || "manifest"}: ${issue.message}`).join("; "),
|
|
265
|
+
ok: false
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export {
|
|
270
|
+
staticRendererSchemaVersionV1,
|
|
271
|
+
staticRendererSchemaVersion,
|
|
272
|
+
staticAttributionPlanSchemaVersion,
|
|
273
|
+
staticAttributionLimits,
|
|
274
|
+
staticAttributionSegmentSchema,
|
|
275
|
+
staticAttributionProvenanceSchema,
|
|
276
|
+
staticAttributionEntrySchema,
|
|
277
|
+
staticAttributionPlanSchemaV1,
|
|
278
|
+
staticRenderManifestV1Schema,
|
|
279
|
+
staticRenderManifestV2Schema,
|
|
280
|
+
staticRenderManifestSchema,
|
|
281
|
+
validateStaticRenderManifest,
|
|
282
|
+
createRenderManifestV1,
|
|
283
|
+
createRenderManifest,
|
|
284
|
+
createRenderManifestV2,
|
|
285
|
+
hashRenderManifest,
|
|
286
|
+
hashStaticSceneRequest
|
|
287
|
+
};
|