@loaders.gl/tile-converter 5.0.0-alpha.1 → 5.0.0-alpha.11
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/LICENSE +14 -3
- package/README.md +497 -0
- package/dist/converter.min.cjs +210 -282
- package/dist/i3s-server/bin/i3s-server.min.cjs +106 -341
- package/dist/index.cjs +38586 -0
- package/dist/index.js +38557 -0
- package/dist/v5/adapters.cjs +3548 -0
- package/dist/v5/adapters.d.ts +33 -0
- package/dist/v5/adapters.js +3537 -0
- package/dist/v5/browser-sink.d.ts +37 -0
- package/dist/v5/browser-source.d.ts +38 -0
- package/dist/v5/browser-tileset-source.d.ts +70 -0
- package/dist/v5/browser.cjs +76 -0
- package/dist/v5/browser.d.ts +44 -0
- package/dist/v5/browser.js +75 -0
- package/dist/v5/conversion-api.d.ts +122 -0
- package/dist/v5/conversion-archive.d.ts +32 -0
- package/dist/v5/core.cjs +917 -0
- package/dist/v5/core.d.ts +16 -0
- package/dist/v5/core.js +899 -0
- package/dist/v5/feature-arrow.d.ts +36 -0
- package/dist/v5/i3s-mesh-conversion.d.ts +32 -0
- package/dist/v5/i3s-mesh-sink.d.ts +25 -0
- package/dist/v5/i3s-mesh-source-features.d.ts +13 -0
- package/dist/v5/i3s-mesh-source-material.d.ts +5 -0
- package/dist/v5/i3s-mesh-source.d.ts +30 -0
- package/dist/v5/index.cjs +73 -0
- package/dist/v5/index.d.ts +40 -0
- package/dist/v5/index.js +75 -0
- package/dist/v5/mesh-conversion.d.ts +71 -0
- package/dist/v5/mesh-draco.d.ts +8 -0
- package/dist/v5/mesh-features.d.ts +9 -0
- package/dist/v5/mesh-source-features.d.ts +21 -0
- package/dist/v5/mesh-source-texture.d.ts +6 -0
- package/dist/v5/mesh-source.d.ts +41 -0
- package/dist/v5/mesh-tileset-archive.d.ts +18 -0
- package/dist/v5/mesh-tileset-sink.d.ts +51 -0
- package/dist/v5/mesh.d.ts +86 -0
- package/dist/v5/point-cloud-conversion.d.ts +31 -0
- package/dist/v5/point-cloud-source-encoder.d.ts +46 -0
- package/dist/v5/point-cloud-source.d.ts +25 -0
- package/dist/v5/point-cloud-spatial.d.ts +22 -0
- package/dist/v5/point-cloud-tileset-sink.d.ts +28 -0
- package/dist/v5/point-cloud.d.ts +49 -0
- package/dist/v5/resource-fetcher.d.ts +18 -0
- package/dist/v5/resource-manifest.d.ts +58 -0
- package/dist/v5/spatial-conversion.d.ts +61 -0
- package/dist/v5/tileset-source.d.ts +21 -0
- package/package.json +52 -26
- package/src/common/README.md +5 -0
- package/src/index.ts +1 -2
- package/src/{3d-tiles-converter → v4/3d-tiles-converter}/3d-tiles-converter.ts +13 -2
- package/src/v4/3d-tiles-converter/helpers/validate-i3s-spatial-reference.ts +124 -0
- package/src/{deps-installer → v4/deps-installer}/deps-installer.ts +10 -7
- package/src/{i3s-converter → v4/i3s-converter}/helpers/batch-ids-extensions.ts +1 -1
- package/src/v4/i3s-converter/helpers/encode-string-attribute.ts +23 -0
- package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-converter.ts +30 -55
- package/src/{i3s-converter → v4/i3s-converter}/helpers/gltf-attributes.ts +5 -4
- package/src/{i3s-converter → v4/i3s-converter}/helpers/load-3d-tiles.ts +5 -9
- package/src/{i3s-converter → v4/i3s-converter}/i3s-converter.ts +6 -7
- package/src/{i3s-server → v4/i3s-server}/README.md +2 -2
- package/src/{i3s-server → v4/i3s-server}/routes/slpk-router.ts +1 -0
- package/src/v4/index.ts +2 -0
- package/src/{lib → v4/lib}/utils/compress-util.ts +8 -7
- package/src/v5/adapters.ts +75 -0
- package/src/v5/browser-sink.ts +130 -0
- package/src/v5/browser-source.ts +155 -0
- package/src/v5/browser-tileset-source.ts +328 -0
- package/src/v5/browser.ts +139 -0
- package/src/v5/conversion-api.ts +287 -0
- package/src/v5/conversion-archive.ts +85 -0
- package/src/v5/core.ts +61 -0
- package/src/v5/feature-arrow.ts +429 -0
- package/src/v5/i3s-mesh-conversion.ts +192 -0
- package/src/v5/i3s-mesh-sink.ts +351 -0
- package/src/v5/i3s-mesh-source-features.ts +279 -0
- package/src/v5/i3s-mesh-source-material.ts +157 -0
- package/src/v5/i3s-mesh-source.ts +227 -0
- package/src/v5/index.ts +131 -0
- package/src/v5/mesh-conversion.ts +230 -0
- package/src/v5/mesh-draco.ts +182 -0
- package/src/v5/mesh-features.ts +319 -0
- package/src/v5/mesh-source-features.ts +263 -0
- package/src/v5/mesh-source-texture.ts +169 -0
- package/src/v5/mesh-source.ts +422 -0
- package/src/v5/mesh-tileset-archive.ts +50 -0
- package/src/v5/mesh-tileset-sink.ts +324 -0
- package/src/v5/mesh.ts +526 -0
- package/src/v5/point-cloud-conversion.ts +88 -0
- package/src/v5/point-cloud-source-encoder.ts +116 -0
- package/src/v5/point-cloud-source.ts +84 -0
- package/src/v5/point-cloud-spatial.ts +280 -0
- package/src/v5/point-cloud-tileset-sink.ts +234 -0
- package/src/v5/point-cloud.ts +398 -0
- package/src/v5/resource-fetcher.ts +110 -0
- package/src/v5/resource-manifest.ts +213 -0
- package/src/v5/spatial-conversion.ts +389 -0
- package/src/v5/tileset-source.ts +54 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/3d-tiles-content-converter.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/i3s-obb-to-3d-tiles-obb.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/load-i3s.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/helpers/texture-atlas.ts +0 -0
- /package/src/{3d-tiles-converter → v4/3d-tiles-converter}/json-templates/tileset.ts +0 -0
- /package/src/{constants.ts → v4/constants.ts} +0 -0
- /package/src/{converter-cli.ts → v4/converter-cli.ts} +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/attribute-metadata-info.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/coordinate-converter.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/create-scene-server-path.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/feature-attributes.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/geometry-attributes.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-debug.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-index-document.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/node-pages.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/preprocess-3d-tiles.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/progress.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/helpers/tileset-traversal.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/geometry-definitions.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/layers.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/metadata.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/node.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/scene-server.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/shared-resources.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/json-templates/store.ts +0 -0
- /package/src/{i3s-converter → v4/i3s-converter}/types.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/app.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/bin/www.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/certs/cert.pem +0 -0
- /package/src/{i3s-server → v4/i3s-server}/certs/key.pem +0 -0
- /package/src/{i3s-server → v4/i3s-server}/controllers/slpk-controller.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/utils/create-scene-server.ts +0 -0
- /package/src/{i3s-server → v4/i3s-server}/utils/server-utils.ts +0 -0
- /package/src/{lib → v4/lib}/json-schemas/conversion-dump-json-schema.ts +0 -0
- /package/src/{lib → v4/lib}/utils/cli-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/conversion-dump.ts +0 -0
- /package/src/{lib → v4/lib}/utils/file-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/geometry-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/lod-conversion-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/queue.ts +0 -0
- /package/src/{lib → v4/lib}/utils/statistic-utils.ts +0 -0
- /package/src/{lib → v4/lib}/utils/write-queue.ts +0 -0
- /package/src/{pgm-loader.ts → v4/pgm-loader.ts} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {TileConversionError} from './conversion-api.js';
|
|
6
|
+
|
|
7
|
+
/** One aggregate transport budget shared by selected contents and their dependencies. */
|
|
8
|
+
export interface TileConversionResourceFetcherOptions {
|
|
9
|
+
/** Maximum total response bytes, including previously inspected root bytes. */
|
|
10
|
+
readonly maxInputBytes: number;
|
|
11
|
+
/** Previously consumed input bytes, such as the inspected tileset JSON. */
|
|
12
|
+
readonly initialInputBytes?: number;
|
|
13
|
+
/** Application transport; also enables archive-backed or hermetic resolution. */
|
|
14
|
+
readonly fetcher?: typeof fetch;
|
|
15
|
+
/** Conversion cancellation, forwarded to every dependency request. */
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Wraps application fetch with a shared byte budget and conversion cancellation.
|
|
21
|
+
* Each requested resource is charged, including repeated requests; HTTP(S), Blob and embedded data
|
|
22
|
+
* URLs are accepted. Response streams are canceled on overflow or failure. The complete bounded
|
|
23
|
+
* response is retained for codecs; this does not bound their decoded allocations.
|
|
24
|
+
*/
|
|
25
|
+
export function createTileConversionResourceFetcher(
|
|
26
|
+
options: TileConversionResourceFetcherOptions
|
|
27
|
+
): typeof fetch {
|
|
28
|
+
let consumed = options.initialInputBytes ?? 0;
|
|
29
|
+
if (
|
|
30
|
+
!Number.isSafeInteger(options.maxInputBytes) ||
|
|
31
|
+
options.maxInputBytes < 1 ||
|
|
32
|
+
!Number.isSafeInteger(consumed) ||
|
|
33
|
+
consumed < 0 ||
|
|
34
|
+
consumed > options.maxInputBytes
|
|
35
|
+
)
|
|
36
|
+
throw new TileConversionError(
|
|
37
|
+
'INVALID_INPUT_MEMORY_LIMIT',
|
|
38
|
+
'Resource fetch requires a positive aggregate byte budget and valid initial bytes'
|
|
39
|
+
);
|
|
40
|
+
const fetcher = options.fetcher ?? fetch;
|
|
41
|
+
return async (input, requestOptions) => {
|
|
42
|
+
options.signal?.throwIfAborted();
|
|
43
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
44
|
+
if (!['http:', 'https:', 'blob:', 'data:'].includes(url.protocol))
|
|
45
|
+
throw new TileConversionError(
|
|
46
|
+
'EXTERNAL_RESOURCE_UNSUPPORTED',
|
|
47
|
+
'Conversion transport requires HTTP(S), Blob or embedded data resources'
|
|
48
|
+
);
|
|
49
|
+
const requestSignal =
|
|
50
|
+
requestOptions?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
51
|
+
const signal =
|
|
52
|
+
options.signal && requestSignal
|
|
53
|
+
? AbortSignal.any([options.signal, requestSignal])
|
|
54
|
+
: (options.signal ?? requestSignal);
|
|
55
|
+
const response = await fetcher(input, {...requestOptions, signal});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
await response.body?.cancel();
|
|
58
|
+
throw new TileConversionError(
|
|
59
|
+
'BROWSER_SOURCE_FETCH_FAILED',
|
|
60
|
+
`Could not fetch conversion resource (${response.status})`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const reader = response.body?.getReader();
|
|
64
|
+
if (!reader)
|
|
65
|
+
throw new TileConversionError(
|
|
66
|
+
'BROWSER_SOURCE_STREAM_UNAVAILABLE',
|
|
67
|
+
'Conversion resources require readable response streams'
|
|
68
|
+
);
|
|
69
|
+
/** Cancels an injected transport stream if it does not observe the request signal itself. */
|
|
70
|
+
const cancelRead = () => {
|
|
71
|
+
void reader.cancel(signal?.reason).catch(() => {});
|
|
72
|
+
};
|
|
73
|
+
signal?.addEventListener('abort', cancelRead, {once: true});
|
|
74
|
+
const parts: Uint8Array[] = [];
|
|
75
|
+
let byteLength = 0;
|
|
76
|
+
try {
|
|
77
|
+
while (true) {
|
|
78
|
+
signal?.throwIfAborted();
|
|
79
|
+
const {done, value} = await reader.read();
|
|
80
|
+
signal?.throwIfAborted();
|
|
81
|
+
if (done) break;
|
|
82
|
+
if (value.byteLength > options.maxInputBytes - consumed)
|
|
83
|
+
throw new TileConversionError(
|
|
84
|
+
'INPUT_RESOURCE_TOO_LARGE',
|
|
85
|
+
'Selected contents and external dependencies exceed the aggregate input byte budget'
|
|
86
|
+
);
|
|
87
|
+
consumed += value.byteLength;
|
|
88
|
+
byteLength += value.byteLength;
|
|
89
|
+
parts.push(value);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
await reader.cancel(error);
|
|
93
|
+
throw error;
|
|
94
|
+
} finally {
|
|
95
|
+
signal?.removeEventListener('abort', cancelRead);
|
|
96
|
+
reader.releaseLock();
|
|
97
|
+
}
|
|
98
|
+
const bytes = new Uint8Array(byteLength);
|
|
99
|
+
let offset = 0;
|
|
100
|
+
for (const part of parts) {
|
|
101
|
+
bytes.set(part, offset);
|
|
102
|
+
offset += part.byteLength;
|
|
103
|
+
}
|
|
104
|
+
return new Response(bytes, {
|
|
105
|
+
status: response.status,
|
|
106
|
+
statusText: response.statusText,
|
|
107
|
+
headers: response.headers
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// loaders.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {TileConversionError} from './conversion-api.js';
|
|
6
|
+
import type {TileConversionReport, TileConversionSink} from './conversion-api.js';
|
|
7
|
+
|
|
8
|
+
const TILE_RESOURCE_MANIFEST_VERSION = 1;
|
|
9
|
+
|
|
10
|
+
/** Durable record for one successfully written, deterministic output resource. */
|
|
11
|
+
export interface TileResourceManifestEntry {
|
|
12
|
+
/** Stable resource identifier supplied by the output profile. */
|
|
13
|
+
readonly resourceId: string;
|
|
14
|
+
/** Byte length of the output resource. */
|
|
15
|
+
readonly byteLength: number;
|
|
16
|
+
/** Stable caller-computed content fingerprint, such as a SHA-256 digest. */
|
|
17
|
+
readonly fingerprint: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Versioned progress record used to resume deterministic output packaging. */
|
|
21
|
+
export interface TileResourceManifest {
|
|
22
|
+
/** Manifest format version. */
|
|
23
|
+
readonly version: 1;
|
|
24
|
+
/** True only after the destination and report have been finalized successfully. */
|
|
25
|
+
readonly complete: boolean;
|
|
26
|
+
/** Resources durably written before interruption or completion. */
|
|
27
|
+
readonly resources: readonly TileResourceManifestEntry[];
|
|
28
|
+
/** Conversion summary saved after successful destination finalization. */
|
|
29
|
+
readonly report?: TileConversionReport;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Application-owned persistence for a conversion manifest. */
|
|
33
|
+
export interface TileResourceManifestStore {
|
|
34
|
+
/** Loads the previous manifest, or returns null when starting a new conversion. */
|
|
35
|
+
load(): Promise<TileResourceManifest | null>;
|
|
36
|
+
/** Atomically replaces the saved manifest with the supplied snapshot. */
|
|
37
|
+
save(manifest: TileResourceManifest): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Options for decorating a resource sink with restart-safe manifest tracking. */
|
|
41
|
+
export interface ManifestBackedTileConversionSinkOptions<TResource> {
|
|
42
|
+
/** Output destination with deterministic, idempotent resource writes. */
|
|
43
|
+
readonly sink: TileConversionSink<TResource>;
|
|
44
|
+
/** Persistence adapter for snapshots written after each resource. */
|
|
45
|
+
readonly manifestStore: TileResourceManifestStore;
|
|
46
|
+
/** Stable profile-specific path or resource key. */
|
|
47
|
+
readonly getResourceId: (resource: TResource) => string;
|
|
48
|
+
/** Byte length used to detect changed content under an existing resource ID. */
|
|
49
|
+
readonly measureResourceBytes: (resource: TResource) => number;
|
|
50
|
+
/** Stable fingerprint used to detect changed content under an existing resource ID. */
|
|
51
|
+
readonly fingerprintResource: (resource: TResource) => string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a sink that skips matching checkpointed resources when a process restarts.
|
|
56
|
+
*
|
|
57
|
+
* The destination must write resources idempotently by the ID returned from `getResourceId`.
|
|
58
|
+
* The manifest is persisted after every successful write; the store should replace snapshots
|
|
59
|
+
* atomically. Existing IDs must retain their byte length and fingerprint or the resumed job fails
|
|
60
|
+
* rather than silently mixing output from different conversion inputs. Writes are serialized even
|
|
61
|
+
* when callers issue them concurrently. Reports count resources processed by the current run,
|
|
62
|
+
* including writes skipped because the matching resource was already present. A graceful abort
|
|
63
|
+
* clears the manifest checkpoint before asking the destination to clean partial output, so the
|
|
64
|
+
* next run starts cleanly. A process interruption that does not call `abort` can resume from its
|
|
65
|
+
* last saved checkpoint.
|
|
66
|
+
*
|
|
67
|
+
* @param options - Destination, manifest store, stable IDs, byte measurement, and fingerprinting.
|
|
68
|
+
* @returns A `TileConversionSink` with resumable per-resource progress.
|
|
69
|
+
*/
|
|
70
|
+
export async function createManifestBackedTileConversionSink<TResource>(
|
|
71
|
+
options: ManifestBackedTileConversionSinkOptions<TResource>
|
|
72
|
+
): Promise<TileConversionSink<TResource>> {
|
|
73
|
+
const priorManifest = await options.manifestStore.load();
|
|
74
|
+
const resources = new Map<string, TileResourceManifestEntry>();
|
|
75
|
+
if (priorManifest) {
|
|
76
|
+
validateManifest(priorManifest);
|
|
77
|
+
for (const entry of priorManifest.resources) {
|
|
78
|
+
resources.set(entry.resourceId, entry);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let writeChain: Promise<void> = Promise.resolve();
|
|
83
|
+
let writeFailed = false;
|
|
84
|
+
let writeFailure: unknown;
|
|
85
|
+
let closed = false;
|
|
86
|
+
|
|
87
|
+
const persistManifest = async (complete: boolean, report?: TileConversionReport) => {
|
|
88
|
+
await options.manifestStore.save({
|
|
89
|
+
version: TILE_RESOURCE_MANIFEST_VERSION,
|
|
90
|
+
complete,
|
|
91
|
+
resources: [...resources.values()].sort((left, right) =>
|
|
92
|
+
left.resourceId < right.resourceId ? -1 : left.resourceId > right.resourceId ? 1 : 0
|
|
93
|
+
),
|
|
94
|
+
...(report ? {report} : {})
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const writeResource = async (resource: TResource, signal?: AbortSignal): Promise<void> => {
|
|
99
|
+
if (closed) {
|
|
100
|
+
throw new TileConversionError('RESOURCE_SINK_CLOSED', 'Cannot write after sink finalization');
|
|
101
|
+
}
|
|
102
|
+
if (writeFailed) {
|
|
103
|
+
throw writeFailure;
|
|
104
|
+
}
|
|
105
|
+
const entry = createManifestEntry(options, resource);
|
|
106
|
+
const priorEntry = resources.get(entry.resourceId);
|
|
107
|
+
if (priorEntry) {
|
|
108
|
+
if (
|
|
109
|
+
priorEntry.byteLength !== entry.byteLength ||
|
|
110
|
+
priorEntry.fingerprint !== entry.fingerprint
|
|
111
|
+
) {
|
|
112
|
+
throw new TileConversionError(
|
|
113
|
+
'RESOURCE_RESUME_MISMATCH',
|
|
114
|
+
`Resource "${entry.resourceId}" changed since the saved manifest`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await options.sink.write(resource, signal);
|
|
121
|
+
resources.set(entry.resourceId, entry);
|
|
122
|
+
await persistManifest(false);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
write(resource, signal) {
|
|
127
|
+
const operation = writeChain.then(() => writeResource(resource, signal));
|
|
128
|
+
writeChain = operation.catch(error => {
|
|
129
|
+
writeFailed = true;
|
|
130
|
+
writeFailure = error;
|
|
131
|
+
});
|
|
132
|
+
return operation;
|
|
133
|
+
},
|
|
134
|
+
async finalize(report) {
|
|
135
|
+
await writeChain;
|
|
136
|
+
if (writeFailed) {
|
|
137
|
+
throw writeFailure;
|
|
138
|
+
}
|
|
139
|
+
if (closed) {
|
|
140
|
+
throw new TileConversionError('RESOURCE_SINK_CLOSED', 'Sink has already been finalized');
|
|
141
|
+
}
|
|
142
|
+
await options.sink.finalize(report);
|
|
143
|
+
await persistManifest(true, report);
|
|
144
|
+
closed = true;
|
|
145
|
+
},
|
|
146
|
+
async abort(reason) {
|
|
147
|
+
await writeChain;
|
|
148
|
+
if (closed) return;
|
|
149
|
+
closed = true;
|
|
150
|
+
resources.clear();
|
|
151
|
+
await persistManifest(false);
|
|
152
|
+
await options.sink.abort(reason);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Creates and validates the deterministic manifest entry for an output resource. */
|
|
158
|
+
function createManifestEntry<TResource>(
|
|
159
|
+
options: ManifestBackedTileConversionSinkOptions<TResource>,
|
|
160
|
+
resource: TResource
|
|
161
|
+
): TileResourceManifestEntry {
|
|
162
|
+
const resourceId = options.getResourceId(resource);
|
|
163
|
+
const byteLength = options.measureResourceBytes(resource);
|
|
164
|
+
const fingerprint = options.fingerprintResource(resource);
|
|
165
|
+
if (
|
|
166
|
+
typeof resourceId !== 'string' ||
|
|
167
|
+
resourceId.length === 0 ||
|
|
168
|
+
typeof fingerprint !== 'string' ||
|
|
169
|
+
fingerprint.length === 0 ||
|
|
170
|
+
!Number.isSafeInteger(byteLength) ||
|
|
171
|
+
byteLength < 0
|
|
172
|
+
) {
|
|
173
|
+
throw new TileConversionError(
|
|
174
|
+
'INVALID_RESOURCE_MANIFEST_ENTRY',
|
|
175
|
+
'Resource manifest entries require an ID, a fingerprint, and a non-negative safe byte length'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return {resourceId, byteLength, fingerprint};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Checks a loaded manifest before trusting it for restart decisions. */
|
|
182
|
+
function validateManifest(manifest: TileResourceManifest): void {
|
|
183
|
+
if (
|
|
184
|
+
typeof manifest !== 'object' ||
|
|
185
|
+
manifest === null ||
|
|
186
|
+
manifest.version !== TILE_RESOURCE_MANIFEST_VERSION ||
|
|
187
|
+
typeof manifest.complete !== 'boolean' ||
|
|
188
|
+
!Array.isArray(manifest.resources)
|
|
189
|
+
) {
|
|
190
|
+
throw new TileConversionError(
|
|
191
|
+
'UNSUPPORTED_RESOURCE_MANIFEST',
|
|
192
|
+
`Unsupported or malformed resource manifest version: ${String(manifest.version)}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const resourceIds = new Set<string>();
|
|
196
|
+
for (const entry of manifest.resources) {
|
|
197
|
+
if (
|
|
198
|
+
typeof entry?.resourceId !== 'string' ||
|
|
199
|
+
entry.resourceId.length === 0 ||
|
|
200
|
+
typeof entry?.fingerprint !== 'string' ||
|
|
201
|
+
entry.fingerprint.length === 0 ||
|
|
202
|
+
!Number.isSafeInteger(entry.byteLength) ||
|
|
203
|
+
entry.byteLength < 0 ||
|
|
204
|
+
resourceIds.has(entry.resourceId)
|
|
205
|
+
) {
|
|
206
|
+
throw new TileConversionError(
|
|
207
|
+
'INVALID_RESOURCE_MANIFEST',
|
|
208
|
+
'Resource manifest contains an invalid or duplicate entry'
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
resourceIds.add(entry.resourceId);
|
|
212
|
+
}
|
|
213
|
+
}
|