@glboost/sdk 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,562 @@
1
+ import {
2
+ DEFAULT_VARIANT_PRESETS,
3
+ PRESETS,
4
+ canonicalSpec,
5
+ isPresetName
6
+ } from "./chunk-YL4SZ5Y4.js";
7
+
8
+ // src/testing.ts
9
+ var DEFAULT_PART_SIZE = 26214400;
10
+ var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
11
+ var UPGRADE_URL = "/app/settings#billing";
12
+ var SIZE_RATIO = { original: 1, compat: 0.6, gpu: 0.3 };
13
+ var GRADE = {
14
+ compat: ["B", 70],
15
+ gpu: ["A", 92],
16
+ original: ["C", 55]
17
+ };
18
+ function createFakeApi(options = {}) {
19
+ const baseUrl = (options.baseUrl ?? "https://api.test").replace(/\/+$/, "");
20
+ const cdnBase = (options.cdnBase ?? "https://cdn.test").replace(/\/+$/, "");
21
+ const apiOrigin = baseUrl ? new URL(baseUrl).origin : null;
22
+ const cdnOrigin = new URL(cdnBase).origin;
23
+ const requireAuth = options.requireAuth ?? baseUrl !== "";
24
+ const partSize = options.partSize ?? DEFAULT_PART_SIZE;
25
+ const readyAfterPolls = options.readyAfterPolls ?? 1;
26
+ const plan = options.plan ?? "pro";
27
+ const project = {
28
+ id: options.project?.id ?? "proj00000001",
29
+ name: options.project?.name ?? "Default",
30
+ slug: options.project?.slug ?? "default"
31
+ };
32
+ const org = { id: "org000000001", name: "Fake org", slug: "fake-org", plan };
33
+ const state = {
34
+ assets: /* @__PURE__ */ new Map(),
35
+ uploads: /* @__PURE__ */ new Map(),
36
+ shares: [],
37
+ exports: [],
38
+ signed: [],
39
+ requests: []
40
+ };
41
+ let counter = 0;
42
+ const nextId = (prefix) => `${prefix}${(++counter).toString(36).padStart(12 - prefix.length, "0")}`;
43
+ const now = () => (/* @__PURE__ */ new Date()).toISOString();
44
+ const json = (status, body) => new Response(JSON.stringify(body), {
45
+ status,
46
+ headers: { "content-type": "application/json" }
47
+ });
48
+ const error = (status, message, extra = {}) => json(status, { error: message, ...extra });
49
+ const specFor = (preset) => preset === "original" ? "original" : canonicalSpec(PRESETS[preset]);
50
+ function makeVariant(version, preset, spec) {
51
+ return {
52
+ id: nextId("var"),
53
+ versionId: version.id,
54
+ spec,
55
+ preset,
56
+ requested: null,
57
+ status: "queued",
58
+ bytes: null,
59
+ stats: null,
60
+ error: null,
61
+ createdAt: now(),
62
+ readyAt: null
63
+ };
64
+ }
65
+ function settle(variant, originalBytes) {
66
+ const key = variant.preset ?? "spec";
67
+ if (options.failPreset && variant.preset === options.failPreset) {
68
+ variant.status = "failed";
69
+ variant.error = "optimizer failed (fake)";
70
+ return;
71
+ }
72
+ variant.status = "ready";
73
+ variant.bytes = Math.max(1, Math.round(originalBytes * (SIZE_RATIO[key] ?? 0.5)));
74
+ variant.readyAt = now();
75
+ }
76
+ function tick(version) {
77
+ version.polls += 1;
78
+ if (version.polls < readyAfterPolls || version.originalBytes === null) return;
79
+ for (const v of version.variants) if (v.status === "queued") settle(v, version.originalBytes);
80
+ if (version.variants.every((v) => v.status === "ready")) version.status = "ready";
81
+ }
82
+ function scoreOf(variant) {
83
+ if (variant.status !== "ready") return null;
84
+ const [grade, points] = GRADE[variant.preset ?? ""] ?? ["B", 75];
85
+ return { version: 1, grade, points, checks: [] };
86
+ }
87
+ const withScore = (v) => ({ ...v, score: scoreOf(v) });
88
+ const versionRow = (v) => {
89
+ const { polls: _polls, variants, ...rest } = v;
90
+ return { ...rest, variants: variants.map(withScore) };
91
+ };
92
+ function addVersion(asset, input) {
93
+ const version = {
94
+ id: nextId("ver"),
95
+ assetId: asset.id,
96
+ number: asset.versions.length + 1,
97
+ status: "uploading",
98
+ originalBytes: input.bytes,
99
+ sourceFormat: input.sourceFormat ?? "glb",
100
+ note: input.note ?? null,
101
+ createdAt: now(),
102
+ variants: [],
103
+ polls: 0
104
+ };
105
+ asset.versions.unshift(version);
106
+ return version;
107
+ }
108
+ function defaultVariants(version) {
109
+ for (const preset of DEFAULT_VARIANT_PRESETS) {
110
+ const variant = makeVariant(version, preset, specFor(preset));
111
+ if (preset === "original" && version.originalBytes !== null) {
112
+ variant.status = "ready";
113
+ variant.bytes = version.originalBytes;
114
+ variant.readyAt = now();
115
+ }
116
+ version.variants.push(variant);
117
+ }
118
+ }
119
+ function seedAsset(input) {
120
+ const asset = {
121
+ id: input.id ?? nextId("ast"),
122
+ projectId: project.id,
123
+ name: input.name,
124
+ description: input.description ?? null,
125
+ visibility: "public",
126
+ tags: input.tags ?? [],
127
+ createdAt: now(),
128
+ deletedAt: input.deleted ? now() : null,
129
+ versions: []
130
+ };
131
+ const bytes = input.bytes ?? 1e6;
132
+ for (let i = 0; i < (input.versions ?? 1); i++) {
133
+ const version = addVersion(asset, { bytes });
134
+ version.status = "uploaded";
135
+ defaultVariants(version);
136
+ if (input.ready ?? true) {
137
+ version.polls = readyAfterPolls;
138
+ for (const v of version.variants) if (v.status === "queued") settle(v, bytes);
139
+ version.status = version.variants.every((v) => v.status === "ready") ? "ready" : "uploaded";
140
+ }
141
+ }
142
+ state.assets.set(asset.id, asset);
143
+ return asset;
144
+ }
145
+ const liveAsset = (id) => {
146
+ const asset = state.assets.get(id);
147
+ return asset && !asset.deletedAt ? asset : void 0;
148
+ };
149
+ const findVersion = (id) => {
150
+ for (const asset of state.assets.values()) {
151
+ const version = asset.versions.find((v) => v.id === id);
152
+ if (version) return { asset, version };
153
+ }
154
+ return void 0;
155
+ };
156
+ const newestUploaded = (asset) => asset.versions.find((v) => v.status !== "uploading" && v.status !== "failed");
157
+ const api = [
158
+ [
159
+ "GET",
160
+ /^\/api\/v1\/me$/,
161
+ () => json(200, {
162
+ org,
163
+ project,
164
+ role: null,
165
+ auth: { kind: "key", keyName: "fake key", scopes: ["read", "write"], projectId: null }
166
+ })
167
+ ],
168
+ ["GET", /^\/api\/v1\/projects$/, () => json(200, { projects: [project] })],
169
+ [
170
+ "GET",
171
+ /^\/api\/v1\/assets$/,
172
+ (_request, url) => {
173
+ const q = url.searchParams.get("q")?.trim().toLowerCase() ?? "";
174
+ const tag = url.searchParams.get("tag");
175
+ const deleted = ["true", "1"].includes(url.searchParams.get("deleted") ?? "");
176
+ const assets = [...state.assets.values()].filter((a) => Boolean(a.deletedAt) === deleted).filter((a) => !tag || a.tags.includes(tag)).filter(
177
+ (a) => !q || a.name.toLowerCase().includes(q) || (a.description ?? "").toLowerCase().includes(q) || a.id.startsWith(q)
178
+ ).map((a) => {
179
+ const { versions, ...rest } = a;
180
+ const latest = versions[0] ?? null;
181
+ return {
182
+ ...rest,
183
+ latestVersion: latest ? versionRow(latest) : null,
184
+ variantSummary: {
185
+ total: latest?.variants.length ?? 0,
186
+ ready: latest?.variants.filter((v) => v.status === "ready").length ?? 0
187
+ },
188
+ reviewState: null
189
+ };
190
+ });
191
+ return json(200, { assets });
192
+ }
193
+ ],
194
+ [
195
+ "POST",
196
+ /^\/api\/v1\/uploads$/,
197
+ async (request) => {
198
+ const body = await request.json().catch(() => null);
199
+ if (!body || !body.filename || typeof body.bytes !== "number")
200
+ return error(400, "invalid body");
201
+ if (body.projectId !== project.id) return error(404, "project not found");
202
+ if (options.refuse) {
203
+ const { status, error: message, ...rest } = options.refuse;
204
+ return error(status, message, { plan, ...rest, upgradeUrl: UPGRADE_URL });
205
+ }
206
+ if (body.bytes > MAX_UPLOAD_BYTES) {
207
+ return error(413, `files over ${MAX_UPLOAD_BYTES} bytes are not supported yet`);
208
+ }
209
+ const ext = body.filename.toLowerCase().match(/\.(glb|gltf|zip)$/)?.[1];
210
+ if (!ext) return error(415, "upload a .glb, .gltf, or .zip");
211
+ let asset;
212
+ if (body.assetId) {
213
+ asset = liveAsset(body.assetId);
214
+ if (!asset) return error(404, "asset not found");
215
+ } else {
216
+ asset = {
217
+ id: nextId("ast"),
218
+ projectId: project.id,
219
+ name: body.filename.replace(/\.[^.]+$/, ""),
220
+ description: null,
221
+ visibility: "public",
222
+ tags: [],
223
+ createdAt: now(),
224
+ deletedAt: null,
225
+ versions: []
226
+ };
227
+ state.assets.set(asset.id, asset);
228
+ }
229
+ const version = addVersion(asset, {
230
+ bytes: null,
231
+ note: body.note ?? null,
232
+ sourceFormat: ext
233
+ });
234
+ const uploadId = nextId("upl");
235
+ const upload = {
236
+ uploadId,
237
+ token: `tok_${uploadId}`,
238
+ assetId: asset.id,
239
+ versionId: version.id,
240
+ maxBytes: body.bytes,
241
+ parts: /* @__PURE__ */ new Map(),
242
+ completed: null,
243
+ aborted: false
244
+ };
245
+ state.uploads.set(upload.token, upload);
246
+ return json(200, {
247
+ assetId: asset.id,
248
+ versionId: version.id,
249
+ uploadId,
250
+ partSize,
251
+ uploadUrl: `${cdnBase}/_/upload`,
252
+ token: upload.token
253
+ });
254
+ }
255
+ ],
256
+ [
257
+ "POST",
258
+ /^\/api\/v1\/uploads\/([^/]+)\/complete$/,
259
+ (_request, _url, [versionId]) => {
260
+ const found = findVersion(versionId);
261
+ if (!found) return error(404, "version not found");
262
+ const { asset, version } = found;
263
+ if (version.status === "failed")
264
+ return error(409, "upload was aborted; start a new upload");
265
+ const upload = [...state.uploads.values()].find((u) => u.versionId === version.id);
266
+ if (version.status === "uploading") {
267
+ if (!upload || upload.completed === null) {
268
+ return error(409, "upload has not been completed on the edge");
269
+ }
270
+ version.status = "uploaded";
271
+ version.originalBytes = upload.completed;
272
+ defaultVariants(version);
273
+ }
274
+ const queued = version.variants.filter((v) => v.status === "queued").length;
275
+ return json(200, { assetId: asset.id, versionId: version.id, queued });
276
+ }
277
+ ],
278
+ [
279
+ "POST",
280
+ /^\/api\/v1\/uploads\/([^/]+)\/abort$/,
281
+ (_request, _url, [versionId]) => {
282
+ const found = findVersion(versionId);
283
+ if (!found) return error(404, "version not found");
284
+ if (found.version.status === "uploading") found.version.status = "failed";
285
+ return json(200, {
286
+ assetId: found.asset.id,
287
+ versionId: found.version.id,
288
+ status: found.version.status
289
+ });
290
+ }
291
+ ],
292
+ [
293
+ "GET",
294
+ /^\/api\/v1\/assets\/([^/]+)$/,
295
+ (_request, _url, [assetId]) => {
296
+ const asset = liveAsset(assetId);
297
+ if (!asset) return error(404, "asset not found");
298
+ for (const v of asset.versions) tick(v);
299
+ const { versions, ...rest } = asset;
300
+ return json(200, {
301
+ asset: rest,
302
+ project: { id: project.id, name: project.name, slug: project.slug },
303
+ versions: versions.map((v) => ({
304
+ ...versionRow(v),
305
+ review: { approved: 0, changesRequested: 0, awaitingApproval: false, reviewers: [] }
306
+ }))
307
+ });
308
+ }
309
+ ],
310
+ [
311
+ "DELETE",
312
+ /^\/api\/v1\/assets\/([^/]+)$/,
313
+ (_request, url, [assetId]) => {
314
+ const asset = state.assets.get(assetId);
315
+ if (!asset) return error(404, "asset not found");
316
+ const permanent = ["1", "true"].includes(url.searchParams.get("permanent") ?? "");
317
+ if (permanent) {
318
+ if (!asset.deletedAt) return error(409, "move the asset to the trash first");
319
+ state.assets.delete(asset.id);
320
+ const objects = asset.versions.reduce((n, v) => n + 1 + v.variants.length, 0);
321
+ return json(200, { deleted: true, permanent: true, objects });
322
+ }
323
+ if (asset.deletedAt) return error(404, "asset not found");
324
+ asset.deletedAt = now();
325
+ return json(200, { deleted: true, permanent: false });
326
+ }
327
+ ],
328
+ [
329
+ "GET",
330
+ /^\/api\/v1\/assets\/([^/]+)\/variants$/,
331
+ (_request, _url, [assetId]) => {
332
+ const asset = liveAsset(assetId);
333
+ if (!asset) return error(404, "asset not found");
334
+ for (const v of asset.versions) tick(v);
335
+ return json(200, {
336
+ versions: asset.versions.map((v) => ({
337
+ id: v.id,
338
+ number: v.number,
339
+ variants: v.variants.map(withScore)
340
+ }))
341
+ });
342
+ }
343
+ ],
344
+ [
345
+ "POST",
346
+ /^\/api\/v1\/assets\/([^/]+)\/variants$/,
347
+ async (request, _url, [assetId]) => {
348
+ const asset = liveAsset(assetId);
349
+ if (!asset) return error(404, "asset not found");
350
+ const body = await request.json().catch(() => null);
351
+ if (!body || (body.preset ? 1 : 0) + (body.spec ? 1 : 0) !== 1) {
352
+ return error(400, "invalid body");
353
+ }
354
+ const version = body.versionId ? asset.versions.find((v) => v.id === body.versionId && v.status !== "uploading") : newestUploaded(asset);
355
+ if (!version) return error(409, "no uploaded version to optimize");
356
+ let spec;
357
+ let preset;
358
+ if (body.preset) {
359
+ if (!isPresetName(body.preset)) return error(400, "unknown preset");
360
+ preset = body.preset;
361
+ spec = specFor(body.preset);
362
+ } else {
363
+ try {
364
+ spec = canonicalSpec(body.spec);
365
+ } catch (e) {
366
+ return error(400, e.message);
367
+ }
368
+ preset = Object.keys(PRESETS).find((p) => specFor(p) === spec) ?? null;
369
+ }
370
+ const existing = version.variants.find((v) => v.spec === spec);
371
+ if (existing) return json(200, { variant: withScore(existing), created: false });
372
+ const variant = makeVariant(version, preset, spec);
373
+ version.variants.push(variant);
374
+ version.polls = 0;
375
+ return json(201, { variant: withScore(variant), created: true });
376
+ }
377
+ ],
378
+ [
379
+ "POST",
380
+ /^\/api\/v1\/assets\/([^/]+)\/sign$/,
381
+ async (request, _url, [assetId]) => {
382
+ const asset = liveAsset(assetId);
383
+ if (!asset) return error(404, "asset not found");
384
+ const body = await request.json().catch(() => ({}));
385
+ if (body.preset && !isPresetName(body.preset)) return error(400, "unknown preset");
386
+ const q = new URLSearchParams();
387
+ q.set("p", body.preset ?? "compat");
388
+ if (body.spec) {
389
+ q.set("p", "compat");
390
+ for (const [key, value] of Object.entries(body.spec)) q.set(key, String(value));
391
+ }
392
+ if (body.versionId) q.set("v", body.versionId);
393
+ if (body.dl) q.set("dl", "1");
394
+ const ttl = Math.min(7 * 24 * 3600, Math.max(60, body.ttlSeconds ?? 600));
395
+ const exp = Math.floor(Date.now() / 1e3) + ttl;
396
+ q.set("exp", String(exp));
397
+ const sig = fakeHex(`${assetId}?${q.toString()}`);
398
+ q.set("sig", sig);
399
+ const result = { url: `${cdnBase}/${assetId}.glb?${q.toString()}`, exp, sig };
400
+ state.signed.push(result);
401
+ return json(200, result);
402
+ }
403
+ ],
404
+ [
405
+ "GET",
406
+ /^\/api\/v1\/assets\/([^/]+)\/exports$/,
407
+ (_request, _url, [assetId]) => {
408
+ if (!liveAsset(assetId)) return error(404, "asset not found");
409
+ return json(200, { exports: state.exports.filter((e) => e.assetId === assetId) });
410
+ }
411
+ ],
412
+ [
413
+ "POST",
414
+ /^\/api\/v1\/assets\/([^/]+)\/exports$/,
415
+ async (request, _url, [assetId]) => {
416
+ const asset = liveAsset(assetId);
417
+ if (!asset) return error(404, "asset not found");
418
+ const body = await request.json().catch(() => null);
419
+ if (!body?.variantId || !["gltf-zip", "c-array", "usdz"].includes(body.format ?? "")) {
420
+ return error(400, "invalid body");
421
+ }
422
+ const variant = asset.versions.flatMap((v) => v.variants).find((v) => v.id === body.variantId);
423
+ if (!variant) return error(404, "variant not found");
424
+ if (body.format === "usdz" && variant.spec.includes("tex=ktx2")) {
425
+ return error(400, "usdz needs a jpeg or png variant");
426
+ }
427
+ if (variant.status !== "ready") return error(409, "variant is not ready yet");
428
+ const existing = state.exports.find(
429
+ (e) => e.variantId === variant.id && e.format === body.format
430
+ );
431
+ if (existing) return json(200, { export: existing, created: false });
432
+ const exp = {
433
+ id: nextId("exp"),
434
+ assetId: asset.id,
435
+ variantId: variant.id,
436
+ format: body.format,
437
+ status: "queued",
438
+ bytes: null,
439
+ createdAt: now()
440
+ };
441
+ state.exports.push(exp);
442
+ return json(201, { export: exp, created: true });
443
+ }
444
+ ],
445
+ [
446
+ "POST",
447
+ /^\/api\/v1\/shares$/,
448
+ async (request) => {
449
+ const body = await request.json().catch(() => null);
450
+ if (!body || Boolean(body.assetId) === Boolean(body.collectionId)) {
451
+ return error(400, "invalid body");
452
+ }
453
+ if (!["link", "password", "email", "org"].includes(body.accessMode ?? "")) {
454
+ return error(400, "invalid body");
455
+ }
456
+ if (body.accessMode === "password" && !body.password)
457
+ return error(400, "password is required");
458
+ if (body.assetId && !liveAsset(body.assetId)) return error(404, "asset not found");
459
+ const token = fakeHex(`share-${state.shares.length}`).slice(0, 22);
460
+ const share = {
461
+ id: nextId("shr"),
462
+ token,
463
+ targetType: body.assetId ? "asset" : "collection",
464
+ targetId: body.assetId ?? body.collectionId,
465
+ accessMode: body.accessMode,
466
+ allowDownload: body.allowDownload ?? "none",
467
+ expiresAt: body.expiresAt ?? null,
468
+ createdAt: now()
469
+ };
470
+ state.shares.push(share);
471
+ return json(201, { share, url: `${baseUrl || "https://glboost.test"}/s/${token}` });
472
+ }
473
+ ]
474
+ ];
475
+ const cdn = [
476
+ [
477
+ "PUT",
478
+ /^\/_\/upload\/part$/,
479
+ async (request, url) => {
480
+ const upload = state.uploads.get(url.searchParams.get("token") ?? "");
481
+ if (!upload) return error(401, "invalid token");
482
+ const n = Number(url.searchParams.get("n"));
483
+ const maxParts = Math.max(1, Math.ceil(upload.maxBytes / partSize));
484
+ if (!Number.isInteger(n) || n < 1 || n > maxParts) {
485
+ return error(400, `part number must be 1..${maxParts}`);
486
+ }
487
+ const bytes = await request.arrayBuffer();
488
+ if (bytes.byteLength <= 0 || bytes.byteLength > partSize) {
489
+ return error(400, `content-length must be 1..${partSize}`);
490
+ }
491
+ upload.parts.set(n, { etag: `"etag-${n}"`, size: bytes.byteLength });
492
+ return json(200, { partNumber: n, etag: `"etag-${n}"` });
493
+ }
494
+ ],
495
+ [
496
+ "POST",
497
+ /^\/_\/upload\/complete$/,
498
+ async (request, url) => {
499
+ const upload = state.uploads.get(url.searchParams.get("token") ?? "");
500
+ if (!upload) return error(401, "invalid token");
501
+ const body = await request.json().catch(() => null);
502
+ if (!body?.parts?.length)
503
+ return error(400, "body must be { parts: [{ partNumber, etag }] }");
504
+ let size = 0;
505
+ for (const p of body.parts) {
506
+ const stored = upload.parts.get(p.partNumber);
507
+ if (!stored || stored.etag !== p.etag) return error(400, "complete failed: unknown part");
508
+ size += stored.size;
509
+ }
510
+ if (size > upload.maxBytes) return error(413, `object exceeds ${upload.maxBytes} bytes`);
511
+ upload.completed = size;
512
+ return json(200, { size, etag: fakeHex(`object-${upload.uploadId}`).slice(0, 32) });
513
+ }
514
+ ],
515
+ [
516
+ "POST",
517
+ /^\/_\/upload\/abort$/,
518
+ (_request, url) => {
519
+ const upload = state.uploads.get(url.searchParams.get("token") ?? "");
520
+ if (upload) upload.aborted = true;
521
+ return new Response(null, { status: 204 });
522
+ }
523
+ ]
524
+ ];
525
+ const fetchImpl = async (input, init) => {
526
+ const request = input instanceof Request ? init ? new Request(input, init) : input : new Request(input, init);
527
+ const url = new URL(request.url);
528
+ state.requests.push({ method: request.method, path: `${url.pathname}${url.search}` });
529
+ const table = url.origin === cdnOrigin && url.pathname.startsWith("/_/") ? cdn : api;
530
+ if (table === api && apiOrigin && url.origin !== apiOrigin) return error(404, "not found");
531
+ if (table === api && requireAuth) {
532
+ const auth = request.headers.get("authorization") ?? "";
533
+ if (!/^Bearer glb_/.test(auth)) return error(401, "unauthorized");
534
+ }
535
+ for (const [method, pattern, handler] of table) {
536
+ if (request.method !== method) continue;
537
+ const match = pattern.exec(url.pathname);
538
+ if (match) return handler(request, url, match.slice(1).map(decodeURIComponent));
539
+ }
540
+ return error(404, "not found");
541
+ };
542
+ return { fetch: fetchImpl, state, baseUrl, cdnBase, project, seedAsset };
543
+ }
544
+ function fakeHex(input) {
545
+ let h1 = 2166136261;
546
+ let h2 = 16777619;
547
+ for (let i = 0; i < input.length; i++) {
548
+ h1 = Math.imul(h1 ^ input.charCodeAt(i), 16777619) >>> 0;
549
+ h2 = Math.imul(h2 ^ input.charCodeAt(i), 2166136261) >>> 0;
550
+ }
551
+ let out = "";
552
+ for (let i = 0; i < 8; i++) {
553
+ h1 = Math.imul(h1 ^ h1 >>> 15, 739982445) >>> 0;
554
+ h2 = Math.imul(h2 ^ h2 >>> 13, 695872825) >>> 0;
555
+ out += ((h1 ^ h2) >>> 0).toString(16).padStart(8, "0");
556
+ }
557
+ return out;
558
+ }
559
+ export {
560
+ createFakeApi
561
+ };
562
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import { DEFAULT_VARIANT_PRESETS, PRESETS, canonicalSpec, isPresetName } from '@glboost/core';\n\n/**\n * An in-memory stand-in for the GLBoost API and the CDN's upload endpoints, for tests of code built\n * on `@glboost/sdk` (the SDK's and the CLI's own tests run against it). Pass `fetch` to\n * `createClient({ fetch })`. It speaks the same routes and shapes as the real API for: `/me`,\n * `/projects`, `/assets` (list, get, delete), `/assets/:id/variants`, `/assets/:id/sign`,\n * `/assets/:id/exports`, `/shares`, the upload flow, and `${cdnBase}/_/upload/*`.\n */\nexport interface FakeApiOptions {\n /** API origin (default `https://api.test`). */\n baseUrl?: string;\n /** CDN origin the upload target points at (default `https://cdn.test`). */\n cdnBase?: string;\n plan?: 'free' | 'pro' | 'business';\n project?: { id: string; name: string; slug?: string };\n /** Part size the upload target advertises (default 25 MiB, like the real edge). */\n partSize?: number;\n /** Variants turn `ready` once a version was polled this many times (default 1). */\n readyAfterPolls?: number;\n /** A preset whose variant fails instead of becoming ready. */\n failPreset?: string | null;\n /** Make `POST /uploads` answer a plan limit (402) or size limit (413) with the real body shape. */\n refuse?: {\n status: 402 | 413;\n error: string;\n hint?: string;\n used?: number;\n limit?: number;\n } | null;\n /** Require `Authorization: Bearer glb_…` on API routes (default: when `baseUrl` is set). */\n requireAuth?: boolean;\n}\n\nexport interface FakeVariant {\n id: string;\n versionId: string;\n spec: string;\n preset: string | null;\n requested: null;\n status: 'queued' | 'processing' | 'ready' | 'failed';\n bytes: number | null;\n stats: null;\n error: string | null;\n createdAt: string;\n readyAt: string | null;\n}\n\nexport interface FakeVersion {\n id: string;\n assetId: string;\n number: number;\n status: 'uploading' | 'uploaded' | 'processing' | 'ready' | 'failed';\n originalBytes: number | null;\n sourceFormat: 'glb' | 'gltf' | 'zip';\n note: string | null;\n createdAt: string;\n variants: FakeVariant[];\n /** Internal: how often this version's variants were read. */\n polls: number;\n}\n\nexport interface FakeAsset {\n id: string;\n projectId: string;\n name: string;\n description: string | null;\n visibility: 'public' | 'private';\n tags: string[];\n createdAt: string;\n deletedAt: string | null;\n versions: FakeVersion[];\n}\n\ninterface FakeUpload {\n uploadId: string;\n token: string;\n assetId: string;\n versionId: string;\n maxBytes: number;\n parts: Map<number, { etag: string; size: number }>;\n completed: number | null;\n aborted: boolean;\n}\n\nexport interface FakeApiState {\n assets: Map<string, FakeAsset>;\n uploads: Map<string, FakeUpload>;\n shares: Record<string, unknown>[];\n exports: Record<string, unknown>[];\n signed: Record<string, unknown>[];\n /** Every request seen, oldest first. */\n requests: { method: string; path: string }[];\n}\n\nexport interface SeedAssetInput {\n id?: string;\n name: string;\n description?: string | null;\n tags?: string[];\n /** Original size of every version (default 1 MB). */\n bytes?: number;\n /** How many versions to create (default 1). */\n versions?: number;\n /** Variants start ready (default) or queued, to exercise polling. */\n ready?: boolean;\n deleted?: boolean;\n}\n\nexport interface FakeApi {\n fetch: typeof fetch;\n state: FakeApiState;\n readonly baseUrl: string;\n readonly cdnBase: string;\n readonly project: { id: string; name: string; slug: string };\n seedAsset(input: SeedAssetInput): FakeAsset;\n}\n\nconst DEFAULT_PART_SIZE = 26_214_400;\nconst MAX_UPLOAD_BYTES = 500 * 1024 * 1024;\nconst UPGRADE_URL = '/app/settings#billing';\nconst SIZE_RATIO: Record<string, number> = { original: 1, compat: 0.6, gpu: 0.3 };\nconst GRADE: Record<string, [string, number]> = {\n compat: ['B', 70],\n gpu: ['A', 92],\n original: ['C', 55],\n};\n\nexport function createFakeApi(options: FakeApiOptions = {}): FakeApi {\n const baseUrl = (options.baseUrl ?? 'https://api.test').replace(/\\/+$/, '');\n const cdnBase = (options.cdnBase ?? 'https://cdn.test').replace(/\\/+$/, '');\n const apiOrigin = baseUrl ? new URL(baseUrl).origin : null;\n const cdnOrigin = new URL(cdnBase).origin;\n const requireAuth = options.requireAuth ?? baseUrl !== '';\n const partSize = options.partSize ?? DEFAULT_PART_SIZE;\n const readyAfterPolls = options.readyAfterPolls ?? 1;\n const plan = options.plan ?? 'pro';\n const project = {\n id: options.project?.id ?? 'proj00000001',\n name: options.project?.name ?? 'Default',\n slug: options.project?.slug ?? 'default',\n };\n const org = { id: 'org000000001', name: 'Fake org', slug: 'fake-org', plan };\n const state: FakeApiState = {\n assets: new Map(),\n uploads: new Map(),\n shares: [],\n exports: [],\n signed: [],\n requests: [],\n };\n let counter = 0;\n const nextId = (prefix: string) =>\n `${prefix}${(++counter).toString(36).padStart(12 - prefix.length, '0')}`;\n const now = () => new Date().toISOString();\n\n const json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n const error = (status: number, message: string, extra: Record<string, unknown> = {}) =>\n json(status, { error: message, ...extra });\n\n const specFor = (preset: string): string =>\n preset === 'original' ? 'original' : canonicalSpec(PRESETS[preset as keyof typeof PRESETS]);\n\n function makeVariant(version: FakeVersion, preset: string | null, spec: string): FakeVariant {\n return {\n id: nextId('var'),\n versionId: version.id,\n spec,\n preset,\n requested: null,\n status: 'queued',\n bytes: null,\n stats: null,\n error: null,\n createdAt: now(),\n readyAt: null,\n };\n }\n\n function settle(variant: FakeVariant, originalBytes: number) {\n const key = variant.preset ?? 'spec';\n if (options.failPreset && variant.preset === options.failPreset) {\n variant.status = 'failed';\n variant.error = 'optimizer failed (fake)';\n return;\n }\n variant.status = 'ready';\n variant.bytes = Math.max(1, Math.round(originalBytes * (SIZE_RATIO[key] ?? 0.5)));\n variant.readyAt = now();\n }\n\n /** A read of the version's variants; readiness arrives after `readyAfterPolls` reads. */\n function tick(version: FakeVersion) {\n version.polls += 1;\n if (version.polls < readyAfterPolls || version.originalBytes === null) return;\n for (const v of version.variants) if (v.status === 'queued') settle(v, version.originalBytes);\n if (version.variants.every((v) => v.status === 'ready')) version.status = 'ready';\n }\n\n function scoreOf(variant: FakeVariant) {\n if (variant.status !== 'ready') return null;\n const [grade, points] = GRADE[variant.preset ?? ''] ?? ['B', 75];\n return { version: 1, grade, points, checks: [] };\n }\n const withScore = (v: FakeVariant) => ({ ...v, score: scoreOf(v) });\n const versionRow = (v: FakeVersion) => {\n const { polls: _polls, variants, ...rest } = v;\n return { ...rest, variants: variants.map(withScore) };\n };\n\n function addVersion(\n asset: FakeAsset,\n input: {\n bytes: number | null;\n note?: string | null;\n sourceFormat?: FakeVersion['sourceFormat'];\n },\n ): FakeVersion {\n const version: FakeVersion = {\n id: nextId('ver'),\n assetId: asset.id,\n number: asset.versions.length + 1,\n status: 'uploading',\n originalBytes: input.bytes,\n sourceFormat: input.sourceFormat ?? 'glb',\n note: input.note ?? null,\n createdAt: now(),\n variants: [],\n polls: 0,\n };\n asset.versions.unshift(version);\n return version;\n }\n\n function defaultVariants(version: FakeVersion) {\n for (const preset of DEFAULT_VARIANT_PRESETS) {\n const variant = makeVariant(version, preset, specFor(preset));\n if (preset === 'original' && version.originalBytes !== null) {\n variant.status = 'ready';\n variant.bytes = version.originalBytes;\n variant.readyAt = now();\n }\n version.variants.push(variant);\n }\n }\n\n function seedAsset(input: SeedAssetInput): FakeAsset {\n const asset: FakeAsset = {\n id: input.id ?? nextId('ast'),\n projectId: project.id,\n name: input.name,\n description: input.description ?? null,\n visibility: 'public',\n tags: input.tags ?? [],\n createdAt: now(),\n deletedAt: input.deleted ? now() : null,\n versions: [],\n };\n const bytes = input.bytes ?? 1_000_000;\n for (let i = 0; i < (input.versions ?? 1); i++) {\n const version = addVersion(asset, { bytes });\n version.status = 'uploaded';\n defaultVariants(version);\n if (input.ready ?? true) {\n version.polls = readyAfterPolls;\n for (const v of version.variants) if (v.status === 'queued') settle(v, bytes);\n version.status = version.variants.every((v) => v.status === 'ready') ? 'ready' : 'uploaded';\n }\n }\n state.assets.set(asset.id, asset);\n return asset;\n }\n\n const liveAsset = (id: string) => {\n const asset = state.assets.get(id);\n return asset && !asset.deletedAt ? asset : undefined;\n };\n const findVersion = (id: string) => {\n for (const asset of state.assets.values()) {\n const version = asset.versions.find((v) => v.id === id);\n if (version) return { asset, version };\n }\n return undefined;\n };\n const newestUploaded = (asset: FakeAsset) =>\n asset.versions.find((v) => v.status !== 'uploading' && v.status !== 'failed');\n\n type Handler = (request: Request, url: URL, params: string[]) => Promise<Response> | Response;\n const api: [string, RegExp, Handler][] = [\n [\n 'GET',\n /^\\/api\\/v1\\/me$/,\n () =>\n json(200, {\n org,\n project,\n role: null,\n auth: { kind: 'key', keyName: 'fake key', scopes: ['read', 'write'], projectId: null },\n }),\n ],\n ['GET', /^\\/api\\/v1\\/projects$/, () => json(200, { projects: [project] })],\n [\n 'GET',\n /^\\/api\\/v1\\/assets$/,\n (_request, url) => {\n const q = url.searchParams.get('q')?.trim().toLowerCase() ?? '';\n const tag = url.searchParams.get('tag');\n const deleted = ['true', '1'].includes(url.searchParams.get('deleted') ?? '');\n const assets = [...state.assets.values()]\n .filter((a) => Boolean(a.deletedAt) === deleted)\n .filter((a) => !tag || a.tags.includes(tag))\n .filter(\n (a) =>\n !q ||\n a.name.toLowerCase().includes(q) ||\n (a.description ?? '').toLowerCase().includes(q) ||\n a.id.startsWith(q),\n )\n .map((a) => {\n const { versions, ...rest } = a;\n const latest = versions[0] ?? null;\n return {\n ...rest,\n latestVersion: latest ? versionRow(latest) : null,\n variantSummary: {\n total: latest?.variants.length ?? 0,\n ready: latest?.variants.filter((v) => v.status === 'ready').length ?? 0,\n },\n reviewState: null,\n };\n });\n return json(200, { assets });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/uploads$/,\n async (request) => {\n const body = (await request.json().catch(() => null)) as {\n projectId?: string;\n filename?: string;\n bytes?: number;\n assetId?: string;\n note?: string | null;\n } | null;\n if (!body || !body.filename || typeof body.bytes !== 'number')\n return error(400, 'invalid body');\n if (body.projectId !== project.id) return error(404, 'project not found');\n if (options.refuse) {\n const { status, error: message, ...rest } = options.refuse;\n return error(status, message, { plan, ...rest, upgradeUrl: UPGRADE_URL });\n }\n if (body.bytes > MAX_UPLOAD_BYTES) {\n return error(413, `files over ${MAX_UPLOAD_BYTES} bytes are not supported yet`);\n }\n const ext = body.filename.toLowerCase().match(/\\.(glb|gltf|zip)$/)?.[1];\n if (!ext) return error(415, 'upload a .glb, .gltf, or .zip');\n let asset: FakeAsset | undefined;\n if (body.assetId) {\n asset = liveAsset(body.assetId);\n if (!asset) return error(404, 'asset not found');\n } else {\n asset = {\n id: nextId('ast'),\n projectId: project.id,\n name: body.filename.replace(/\\.[^.]+$/, ''),\n description: null,\n visibility: 'public',\n tags: [],\n createdAt: now(),\n deletedAt: null,\n versions: [],\n };\n state.assets.set(asset.id, asset);\n }\n const version = addVersion(asset, {\n bytes: null,\n note: body.note ?? null,\n sourceFormat: ext as FakeVersion['sourceFormat'],\n });\n const uploadId = nextId('upl');\n const upload: FakeUpload = {\n uploadId,\n token: `tok_${uploadId}`,\n assetId: asset.id,\n versionId: version.id,\n maxBytes: body.bytes,\n parts: new Map(),\n completed: null,\n aborted: false,\n };\n state.uploads.set(upload.token, upload);\n return json(200, {\n assetId: asset.id,\n versionId: version.id,\n uploadId,\n partSize,\n uploadUrl: `${cdnBase}/_/upload`,\n token: upload.token,\n });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/uploads\\/([^/]+)\\/complete$/,\n (_request, _url, [versionId]) => {\n const found = findVersion(versionId!);\n if (!found) return error(404, 'version not found');\n const { asset, version } = found;\n if (version.status === 'failed')\n return error(409, 'upload was aborted; start a new upload');\n const upload = [...state.uploads.values()].find((u) => u.versionId === version.id);\n if (version.status === 'uploading') {\n if (!upload || upload.completed === null) {\n return error(409, 'upload has not been completed on the edge');\n }\n version.status = 'uploaded';\n version.originalBytes = upload.completed;\n defaultVariants(version);\n }\n const queued = version.variants.filter((v) => v.status === 'queued').length;\n return json(200, { assetId: asset.id, versionId: version.id, queued });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/uploads\\/([^/]+)\\/abort$/,\n (_request, _url, [versionId]) => {\n const found = findVersion(versionId!);\n if (!found) return error(404, 'version not found');\n if (found.version.status === 'uploading') found.version.status = 'failed';\n return json(200, {\n assetId: found.asset.id,\n versionId: found.version.id,\n status: found.version.status,\n });\n },\n ],\n [\n 'GET',\n /^\\/api\\/v1\\/assets\\/([^/]+)$/,\n (_request, _url, [assetId]) => {\n const asset = liveAsset(assetId!);\n if (!asset) return error(404, 'asset not found');\n for (const v of asset.versions) tick(v);\n const { versions, ...rest } = asset;\n return json(200, {\n asset: rest,\n project: { id: project.id, name: project.name, slug: project.slug },\n versions: versions.map((v) => ({\n ...versionRow(v),\n review: { approved: 0, changesRequested: 0, awaitingApproval: false, reviewers: [] },\n })),\n });\n },\n ],\n [\n 'DELETE',\n /^\\/api\\/v1\\/assets\\/([^/]+)$/,\n (_request, url, [assetId]) => {\n const asset = state.assets.get(assetId!);\n if (!asset) return error(404, 'asset not found');\n const permanent = ['1', 'true'].includes(url.searchParams.get('permanent') ?? '');\n if (permanent) {\n if (!asset.deletedAt) return error(409, 'move the asset to the trash first');\n state.assets.delete(asset.id);\n const objects = asset.versions.reduce((n, v) => n + 1 + v.variants.length, 0);\n return json(200, { deleted: true, permanent: true, objects });\n }\n if (asset.deletedAt) return error(404, 'asset not found');\n asset.deletedAt = now();\n return json(200, { deleted: true, permanent: false });\n },\n ],\n [\n 'GET',\n /^\\/api\\/v1\\/assets\\/([^/]+)\\/variants$/,\n (_request, _url, [assetId]) => {\n const asset = liveAsset(assetId!);\n if (!asset) return error(404, 'asset not found');\n for (const v of asset.versions) tick(v);\n return json(200, {\n versions: asset.versions.map((v) => ({\n id: v.id,\n number: v.number,\n variants: v.variants.map(withScore),\n })),\n });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/assets\\/([^/]+)\\/variants$/,\n async (request, _url, [assetId]) => {\n const asset = liveAsset(assetId!);\n if (!asset) return error(404, 'asset not found');\n const body = (await request.json().catch(() => null)) as {\n versionId?: string;\n preset?: string;\n spec?: Parameters<typeof canonicalSpec>[0];\n } | null;\n if (!body || (body.preset ? 1 : 0) + (body.spec ? 1 : 0) !== 1) {\n return error(400, 'invalid body');\n }\n const version = body.versionId\n ? asset.versions.find((v) => v.id === body.versionId && v.status !== 'uploading')\n : newestUploaded(asset);\n if (!version) return error(409, 'no uploaded version to optimize');\n let spec: string;\n let preset: string | null;\n if (body.preset) {\n if (!isPresetName(body.preset)) return error(400, 'unknown preset');\n preset = body.preset;\n spec = specFor(body.preset);\n } else {\n try {\n spec = canonicalSpec(body.spec!);\n } catch (e) {\n return error(400, (e as Error).message);\n }\n preset =\n (Object.keys(PRESETS) as (keyof typeof PRESETS)[]).find((p) => specFor(p) === spec) ??\n null;\n }\n const existing = version.variants.find((v) => v.spec === spec);\n if (existing) return json(200, { variant: withScore(existing), created: false });\n const variant = makeVariant(version, preset, spec);\n version.variants.push(variant);\n version.polls = 0;\n return json(201, { variant: withScore(variant), created: true });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/assets\\/([^/]+)\\/sign$/,\n async (request, _url, [assetId]) => {\n const asset = liveAsset(assetId!);\n if (!asset) return error(404, 'asset not found');\n const body = (await request.json().catch(() => ({}))) as {\n preset?: string;\n spec?: Parameters<typeof canonicalSpec>[0];\n versionId?: string;\n ttlSeconds?: number;\n dl?: boolean;\n };\n if (body.preset && !isPresetName(body.preset)) return error(400, 'unknown preset');\n const q = new URLSearchParams();\n q.set('p', body.preset ?? 'compat');\n if (body.spec) {\n q.set('p', 'compat');\n for (const [key, value] of Object.entries(body.spec)) q.set(key, String(value));\n }\n if (body.versionId) q.set('v', body.versionId);\n if (body.dl) q.set('dl', '1');\n const ttl = Math.min(7 * 24 * 3600, Math.max(60, body.ttlSeconds ?? 600));\n const exp = Math.floor(Date.now() / 1000) + ttl;\n q.set('exp', String(exp));\n const sig = fakeHex(`${assetId}?${q.toString()}`);\n q.set('sig', sig);\n const result = { url: `${cdnBase}/${assetId}.glb?${q.toString()}`, exp, sig };\n state.signed.push(result);\n return json(200, result);\n },\n ],\n [\n 'GET',\n /^\\/api\\/v1\\/assets\\/([^/]+)\\/exports$/,\n (_request, _url, [assetId]) => {\n if (!liveAsset(assetId!)) return error(404, 'asset not found');\n return json(200, { exports: state.exports.filter((e) => e.assetId === assetId) });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/assets\\/([^/]+)\\/exports$/,\n async (request, _url, [assetId]) => {\n const asset = liveAsset(assetId!);\n if (!asset) return error(404, 'asset not found');\n const body = (await request.json().catch(() => null)) as {\n variantId?: string;\n format?: string;\n } | null;\n if (!body?.variantId || !['gltf-zip', 'c-array', 'usdz'].includes(body.format ?? '')) {\n return error(400, 'invalid body');\n }\n const variant = asset.versions\n .flatMap((v) => v.variants)\n .find((v) => v.id === body.variantId);\n if (!variant) return error(404, 'variant not found');\n if (body.format === 'usdz' && variant.spec.includes('tex=ktx2')) {\n return error(400, 'usdz needs a jpeg or png variant');\n }\n if (variant.status !== 'ready') return error(409, 'variant is not ready yet');\n const existing = state.exports.find(\n (e) => e.variantId === variant.id && e.format === body.format,\n );\n if (existing) return json(200, { export: existing, created: false });\n const exp = {\n id: nextId('exp'),\n assetId: asset.id,\n variantId: variant.id,\n format: body.format,\n status: 'queued',\n bytes: null,\n createdAt: now(),\n };\n state.exports.push(exp);\n return json(201, { export: exp, created: true });\n },\n ],\n [\n 'POST',\n /^\\/api\\/v1\\/shares$/,\n async (request) => {\n const body = (await request.json().catch(() => null)) as {\n assetId?: string;\n collectionId?: string;\n accessMode?: string;\n password?: string | null;\n expiresAt?: string | null;\n allowDownload?: string;\n } | null;\n if (!body || Boolean(body.assetId) === Boolean(body.collectionId)) {\n return error(400, 'invalid body');\n }\n if (!['link', 'password', 'email', 'org'].includes(body.accessMode ?? '')) {\n return error(400, 'invalid body');\n }\n if (body.accessMode === 'password' && !body.password)\n return error(400, 'password is required');\n if (body.assetId && !liveAsset(body.assetId)) return error(404, 'asset not found');\n const token = fakeHex(`share-${state.shares.length}`).slice(0, 22);\n const share = {\n id: nextId('shr'),\n token,\n targetType: body.assetId ? 'asset' : 'collection',\n targetId: body.assetId ?? body.collectionId,\n accessMode: body.accessMode,\n allowDownload: body.allowDownload ?? 'none',\n expiresAt: body.expiresAt ?? null,\n createdAt: now(),\n };\n state.shares.push(share);\n return json(201, { share, url: `${baseUrl || 'https://glboost.test'}/s/${token}` });\n },\n ],\n ];\n\n const cdn: [string, RegExp, Handler][] = [\n [\n 'PUT',\n /^\\/_\\/upload\\/part$/,\n async (request, url) => {\n const upload = state.uploads.get(url.searchParams.get('token') ?? '');\n if (!upload) return error(401, 'invalid token');\n const n = Number(url.searchParams.get('n'));\n const maxParts = Math.max(1, Math.ceil(upload.maxBytes / partSize));\n if (!Number.isInteger(n) || n < 1 || n > maxParts) {\n return error(400, `part number must be 1..${maxParts}`);\n }\n const bytes = await request.arrayBuffer();\n if (bytes.byteLength <= 0 || bytes.byteLength > partSize) {\n return error(400, `content-length must be 1..${partSize}`);\n }\n upload.parts.set(n, { etag: `\"etag-${n}\"`, size: bytes.byteLength });\n return json(200, { partNumber: n, etag: `\"etag-${n}\"` });\n },\n ],\n [\n 'POST',\n /^\\/_\\/upload\\/complete$/,\n async (request, url) => {\n const upload = state.uploads.get(url.searchParams.get('token') ?? '');\n if (!upload) return error(401, 'invalid token');\n const body = (await request.json().catch(() => null)) as {\n parts?: { partNumber: number; etag: string }[];\n } | null;\n if (!body?.parts?.length)\n return error(400, 'body must be { parts: [{ partNumber, etag }] }');\n let size = 0;\n for (const p of body.parts) {\n const stored = upload.parts.get(p.partNumber);\n if (!stored || stored.etag !== p.etag) return error(400, 'complete failed: unknown part');\n size += stored.size;\n }\n if (size > upload.maxBytes) return error(413, `object exceeds ${upload.maxBytes} bytes`);\n upload.completed = size;\n return json(200, { size, etag: fakeHex(`object-${upload.uploadId}`).slice(0, 32) });\n },\n ],\n [\n 'POST',\n /^\\/_\\/upload\\/abort$/,\n (_request, url) => {\n const upload = state.uploads.get(url.searchParams.get('token') ?? '');\n if (upload) upload.aborted = true;\n return new Response(null, { status: 204 });\n },\n ],\n ];\n\n const fetchImpl: typeof fetch = async (input, init) => {\n const request =\n input instanceof Request\n ? init\n ? new Request(input, init)\n : input\n : new Request(input, init);\n const url = new URL(request.url);\n state.requests.push({ method: request.method, path: `${url.pathname}${url.search}` });\n const table = url.origin === cdnOrigin && url.pathname.startsWith('/_/') ? cdn : api;\n if (table === api && apiOrigin && url.origin !== apiOrigin) return error(404, 'not found');\n if (table === api && requireAuth) {\n const auth = request.headers.get('authorization') ?? '';\n if (!/^Bearer glb_/.test(auth)) return error(401, 'unauthorized');\n }\n for (const [method, pattern, handler] of table) {\n if (request.method !== method) continue;\n const match = pattern.exec(url.pathname);\n if (match) return handler(request, url, match.slice(1).map(decodeURIComponent));\n }\n return error(404, 'not found');\n };\n\n return { fetch: fetchImpl, state, baseUrl, cdnBase, project, seedAsset };\n}\n\n/** 64 hex chars derived from the input; stable across runs, nothing cryptographic. */\nfunction fakeHex(input: string): string {\n let h1 = 0x811c9dc5;\n let h2 = 0x01000193;\n for (let i = 0; i < input.length; i++) {\n h1 = Math.imul(h1 ^ input.charCodeAt(i), 0x01000193) >>> 0;\n h2 = Math.imul(h2 ^ input.charCodeAt(i), 0x811c9dc5) >>> 0;\n }\n let out = '';\n for (let i = 0; i < 8; i++) {\n h1 = Math.imul(h1 ^ (h1 >>> 15), 0x2c1b3c6d) >>> 0;\n h2 = Math.imul(h2 ^ (h2 >>> 13), 0x297a2d39) >>> 0;\n out += ((h1 ^ h2) >>> 0).toString(16).padStart(8, '0');\n }\n return out;\n}\n"],"mappings":"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","names":[]}