From 1528983d642a39d63afeeada7b92dd954011f227 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 15 Mar 2026 14:39:56 +0900 Subject: [PATCH 1/8] Update dts gen --- package-lock.json | 34 ++- package.json | 2 + src/core/plugins/CesiumIonAuthPlugin.d.ts | 12 - .../plugins/EnforceNonZeroErrorPlugin.d.ts | 1 - src/core/plugins/GoogleCloudAuthPlugin.d.ts | 11 - src/core/plugins/GoogleCloudAuthPlugin.js | 2 +- src/core/plugins/ImplicitTilingPlugin.d.ts | 1 - src/core/plugins/index.d.ts | 289 +++++++++++++++++- tsconfig.core-plugins.json | 23 ++ utils/gen-dts.js | 160 ++++++++++ 10 files changed, 502 insertions(+), 33 deletions(-) delete mode 100644 src/core/plugins/CesiumIonAuthPlugin.d.ts delete mode 100644 src/core/plugins/EnforceNonZeroErrorPlugin.d.ts delete mode 100644 src/core/plugins/GoogleCloudAuthPlugin.d.ts delete mode 100644 src/core/plugins/ImplicitTilingPlugin.d.ts create mode 100644 tsconfig.core-plugins.json create mode 100644 utils/gen-dts.js diff --git a/package-lock.json b/package-lock.json index fffc871ac..88ef90245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,8 @@ "leva": "^0.10.0", "lil-gui": "^0.21.0", "postprocessing": "^6.36.4", + "rollup": "^4.59.0", + "rollup-plugin-dts": "^6.4.0", "three": "^0.170.0", "typescript": "^5.6.0", "typescript-eslint": "^8.48.1", @@ -70,9 +72,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -7259,6 +7261,32 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-dts": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.4.0.tgz", + "integrity": "sha512-2i00A5UoPCoDecLEs13Eu105QegSGfrbp1sDeUj/54LKGmv6XFHDxWKC6Wsb4BobGUWYVCWWjmjAc8bXXbXH/Q==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.5.5", + "convert-source-map": "^2.0.0", + "magic-string": "^0.30.21" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.29.0" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0 || ^6.0" + } + }, "node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", diff --git a/package.json b/package.json index 95b22bff4..2711a4617 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,8 @@ "leva": "^0.10.0", "lil-gui": "^0.21.0", "postprocessing": "^6.36.4", + "rollup": "^4.59.0", + "rollup-plugin-dts": "^6.4.0", "three": "^0.170.0", "typescript": "^5.6.0", "typescript-eslint": "^8.48.1", diff --git a/src/core/plugins/CesiumIonAuthPlugin.d.ts b/src/core/plugins/CesiumIonAuthPlugin.d.ts deleted file mode 100644 index adaeb0e54..000000000 --- a/src/core/plugins/CesiumIonAuthPlugin.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TilesRendererBase } from '3d-tiles-renderer/core'; - -export class CesiumIonAuthPlugin { - - constructor( options : { - apiToken: string, - assetId?: string | null, - autoRefreshToken?: boolean, - assetTypeHandler?: ( type: string, tiles: TilesRendererBase, asset: object ) => void, - } ); - -} diff --git a/src/core/plugins/EnforceNonZeroErrorPlugin.d.ts b/src/core/plugins/EnforceNonZeroErrorPlugin.d.ts deleted file mode 100644 index 2fda59b1b..000000000 --- a/src/core/plugins/EnforceNonZeroErrorPlugin.d.ts +++ /dev/null @@ -1 +0,0 @@ -export class EnforceNonZeroErrorPlugin {} diff --git a/src/core/plugins/GoogleCloudAuthPlugin.d.ts b/src/core/plugins/GoogleCloudAuthPlugin.d.ts deleted file mode 100644 index 803f8bb4a..000000000 --- a/src/core/plugins/GoogleCloudAuthPlugin.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class GoogleCloudAuthPlugin { - - constructor( options: { - apiToken: string, - autoRefreshToken?: boolean, - logoUrl?: string, - useRecommendedSettings?: boolean; - sessionOptions?: null | { mapType: string, language: string, region: string, [key: string]: any }, - } ); - -} diff --git a/src/core/plugins/GoogleCloudAuthPlugin.js b/src/core/plugins/GoogleCloudAuthPlugin.js index 9a92999bb..4b38acd94 100644 --- a/src/core/plugins/GoogleCloudAuthPlugin.js +++ b/src/core/plugins/GoogleCloudAuthPlugin.js @@ -1,4 +1,4 @@ -import { GoogleCloudAuth } from '3d-tiles-renderer/core/plugins'; +import { GoogleCloudAuth } from './auth/GoogleCloudAuth.js'; import { GoogleAttributionsManager } from './GoogleAttributionsManager.js'; const TILES_3D_API = 'https://tile.googleapis.com/v1/3dtiles/root.json'; diff --git a/src/core/plugins/ImplicitTilingPlugin.d.ts b/src/core/plugins/ImplicitTilingPlugin.d.ts deleted file mode 100644 index 4ee219bf9..000000000 --- a/src/core/plugins/ImplicitTilingPlugin.d.ts +++ /dev/null @@ -1 +0,0 @@ -export class ImplicitTilingPlugin {} diff --git a/src/core/plugins/index.d.ts b/src/core/plugins/index.d.ts index 84c01d3f2..007996745 100644 --- a/src/core/plugins/index.d.ts +++ b/src/core/plugins/index.d.ts @@ -1,4 +1,285 @@ -export * from './CesiumIonAuthPlugin.js'; -export * from './GoogleCloudAuthPlugin.js'; -export * from './ImplicitTilingPlugin.js'; -export * from './EnforceNonZeroErrorPlugin.js'; +import { LoaderBase } from '3d-tiles-renderer/core'; + +/** + * @classdesc + * Authentication helper for Cesium Ion. Fetches and caches a bearer token from the + * Cesium Ion endpoint and injects it into outgoing requests. Supports optional + * automatic token refresh on 4xx responses. + */ +declare class CesiumIonAuth { + /** + * @param {Object} [options={}] + * @param {string} options.apiToken + * @param {boolean} [options.autoRefreshToken=false] + */ + constructor(options?: { + apiToken: string; + autoRefreshToken?: boolean; + }); + /** + * The Cesium Ion access token. + * @type {string} + */ + apiToken: string; + /** + * Whether to automatically refresh the token on 4xx errors. + * @type {boolean} + */ + autoRefreshToken: boolean; + /** + * The endpoint URL used to fetch the bearer token. + * @type {string|null} + */ + authURL: string | null; + fetch(url: any, options: any): Promise; + refreshToken(options: any): any; +} + +/** + * @callback AssetTypeHandlerCallback + * @param {string} type - The Cesium Ion asset type (e.g. `'TERRAIN'`, `'GLTF'`, `'CZML'`). + * @param {TilesRendererBase} tiles - The tiles renderer instance. + * @param {Object} asset - The full asset endpoint JSON response. + */ +/** + * @classdesc + * Plugin for authenticating requests to Cesium Ion. Handles token refresh, asset endpoint + * resolution, and attribution collection. Automatically registers a GoogleCloudAuthPlugin + * when the resolved asset is an external Google photorealistic tileset. + */ +declare class CesiumIonAuthPlugin { + /** + * @param {Object} options + * @param {string} options.apiToken + * @param {number|null} [options.assetId=null] + * @param {boolean} [options.autoRefreshToken=false] + * @param {boolean} [options.useRecommendedSettings=true] + * @param {AssetTypeHandlerCallback} [options.assetTypeHandler] + */ + constructor(options?: { + apiToken: string; + assetId?: number | null; + autoRefreshToken?: boolean; + useRecommendedSettings?: boolean; + assetTypeHandler?: AssetTypeHandlerCallback; + }); + set apiToken(v: string); + get apiToken(): string; + set autoRefreshToken(v: boolean); + get autoRefreshToken(): boolean; + name: string; + auth: CesiumIonAuth; + /** + * The Cesium Ion asset ID to load, or null if using an explicit root URL. + * @type {number|null} + */ + assetId: number | null; + /** + * Whether to apply recommended renderer settings for Cesium Ion assets. + * @type {boolean} + */ + useRecommendedSettings: boolean; + /** + * Callback invoked when the resolved Cesium Ion asset type is not `3DTILES`. + * @type {AssetTypeHandlerCallback} + */ + assetTypeHandler: AssetTypeHandlerCallback; + /** + * The TilesRenderer instance this plugin is registered with. + * @type {Object|null} + */ + tiles: any | null; + init(tiles: any): void; + loadRootTileset(): any; + preprocessURL(uri: any): any; + fetchData(uri: any, options: any): Promise; + getAttributions(target: any): void; +} +type AssetTypeHandlerCallback = (type: string, tiles: TilesRendererBase, asset: any) => any; + +/** + * @classdesc + * Authentication helper for Google Cloud Maps APIs. Manages session-token creation and + * renewal for both the Photorealistic 3D Tiles API and the 2D Map Tiles API, injecting + * the API key and session token into outgoing requests. + */ +declare class GoogleCloudAuth { + /** + * @param {Object} [options={}] + * @param {string} options.apiToken + * @param {Object|null} [options.sessionOptions=null] + * @param {boolean} [options.autoRefreshToken=false] + */ + constructor(options?: { + apiToken: string; + sessionOptions?: any | null; + autoRefreshToken?: boolean; + }); + get isMapTilesSession(): boolean; + /** + * The Google Cloud API key. + * @type {string} + */ + apiToken: string; + /** + * Whether to automatically refresh the session token on 4xx errors. + * @type {boolean} + */ + autoRefreshToken: boolean; + /** + * The endpoint URL used to create or refresh the session token. + * @type {string} + */ + authURL: string; + /** + * The current session token, or null if not yet established. + * @type {string|null} + */ + sessionToken: string | null; + /** + * Session options passed as the POST body when creating a Map Tiles session. + * @type {Object|null} + */ + sessionOptions: any | null; + fetch(url: any, options: any): Promise; + refreshToken(options: any): any; +} + +declare class GoogleAttributionsManager { + creditsCount: {}; + addAttributions(line: any): void; + removeAttributions(line: any): void; + toString(): string; +} + +/** + * @classdesc + * Plugin for authenticating requests to the Google Cloud Maps APIs, including the + * Photorealistic 3D Tiles and 2D Map Tiles APIs. Handles session-token management, + * per-tile attribution collection, and optional logo attribution. + */ +declare class GoogleCloudAuthPlugin { + /** + * @param {Object} options + * @param {string} options.apiToken + * @param {Object|null} [options.sessionOptions=null] + * @param {boolean} [options.autoRefreshToken=false] + * @param {string|null} [options.logoUrl=null] + * @param {boolean} [options.useRecommendedSettings=true] + */ + constructor({ apiToken, sessionOptions, autoRefreshToken, logoUrl, useRecommendedSettings, }: { + apiToken: string; + sessionOptions?: any | null; + autoRefreshToken?: boolean; + logoUrl?: string | null; + useRecommendedSettings?: boolean; + }); + name: string; + /** + * The Google Cloud API key. + * @type {string} + */ + apiToken: string; + /** + * Whether to apply recommended renderer settings for photorealistic tiles. + * @type {boolean} + */ + useRecommendedSettings: boolean; + /** + * URL of a logo image to include in attribution output, or null if not set. + * @type {string|null} + */ + logoUrl: string | null; + auth: GoogleCloudAuth; + /** + * The TilesRenderer instance this plugin is registered with. + * @type {Object|null} + */ + tiles: any | null; + init(tiles: any): void; + getAttributions(target: any): void; + dispose(): void; + fetchData(uri: any, options: any): Promise; +} + +/** + * @classdesc + * Plugin that adds support for 3D Tiles 1.1 implicit tiling. Intercepts tiles that carry + * an `implicitTiling` field and expands them by loading and parsing `.subtree` files, + * generating child tiles according to the implicit subdivision scheme. + */ +declare class ImplicitTilingPlugin { + name: string; + init(tiles: any): void; + tiles: any; + preprocessNode(tile: any, tilesetDir: any, parentTile: any): void; + parseTile(buffer: any, tile: any, extension: any): Promise; + preprocessURL(url: any, tile: any): any; + disposeTile(tile: any): void; +} + +/** + * @classdesc + * Plugin that ensures every tile has a non-zero geometric error. Tiles with a geometric + * error of zero are assigned a derived value based on the nearest ancestor with a non-zero + * error, halved once per level of depth below that ancestor. + */ +declare class EnforceNonZeroErrorPlugin { + name: string; + priority: number; + originalError: Map; + preprocessNode(tile: any): void; +} + +/** + * @classdesc + * Base loader for quantized-mesh terrain tiles. Parses the binary quantized-mesh format + * into structured vertex, index, edge, and extension data. Sets the required `Accept` + * header automatically. Subclasses should implement geometry construction from the + * parsed result. + * @augments LoaderBase + */ +declare class QuantizedMeshLoaderBase extends LoaderBase> { + constructor(...args: any[]); + loadAsync(...args: any[]): Promise; + parse(buffer: any): { + header: { + center: number[]; + minHeight: number; + maxHeight: number; + sphereCenter: number[]; + sphereRadius: number; + horizonOcclusionPoint: number[]; + }; + indices: any; + vertexData: { + u: Float32Array; + v: Float32Array; + height: Float32Array; + }; + edgeIndices: { + westIndices: any; + southIndices: any; + eastIndices: any; + northIndices: any; + }; + extensions: { + octvertexnormals: { + extensionId: number; + normals: Float32Array; + }; + watermask: { + extensionId: number; + mask: any; + size: number; + }; + metadata: { + extensionId: number; + json: any; + }; + }; + }; +} + +export { CesiumIonAuth, CesiumIonAuthPlugin, EnforceNonZeroErrorPlugin, GoogleCloudAuth, GoogleCloudAuthPlugin, ImplicitTilingPlugin, QuantizedMeshLoaderBase }; +export type { AssetTypeHandlerCallback }; diff --git a/tsconfig.core-plugins.json b/tsconfig.core-plugins.json new file mode 100644 index 000000000..be4e90378 --- /dev/null +++ b/tsconfig.core-plugins.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "checkJs": false, + "skipLibCheck": true, + "module": "esnext", + "moduleResolution": "bundler", + "rootDir": "src" + }, + "files": [ + "src/core/plugins/index.js", + "src/core/plugins/CesiumIonAuthPlugin.js", + "src/core/plugins/GoogleCloudAuthPlugin.js", + "src/core/plugins/ImplicitTilingPlugin.js", + "src/core/plugins/EnforceNonZeroErrorPlugin.js", + "src/core/plugins/auth/CesiumIonAuth.js", + "src/core/plugins/GoogleAttributionsManager.js", + "src/core/plugins/auth/GoogleCloudAuth.js", + "src/core/plugins/loaders/QuantizedMeshLoaderBase.js" + ] +} diff --git a/utils/gen-dts.js b/utils/gen-dts.js new file mode 100644 index 000000000..fc5c9fb26 --- /dev/null +++ b/utils/gen-dts.js @@ -0,0 +1,160 @@ +/** + * Generates src/core/plugins/index.d.ts from JSDoc-annotated JS source. + * + * Steps: + * 1. Delete stale .d.ts files so tsc generates fresh declarations + * 2. tsc emits per-file .d.ts into a temp directory + * 3. rollup-plugin-dts bundles them into a single declaration file + * 4. A transform strips any underscore-prefixed members from the output + */ + +import { execSync } from 'child_process'; +import { existsSync, mkdtempSync, readdirSync, rmSync, unlinkSync } from 'fs'; +import { tmpdir } from 'os'; +import { dirname, join, resolve } from 'path'; +import { rollup } from 'rollup'; +import dts from 'rollup-plugin-dts'; + +const ROOT = resolve( import.meta.dirname, '..' ); +const PLUGINS_DIR = join( ROOT, 'src/core/plugins' ); +const OUT_FILE = join( PLUGINS_DIR, 'index.d.ts' ); + +// Step 1: delete stale .d.ts files (recursively) so tsc generates fresh ones +function deleteDtsFiles( dir ) { + + for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { + + const full = join( dir, entry.name ); + if ( entry.isDirectory() ) { + + deleteDtsFiles( full ); + + } else if ( entry.name.endsWith( '.d.ts' ) ) { + + unlinkSync( full ); + + } + + } + +} + +deleteDtsFiles( PLUGINS_DIR ); + +// Step 2: emit .d.ts to a temp directory +const tmpDir = mkdtempSync( join( tmpdir(), 'dts-' ) ); +try { + + execSync( + `npx tsc -p tsconfig.core-plugins.json --declarationDir ${ tmpDir }`, + { cwd: ROOT, stdio: 'inherit' }, + ); + + // Step 3+4: bundle with rollup-dts and strip _ members + const entry = join( tmpDir, 'core/plugins/index.d.ts' ); + + const bundle = await rollup( { + input: entry, + plugins: [ + resolveDtsExtensions( tmpDir ), + dts(), + stripUnderscoreMembers(), + ], + external: [ /^3d-tiles-renderer/ ], + } ); + + await bundle.write( { + file: OUT_FILE, + format: 'es', + } ); + + await bundle.close(); + console.log( `Written: ${ OUT_FILE }` ); + +} finally { + + rmSync( tmpDir, { recursive: true, force: true } ); + +} + +/** + * Resolves .js imports inside the temp directory to their .d.ts counterparts. + */ +function resolveDtsExtensions( tmpDir ) { + + return { + name: 'resolve-dts-extensions', + resolveId( id, importer ) { + + if ( importer && importer.startsWith( tmpDir ) && id.endsWith( '.js' ) ) { + + const candidate = join( dirname( importer ), id.replace( /\.js$/, '.d.ts' ) ); + if ( existsSync( candidate ) ) return candidate; + + } + + return null; + + }, + }; + +} + +/** + * Rollup transform that removes underscore-prefixed class members from .d.ts output. + * Handles single-line and multiline (inline object type) member declarations. + */ +function stripUnderscoreMembers() { + + return { + name: 'strip-underscore-members', + renderChunk( code ) { + + // Match underscore-prefixed member declarations, including multiline ones + // where the type spans multiple lines (e.g. _foo: {\n bar: any;\n};) + // Strategy: track brace depth after matching a _ member opener. + const lines = code.split( '\n' ); + const out = []; + let skip = 0; // brace depth while skipping a multiline member + + for ( const line of lines ) { + + if ( skip > 0 ) { + + // count braces to know when the member body ends + for ( const ch of line ) { + + if ( ch === '{' ) skip ++; + else if ( ch === '}' ) skip --; + + } + + continue; + + } + + if ( /^\s+(private\s+)?_\w+[\s:(]/.test( line ) ) { + + // count any opening braces on this line to detect multiline type + for ( const ch of line ) { + + if ( ch === '{' ) skip ++; + else if ( ch === '}' ) skip --; + + } + + // if skip > 0 the body continues on subsequent lines; either way skip this line + continue; + + } + + out.push( line ); + + } + + return { code: out.join( '\n' ), map: null }; + + }, + }; + +} From 9ca5979d1493d4068e5f5effb11d6d891a6fb174 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 15 Mar 2026 15:37:00 +0900 Subject: [PATCH 2/8] Generate d.ts files --- src/core/plugins/CesiumIonAuthPlugin.js | 4 +++ src/core/plugins/auth/GoogleCloudAuth.js | 4 +-- src/core/plugins/index.d.ts | 24 +++++++++++--- utils/gen-dts.js | 41 ++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/core/plugins/CesiumIonAuthPlugin.js b/src/core/plugins/CesiumIonAuthPlugin.js index 65b07b7b3..f5d0e11f8 100644 --- a/src/core/plugins/CesiumIonAuthPlugin.js +++ b/src/core/plugins/CesiumIonAuthPlugin.js @@ -1,6 +1,10 @@ import { CesiumIonAuth } from './auth/CesiumIonAuth.js'; import { GoogleCloudAuthPlugin } from './GoogleCloudAuthPlugin.js'; +/** + * @typedef {import('3d-tiles-renderer/core').TilesRendererBase} TilesRendererBase + */ + /** * @callback AssetTypeHandlerCallback * @param {string} type - The Cesium Ion asset type (e.g. `'TERRAIN'`, `'GLTF'`, `'CZML'`). diff --git a/src/core/plugins/auth/GoogleCloudAuth.js b/src/core/plugins/auth/GoogleCloudAuth.js index cae79c921..01026a7a9 100644 --- a/src/core/plugins/auth/GoogleCloudAuth.js +++ b/src/core/plugins/auth/GoogleCloudAuth.js @@ -19,7 +19,7 @@ export class GoogleCloudAuth { /** * @param {Object} [options={}] * @param {string} options.apiToken - * @param {Object|null} [options.sessionOptions=null] + * @param {{ mapType?: string, language?: string, region?: string, [key: string]: any }|null} [options.sessionOptions=null] * @param {boolean} [options.autoRefreshToken=false] */ constructor( options = {} ) { @@ -47,7 +47,7 @@ export class GoogleCloudAuth { this.sessionToken = null; /** * Session options passed as the POST body when creating a Map Tiles session. - * @type {Object|null} + * @type {{ mapType?: string, language?: string, region?: string, [key: string]: any }|null} */ this.sessionOptions = sessionOptions; this._tokenRefreshPromise = null; diff --git a/src/core/plugins/index.d.ts b/src/core/plugins/index.d.ts index 007996745..298c9b32f 100644 --- a/src/core/plugins/index.d.ts +++ b/src/core/plugins/index.d.ts @@ -1,4 +1,4 @@ -import { LoaderBase } from '3d-tiles-renderer/core'; +import { TilesRendererBase, LoaderBase } from '3d-tiles-renderer/core'; /** * @classdesc @@ -35,6 +35,9 @@ declare class CesiumIonAuth { refreshToken(options: any): any; } +/** + * @typedef {import('3d-tiles-renderer/core').TilesRendererBase} TilesRendererBase + */ /** * @callback AssetTypeHandlerCallback * @param {string} type - The Cesium Ion asset type (e.g. `'TERRAIN'`, `'GLTF'`, `'CZML'`). @@ -95,6 +98,7 @@ declare class CesiumIonAuthPlugin { fetchData(uri: any, options: any): Promise; getAttributions(target: any): void; } + type AssetTypeHandlerCallback = (type: string, tiles: TilesRendererBase, asset: any) => any; /** @@ -107,12 +111,17 @@ declare class GoogleCloudAuth { /** * @param {Object} [options={}] * @param {string} options.apiToken - * @param {Object|null} [options.sessionOptions=null] + * @param {{ mapType?: string, language?: string, region?: string, [key: string]: any }|null} [options.sessionOptions=null] * @param {boolean} [options.autoRefreshToken=false] */ constructor(options?: { apiToken: string; - sessionOptions?: any | null; + sessionOptions?: { + mapType?: string; + language?: string; + region?: string; + [key: string]: any; + } | null; autoRefreshToken?: boolean; }); get isMapTilesSession(): boolean; @@ -138,9 +147,14 @@ declare class GoogleCloudAuth { sessionToken: string | null; /** * Session options passed as the POST body when creating a Map Tiles session. - * @type {Object|null} + * @type {{ mapType?: string, language?: string, region?: string, [key: string]: any }|null} */ - sessionOptions: any | null; + sessionOptions: { + mapType?: string; + language?: string; + region?: string; + [key: string]: any; + } | null; fetch(url: any, options: any): Promise; refreshToken(options: any): any; } diff --git a/utils/gen-dts.js b/utils/gen-dts.js index fc5c9fb26..51ce87912 100644 --- a/utils/gen-dts.js +++ b/utils/gen-dts.js @@ -4,12 +4,13 @@ * Steps: * 1. Delete stale .d.ts files so tsc generates fresh declarations * 2. tsc emits per-file .d.ts into a temp directory - * 3. rollup-plugin-dts bundles them into a single declaration file - * 4. A transform strips any underscore-prefixed members from the output + * 3. Type alias imports are rewritten to avoid a rollup-plugin-dts bug with invalid identifiers + * 4. rollup-plugin-dts bundles them into a single declaration file + * 5. A transform strips any underscore-prefixed members from the output */ import { execSync } from 'child_process'; -import { existsSync, mkdtempSync, readdirSync, rmSync, unlinkSync } from 'fs'; +import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs'; import { tmpdir } from 'os'; import { dirname, join, resolve } from 'path'; import { rollup } from 'rollup'; @@ -50,6 +51,9 @@ try { { cwd: ROOT, stdio: 'inherit' }, ); + // Fix any invalid namespace imports tsc emitted (e.g. `import * as 3d_...`) + fixTypeAliasImportsInDir( tmpDir ); + // Step 3+4: bundle with rollup-dts and strip _ members const entry = join( tmpDir, 'core/plugins/index.d.ts' ); @@ -100,6 +104,37 @@ function resolveDtsExtensions( tmpDir ) { } +/** + * Walks a directory and rewrites .d.ts files, converting tsc's `export type X = import("pkg").X` + * aliases to `import type { X } from "pkg"`. This avoids rollup-plugin-dts generating namespace + * imports for packages whose names are invalid JS identifiers (e.g. '3d-tiles-renderer/core'). + */ +function fixTypeAliasImportsInDir( dir ) { + + for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { + + const full = join( dir, entry.name ); + if ( entry.isDirectory() ) { + + fixTypeAliasImportsInDir( full ); + + } else if ( entry.name.endsWith( '.d.ts' ) ) { + + const code = readFileSync( full, 'utf8' ); + const result = code.replace( + /^export type (\w+) = import\(["']([^"']+)["']\)\.(\w+);$/gm, + ( _, local, pkg, exported ) => local === exported + ? `import type { ${ local } } from "${ pkg }";` + : `import type { ${ exported } as ${ local } } from "${ pkg }";`, + ); + if ( result !== code ) writeFileSync( full, result, 'utf8' ); + + } + + } + +} + /** * Rollup transform that removes underscore-prefixed class members from .d.ts output. * Handles single-line and multiline (inline object type) member declarations. From 0896cd698c67da23a4d297e256a32d72c8709b33 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 15 Mar 2026 15:45:03 +0900 Subject: [PATCH 3/8] Add jsdoc linting --- eslint.config.js | 33 ++++ package-lock.json | 246 +++++++++++++++++++++++++++++- package.json | 1 + src/core/plugins/SUBTREELoader.js | 16 +- 4 files changed, 285 insertions(+), 11 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 101886499..c7aeb10ad 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,7 @@ import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; import globals from 'globals'; import mdcs from 'eslint-config-mdcs'; +import jsdoc from 'eslint-plugin-jsdoc'; export default [ // files to ignore @@ -99,6 +100,38 @@ export default [ }, }, + // jsdoc + { + name: 'jsdoc rules', + files: [ '**/*.js', '**/*.jsx' ], + plugins: { + jsdoc, + }, + settings: { + jsdoc: { + preferredTypes: { + Any: 'any', + Boolean: 'boolean', + Number: 'number', + object: 'Object', + String: 'string', + }, + tagNamePreference: { + returns: 'return', + extends: 'augments', + }, + }, + }, + rules: { + 'jsdoc/check-types': 'error', + 'jsdoc/require-param-type': 'error', + 'jsdoc/require-returns-type': 'error', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-returns-description': 'off', + }, + }, + // vitest { name: 'vitest rules', diff --git a/package-lock.json b/package-lock.json index fffc871ac..436ba0d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "concurrently": "^6.2.1", "eslint": "^9.0.0", "eslint-config-mdcs": "^5.0.0", + "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^16.5.0", @@ -790,6 +791,33 @@ "node": ">=20.19.0" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.84.0.tgz", + "integrity": "sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.54.0", + "comment-parser": "1.4.5", + "esquery": "^1.7.0", + "jsdoc-type-pratt-parser": "~7.1.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@es-joy/resolve.exports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", + "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -2775,6 +2803,19 @@ "win32" ] }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@spz-loader/core": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@spz-loader/core/-/core-0.3.0.tgz", @@ -3458,9 +3499,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3523,6 +3564,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4089,6 +4140,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comment-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz", + "integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4734,6 +4795,79 @@ "dev": true, "license": "MIT" }, + "node_modules/eslint-plugin-jsdoc": { + "version": "62.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.8.0.tgz", + "integrity": "sha512-hu3r9/6JBmPG6wTcqtYzgZAnjEG2eqRUATfkFscokESg1VDxZM21ZaMire0KjeMwfj+SXvgB4Rvh5LBuesj92w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.84.0", + "@es-joy/resolve.exports": "1.2.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.5", + "debug": "^4.4.3", + "escape-string-regexp": "^4.0.0", + "espree": "^11.1.0", + "esquery": "^1.7.0", + "html-entities": "^2.6.0", + "object-deep-merge": "^2.0.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "to-valid-identifier": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -5458,6 +5592,23 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6078,6 +6229,16 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.1.tgz", + "integrity": "sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/jsdoc/node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -6565,6 +6726,13 @@ "node": ">=0.10.0" } }, + "node_modules/object-deep-merge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", + "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", + "dev": true, + "license": "MIT" + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -6762,6 +6930,23 @@ "node": ">=6" } }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7186,6 +7371,19 @@ "lodash": "^4.17.21" } }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -7548,6 +7746,31 @@ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -7889,6 +8112,23 @@ "node": ">=14.0.0" } }, + "node_modules/to-valid-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/base62": "^1.0.0", + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/topojson-client": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", diff --git a/package.json b/package.json index 95b22bff4..7049a4207 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "concurrently": "^6.2.1", "eslint": "^9.0.0", "eslint-config-mdcs": "^5.0.0", + "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^5.0.0", "globals": "^16.5.0", diff --git a/src/core/plugins/SUBTREELoader.js b/src/core/plugins/SUBTREELoader.js index a7213ccfe..067fa93e5 100644 --- a/src/core/plugins/SUBTREELoader.js +++ b/src/core/plugins/SUBTREELoader.js @@ -84,7 +84,7 @@ export class SUBTREELoader extends LoaderBase { /** * A helper object for storing the two parts of the subtree binary * - * @typedef {object} Subtree + * @typedef {Object} Subtree * @property {number} version * @property {JSON} subtreeJson * @property {ArrayBuffer} subtreeByte @@ -93,7 +93,7 @@ export class SUBTREELoader extends LoaderBase { /** * - * @param buffer + * @param {ArrayBuffer} buffer * @return {Subtree} */ parseBuffer( buffer ) { @@ -272,7 +272,7 @@ export class SUBTREELoader extends LoaderBase { *

* @param {BufferHeader[]} bufferHeaders The preprocessed buffer headers * @param {ArrayBuffer} internalBuffer The binary chunk of the subtree file - * @returns {object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. + * @returns {Object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. * @private */ async requestActiveBuffers( bufferHeaders, internalBuffer ) { @@ -341,8 +341,8 @@ export class SUBTREELoader extends LoaderBase { * extract a subarray from one of the active buffers. * * @param {BufferViewHeader[]} bufferViewHeaders - * @param {object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. - * @returns {object} A dictionary of buffer view index to a Uint8Array of its contents. + * @param {Object} buffersU8 A dictionary of buffer index to a Uint8Array of its contents. + * @returns {Object} A dictionary of buffer view index to a Uint8Array of its contents. * @private */ parseActiveBufferViews( bufferViewHeaders, buffersU8 ) { @@ -375,7 +375,7 @@ export class SUBTREELoader extends LoaderBase { * Buffers are assumed inactive until explicitly marked active. This is used * to avoid fetching unneeded buffers. * - * @typedef {object} BufferHeader + * @typedef {Object} BufferHeader * @property {boolean} isActive Whether this buffer is currently used. * @property {string} [uri] The URI of the buffer (external buffers only) * @property {number} byteLength The byte length of the buffer, including any padding contained within. @@ -407,7 +407,7 @@ export class SUBTREELoader extends LoaderBase { * A buffer view header is the JSON header from the subtree JSON chunk plus * the isActive flag and a reference to the header for the underlying buffer. * - * @typedef {object} BufferViewHeader + * @typedef {Object} BufferViewHeader * @property {BufferHeader} bufferHeader A reference to the header for the underlying buffer * @property {boolean} isActive Whether this bufferView is currently used. * @property {number} buffer The index of the underlying buffer. @@ -489,7 +489,7 @@ export class SUBTREELoader extends LoaderBase { * @param {Object} availabilityJson A JSON object representing the availability. * @param {Object} bufferViewsU8 A dictionary of buffer view index to its Uint8Array contents. * @param {number} lengthBits The length of the availability bitstream in bits. - * @returns {object} + * @returns {Object} * @private */ parseAvailabilityBitstream( From 8a9d216dcd30747ad917649b6152aaaf27ea9a2e Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 15 Mar 2026 16:36:38 +0900 Subject: [PATCH 4/8] Remove old d.ts --- src/core/renderer/constants.d.ts | 3 - src/core/renderer/loaders/B3DMLoaderBase.d.ts | 17 --- src/core/renderer/loaders/CMPTLoaderBase.d.ts | 21 --- src/core/renderer/loaders/I3DMLoaderBase.d.ts | 17 --- src/core/renderer/loaders/LoaderBase.d.ts | 11 -- src/core/renderer/loaders/PNTSLoaderBase.d.ts | 16 -- src/core/renderer/tiles/Tile.d.ts | 140 ------------------ .../renderer/tiles/TilesRendererBase.d.ts | 86 ----------- src/core/renderer/tiles/Tileset.d.ts | 66 --------- src/core/renderer/utilities/BatchTable.d.ts | 26 ---- src/core/renderer/utilities/FeatureTable.d.ts | 33 ----- src/core/renderer/utilities/LRUCache.d.ts | 14 -- .../renderer/utilities/PriorityQueue.d.ts | 17 --- 13 files changed, 467 deletions(-) delete mode 100644 src/core/renderer/constants.d.ts delete mode 100644 src/core/renderer/loaders/B3DMLoaderBase.d.ts delete mode 100644 src/core/renderer/loaders/CMPTLoaderBase.d.ts delete mode 100644 src/core/renderer/loaders/I3DMLoaderBase.d.ts delete mode 100644 src/core/renderer/loaders/LoaderBase.d.ts delete mode 100644 src/core/renderer/loaders/PNTSLoaderBase.d.ts delete mode 100644 src/core/renderer/tiles/Tile.d.ts delete mode 100644 src/core/renderer/tiles/TilesRendererBase.d.ts delete mode 100644 src/core/renderer/tiles/Tileset.d.ts delete mode 100644 src/core/renderer/utilities/BatchTable.d.ts delete mode 100644 src/core/renderer/utilities/FeatureTable.d.ts delete mode 100644 src/core/renderer/utilities/LRUCache.d.ts delete mode 100644 src/core/renderer/utilities/PriorityQueue.d.ts diff --git a/src/core/renderer/constants.d.ts b/src/core/renderer/constants.d.ts deleted file mode 100644 index f9b5c144b..000000000 --- a/src/core/renderer/constants.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const WGS84_RADIUS: number; -export const WGS84_FLATTENING: number; -export const WGS84_HEIGHT: number; diff --git a/src/core/renderer/loaders/B3DMLoaderBase.d.ts b/src/core/renderer/loaders/B3DMLoaderBase.d.ts deleted file mode 100644 index 62880bacb..000000000 --- a/src/core/renderer/loaders/B3DMLoaderBase.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { BatchTable } from '../utilities/BatchTable.js'; -import { FeatureTable } from '../utilities/FeatureTable.js'; -import { LoaderBase } from './LoaderBase.js'; - -export interface B3DMBaseResult { - - version : string; - featureTable: FeatureTable; - batchTable : BatchTable; - glbBytes : Uint8Array; - -} - -export class B3DMLoaderBase - extends LoaderBase { - -} diff --git a/src/core/renderer/loaders/CMPTLoaderBase.d.ts b/src/core/renderer/loaders/CMPTLoaderBase.d.ts deleted file mode 100644 index 525a1c4cd..000000000 --- a/src/core/renderer/loaders/CMPTLoaderBase.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { LoaderBase } from './LoaderBase.js'; - -interface TileInfo { - - type : string; - buffer : Uint8Array; - version : string; - -} - -export interface CMPTBaseResult { - - version : string; - tiles : Array< TileInfo >; - -} - -export class CMPTLoaderBase - extends LoaderBase { - -} diff --git a/src/core/renderer/loaders/I3DMLoaderBase.d.ts b/src/core/renderer/loaders/I3DMLoaderBase.d.ts deleted file mode 100644 index 7ed313ec6..000000000 --- a/src/core/renderer/loaders/I3DMLoaderBase.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { BatchTable } from '../utilities/BatchTable.js'; -import { FeatureTable } from '../utilities/FeatureTable.js'; -import { LoaderBase } from './LoaderBase.js'; - -export interface I3DMBaseResult { - - version : string; - featureTable: FeatureTable; - batchTable : BatchTable; - glbBytes : Uint8Array; - -} - -export class I3DMLoaderBase - extends LoaderBase { - -} diff --git a/src/core/renderer/loaders/LoaderBase.d.ts b/src/core/renderer/loaders/LoaderBase.d.ts deleted file mode 100644 index 5e73d3540..000000000 --- a/src/core/renderer/loaders/LoaderBase.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class LoaderBase> { - - fetchOptions: any; - workingPath: string; - /** @deprecated in favor of `loadAsync` */ - load( url: string ): Promise< Result >; - loadAsync( url: string ): Promise< Result >; - resolveExternalURL( url: string ): string; - parse( buffer: ArrayBuffer ): ParseResult; - -} diff --git a/src/core/renderer/loaders/PNTSLoaderBase.d.ts b/src/core/renderer/loaders/PNTSLoaderBase.d.ts deleted file mode 100644 index 25b5a002e..000000000 --- a/src/core/renderer/loaders/PNTSLoaderBase.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BatchTable } from '../utilities/BatchTable.js'; -import { FeatureTable } from '../utilities/FeatureTable.js'; -import { LoaderBase } from './LoaderBase.js'; - -export interface PNTSBaseResult { - - version : string; - featureTable: FeatureTable; - batchTable : BatchTable; - -} - -export class PNTSLoaderBase - extends LoaderBase { - -} diff --git a/src/core/renderer/tiles/Tile.d.ts b/src/core/renderer/tiles/Tile.d.ts deleted file mode 100644 index 21e338596..000000000 --- a/src/core/renderer/tiles/Tile.d.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Internal implementation details for tile management - */ -export interface TileInternalData { - hasContent: boolean; - hasRenderableContent: boolean; - hasUnrenderableContent: boolean; - loadingState: number; - basePath: string; - depth: number; - depthFromRenderedParent: number; - /** - * Whether this tile was synthetically based on loaded parent tile state. - */ - isVirtual: boolean; - /** - * The number of virtual children appended to this tile's children array by plugins. - */ - virtualChildCount: number; -} - -/** - * Traversal state data updated during each frame's tile traversal - */ -export interface TileTraversalData { - /** - * How far this tile's bounds are from the nearest active camera. - * Expected to be filled in during calculateError implementations. - */ - distanceFromCamera: number; - /** - * The screen space error for this tile - */ - error: number; - /** - * Whether or not the tile was within the frustum on the last update run - */ - inFrustum: boolean; - /** - * Whether this tile is a leaf node in the used tree - */ - isLeaf: boolean; - /** - * Whether or not the tile was visited during the last update run - */ - used: boolean; - /** - * Whether or not the tile was used in the previous frame - */ - usedLastFrame: boolean; - /** - * This tile is currently visible if: - * 1: Tile content is loaded - * 2: Tile is within a camera frustum - * 3: Tile meets the SSE requirements - */ - visible: boolean; -} - -/** - * A 3D Tiles tile with both spec fields (from tileset JSON) and renderer-managed state. - * - * See spec for full schema: https://github.com/CesiumGS/3d-tiles/blob/master/specification/schema/tile.schema.json - */ -export interface Tile { - - boundingVolume: { - - /** - * An array of 12 numbers that define an oriented bounding box. The first three elements define the x, y, and z - * values for the center of the box. The next three elements (with indices 3, 4, and 5) define the x axis - * direction and half-length. The next three elements (indices 6, 7, and 8) define the y axis direction and - * half-length. The last three elements (indices 9, 10, and 11) define the z axis direction and half-length. - */ - box?: number[]; - - /** - * An array of four numbers that define a bounding sphere. The first three elements define the x, y, and z - * values for the center of the sphere. The last element (with index 3) defines the radius in meters. - */ - sphere?: number[]; - - }; - - /** - * The error, in meters, introduced if this tileset is not rendered. At runtime, the geometric error is used to compute screen space error (SSE), i.e., the error measured in pixels. - */ - geometricError: number; - - parent: Tile | null; - - children?: Tile[]; - - content?: { - - uri: string; - - /** - * Dictionary object with content specific extension objects. - */ - extensions?: Record; - - extras?: Record; - - // Non standard, noted here as it exists in the code in this package to support old pre-1.0 tilesets - url?: string; - - }; - - // An object that describes the implicit subdivision of this tile. - implicitTiling?: { - // A string describing the subdivision scheme used within the tileset. - subdivisionScheme: 'QUADTREE' | 'OCTREE'; - subtreeLevels: number; - availableLevels: number; - // An object describing the location of subtree files. - subtrees: { - // A template URI pointing to subtree files - uri: string; - } - }; - - /** - * Dictionary object with tile specific extension objects. - */ - extensions?: Record; - - extras?: Record; - - refine?: 'REPLACE' | 'ADD'; - - transform?: number[]; - - // Internal implementation details for tile management - internal: TileInternalData; - - // Traversal state data updated during each frame's tile traversal - traversal: TileTraversalData; - -} diff --git a/src/core/renderer/tiles/TilesRendererBase.d.ts b/src/core/renderer/tiles/TilesRendererBase.d.ts deleted file mode 100644 index 2cc6be490..000000000 --- a/src/core/renderer/tiles/TilesRendererBase.d.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { LRUCache } from '../utilities/LRUCache.js'; -import { PriorityQueue } from '../utilities/PriorityQueue.js'; -import { Tile } from './Tile.js'; -import { Tileset } from './Tileset.js'; - -// Events dispatched by TilesRendererBase, available across all renderer implementations. -export interface TilesRendererBaseEventMap { - 'needs-update': {}; - 'load-content': {}; - 'load-tileset': { tileset : Tileset, /* @deprecated Use tileset instead */ tileSet? : Tileset, url : string }; - /* @deprecated Use 'load-tileset' instead */ - 'load-tile-set': { tileset : Tileset, /* @deprecated Use tileset instead */ tileSet? : Tileset, url : string }; - 'load-root-tileset': { tileset : Tileset, url : string }; - 'tiles-load-start': {}; - 'tiles-load-end': {}; - 'tile-download-start': { tile : Tile, uri : string }; - 'load-model': { scene : TScene, tile : Tile, url : string }; - 'dispose-model': { scene : TScene, tile : Tile }; - 'tile-visibility-change': { scene : TScene, tile : Tile, visible : boolean }; - 'update-before': {}; - 'update-after': {}; - 'load-error': { tile : Tile | null, error : Error, url : string | URL }; -} - -export class TilesRendererBase { - - readonly rootTileset : Tileset | null; - /** @deprecated Use rootTileset instead */ - readonly rootTileSet : Tileset | null; - readonly root : Tile | null; - readonly visibleTiles : Set; - readonly activeTiles : Set; - - errorTarget : number; - errorThreshold : number; - displayActiveTiles : boolean; - maxDepth : number; - loadSiblings : boolean; - optimizedLoadStrategy : boolean; - maxTilesProcessed : number; - - readonly loadProgress : number; - - fetchOptions : RequestInit; - - lruCache : LRUCache; - parseQueue : PriorityQueue; - downloadQueue : PriorityQueue; - processNodeQueue: PriorityQueue; - - constructor( url?: string ); - update() : void; - registerPlugin( plugin: object ) : void; - unregisterPlugin( plugin: object | string ) : boolean; - getPluginByName( plugin: object | string ) : object; - traverse( - beforeCb : ( ( tile : object, parent : object, depth : number ) => boolean ) | null, - afterCb : ( ( tile : object, parent : object, depth : number ) => boolean ) | null - ) : void; - getAttributions( target? : Array<{ type: string, value: any }> ) : Array<{ type: string, value: any }>; - - addEventListener( - name : T, - callback : ( event : TEventMap[ T ] & { type : T } ) => void - ) : void; - addEventListener( name : string, callback : ( event : any ) => void ) : void; - - removeEventListener( - name : T, - callback : ( event : TEventMap[ T ] & { type : T } ) => void - ) : void; - removeEventListener( name : string, callback : ( event : any ) => void ) : void; - - hasEventListener( - name : T, - callback : ( event : TEventMap[ T ] & { type : T } ) => void - ) : boolean; - hasEventListener( name : string, callback : ( event : any ) => void ) : boolean; - - dispatchEvent( event : TEventMap[ T ] & { type : T } ) : void; - dispatchEvent( event : { type : string } ) : void; - - dispose() : void; - resetFailedTiles() : void; - -} diff --git a/src/core/renderer/tiles/Tileset.d.ts b/src/core/renderer/tiles/Tileset.d.ts deleted file mode 100644 index 46049c287..000000000 --- a/src/core/renderer/tiles/Tileset.d.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Tile } from './Tile.js'; - -/** - * A 3d-tiles tileset. - * - * Schema, see: https://github.com/CesiumGS/3d-tiles/blob/main/specification/schema/tileset.schema.json - */ -export interface Tileset { - - /** - * Metadata about the entire tileset. - */ - asset: { - - /** - * 3d-tiles version - */ - version: string, - - /** - * Application specific version - */ - tilesetVersion?: string, - - /** - * Dictionary object with extension-specific objects. - */ - extensions? : Record, - - }; - - /** - * The error, in meters, introduced if this tileset is not rendered. At runtime, the geometric error is used to compute screen space error (SSE), i.e., the error measured in pixels. - */ - geometricError: number; - - /** - * The root tile. - */ - root: Tile; - - // optional properties - - /** - * Names of 3D Tiles extensions used somewhere in this tileset. - */ - extensionsUsed?: string[]; - - /** - * Names of 3D Tiles extensions required to properly load this tileset. - */ - extensionsRequired?: string[]; - - /** - * A dictionary object of metadata about per-feature properties. - */ - properties?: Record; - - /** - * Dictionary object with extension-specific objects. - */ - extensions? : Record; - - extras? : Record; - -} diff --git a/src/core/renderer/utilities/BatchTable.d.ts b/src/core/renderer/utilities/BatchTable.d.ts deleted file mode 100644 index 0798e9881..000000000 --- a/src/core/renderer/utilities/BatchTable.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { FeatureTable } from './FeatureTable.js'; - -export class BatchTable extends FeatureTable { - - count : number; - - constructor( - buffer : ArrayBuffer, - count : number, - start : number, - headerLength : number, - binLength : number - ); - - getKeys() : Array< string >; - - getDataFromId( - id: number, - target?: object - ) : object; - - getPropertyArray( - key: string, - ) : number | string | ArrayBufferView; - -} diff --git a/src/core/renderer/utilities/FeatureTable.d.ts b/src/core/renderer/utilities/FeatureTable.d.ts deleted file mode 100644 index d9c8eb2e2..000000000 --- a/src/core/renderer/utilities/FeatureTable.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -interface FeatureTableHeader { - - extensions?: object; - extras?: any; - -} - -export class FeatureTable { - - buffer : ArrayBuffer; - binOffset : number; - binLength : number; - header : FeatureTableHeader; - - constructor( - buffer : ArrayBuffer, - start : number, - headerLength : number, - binLength : number - ); - - getKeys() : Array< string >; - - getData( - key : string, - count : number, - defaultComponentType? : string | null, - defaultType? : string | null - ) : number | string | ArrayBufferView; - - getBuffer( byteOffset : number, byteLength : number ) : ArrayBuffer; - -} diff --git a/src/core/renderer/utilities/LRUCache.d.ts b/src/core/renderer/utilities/LRUCache.d.ts deleted file mode 100644 index a54968185..000000000 --- a/src/core/renderer/utilities/LRUCache.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class LRUCache { - - minSize: number; - maxSize: number; - minBytesSize: number; - maxBytesSize: number; - unloadPercent: number; - autoMarkUnused: boolean; - - unloadPriorityCallback: ( item: any ) => number; - - isUsed( item: any ): boolean; - -} diff --git a/src/core/renderer/utilities/PriorityQueue.d.ts b/src/core/renderer/utilities/PriorityQueue.d.ts deleted file mode 100644 index 5ebab4f4f..000000000 --- a/src/core/renderer/utilities/PriorityQueue.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class PriorityQueue { - - maxJobs : number; - autoUpdate : boolean; - priorityCallback : ( itemA : any, itemB : any ) => number; - - schedulingCallback : ( func : () => void ) => void; - - sort() : void; - add( item : any, callback : ( item : any ) => any ) : Promise< any >; - remove( item : any ) : void; - removeByFilter( filter : ( item : any ) => boolean ) : void; - - tryRunJobs() : void; - scheduleJobRun() : void; - -} From 1ffe0360b6cb6de04d1c4587b210d1e99eff9065 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Sun, 15 Mar 2026 18:23:29 +0900 Subject: [PATCH 5/8] Updates --- eslint.config.js | 1 + src/core/plugins/index.d.ts | 4 +- src/core/renderer/index.d.ts | 1096 +++++++++++++++++++++++++++++++++- tsconfig.core-plugins.json | 23 - tsconfig.dts.json | 15 + utils/gen-dts.js | 437 ++++++++++++-- 6 files changed, 1489 insertions(+), 87 deletions(-) delete mode 100644 tsconfig.core-plugins.json create mode 100644 tsconfig.dts.json diff --git a/eslint.config.js b/eslint.config.js index c7aeb10ad..40e01b666 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,6 +14,7 @@ export default [ ignores: [ '**/node_modules/**', '**/build/**', + '**/*.d.ts', ], }, diff --git a/src/core/plugins/index.d.ts b/src/core/plugins/index.d.ts index 298c9b32f..6820d0ee1 100644 --- a/src/core/plugins/index.d.ts +++ b/src/core/plugins/index.d.ts @@ -253,9 +253,9 @@ declare class EnforceNonZeroErrorPlugin { * parsed result. * @augments LoaderBase */ -declare class QuantizedMeshLoaderBase extends LoaderBase> { +declare class QuantizedMeshLoaderBase extends LoaderBase { constructor(...args: any[]); - loadAsync(...args: any[]): Promise; + loadAsync(...args: any[]): Promise; parse(buffer: any): { header: { center: number[]; diff --git a/src/core/renderer/index.d.ts b/src/core/renderer/index.d.ts index 716ae3b8e..1144fcbb3 100644 --- a/src/core/renderer/index.d.ts +++ b/src/core/renderer/index.d.ts @@ -1,15 +1,1081 @@ -// common -export * from './tiles/TilesRendererBase.js'; -export { Tile, TileInternalData, TileTraversalData } from './tiles/Tile.js'; -export { Tileset } from './tiles/Tileset.js'; -export * from './loaders/B3DMLoaderBase.js'; -export * from './loaders/I3DMLoaderBase.js'; -export * from './loaders/PNTSLoaderBase.js'; -export * from './loaders/CMPTLoaderBase.js'; -export * from './loaders/LoaderBase.js'; -export * from './constants.js'; - -export { LRUCache } from './utilities/LRUCache.js'; -export { PriorityQueue } from './utilities/PriorityQueue.js'; -export { BatchTable } from './utilities/BatchTable.js'; -export { FeatureTable } from './utilities/FeatureTable.js'; +type UnloadPriorityCallback = (a: any, b: any) => number; +type RemoveCallback = (item: any) => any; +/** + * @callback UnloadPriorityCallback + * @param {any} a + * @param {any} b + * @returns {number} + */ +/** + * @callback RemoveCallback + * @param {any} item + */ +/** + * Least-recently-used cache for managing tile content lifecycle. Tracks which items + * are in use each frame and evicts unused items when the cache exceeds its size limits. + */ +declare class LRUCache { + set unloadPriorityCallback(cb: UnloadPriorityCallback | null); + /** + * Comparator used to determine eviction order. Items that sort last are evicted first. + * Defaults to `null` (eviction order is by last-used time). + * @type {UnloadPriorityCallback|null} + */ + get unloadPriorityCallback(): UnloadPriorityCallback | null; + /** + * Minimum number of items to keep in the cache after eviction. + * @type {number} + */ + minSize: number; + /** + * Maximum number of items before eviction is triggered. + * @type {number} + */ + maxSize: number; + /** + * Minimum total bytes to retain after eviction. + * @type {number} + */ + minBytesSize: number; + /** + * Maximum total bytes before eviction is triggered. + * @type {number} + */ + maxBytesSize: number; + /** + * Fraction of excess items/bytes to unload per eviction pass. + * @type {number} + */ + unloadPercent: number; + /** + * If true, items are automatically marked as unused at the start of each eviction pass. + * @type {boolean} + */ + autoMarkUnused: boolean; + itemSet: Map; + itemList: any[]; + usedSet: Set; + callbacks: Map; + unloadingHandle: number; + cachedBytes: number; + bytesMap: Map; + loadedSet: Set; + defaultPriorityCallback: (item: any) => any; + /** + * Returns whether the cache has reached its maximum item count or byte size. + * @returns {boolean} + */ + isFull(): boolean; + /** + * Returns the byte size registered for the given item, or 0 if not tracked. + * @param {any} item + * @returns {number} + */ + getMemoryUsage(item: any): number; + /** + * Sets the byte size for the given item, updating the total `cachedBytes` count. + * @param {any} item + * @param {number} bytes + */ + setMemoryUsage(item: any, bytes: number): void; + /** + * Adds an item to the cache. Returns false if the item already exists or the cache is full. + * @param {any} item + * @param {RemoveCallback} removeCb - Called with the item when it is evicted + * @returns {boolean} + */ + add(item: any, removeCb: RemoveCallback): boolean; + /** + * Returns whether the given item is in the cache. + * @param {any} item + * @returns {boolean} + */ + has(item: any): boolean; + /** + * Removes an item from the cache immediately, invoking its removal callback. + * Returns false if the item was not in the cache. + * @param {any} item + * @returns {boolean} + */ + remove(item: any): boolean; + /** + * Marks whether an item has finished loading. Unloaded items may be evicted early + * when the cache is over its max size limits, even if they are marked as used. + * @param {any} item + * @param {boolean} value + */ + setLoaded(item: any, value: boolean): void; + /** + * Marks an item as used in the current frame, preventing it from being evicted. + * @param {any} item + */ + markUsed(item: any): void; + /** + * Marks an item as unused, making it eligible for eviction. + * @param {any} item + */ + markUnused(item: any): void; + /** + * Marks all items in the cache as unused. + */ + markAllUnused(): void; + /** + * Returns whether the given item is currently marked as used. + * @param {any} item + * @returns {boolean} + */ + isUsed(item: any): boolean; + /** + * Evicts unused items until the cache is within its min size and byte limits. + * Items are sorted by `unloadPriorityCallback` before eviction. + */ + unloadUnusedContent(): void; + /** + * Schedules `unloadUnusedContent` to run asynchronously via microtask. + */ + scheduleUnload(): void; + scheduled: boolean; +} + +/** + * Error thrown when a queued item's promise is rejected because the item was removed + * before its callback could run. + * + * @extends Error + */ +declare class PriorityQueueItemRemovedError extends Error { + constructor(); +} +/** + * @callback PriorityCallback + * @param {any} a + * @param {any} b + * @returns {number} + */ +/** + * @callback SchedulingCallback + * @param {Function} func + */ +/** + * @callback ItemCallback + * @param {any} item + * @returns {Promise|any} + */ +/** + * @callback FilterCallback + * @param {any} item + * @returns {boolean} + */ +/** + * Priority queue for scheduling async work with a concurrency limit. Items are + * sorted by `priorityCallback` and dispatched up to `maxJobs` at a time. + */ +declare class PriorityQueue { + get running(): boolean; + /** + * Maximum number of jobs that can run concurrently. + * @type {number} + */ + maxJobs: number; + items: any[]; + callbacks: Map; + currJobs: number; + scheduled: boolean; + /** + * If true, job runs are automatically scheduled after `add` and after each job completes. + * @type {boolean} + */ + autoUpdate: boolean; + /** + * Comparator used to sort queued items. Higher-priority items should sort last + * (i.e. return positive when `itemA` should run before `itemB`). Defaults to `null`. + * @type {PriorityCallback|null} + */ + priorityCallback: PriorityCallback | null; + /** + * Callback used to schedule a deferred job run. Defaults to `requestAnimationFrame`. + * @type {SchedulingCallback} + */ + schedulingCallback: SchedulingCallback; + /** + * Sorts the pending item list using `priorityCallback`, if set. + */ + sort(): void; + /** + * Returns whether the given item is currently queued. + * @param {any} item + * @returns {boolean} + */ + has(item: any): boolean; + /** + * Adds an item to the queue and returns a Promise that resolves when the item's + * callback completes, or rejects if the item is removed before running. + * @param {any} item + * @param {ItemCallback} callback - Invoked with `item` when it is dequeued; may return a Promise + * @returns {Promise} + */ + add(item: any, callback: ItemCallback): Promise; + /** + * Removes an item from the queue, rejecting its promise with `PriorityQueueItemRemovedError`. + * @param {any} item + */ + remove(item: any): void; + /** + * Removes all queued items for which `filter` returns true. + * @param {FilterCallback} filter - Called with each item; return true to remove + */ + removeByFilter(filter: FilterCallback): void; + /** + * Immediately attempts to dequeue and run pending jobs up to `maxJobs` concurrency. + */ + tryRunJobs(): void; + /** + * Schedules a deferred call to `tryRunJobs` via `schedulingCallback`. + */ + scheduleJobRun(): void; +} +type PriorityCallback = (a: any, b: any) => number; +type SchedulingCallback = (func: Function) => any; +type ItemCallback = (item: any) => Promise | any; +type FilterCallback = (item: any) => boolean; + +/** + * Internal renderer state added to each tile during preprocessing. + * @typedef {Object} TileInternalData + * @property {boolean} hasContent - Whether the tile has a content URI. + * @property {boolean} hasRenderableContent - Whether the tile content is a renderable model (not an external tileset). + * @property {boolean} hasUnrenderableContent - Whether the tile content is an external tileset JSON. + * @property {number} loadingState - Current loading state constant (UNLOADED, QUEUED, LOADING, PARSING, LOADED, or FAILED). + * @property {string} basePath - Base URL used to resolve relative content URIs. + * @property {number} depth - Depth of this tile in the full tile hierarchy. + * @property {number} depthFromRenderedParent - Depth from the nearest ancestor with renderable content. + * @property {boolean} isVirtual - Whether this tile was synthetically generated by a plugin. + * @property {number} virtualChildCount - Number of virtual children appended to this tile by plugins. + */ +/** + * Per-frame traversal state updated on each tile during `TilesRendererBase.update`. + * @typedef {Object} TileTraversalData + * @property {number} distanceFromCamera - Distance from the tile bounds to the nearest active camera. + * @property {number} error - Screen space error computed for this tile. + * @property {boolean} inFrustum - Whether the tile was within the camera frustum on the last update. + * @property {boolean} isLeaf - Whether this tile is a leaf node in the used tile tree. + * @property {boolean} used - Whether this tile was visited during the last update traversal. + * @property {boolean} usedLastFrame - Whether this tile was visited in the previous frame. + * @property {boolean} visible - Whether this tile is currently visible (loaded, in frustum, meets SSE). + */ +/** + * A 3D Tiles tile with both spec fields (from tileset JSON) and renderer-managed state. + * @typedef {Object} Tile + * @property {Object} boundingVolume - Bounding volume. Has either a `box` (12-element array) or `sphere` (4-element array) field. + * @property {number} geometricError - Error in meters introduced if this tile is not rendered. + * @property {Tile|null} parent - Parent tile, or null for the root. + * @property {Tile[]} [children] - Child tiles. + * @property {Object} [content] - Loadable content URI reference. + * @property {'REPLACE'|'ADD'} [refine] - Refinement strategy; inherited from the parent if omitted. + * @property {number[]} [transform] - Optional 4x4 column-major transform matrix. + * @property {Object} [extensions] - Extension-specific objects. + * @property {Object} [extras] - Extra application-specific data. + * @property {TileInternalData} internal - Internal renderer state. + * @property {TileTraversalData} traversal - Per-frame traversal state. + */ +/** + * A loaded 3D Tiles tileset JSON object. + * @typedef {Object} Tileset + * @property {Object} asset - Metadata about the tileset. Contains `version` (string) and optional `tilesetVersion` (string). + * @property {number} geometricError - Error in meters for the entire tileset. + * @property {Tile} root - The root tile. + * @property {string[]} [extensionsUsed] - Names of extensions used somewhere in the tileset. + * @property {string[]} [extensionsRequired] - Names of extensions required to load the tileset. + * @property {Object} [properties] - Metadata about per-feature properties. + * @property {Object} [extensions] - Extension-specific objects. + * @property {Object} [extras] - Extra application-specific data. + */ +/** + * Fired when the renderer determines a new render is required — e.g. after a tile loads. + * @event TilesRendererBase#needs-update + */ +/** + * Fired when any tile content (model or external tileset) finishes loading. + * @event TilesRendererBase#load-content + */ +/** + * Fired when any tileset JSON finishes loading. + * @event TilesRendererBase#load-tileset + * @property {Tileset} tileset - The loaded tileset object. + * @property {string} url - The URL from which the tileset was loaded. + */ +/** + * Fired when the root tileset JSON finishes loading. + * @event TilesRendererBase#load-root-tileset + * @property {Tileset} tileset - The loaded root tileset object. + * @property {string} url - The URL from which the tileset was loaded. + */ +/** + * Fired when tile downloads begin after a period of inactivity. + * @event TilesRendererBase#tiles-load-start + */ +/** + * Fired when all pending tile downloads and parses have completed. + * @event TilesRendererBase#tiles-load-end + */ +/** + * Fired when a tile content download begins. + * @event TilesRendererBase#tile-download-start + * @property {Tile} tile - The tile being downloaded. + * @property {string} uri - The URI being fetched. + */ +/** + * Fired when a tile's renderable content (model/scene) is created. + * The `scene` type is engine-specific (e.g. `THREE.Group` in three.js). + * @event TilesRendererBase#load-model + * @property {Object} scene - The engine-specific scene object created for this tile. + * @property {Tile} tile - The tile the scene belongs to. + * @property {string} url - The URL the content was loaded from. + */ +/** + * Fired when a tile's renderable content is about to be removed and destroyed. + * The `scene` type is engine-specific (e.g. `THREE.Group` in three.js). + * @event TilesRendererBase#dispose-model + * @property {Object} scene - The engine-specific scene object being disposed. + * @property {Tile} tile - The tile the scene belonged to. + */ +/** + * Fired when a tile transitions between visible and hidden. + * The `scene` type is engine-specific (e.g. `THREE.Group` in three.js). + * @event TilesRendererBase#tile-visibility-change + * @property {Object} scene - The engine-specific scene object. + * @property {Tile} tile - The tile whose visibility changed. + * @property {boolean} visible - Whether the tile is now visible. + */ +/** + * Fired at the start of each `update()` call, before traversal begins. + * @event TilesRendererBase#update-before + */ +/** + * Fired at the end of each `update()` call, after traversal completes. + * @event TilesRendererBase#update-after + */ +/** + * Fired when a tile or tileset fails to load. + * @event TilesRendererBase#load-error + * @property {Tile|null} tile - The tile that failed, or null if a root tileset failed. + * @property {Error} error - The error that occurred. + * @property {string|URL} url - The URL that failed to load. + */ +/** + * Base class for 3D Tiles renderers. Manages tile loading, caching, traversal, + * and a plugin system for extending rendering behavior. Engine-specific renderers + * extend this class to add camera projection, scene management, and tile display. + */ +interface TilesRendererBaseEventMap { + 'needs-update': {}; + 'load-content': {}; + 'load-tileset': { tileset: Tileset; url: string }; + 'load-root-tileset': { tileset: Tileset; url: string }; + 'tiles-load-start': {}; + 'tiles-load-end': {}; + 'tile-download-start': { tile: Tile; uri: string }; + 'load-model': { scene: TScene; tile: Tile; url: string }; + 'dispose-model': { scene: TScene; tile: Tile }; + 'tile-visibility-change': { scene: TScene; tile: Tile; visible: boolean }; + 'update-before': {}; + 'update-after': {}; + 'load-error': { tile: Tile | null; error: Error; url: string | URL }; +} + +declare class TilesRendererBase { + /** + * @param {string} [url] - URL of the root tileset JSON to load. + */ + constructor(url?: string); + /** + * Root tile of the loaded root tileset, or null if not yet loaded. + * @type {Tile|null} + */ + get root(): Tile | null; + get rootTileSet(): Tileset; + /** + * Fraction of tiles loaded since the last idle state, from 0 (nothing loaded) to 1 (all loaded). + * @type {number} + */ + get loadProgress(): number; + set errorThreshold(v: number); + get errorThreshold(): number; + rootLoadingState: number; + /** + * The loaded root tileset object, or null if not yet loaded. + * @type {Tileset|null} + * @readonly + */ + readonly rootTileset: Tileset | null; + rootURL: string; + /** + * Options passed to `fetch` when loading tile and tileset resources. + * @type {RequestInit} + */ + fetchOptions: RequestInit; + plugins: any[]; + queuedTiles: any[]; + cachedSinceLoadComplete: Set; + isLoading: boolean; + processedTiles: WeakSet; + /** + * Set of all tiles that are currently visible. + * @type {Set} + * @readonly + */ + readonly visibleTiles: Set; + /** + * Set of all tiles that are currently active (displayed as a stand-in while children load). + * @type {Set} + * @readonly + */ + readonly activeTiles: Set; + usedSet: Set; + loadingTiles: Set; + /** + * LRU cache managing loaded tile lifecycle and memory eviction. + * @type {LRUCache} + */ + lruCache: LRUCache; + /** + * Priority queue controlling concurrent tile downloads. + * @type {PriorityQueue} + */ + downloadQueue: PriorityQueue; + /** + * Priority queue controlling concurrent tile parsing. + * @type {PriorityQueue} + */ + parseQueue: PriorityQueue; + /** + * Priority queue controlling deferred tile child preprocessing. + * @type {PriorityQueue} + */ + processNodeQueue: PriorityQueue; + /** + * Loading and rendering statistics updated each frame. Fields: + * - `inCache` — tiles currently in the LRU cache + * - `queued` — tiles queued for download + * - `downloading` — tiles currently downloading + * - `parsing` — tiles currently being parsed + * - `loaded` — tiles that have finished loading + * - `failed` — tiles that failed to load + * - `inFrustum` — tiles inside the camera frustum after the last update + * - `used` — tiles visited during the last traversal + * - `active` — tiles currently set as active + * - `visible` — tiles currently visible + * @type {Object} + */ + stats: any; + frameCount: number; + /** + * Target screen-space error in pixels. Tiles with a higher SSE are subdivided. + * @type {number} + */ + errorTarget: number; + /** + * If true, tiles are displayed at their current LOD while waiting for higher-detail + * children to finish loading, rather than hiding them. + * @type {boolean} + */ + displayActiveTiles: boolean; + /** + * Maximum depth in the tile hierarchy to traverse. Tiles deeper than this are skipped. + * @type {number} + */ + maxDepth: number; + /** + * If true, uses an optimized traversal strategy that prioritizes distance over error. + * @type {boolean} + */ + optimizedLoadStrategy: boolean; + /** + * If true, sibling tiles of visible tiles are also loaded to reduce pop-in during camera movement. + * @type {boolean} + */ + loadSiblings: boolean; + /** + * Maximum number of tile children to preprocess per `update` call. Excess tiles are deferred + * to `processNodeQueue`. + * @type {number} + */ + maxTilesProcessed: number; + /** + * Registers a plugin with this renderer. Plugins are inserted in priority order and + * receive lifecycle callbacks throughout the tile loading and rendering process. + * A plugin instance may only be registered to one renderer at a time. + * @param {Object} plugin + */ + registerPlugin(plugin: any): void; + /** + * Removes a registered plugin. Calls `plugin.dispose()` if defined. + * Accepts either the plugin instance or its string name. + * Returns true if the plugin was found and removed. + * @param {Object|string} plugin + * @returns {boolean} + */ + unregisterPlugin(plugin: any | string): boolean; + /** + * Returns the first registered plugin whose `name` property matches, or null. + * @param {string} name + * @returns {Object|null} + */ + getPluginByName(name: string): any | null; + invokeOnePlugin(func: any): any; + invokeAllPlugins(func: any): Promise; + /** + * Iterates over all tiles in the loaded hierarchy. `beforecb` is called before + * descending into a tile's children; returning true from it skips the subtree. + * `aftercb` is called after all children have been visited. + * @param {TileBeforeCallback|null} [beforecb] + * @param {TileAfterCallback|null} [aftercb] + */ + traverse(beforecb?: TileBeforeCallback | null, aftercb?: TileAfterCallback | null, ensureFullyProcessed?: boolean): void; + /** + * Collects attribution data from all registered plugins into `target` and returns it. + * @param {Array<{type: string, value: any}>} [target] + * @returns {Array<{type: string, value: any}>} + */ + getAttributions(target?: Array<{ + type: string; + value: any; + }>): Array<{ + type: string; + value: any; + }>; + /** + * Runs the tile traversal and update loop. Should be called once per frame after + * camera matrices have been updated. Triggers tile loading, visibility updates, + * and LRU cache eviction. + */ + update(): void; + /** + * Resets any tiles that previously failed to load so they will be retried on the next `update`. + */ + resetFailedTiles(): void; + calculateTileViewErrorWithPlugin(tile: any, target: any): void; + /** + * Disposes all loaded tiles and unregisters all plugins. The renderer should not + * be used after calling this. + */ + dispose(): void; + calculateBytesUsed(scene: any, tile: any): number; + /** + * Dispatches an event to all registered listeners for the given event type. + * @param {{ type: string }} e + */ + /** + * Registers a listener for the given event type. + * @param {string} name + * @param {EventCallback} callback + */ + /** + * Removes a previously registered event listener. + * @param {string} name + * @param {EventCallback} callback + */ + parseTile(buffer: any, tile: any, extension: any): any; + prepareForTraversal(): void; + disposeTile(tile: any): void; + preprocessNode(tile: any, tilesetDir: any, parentTile?: any): void; + setTileActive(tile: any, active: any): void; + setTileVisible(tile: any, visible: any): void; + calculateTileViewError(tile: any, target: any): void; + removeUnusedPendingTiles(): void; + queueTileForDownload(tile: any): void; + markTileUsed(tile: any): void; + fetchData(url: any, options: any): Promise; + ensureChildrenArePreprocessed(tile: any, forceImmediate?: boolean): void; + getBytesUsed(tile: any): number; + recalculateBytesUsed(tile?: any): void; + preprocessTileset(json: any, url: any, parent?: any): void; + preprocessTileSet(...args: any[]): void; + loadRootTileset(): any; + loadRootTileSet(...args: any[]): any; + requestTileContents(tile: any): Promise; + + addEventListener( name: T, callback: ( event: TEventMap[ T ] & { type: T } ) => void ): void; + addEventListener( name: string, callback: ( event: any ) => void ): void; + + removeEventListener( name: T, callback: ( event: TEventMap[ T ] & { type: T } ) => void ): void; + removeEventListener( name: string, callback: ( event: any ) => void ): void; + + hasEventListener( name: T, callback: ( event: TEventMap[ T ] & { type: T } ) => void ): boolean; + hasEventListener( name: string, callback: ( event: any ) => void ): boolean; + + dispatchEvent( event: TEventMap[ T ] & { type: T } ): void; + dispatchEvent( event: { type: string } ): void; +} +type TileBeforeCallback = (tile: Tile, parent: Tile | null, depth: number) => boolean; +type TileAfterCallback = (tile: Tile, parent: Tile | null, depth: number) => any; +type EventCallback = (event: any) => any; +/** + * Internal renderer state added to each tile during preprocessing. + */ +type TileInternalData = { + /** + * - Whether the tile has a content URI. + */ + hasContent: boolean; + /** + * - Whether the tile content is a renderable model (not an external tileset). + */ + hasRenderableContent: boolean; + /** + * - Whether the tile content is an external tileset JSON. + */ + hasUnrenderableContent: boolean; + /** + * - Current loading state constant (UNLOADED, QUEUED, LOADING, PARSING, LOADED, or FAILED). + */ + loadingState: number; + /** + * - Base URL used to resolve relative content URIs. + */ + basePath: string; + /** + * - Depth of this tile in the full tile hierarchy. + */ + depth: number; + /** + * - Depth from the nearest ancestor with renderable content. + */ + depthFromRenderedParent: number; + /** + * - Whether this tile was synthetically generated by a plugin. + */ + isVirtual: boolean; + /** + * - Number of virtual children appended to this tile by plugins. + */ + virtualChildCount: number; +}; +/** + * Per-frame traversal state updated on each tile during `TilesRendererBase.update`. + */ +type TileTraversalData = { + /** + * - Distance from the tile bounds to the nearest active camera. + */ + distanceFromCamera: number; + /** + * - Screen space error computed for this tile. + */ + error: number; + /** + * - Whether the tile was within the camera frustum on the last update. + */ + inFrustum: boolean; + /** + * - Whether this tile is a leaf node in the used tile tree. + */ + isLeaf: boolean; + /** + * - Whether this tile was visited during the last update traversal. + */ + used: boolean; + /** + * - Whether this tile was visited in the previous frame. + */ + usedLastFrame: boolean; + /** + * - Whether this tile is currently visible (loaded, in frustum, meets SSE). + */ + visible: boolean; +}; +/** + * A 3D Tiles tile with both spec fields (from tileset JSON) and renderer-managed state. + */ +type Tile = { + /** + * - Bounding volume. Has either a `box` (12-element array) or `sphere` (4-element array) field. + */ + boundingVolume: any; + /** + * - Error in meters introduced if this tile is not rendered. + */ + geometricError: number; + /** + * - Parent tile, or null for the root. + */ + parent: Tile | null; + /** + * - Child tiles. + */ + children?: Tile[]; + /** + * - Loadable content URI reference. + */ + content?: any; + /** + * - Refinement strategy; inherited from the parent if omitted. + */ + refine?: "REPLACE" | "ADD"; + /** + * - Optional 4x4 column-major transform matrix. + */ + transform?: number[]; + /** + * - Extension-specific objects. + */ + extensions?: any; + /** + * - Extra application-specific data. + */ + extras?: any; + /** + * - Internal renderer state. + */ + internal: TileInternalData; + /** + * - Per-frame traversal state. + */ + traversal: TileTraversalData; +}; +/** + * A loaded 3D Tiles tileset JSON object. + */ +type Tileset = { + /** + * - Metadata about the tileset. Contains `version` (string) and optional `tilesetVersion` (string). + */ + asset: any; + /** + * - Error in meters for the entire tileset. + */ + geometricError: number; + /** + * - The root tile. + */ + root: Tile; + /** + * - Names of extensions used somewhere in the tileset. + */ + extensionsUsed?: string[]; + /** + * - Names of extensions required to load the tileset. + */ + extensionsRequired?: string[]; + /** + * - Metadata about per-feature properties. + */ + properties?: any; + /** + * - Extension-specific objects. + */ + extensions?: any; + /** + * - Extra application-specific data. + */ + extras?: any; +}; + +/** + * Base class for all 3D Tiles content loaders. Handles fetching and parsing tile content. + */ +declare class LoaderBase { + /** + * Options passed to `fetch` when loading tile content. + * @type {Object} + */ + fetchOptions: any; + /** + * Base URL used to resolve relative external URLs. + * @type {string} + */ + workingPath: string; + /** + * @deprecated Use `loadAsync` instead. + * @param {string} url + * @returns {Promise} + */ + load(...args: any[]): Promise; + /** + * Fetches and parses content from the given URL. + * @param {string} url + * @returns {Promise} + */ + loadAsync(url: string): Promise; + /** + * Resolves a relative URL against `workingPath`. + * @param {string} url + * @returns {string} + */ + resolveExternalURL(url: string): string; + /** + * Parses a raw buffer into a tile result object. Must be implemented by subclasses. + * @param {ArrayBuffer} buffer + * @returns {any} + */ + parse(buffer: ArrayBuffer): any; +} + +/** + * Parses a 3D Tiles feature table from a binary buffer, providing access to + * per-feature properties stored as JSON scalars or typed binary arrays. + */ +declare class FeatureTable { + /** + * @param {ArrayBuffer} buffer + * @param {number} start - Byte offset of the feature table within the buffer + * @param {number} headerLength - Byte length of the JSON header + * @param {number} binLength - Byte length of the binary body + */ + constructor(buffer: ArrayBuffer, start: number, headerLength: number, binLength: number); + /** + * The underlying buffer containing the feature table data. + * @type {ArrayBuffer} + */ + buffer: ArrayBuffer; + /** + * Byte offset of the binary body within the buffer. + * @type {number} + */ + binOffset: number; + /** + * Byte length of the binary body. + * @type {number} + */ + binLength: number; + /** + * Parsed JSON header object. + * @type {Object} + */ + header: any; + /** + * Returns all property key names defined in the feature table header, excluding `extensions`. + * @returns {Array} + */ + getKeys(): Array; + /** + * Returns the value for the given property key. For binary properties, reads typed array data + * from the binary body using the provided count, component type, and vector type. + * @param {string} key + * @param {number} count - Number of elements to read for binary properties + * @param {string | null} [defaultComponentType] - Fallback component type (e.g. `'FLOAT'`, `'UNSIGNED_SHORT'`) + * @param {string | null} [defaultType] - Fallback vector type (e.g. `'SCALAR'`, `'VEC3'`) + * @returns {number | string | ArrayBufferView | null} + */ + getData(key: string, count: number, defaultComponentType?: string | null, defaultType?: string | null): number | string | ArrayBufferView | null; + /** + * Returns a slice of the binary body at the given offset and length. + * @param {number} byteOffset + * @param {number} byteLength + * @returns {ArrayBuffer} + */ + getBuffer(byteOffset: number, byteLength: number): ArrayBuffer; +} + +/** + * Extends FeatureTable to provide indexed access to per-feature batch properties, + * as found in B3DM and PNTS tiles. + * + * @extends FeatureTable + */ +declare class BatchTable extends FeatureTable { + /** + * @param {ArrayBuffer} buffer + * @param {number} count - Number of features in the batch + * @param {number} start - Byte offset of the batch table within the buffer + * @param {number} headerLength - Byte length of the JSON header + * @param {number} binLength - Byte length of the binary body + */ + constructor(buffer: ArrayBuffer, count: number, start: number, headerLength: number, binLength: number); + get batchSize(): number; + /** + * Total number of features in the batch. + * @type {number} + */ + count: number; + /** + * Parsed extension objects keyed by extension name. + * @type {Object} + */ + extensions: any; + /** + * @deprecated Use `getDataFromId` or `getPropertyArray` instead. + * @param {string} key + * @param {string | null} [componentType] + * @param {string | null} [type] + * @returns {number | string | ArrayBufferView | null} + */ + getData(key: string, componentType?: string | null, type?: string | null): number | string | ArrayBufferView | null; + /** + * Returns all batch table properties for the given feature id as an object. + * @param {number} id - Feature index (0 to count - 1) + * @param {Object} [target={}] - Optional object to write properties into + * @returns {Object} + */ + getDataFromId(id: number, target?: any): any; + /** + * Returns the full typed array of values for the given property key across all features. + * @param {string} key + * @returns {number | string | ArrayBufferView} + */ + getPropertyArray(key: string): number | string | ArrayBufferView; +} + +/** + * Base loader for the B3DM (Batched 3D Model) tile format. Parses the B3DM binary + * structure and extracts the embedded GLB bytes along with batch and feature tables. + * Extend this class to integrate B3DM loading into a specific rendering engine. + * + * @extends LoaderBase + */ +declare class B3DMLoaderBase extends LoaderBase { + /** + * Parses a B3DM buffer and returns the raw tile data. + * @param {ArrayBuffer} buffer + * @returns {{ version: string, featureTable: FeatureTable, batchTable: BatchTable, glbBytes: Uint8Array }} + */ + parse(buffer: ArrayBuffer): { + version: string; + featureTable: FeatureTable; + batchTable: BatchTable; + glbBytes: Uint8Array; + }; +} + +/** + * Base loader for the I3DM (Instanced 3D Model) tile format. Parses the I3DM binary + * structure and extracts the embedded GLB bytes (or fetches an external GLTF) along + * with batch and feature tables. Extend this class to integrate I3DM loading into a + * specific rendering engine. + * + * @extends LoaderBase + */ +declare class I3DMLoaderBase extends LoaderBase { + /** + * Parses an I3DM buffer and returns the raw tile data. + * @param {ArrayBuffer} buffer + * @returns {Promise<{ version: string, featureTable: FeatureTable, batchTable: BatchTable, glbBytes: Uint8Array, gltfWorkingPath: string }>} + */ + parse(buffer: ArrayBuffer): Promise<{ + version: string; + featureTable: FeatureTable; + batchTable: BatchTable; + glbBytes: Uint8Array; + gltfWorkingPath: string; + }>; +} + +/** + * Base loader for the PNTS (Point Cloud) tile format. Parses the PNTS binary + * structure and extracts the feature and batch tables containing point positions, + * colors, and normals. Extend this class to integrate PNTS loading into a specific + * rendering engine. + * + * @extends LoaderBase + */ +declare class PNTSLoaderBase extends LoaderBase { + /** + * Parses a PNTS buffer and returns the raw tile data. + * @param {ArrayBuffer} buffer + * @returns {Promise<{ version: string, featureTable: FeatureTable, batchTable: BatchTable }>} + */ + parse(buffer: ArrayBuffer): Promise<{ + version: string; + featureTable: FeatureTable; + batchTable: BatchTable; + }>; +} + +/** + * Base loader for the CMPT (Composite) tile format. Parses the CMPT binary structure + * and returns the individual inner tile buffers with their format types. Extend this + * class to integrate CMPT loading into a specific rendering engine. + * + * @extends LoaderBase + */ +declare class CMPTLoaderBase extends LoaderBase { + /** + * Parses a CMPT buffer and returns an object containing each inner tile's type and raw buffer. + * @param {ArrayBuffer} buffer + * @returns {{ version: string, tiles: Array<{ type: string, buffer: Uint8Array, version: number }> }} + */ + parse(buffer: ArrayBuffer): { + version: string; + tiles: Array<{ + type: string; + buffer: Uint8Array; + version: number; + }>; + }; +} + +/** + * Tile content failed to load. Sorted first for eviction by the LRU cache. + * @type {number} + */ +declare const FAILED: number; +/** + * Tile content has not been requested. + * @type {number} + */ +declare const UNLOADED: number; +/** + * Tile content is queued for download. + * @type {number} + */ +declare const QUEUED: number; +/** + * Tile content is currently downloading. + * @type {number} + */ +declare const LOADING: number; +/** + * Tile content has been downloaded and is being parsed. + * @type {number} + */ +declare const PARSING: number; +/** + * Tile content has been parsed and is ready to display. + * @type {number} + */ +declare const LOADED: number; +/** + * WGS84 ellipsoid semi-major axis radius in meters. + * @type {number} + */ +declare const WGS84_RADIUS: number; +/** + * WGS84 ellipsoid flattening factor. + * @type {number} + */ +declare const WGS84_FLATTENING: number; +/** + * WGS84 ellipsoid height offset (difference between equatorial and polar radii) in meters. + * @type {number} + */ +declare const WGS84_HEIGHT: number; + +declare function traverseSet(tile: any, beforeCb?: any, afterCb?: any): void; +declare function traverseAncestors(tile: any, callback?: any): void; + +declare const TraversalUtils_d_traverseAncestors: typeof traverseAncestors; +declare const TraversalUtils_d_traverseSet: typeof traverseSet; +declare namespace TraversalUtils_d { + export { + TraversalUtils_d_traverseAncestors as traverseAncestors, + TraversalUtils_d_traverseSet as traverseSet, + }; +} + +declare function readMagicBytes(bufferOrDataView: any): string; +declare function arrayToString(array: any): string; +declare function getWorkingPath(url: any): string; + +declare const LoaderUtils_d_arrayToString: typeof arrayToString; +declare const LoaderUtils_d_getWorkingPath: typeof getWorkingPath; +declare const LoaderUtils_d_readMagicBytes: typeof readMagicBytes; +declare namespace LoaderUtils_d { + export { + LoaderUtils_d_arrayToString as arrayToString, + LoaderUtils_d_getWorkingPath as getWorkingPath, + LoaderUtils_d_readMagicBytes as readMagicBytes, + }; +} + +export { B3DMLoaderBase, BatchTable, CMPTLoaderBase, FAILED, FeatureTable, I3DMLoaderBase, LOADED, LOADING, LRUCache, LoaderBase, LoaderUtils_d as LoaderUtils, PARSING, PNTSLoaderBase, PriorityQueue, PriorityQueueItemRemovedError, QUEUED, TilesRendererBase, TraversalUtils_d as TraversalUtils, UNLOADED, WGS84_FLATTENING, WGS84_HEIGHT, WGS84_RADIUS }; +export type { FilterCallback, ItemCallback, PriorityCallback, SchedulingCallback }; diff --git a/tsconfig.core-plugins.json b/tsconfig.core-plugins.json deleted file mode 100644 index be4e90378..000000000 --- a/tsconfig.core-plugins.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "declaration": true, - "emitDeclarationOnly": true, - "checkJs": false, - "skipLibCheck": true, - "module": "esnext", - "moduleResolution": "bundler", - "rootDir": "src" - }, - "files": [ - "src/core/plugins/index.js", - "src/core/plugins/CesiumIonAuthPlugin.js", - "src/core/plugins/GoogleCloudAuthPlugin.js", - "src/core/plugins/ImplicitTilingPlugin.js", - "src/core/plugins/EnforceNonZeroErrorPlugin.js", - "src/core/plugins/auth/CesiumIonAuth.js", - "src/core/plugins/GoogleAttributionsManager.js", - "src/core/plugins/auth/GoogleCloudAuth.js", - "src/core/plugins/loaders/QuantizedMeshLoaderBase.js" - ] -} diff --git a/tsconfig.dts.json b/tsconfig.dts.json new file mode 100644 index 000000000..c820ce3ee --- /dev/null +++ b/tsconfig.dts.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "allowJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "checkJs": false, + "skipLibCheck": true, + "module": "esnext", + "moduleResolution": "bundler", + "rootDir": "src" + }, + "include": [ + "src/core" + ] +} diff --git a/utils/gen-dts.js b/utils/gen-dts.js index 51ce87912..254aca1c9 100644 --- a/utils/gen-dts.js +++ b/utils/gen-dts.js @@ -1,79 +1,105 @@ /** - * Generates src/core/plugins/index.d.ts from JSDoc-annotated JS source. + * Generates a bundled index.d.ts from JSDoc-annotated JS source files. + * + * Usage: node utils/gen-dts.js * * Steps: - * 1. Delete stale .d.ts files so tsc generates fresh declarations - * 2. tsc emits per-file .d.ts into a temp directory - * 3. Type alias imports are rewritten to avoid a rollup-plugin-dts bug with invalid identifiers - * 4. rollup-plugin-dts bundles them into a single declaration file - * 5. A transform strips any underscore-prefixed members from the output + * 1. Parse tsconfig to find source files, rootDir, and output path + * 2. Scan source files for @event ClassName#event-name annotations, grouped by class + * 3. Delete stale .d.ts files from the module directory + * 4. tsc emits per-file .d.ts into a temp directory + * 5. Type alias imports are rewritten to avoid a rollup-plugin-dts bug with invalid identifiers + * 6. rollup-plugin-dts bundles them into a single declaration file + * 7. Transforms: strip underscore-prefixed members, inject typed event maps for any + * class with @event annotations */ import { execSync } from 'child_process'; -import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs'; +import { existsSync, globSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs'; import { tmpdir } from 'os'; -import { dirname, join, resolve } from 'path'; +import { dirname, join, relative, resolve, sep } from 'path'; import { rollup } from 'rollup'; import dts from 'rollup-plugin-dts'; const ROOT = resolve( import.meta.dirname, '..' ); -const PLUGINS_DIR = join( ROOT, 'src/core/plugins' ); -const OUT_FILE = join( PLUGINS_DIR, 'index.d.ts' ); -// Step 1: delete stale .d.ts files (recursively) so tsc generates fresh ones -function deleteDtsFiles( dir ) { +// Parse CLI argument +const tsconfigArg = process.argv[ 2 ]; +if ( ! tsconfigArg ) { - for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { + console.error( 'Usage: node utils/gen-dts.js ' ); + process.exit( 1 ); - const full = join( dir, entry.name ); - if ( entry.isDirectory() ) { +} - deleteDtsFiles( full ); +const tsconfigPath = resolve( ROOT, tsconfigArg ); +const tsconfig = JSON.parse( readFileSync( tsconfigPath, 'utf8' ) ); +const { include, compilerOptions: { rootDir = '.' } = {} } = tsconfig; - } else if ( entry.name.endsWith( '.d.ts' ) ) { +if ( ! include || ! include.length ) { - unlinkSync( full ); + console.error( `No "include" array found in ${ tsconfigPath }` ); + process.exit( 1 ); - } +} - } +// Expand include entries to source files; treat each entry as a base directory +const resolvedFiles = include.flatMap( p => globSync( `${ resolve( ROOT, p ) }/**/*.js` ) ); +const indexFiles = resolvedFiles.filter( f => f.endsWith( `${ sep }index.js` ) ); +if ( ! indexFiles.length ) { + + console.error( `No index.js found under include paths` ); + process.exit( 1 ); } -deleteDtsFiles( PLUGINS_DIR ); +const rootDirAbs = resolve( ROOT, rootDir ); + +// Step 1: scan all source files for @event ClassName#event-name annotations +const eventsByClass = parseEventsByClass( resolvedFiles ); + +// Step 2: delete stale .d.ts files from each module directory +for ( const indexFile of indexFiles ) { + + deleteDtsFiles( dirname( indexFile ) ); + +} -// Step 2: emit .d.ts to a temp directory +// Step 3: emit .d.ts for all files to a temp directory in one tsc pass const tmpDir = mkdtempSync( join( tmpdir(), 'dts-' ) ); try { execSync( - `npx tsc -p tsconfig.core-plugins.json --declarationDir ${ tmpDir }`, + `npx tsc -p ${ tsconfigPath } --declarationDir ${ tmpDir }`, { cwd: ROOT, stdio: 'inherit' }, ); // Fix any invalid namespace imports tsc emitted (e.g. `import * as 3d_...`) fixTypeAliasImportsInDir( tmpDir ); - // Step 3+4: bundle with rollup-dts and strip _ members - const entry = join( tmpDir, 'core/plugins/index.d.ts' ); + // Step 4: rollup-bundle each index.js into its own index.d.ts + for ( const indexFile of indexFiles ) { - const bundle = await rollup( { - input: entry, - plugins: [ - resolveDtsExtensions( tmpDir ), - dts(), - stripUnderscoreMembers(), - ], - external: [ /^3d-tiles-renderer/ ], - } ); + const outFile = join( dirname( indexFile ), 'index.d.ts' ); + const entryRel = relative( rootDirAbs, indexFile ).replace( /\.js$/, '.d.ts' ); + const entry = join( tmpDir, entryRel ); - await bundle.write( { - file: OUT_FILE, - format: 'es', - } ); + const bundle = await rollup( { + input: entry, + plugins: [ + resolveDtsExtensions( tmpDir ), + dts(), + stripUnderscoreMembers(), + injectEventMaps( eventsByClass ), + ], + external: [ /^3d-tiles-renderer/ ], + } ); - await bundle.close(); - console.log( `Written: ${ OUT_FILE }` ); + await bundle.write( { file: outFile, format: 'es' } ); + await bundle.close(); + console.log( `Written: ${ outFile }` ); + + } } finally { @@ -81,6 +107,31 @@ try { } +// --- Helpers --- + + +/** + * Deletes all .d.ts files recursively under a directory. + */ +function deleteDtsFiles( dir ) { + + for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { + + const full = join( dir, entry.name ); + if ( entry.isDirectory() ) { + + deleteDtsFiles( full ); + + } else if ( entry.name.endsWith( '.d.ts' ) ) { + + unlinkSync( full ); + + } + + } + +} + /** * Resolves .js imports inside the temp directory to their .d.ts counterparts. */ @@ -145,18 +196,14 @@ function stripUnderscoreMembers() { name: 'strip-underscore-members', renderChunk( code ) { - // Match underscore-prefixed member declarations, including multiline ones - // where the type spans multiple lines (e.g. _foo: {\n bar: any;\n};) - // Strategy: track brace depth after matching a _ member opener. const lines = code.split( '\n' ); const out = []; - let skip = 0; // brace depth while skipping a multiline member + let skip = 0; for ( const line of lines ) { if ( skip > 0 ) { - // count braces to know when the member body ends for ( const ch of line ) { if ( ch === '{' ) skip ++; @@ -170,7 +217,6 @@ function stripUnderscoreMembers() { if ( /^\s+(private\s+)?_\w+[\s:(]/.test( line ) ) { - // count any opening braces on this line to detect multiline type for ( const ch of line ) { if ( ch === '{' ) skip ++; @@ -178,7 +224,6 @@ function stripUnderscoreMembers() { } - // if skip > 0 the body continues on subsequent lines; either way skip this line continue; } @@ -193,3 +238,301 @@ function stripUnderscoreMembers() { }; } + +/** + * Scans a list of source files for JSDoc @event ClassName#event-name blocks, + * grouped by class name. Returns a Map. + */ +function parseEventsByClass( sourceFiles ) { + + const eventsByClass = new Map(); + + for ( const filePath of sourceFiles ) { + + const source = readFileSync( filePath, 'utf8' ); + const blockRegex = /\/\*\*([\s\S]*?)\*\//g; + let match; + + while ( ( match = blockRegex.exec( source ) ) !== null ) { + + const block = match[ 1 ]; + const eventMatch = block.match( /@event\s+(\w+)#([\w-]+)/ ); + if ( ! eventMatch ) continue; + + const className = eventMatch[ 1 ]; + const eventName = eventMatch[ 2 ]; + const deprecated = /@deprecated/.test( block ); + const props = []; + const propRegex = /@property\s+\{([^}]+)\}\s+(\[?\w+\]?)/g; + let propMatch; + + while ( ( propMatch = propRegex.exec( block ) ) !== null ) { + + const optional = propMatch[ 2 ].startsWith( '[' ); + const propName = propMatch[ 2 ].replace( /[[\]]/g, '' ); + props.push( { type: propMatch[ 1 ], name: propName, optional } ); + + } + + if ( ! eventsByClass.has( className ) ) { + + eventsByClass.set( className, { mapName: `${ className }EventMap`, events: [] } ); + + } + + eventsByClass.get( className ).events.push( { name: eventName, deprecated, props } ); + + } + + } + + return eventsByClass; + +} + +/** + * Maps JSDoc type names to TypeScript type names. + */ +function jsDocTypeToTs( type ) { + + return type.split( '|' ).map( t => { + + t = t.trim(); + return { + Object: 'any', + object: 'object', + string: 'string', + number: 'number', + boolean: 'boolean', + Error: 'Error', + URL: 'URL', + null: 'null', + }[ t ] ?? t; + + } ).join( ' | ' ); + +} + +/** + * Builds a TypeScript interface string for an event map. + * Properties named 'scene' of type Object/any are mapped to the TScene type parameter. + */ +function buildEventMapInterface( mapName, events ) { + + const lines = [ `interface ${ mapName } {` ]; + + for ( const event of events ) { + + if ( event.deprecated ) lines.push( `\t/** @deprecated */` ); + + const propStr = event.props.map( p => { + + let type = jsDocTypeToTs( p.type ); + if ( p.name === 'scene' && type === 'any' ) type = 'TScene'; + return `${ p.name }${ p.optional ? '?' : '' }: ${ type }`; + + } ).join( '; ' ); + + lines.push( `\t'${ event.name }': ${ propStr ? `{ ${ propStr } }` : '{}' };` ); + + } + + lines.push( `}` ); + return lines.join( '\n' ); + +} + +/** + * Builds the typed event listener overload declarations for a class with a TEventMap type parameter. + */ +function buildEventListenerOverloads() { + + const T = `T extends keyof TEventMap`; + const typedCallback = `callback: ( event: TEventMap[ T ] & { type: T } ) => void`; + const anyCallback = `callback: ( event: any ) => void`; + const typedEvent = `event: TEventMap[ T ] & { type: T }`; + + return [ + `\taddEventListener<${ T }>( name: T, ${ typedCallback } ): void;`, + `\taddEventListener( name: string, ${ anyCallback } ): void;`, + ``, + `\tremoveEventListener<${ T }>( name: T, ${ typedCallback } ): void;`, + `\tremoveEventListener( name: string, ${ anyCallback } ): void;`, + ``, + `\thasEventListener<${ T }>( name: T, ${ typedCallback } ): boolean;`, + `\thasEventListener( name: string, ${ anyCallback } ): boolean;`, + ``, + `\tdispatchEvent<${ T }>( ${ typedEvent } ): void;`, + `\tdispatchEvent( event: { type: string } ): void;`, + ].join( '\n' ); + +} + +/** + * Removes declaration lines for the given method names from a block of code. + * Handles both single-line and multi-line method signatures. + */ +function removeMethodDeclarations( code, methodNames ) { + + const lines = code.split( '\n' ); + const out = []; + let inMethod = false; + let parenDepth = 0; + + for ( const line of lines ) { + + if ( inMethod ) { + + for ( const ch of line ) { + + if ( ch === '(' ) parenDepth ++; + else if ( ch === ')' ) parenDepth --; + + } + + if ( parenDepth <= 0 && line.trimEnd().endsWith( ';' ) ) { + + inMethod = false; + parenDepth = 0; + + } + + continue; + + } + + const trimmed = line.trimStart(); + const isMethod = methodNames.some( name => trimmed.startsWith( `${ name }(` ) ); + + if ( isMethod ) { + + inMethod = true; + parenDepth = 0; + + for ( const ch of line ) { + + if ( ch === '(' ) parenDepth ++; + else if ( ch === ')' ) parenDepth --; + + } + + if ( parenDepth <= 0 && line.trimEnd().endsWith( ';' ) ) { + + inMethod = false; + parenDepth = 0; + + } + + continue; + + } + + out.push( line ); + + } + + return out.join( '\n' ); + +} + +/** + * Within a class body starting at classStart, removes event listener method stubs + * and appends typed event listener overloads. + */ +function replaceEventMethodsInClass( code, classStart ) { + + const openBrace = code.indexOf( '{', classStart ); + if ( openBrace === -1 ) return code; + + // Find the matching closing brace of the class + let depth = 0; + let classEnd = -1; + for ( let i = openBrace; i < code.length; i ++ ) { + + const ch = code[ i ]; + if ( ch === '{' ) depth ++; + else if ( ch === '}' ) { + + depth --; + if ( depth === 0 ) { + + classEnd = i; + break; + + } + + } + + } + + if ( classEnd === -1 ) return code; + + const before = code.slice( 0, openBrace + 1 ); + let body = code.slice( openBrace + 1, classEnd ); + const after = code.slice( classEnd ); + + const eventListenerMethods = [ + 'addEventListener', + 'removeEventListener', + 'hasEventListener', + 'dispatchEvent', + ]; + + body = removeMethodDeclarations( body, eventListenerMethods ); + body = body.trimEnd() + '\n\n' + buildEventListenerOverloads() + '\n'; + + return before + body + after; + +} + +/** + * Rollup plugin that, for each class with @event annotations: + * 1. Inserts a typed event map interface before the class declaration + * 2. Makes the class generic: ClassName + * 3. Replaces generic event listener stubs with typed overloads + */ +function injectEventMaps( eventsByClass ) { + + return { + name: 'inject-event-maps', + renderChunk( code ) { + + for ( const [ className, { mapName, events } ] of eventsByClass ) { + + const iface = buildEventMapInterface( mapName, events ); + + // Find the class declaration + const classPattern = new RegExp( `(?:export )?declare class ${ className }\\b` ); + const classMatch = classPattern.exec( code ); + if ( ! classMatch ) continue; + + // Find the start of the line containing the class declaration + const lineStart = code.lastIndexOf( '\n', classMatch.index ) + 1; + + // Insert the event map interface before the class declaration line + code = code.slice( 0, lineStart ) + iface + '\n\n' + code.slice( lineStart ); + + // Make the class generic (re-search after insertion) + code = code.replace( + new RegExp( `((?:export )?declare class ${ className })\\b` ), + `$1`, + ); + + // Replace event listener stubs with typed overloads in the class body + const classBodyStart = code.search( + new RegExp( `declare class ${ className } Date: Sun, 15 Mar 2026 18:35:13 +0900 Subject: [PATCH 6/8] Update --- utils/gen-dts.js | 63 +++++++++--------------------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/utils/gen-dts.js b/utils/gen-dts.js index 254aca1c9..1ad05c1d3 100644 --- a/utils/gen-dts.js +++ b/utils/gen-dts.js @@ -15,7 +15,7 @@ */ import { execSync } from 'child_process'; -import { existsSync, globSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'fs'; +import { existsSync, globSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'; import { tmpdir } from 'os'; import { dirname, join, relative, resolve, sep } from 'path'; import { rollup } from 'rollup'; @@ -58,14 +58,7 @@ const rootDirAbs = resolve( ROOT, rootDir ); // Step 1: scan all source files for @event ClassName#event-name annotations const eventsByClass = parseEventsByClass( resolvedFiles ); -// Step 2: delete stale .d.ts files from each module directory -for ( const indexFile of indexFiles ) { - - deleteDtsFiles( dirname( indexFile ) ); - -} - -// Step 3: emit .d.ts for all files to a temp directory in one tsc pass +// Step 2: emit .d.ts for all files to a temp directory in one tsc pass const tmpDir = mkdtempSync( join( tmpdir(), 'dts-' ) ); try { @@ -92,7 +85,7 @@ try { stripUnderscoreMembers(), injectEventMaps( eventsByClass ), ], - external: [ /^3d-tiles-renderer/ ], + external: id => ! id.startsWith( '.' ) && ! id.startsWith( '/' ), } ); await bundle.write( { file: outFile, format: 'es' } ); @@ -109,29 +102,6 @@ try { // --- Helpers --- - -/** - * Deletes all .d.ts files recursively under a directory. - */ -function deleteDtsFiles( dir ) { - - for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { - - const full = join( dir, entry.name ); - if ( entry.isDirectory() ) { - - deleteDtsFiles( full ); - - } else if ( entry.name.endsWith( '.d.ts' ) ) { - - unlinkSync( full ); - - } - - } - -} - /** * Resolves .js imports inside the temp directory to their .d.ts counterparts. */ @@ -162,25 +132,16 @@ function resolveDtsExtensions( tmpDir ) { */ function fixTypeAliasImportsInDir( dir ) { - for ( const entry of readdirSync( dir, { withFileTypes: true } ) ) { - - const full = join( dir, entry.name ); - if ( entry.isDirectory() ) { - - fixTypeAliasImportsInDir( full ); + for ( const full of globSync( `${ dir }/**/*.d.ts` ) ) { - } else if ( entry.name.endsWith( '.d.ts' ) ) { - - const code = readFileSync( full, 'utf8' ); - const result = code.replace( - /^export type (\w+) = import\(["']([^"']+)["']\)\.(\w+);$/gm, - ( _, local, pkg, exported ) => local === exported - ? `import type { ${ local } } from "${ pkg }";` - : `import type { ${ exported } as ${ local } } from "${ pkg }";`, - ); - if ( result !== code ) writeFileSync( full, result, 'utf8' ); - - } + const code = readFileSync( full, 'utf8' ); + const result = code.replace( + /^export type (\w+) = import\(["']([^"']+)["']\)\.(\w+);$/gm, + ( _, local, pkg, exported ) => local === exported + ? `import type { ${ local } } from "${ pkg }";` + : `import type { ${ exported } as ${ local } } from "${ pkg }";`, + ); + if ( result !== code ) writeFileSync( full, result, 'utf8' ); } From 0de6f53bd60eb1a5130048f5588b3a892213b84e Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Mon, 16 Mar 2026 12:49:30 +0900 Subject: [PATCH 7/8] Update dts gen --- src/babylonjs/renderer/index.d.ts | 50 ++++-- src/babylonjs/renderer/tiles/TilesRenderer.js | 1 + src/core/plugins/index.d.ts | 9 +- src/core/renderer/index.d.ts | 3 +- tsconfig.dts.json | 3 +- utils/gen-dts.js | 147 +++++++++++++++--- 6 files changed, 180 insertions(+), 33 deletions(-) diff --git a/src/babylonjs/renderer/index.d.ts b/src/babylonjs/renderer/index.d.ts index 78c282481..ee8de1e6f 100644 --- a/src/babylonjs/renderer/index.d.ts +++ b/src/babylonjs/renderer/index.d.ts @@ -1,12 +1,44 @@ -import { TilesRendererBase, TilesRendererBaseEventMap } from '3d-tiles-renderer/core'; -import { Scene } from '@babylonjs/core/scene'; +import type { Scene } from '@babylonjs/core/scene'; +import { TilesRendererBase } from '3d-tiles-renderer/core'; import { TransformNode } from '@babylonjs/core/Meshes/transformNode'; +import { Matrix } from '@babylonjs/core/Maths/math.vector'; -export class TilesRenderer extends TilesRendererBase> { - - group: TransformNode; - checkCollisions: boolean; - - constructor( url: string, scene: Scene ); - +import type { TilesRendererBaseEventMap } from "3d-tiles-renderer/core"; +/** + * @classdesc + * Babylon.js implementation of the 3D Tiles renderer. Manages tile loading, caching, traversal, + * and scene management using the Babylon.js scene graph and camera APIs. Dispatches all events + * defined by TilesRendererBase via Babylon.js Observables. + * @augments TilesRendererBase + */ +declare class TilesRenderer extends TilesRendererBase> { + /** + * @param {string} url - URL of the root tileset JSON. + * @param {Scene} scene - The Babylon.js scene to render tiles into. + */ + constructor(url: string, scene: Scene); + /** + * The Babylon.js scene tiles are rendered into. + * @type {Scene} + */ + scene: Scene; + /** + * Root node that all loaded tile scenes are parented to. + * @type {TransformNode} + */ + group: TransformNode; + /** + * Whether to enable collision checking on loaded tile meshes. + * @type {boolean} + */ + checkCollisions: boolean; + loadRootTileset(...args: any[]): any; + preprocessNode(tile: any, tilesetDir: any, parentTile?: any): void; + parseTile(buffer: any, tile: any, extension: any, uri: any, abortSignal: any): Promise; + disposeTile(tile: any): void; + setTileVisible(tile: any, visible: any): void; + calculateBytesUsed(tile: any): number; + calculateTileViewError(tile: any, target: any): void; } + +export { TilesRenderer }; diff --git a/src/babylonjs/renderer/tiles/TilesRenderer.js b/src/babylonjs/renderer/tiles/TilesRenderer.js index cb8fff0a2..e33949143 100644 --- a/src/babylonjs/renderer/tiles/TilesRenderer.js +++ b/src/babylonjs/renderer/tiles/TilesRenderer.js @@ -1,3 +1,4 @@ +/** @typedef {import('@babylonjs/core/scene').Scene} Scene */ import { TilesRendererBase, LoaderUtils } from '3d-tiles-renderer/core'; import { TransformNode } from '@babylonjs/core/Meshes/transformNode'; import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector'; diff --git a/src/core/plugins/index.d.ts b/src/core/plugins/index.d.ts index 6820d0ee1..0094178a8 100644 --- a/src/core/plugins/index.d.ts +++ b/src/core/plugins/index.d.ts @@ -1,4 +1,4 @@ -import { TilesRendererBase, LoaderBase } from '3d-tiles-renderer/core'; +import { LoaderBase } from '3d-tiles-renderer/core'; /** * @classdesc @@ -35,8 +35,9 @@ declare class CesiumIonAuth { refreshToken(options: any): any; } +import type { TilesRendererBase } from "3d-tiles-renderer/core"; /** - * @typedef {import('3d-tiles-renderer/core').TilesRendererBase} TilesRendererBase + * @typedef {TilesRendererBase} TilesRendererBase */ /** * @callback AssetTypeHandlerCallback @@ -98,7 +99,7 @@ declare class CesiumIonAuthPlugin { fetchData(uri: any, options: any): Promise; getAttributions(target: any): void; } - +import type { TilesRendererBase } from "3d-tiles-renderer/core"; type AssetTypeHandlerCallback = (type: string, tiles: TilesRendererBase, asset: any) => any; /** @@ -255,7 +256,7 @@ declare class EnforceNonZeroErrorPlugin { */ declare class QuantizedMeshLoaderBase extends LoaderBase { constructor(...args: any[]); - loadAsync(...args: any[]): Promise; + loadAsync(...args: any[]): Promise; parse(buffer: any): { header: { center: number[]; diff --git a/src/core/renderer/index.d.ts b/src/core/renderer/index.d.ts index 1144fcbb3..6868216bc 100644 --- a/src/core/renderer/index.d.ts +++ b/src/core/renderer/index.d.ts @@ -1,3 +1,4 @@ +import type { TilesRendererBase } from '3d-tiles-renderer/core'; type UnloadPriorityCallback = (a: any, b: any) => number; type RemoveCallback = (item: any) => any; /** @@ -367,7 +368,7 @@ type FilterCallback = (item: any) => boolean; * and a plugin system for extending rendering behavior. Engine-specific renderers * extend this class to add camera projection, scene management, and tile display. */ -interface TilesRendererBaseEventMap { +export interface TilesRendererBaseEventMap { 'needs-update': {}; 'load-content': {}; 'load-tileset': { tileset: Tileset; url: string }; diff --git a/tsconfig.dts.json b/tsconfig.dts.json index c820ce3ee..935b3f312 100644 --- a/tsconfig.dts.json +++ b/tsconfig.dts.json @@ -10,6 +10,7 @@ "rootDir": "src" }, "include": [ - "src/core" + "src/core", + "src/babylonjs" ] } diff --git a/utils/gen-dts.js b/utils/gen-dts.js index 1ad05c1d3..7854e4255 100644 --- a/utils/gen-dts.js +++ b/utils/gen-dts.js @@ -55,8 +55,9 @@ if ( ! indexFiles.length ) { const rootDirAbs = resolve( ROOT, rootDir ); -// Step 1: scan all source files for @event ClassName#event-name annotations +// Step 1: scan all source files for @event and @typedef annotations const eventsByClass = parseEventsByClass( resolvedFiles ); +const typedefImports = parseTypedefImports( resolvedFiles ); // Step 2: emit .d.ts for all files to a temp directory in one tsc pass const tmpDir = mkdtempSync( join( tmpdir(), 'dts-' ) ); @@ -67,8 +68,18 @@ try { { cwd: ROOT, stdio: 'inherit' }, ); - // Fix any invalid namespace imports tsc emitted (e.g. `import * as 3d_...`) - fixTypeAliasImportsInDir( tmpDir ); + // Step 3: fix invalid tsc-emitted imports on disk before rollup reads them + for ( const srcFile of resolvedFiles ) { + + const rel = relative( rootDirAbs, srcFile ).replace( /\.js$/, '.d.ts' ); + const tmpFile = join( tmpDir, rel ); + if ( ! existsSync( tmpFile ) ) continue; + + const original = readFileSync( tmpFile, 'utf8' ); + const fixed = fixTscEmittedImports( original ); + if ( fixed !== original ) writeFileSync( tmpFile, fixed ); + + } // Step 4: rollup-bundle each index.js into its own index.d.ts for ( const indexFile of indexFiles ) { @@ -90,6 +101,14 @@ try { await bundle.write( { file: outFile, format: 'es' } ); await bundle.close(); + + // Uncomment any // @import lines left by fixTscEmittedImports, then inject + // missing imports for @typedef {import('pkg').Type} Name annotations in sources + let out = readFileSync( outFile, 'utf8' ); + out = out.replace( /^\/\/ @import /gm, 'import ' ); + out = injectTypedefImports( out, typedefImports ); + writeFileSync( outFile, out ); + console.log( `Written: ${ outFile }` ); } @@ -102,6 +121,64 @@ try { // --- Helpers --- +/** + * Scans source files for @typedef {import('pkg').Export} LocalName annotations. + * Returns an array of { pkg, exportedName, localName } objects. + */ +function parseTypedefImports( sourceFiles ) { + + const results = []; + const seen = new Set(); + + for ( const filePath of sourceFiles ) { + + const source = readFileSync( filePath, 'utf8' ); + for ( const [ , pkg, exportedName, localName ] of source.matchAll( + /@typedef\s+\{import\(['"]([^'"]+)['"]\)\.(\w+)\}\s+(\w+)/g, + ) ) { + + const key = `${ pkg }:${ exportedName }:${ localName }`; + if ( ! seen.has( key ) ) { + + seen.add( key ); + results.push( { pkg, exportedName, localName } ); + + } + + } + + } + + return results; + +} + +/** + * For each typedef import whose localName appears in the bundle but has no import, + * prepends the appropriate import type statement. + */ +function injectTypedefImports( code, typedefImports ) { + + for ( const { pkg, exportedName, localName } of typedefImports ) { + + // Skip if already imported + const alreadyImported = new RegExp( `from ['"]${ pkg.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) }['"]` ).test( code ); + if ( alreadyImported ) continue; + + // Only inject if the localName is actually used in the bundle + if ( ! new RegExp( `\\b${ localName }\\b` ).test( code ) ) continue; + + const importLine = exportedName === localName + ? `import type { ${ localName } } from '${ pkg }';` + : `import type { ${ exportedName } as ${ localName } } from '${ pkg }';`; + code = importLine + '\n' + code; + + } + + return code; + +} + /** * Resolves .js imports inside the temp directory to their .d.ts counterparts. */ @@ -126,24 +203,53 @@ function resolveDtsExtensions( tmpDir ) { } /** - * Walks a directory and rewrites .d.ts files, converting tsc's `export type X = import("pkg").X` - * aliases to `import type { X } from "pkg"`. This avoids rollup-plugin-dts generating namespace - * imports for packages whose names are invalid JS identifiers (e.g. '3d-tiles-renderer/core'). + * Fixes tsc emission patterns that cause rollup-plugin-dts to fail on packages whose names + * are invalid JS identifiers (e.g. '3d-tiles-renderer/core'). Applied directly to .d.ts + * files on disk before rollup reads them. + * + * 1. tsc re-export aliases: `export type X = import("pkg").X` → `// @import type { X } from "pkg"` + * 2. tsc namespace imports with invalid identifiers: `import * as 3d_foo from "pkg"` → + * `// @import type { X, Y } from "pkg"` with inline references de-qualified + * 3. inline import expressions: `import("pkg").TypeName` → `TypeName` with hoisted import added */ -function fixTypeAliasImportsInDir( dir ) { +function fixTscEmittedImports( code ) { + + // Fix 1: re-export alias pattern + code = code.replace( + /^export type (\w+) = import\(["']([^"']+)["']\)\.(\w+);$/gm, + ( _, local, pkg, exported ) => local === exported + ? `// @import type { ${ local } } from "${ pkg }";` + : `// @import type { ${ exported } as ${ local } } from "${ pkg }";`, + ); + + // Fix 2: namespace imports whose alias starts with a digit (invalid identifier) + for ( const [ fullMatch, alias, pkg ] of code.matchAll( /^import \* as (\d\w*) from "([^"]+)";$/gm ) ) { + + const usedNames = [ ...new Set( + [ ...code.matchAll( new RegExp( `\\b${ alias }\\.(\\w+)`, 'g' ) ) ].map( m => m[ 1 ] ), + ) ]; + code = code.replace( fullMatch, `// @import type { ${ usedNames.join( ', ' ) } } from "${ pkg }";` ); + code = code.replace( new RegExp( `\\b${ alias }\\.(\\w+)`, 'g' ), '$1' ); + + } - for ( const full of globSync( `${ dir }/**/*.d.ts` ) ) { + // Fix 3: inline import("pkg").TypeName expressions — hoist to import statements + const byPkg = new Map(); + for ( const [ , pkg, name ] of code.matchAll( /import\(["']([^"']+)["']\)\.(\w+)/g ) ) { - const code = readFileSync( full, 'utf8' ); - const result = code.replace( - /^export type (\w+) = import\(["']([^"']+)["']\)\.(\w+);$/gm, - ( _, local, pkg, exported ) => local === exported - ? `import type { ${ local } } from "${ pkg }";` - : `import type { ${ exported } as ${ local } } from "${ pkg }";`, - ); - if ( result !== code ) writeFileSync( full, result, 'utf8' ); + if ( ! byPkg.has( pkg ) ) byPkg.set( pkg, new Set() ); + byPkg.get( pkg ).add( name ); } + for ( const [ pkg, names ] of byPkg ) { + + const importLine = `// @import type { ${ [ ...names ].join( ', ' ) } } from "${ pkg }";`; + code = importLine + '\n' + code; + code = code.replace( new RegExp( `import\\(["']${ pkg.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) }["']\\)\\.(\\w+)`, 'g' ), '$1' ); + + } + + return code; } @@ -193,7 +299,12 @@ function stripUnderscoreMembers() { } - return { code: out.join( '\n' ), map: null }; + let result = out.join( '\n' ); + + // Strip untyped event listener stubs — they shadow typed overloads from the base class + result = result.replace( /^[ \t]*(addEventListener|removeEventListener|hasEventListener|dispatchEvent)\([^)]*\bany\b[^)]*\):.*;\n/gm, '' ); + + return { code: result, map: null }; }, }; @@ -280,7 +391,7 @@ function jsDocTypeToTs( type ) { */ function buildEventMapInterface( mapName, events ) { - const lines = [ `interface ${ mapName } {` ]; + const lines = [ `export interface ${ mapName } {` ]; for ( const event of events ) { From 4565fc0f4b3fc14e9462c29490e35a9cd1383976 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Mon, 16 Mar 2026 13:00:58 +0900 Subject: [PATCH 8/8] Updates --- eslint.config.js | 2 +- utils/gen-dts.js | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 40e01b666..023e2d451 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -32,7 +32,7 @@ export default [ name: 'base rules', files: [ '**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx' ], languageOptions: { - ecmaVersion: 2020, + ecmaVersion: 2022, sourceType: 'module', globals: { ...globals.browser, diff --git a/utils/gen-dts.js b/utils/gen-dts.js index 7854e4255..62e4e58c1 100644 --- a/utils/gen-dts.js +++ b/utils/gen-dts.js @@ -48,7 +48,7 @@ const resolvedFiles = include.flatMap( p => globSync( `${ resolve( ROOT, p ) }/* const indexFiles = resolvedFiles.filter( f => f.endsWith( `${ sep }index.js` ) ); if ( ! indexFiles.length ) { - console.error( `No index.js found under include paths` ); + console.error( 'No index.js found under include paths' ); process.exit( 1 ); } @@ -241,6 +241,7 @@ function fixTscEmittedImports( code ) { byPkg.get( pkg ).add( name ); } + for ( const [ pkg, names ] of byPkg ) { const importLine = `// @import type { ${ [ ...names ].join( ', ' ) } } from "${ pkg }";`; @@ -301,10 +302,10 @@ function stripUnderscoreMembers() { let result = out.join( '\n' ); - // Strip untyped event listener stubs — they shadow typed overloads from the base class - result = result.replace( /^[ \t]*(addEventListener|removeEventListener|hasEventListener|dispatchEvent)\([^)]*\bany\b[^)]*\):.*;\n/gm, '' ); + // Strip untyped event listener stubs — they shadow typed overloads from the base class + result = result.replace( /^[ \t]*(addEventListener|removeEventListener|hasEventListener|dispatchEvent)\([^)]*\bany\b[^)]*\):.*;\n/gm, '' ); - return { code: result, map: null }; + return { code: result, map: null }; }, }; @@ -395,7 +396,7 @@ function buildEventMapInterface( mapName, events ) { for ( const event of events ) { - if ( event.deprecated ) lines.push( `\t/** @deprecated */` ); + if ( event.deprecated ) lines.push( '\t/** @deprecated */' ); const propStr = event.props.map( p => { @@ -409,7 +410,7 @@ function buildEventMapInterface( mapName, events ) { } - lines.push( `}` ); + lines.push( '}' ); return lines.join( '\n' ); } @@ -419,23 +420,23 @@ function buildEventMapInterface( mapName, events ) { */ function buildEventListenerOverloads() { - const T = `T extends keyof TEventMap`; - const typedCallback = `callback: ( event: TEventMap[ T ] & { type: T } ) => void`; - const anyCallback = `callback: ( event: any ) => void`; - const typedEvent = `event: TEventMap[ T ] & { type: T }`; + const T = 'T extends keyof TEventMap'; + const typedCallback = 'callback: ( event: TEventMap[ T ] & { type: T } ) => void'; + const anyCallback = 'callback: ( event: any ) => void'; + const typedEvent = 'event: TEventMap[ T ] & { type: T }'; return [ `\taddEventListener<${ T }>( name: T, ${ typedCallback } ): void;`, `\taddEventListener( name: string, ${ anyCallback } ): void;`, - ``, + '', `\tremoveEventListener<${ T }>( name: T, ${ typedCallback } ): void;`, `\tremoveEventListener( name: string, ${ anyCallback } ): void;`, - ``, + '', `\thasEventListener<${ T }>( name: T, ${ typedCallback } ): boolean;`, `\thasEventListener( name: string, ${ anyCallback } ): boolean;`, - ``, + '', `\tdispatchEvent<${ T }>( ${ typedEvent } ): void;`, - `\tdispatchEvent( event: { type: string } ): void;`, + '\tdispatchEvent( event: { type: string } ): void;', ].join( '\n' ); } @@ -514,11 +515,11 @@ function removeMethodDeclarations( code, methodNames ) { function replaceEventMethodsInClass( code, classStart ) { const openBrace = code.indexOf( '{', classStart ); - if ( openBrace === -1 ) return code; + if ( openBrace === - 1 ) return code; // Find the matching closing brace of the class let depth = 0; - let classEnd = -1; + let classEnd = - 1; for ( let i = openBrace; i < code.length; i ++ ) { const ch = code[ i ]; @@ -537,7 +538,7 @@ function replaceEventMethodsInClass( code, classStart ) { } - if ( classEnd === -1 ) return code; + if ( classEnd === - 1 ) return code; const before = code.slice( 0, openBrace + 1 ); let body = code.slice( openBrace + 1, classEnd );