chiitiler 1.25.2 → 1.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Point it at any `style.json` and get back PNG / WebP / JPEG tiles, static images
14
14
  [![codecov](https://codecov.io/gh/Kanahiro/chiitiler/graph/badge.svg?token=9RVLAJG126)](https://codecov.io/gh/Kanahiro/chiitiler)
15
15
  [![License](https://img.shields.io/github/license/Kanahiro/chiitiler?color=blue)](./LICENSE)
16
16
 
17
- [Quickstart](#quickstart) · [HTTP API](#http-api) · [Library](#library-usage) · [Deployment](#deployment)
17
+ [Live Demo](https://spatialty-io.github.io/chiitiler-demo/) · [Quickstart](#quickstart) · [HTTP API](#http-api) · [Library](#library-usage) · [Deployment](#deployment)
18
18
 
19
19
  </div>
20
20
 
@@ -36,10 +36,10 @@ http://localhost:3000/tiles/0/0/0.png?url=https://tile.openstreetmap.jp/styles/o
36
36
 
37
37
  You're done. That same endpoint works as an XYZ tile source for Leaflet, MapLibre, OpenLayers, QGIS, Cesium, or anywhere else that speaks `{z}/{x}/{y}`.
38
38
 
39
- > **No Docker?** `npx tsx` works too:
39
+ > **No Docker?** Run it from source with [pnpm](https://pnpm.io):
40
40
  > ```bash
41
- > git clone https://github.com/Kanahiro/chiitiler && cd chiitiler && npm i
42
- > npx tsx src/main.ts tile-server --debug
41
+ > git clone https://github.com/Kanahiro/chiitiler && cd chiitiler && pnpm install
42
+ > pnpm tsx src/main.ts tile-server --debug
43
43
  > ```
44
44
  > Then visit `http://localhost:3000/debug` to preview styles interactively.
45
45
 
@@ -230,17 +230,18 @@ const cache = ChiitilerCache.withMemoryCache(
230
230
 
231
231
  ## Develop
232
232
 
233
- Requires Node.js 24.12+ and `sharp` system deps (see [Dockerfile](./Dockerfile)).
233
+ Requires Node.js 24.22.1+, pnpm (`corepack enable`) and `sharp` system deps (see [Dockerfile](./Dockerfile)).
234
234
 
235
235
  ```bash
236
236
  git clone https://github.com/Kanahiro/chiitiler.git
237
237
  cd chiitiler
238
- npm install
239
- npm run dev # tsx watch mode
240
- npm run test:unit # vitest
241
- npm run test:integration # end-to-end
242
- npm run test:benchmark # see bench/BENCHMARK.md
243
- npm run build # bundle to build/main.cjs
238
+ pnpm install
239
+ pnpm dev # tsx watch mode
240
+ pnpm lint # biome lint + format check
241
+ pnpm test:unit # vitest
242
+ pnpm test:integration # end-to-end
243
+ pnpm test:benchmark # see bench/BENCHMARK.md
244
+ pnpm build # bundle to build/main.cjs
244
245
  ```
245
246
 
246
247
  ## Architecture
@@ -1,7 +1,7 @@
1
- import { type Cache } from './index.js';
1
+ import type { Cache } from './index.js';
2
2
  type fileCacheOptions = {
3
3
  dir: string;
4
4
  ttl: number;
5
5
  };
6
6
  declare const fileCache: (options: fileCacheOptions) => Cache;
7
- export { fileCache };
7
+ export { fileCache, type fileCacheOptions };
@@ -1,5 +1,5 @@
1
1
  import { Cache as FileSystemCache } from 'file-system-cache';
2
- const fileCache = function (options) {
2
+ const fileCache = (options) => {
3
3
  const cache = new FileSystemCache({
4
4
  basePath: options.dir,
5
5
  hash: 'sha1',
@@ -7,7 +7,7 @@ const fileCache = function (options) {
7
7
  });
8
8
  return {
9
9
  name: 'file',
10
- set: async function (key, value) {
10
+ set: async (key, value) => {
11
11
  try {
12
12
  await cache.set(key, value.toString('hex'));
13
13
  }
@@ -15,7 +15,7 @@ const fileCache = function (options) {
15
15
  console.error(e);
16
16
  }
17
17
  },
18
- get: async function (key) {
18
+ get: async (key) => {
19
19
  try {
20
20
  const val = await cache.get(key, undefined);
21
21
  if (val === undefined)
@@ -37,12 +37,15 @@ export function withMemoryCache(backing, { ttlSeconds = 10, maxBytes = 64 * 1024
37
37
  const pending = reads.get(key);
38
38
  if (pending !== undefined)
39
39
  return pending;
40
- const read = backing.get(key).then((value) => {
40
+ const read = backing
41
+ .get(key)
42
+ .then((value) => {
41
43
  // A newer write must not be overwritten by an older read.
42
44
  if (value !== undefined && reads.get(key) === read)
43
45
  memory.set(key, value);
44
46
  return value;
45
- }).finally(() => {
47
+ })
48
+ .finally(() => {
46
49
  if (reads.get(key) === read)
47
50
  reads.delete(key);
48
51
  });
@@ -1,4 +1,4 @@
1
- import { type Cache } from './index.js';
1
+ import type { Cache } from './index.js';
2
2
  type GCSCacheOptions = {
3
3
  bucket: string;
4
4
  prefix?: string;
@@ -7,4 +7,4 @@ type GCSCacheOptions = {
7
7
  apiEndpoint?: string;
8
8
  };
9
9
  declare function gcsCache(options: GCSCacheOptions): Cache;
10
- export { gcsCache };
10
+ export { gcsCache, type GCSCacheOptions };
package/dist/cache/gcs.js CHANGED
@@ -11,12 +11,14 @@ function gcsCache(options) {
11
11
  const bucket = storageClient.bucket(options.bucket);
12
12
  function bucketFile(key) {
13
13
  const name = escapeFileName(key);
14
- const nameWithPrefix = options.prefix ? `${options.prefix}/${name}` : name;
14
+ const nameWithPrefix = options.prefix
15
+ ? `${options.prefix}/${name}`
16
+ : name;
15
17
  return bucket.file(nameWithPrefix);
16
18
  }
17
19
  return {
18
20
  name: 'gcs',
19
- set: async function (key, value) {
21
+ set: async (key, value) => {
20
22
  const file = bucketFile(key);
21
23
  try {
22
24
  await file.save(value, {
@@ -27,7 +29,7 @@ function gcsCache(options) {
27
29
  console.log(`[error]: ${e}`);
28
30
  }
29
31
  },
30
- get: async function (key) {
32
+ get: async (key) => {
31
33
  const file = bucketFile(key);
32
34
  try {
33
35
  const [content] = await file.download();
@@ -3,6 +3,10 @@ import { s3Cache } from './s3.js';
3
3
  import { gcsCache } from './gcs.js';
4
4
  import { fileCache } from './file.js';
5
5
  export { withMemoryCache } from './front.js';
6
+ export type { MemoryCacheOptions } from './memory.js';
7
+ export type { S3CacheOptions } from './s3.js';
8
+ export type { GCSCacheOptions } from './gcs.js';
9
+ export type { fileCacheOptions } from './file.js';
6
10
  type Value = Buffer;
7
11
  type Cache = {
8
12
  name: string;
@@ -12,4 +16,4 @@ type Cache = {
12
16
  memory?: Cache;
13
17
  };
14
18
  declare const noneCache: () => Cache;
15
- export { noneCache, memoryCache, s3Cache, gcsCache, fileCache, type Value, type Cache };
19
+ export { noneCache, memoryCache, s3Cache, gcsCache, fileCache, type Value, type Cache, };
@@ -8,4 +8,4 @@ const noneCache = () => ({
8
8
  get: async () => undefined,
9
9
  set: async () => undefined,
10
10
  });
11
- export { noneCache, memoryCache, s3Cache, gcsCache, fileCache };
11
+ export { noneCache, memoryCache, s3Cache, gcsCache, fileCache, };
@@ -1,7 +1,7 @@
1
- import { type Cache } from './index.js';
1
+ import type { Cache } from './index.js';
2
2
  type MemoryCacheOptions = {
3
3
  ttl: number;
4
4
  maxItemCount: number;
5
5
  };
6
6
  declare const memoryCache: (options: MemoryCacheOptions) => Cache;
7
- export { memoryCache };
7
+ export { memoryCache, type MemoryCacheOptions };
@@ -1,15 +1,15 @@
1
1
  import { LRUCache } from 'lru-cache';
2
- const memoryCache = function (options) {
2
+ const memoryCache = (options) => {
3
3
  const MEMORY_CACHE = new LRUCache({
4
4
  max: options.maxItemCount,
5
5
  ttl: options.ttl * 1000,
6
6
  });
7
7
  return {
8
8
  name: 'memory',
9
- set: async function (key, value) {
9
+ set: async (key, value) => {
10
10
  MEMORY_CACHE.set(key, value);
11
11
  },
12
- get: async function (key) {
12
+ get: async (key) => {
13
13
  const item = MEMORY_CACHE.get(key);
14
14
  return item;
15
15
  },
@@ -1,4 +1,4 @@
1
- import { type Cache } from './index.js';
1
+ import type { Cache } from './index.js';
2
2
  type S3CacheOptions = {
3
3
  bucket: string;
4
4
  region: string;
@@ -6,4 +6,4 @@ type S3CacheOptions = {
6
6
  forcePathStyle?: boolean;
7
7
  };
8
8
  declare function s3Cache(options: S3CacheOptions): Cache;
9
- export { s3Cache };
9
+ export { s3Cache, type S3CacheOptions };
package/dist/cache/s3.js CHANGED
@@ -8,7 +8,7 @@ function s3Cache(options) {
8
8
  });
9
9
  return {
10
10
  name: 's3',
11
- set: async function (key, value) {
11
+ set: async (key, value) => {
12
12
  try {
13
13
  const cmd = new PutObjectCommand({
14
14
  Bucket: options.bucket,
@@ -21,7 +21,7 @@ function s3Cache(options) {
21
21
  console.log(`[error]: ${e}`);
22
22
  }
23
23
  },
24
- get: async function (key) {
24
+ get: async (key) => {
25
25
  const cmd = new GetObjectCommand({
26
26
  Bucket: options.bucket,
27
27
  Key: escapeFileName(key),
package/dist/cli.js CHANGED
@@ -112,16 +112,21 @@ export function createProgram() {
112
112
  .option('-p, --port <port>', 'port number')
113
113
  .option('--user-agent <user-agent>', 'User-Agent header for outbound HTTP requests')
114
114
  .addOption(new Option('--prewarm [boolean]', 'startup renderer warmup')
115
- .choices(['true', 'false']).preset('true').env('CHIITILER_PREWARM').default('true'))
115
+ .choices(['true', 'false'])
116
+ .preset('true')
117
+ .env('CHIITILER_PREWARM')
118
+ .default('true'))
116
119
  .addOption(new Option('--processes <n>', 'worker processes (0 for all CPUs)')
117
- .env('CHIITILER_PROCESSES').argParser(parseProcesses).default(1))
118
- .addOption(new Option('--front-cache-ttl-sec <seconds>', 'memory front cache TTL (0 to disable)')
119
- .env('CHIITILER_FRONT_CACHE_TTL_SEC'))
120
- .addOption(new Option('--front-cache-max-bytes <bytes>', 'memory front cache byte limit (0 to disable)')
121
- .env('CHIITILER_FRONT_CACHE_MAX_BYTES'))
120
+ .env('CHIITILER_PROCESSES')
121
+ .argParser(parseProcesses)
122
+ .default(1))
123
+ .addOption(new Option('--front-cache-ttl-sec <seconds>', 'memory front cache TTL (0 to disable)').env('CHIITILER_FRONT_CACHE_TTL_SEC'))
124
+ .addOption(new Option('--front-cache-max-bytes <bytes>', 'memory front cache byte limit (0 to disable)').env('CHIITILER_FRONT_CACHE_MAX_BYTES'))
122
125
  .option('-D, --debug', 'debug mode')
123
126
  .action(async (options) => {
124
- const numProcesses = options.processes === 0 ? availableParallelism() : options.processes;
127
+ const numProcesses = options.processes === 0
128
+ ? availableParallelism()
129
+ : options.processes;
125
130
  if (cluster.isPrimary && numProcesses !== 1) {
126
131
  // Workers inherit argv and env, and initialize their own renderer and cache.
127
132
  for (let i = 0; i < numProcesses; i++)
@@ -154,8 +159,12 @@ export function createProgram() {
154
159
  const ttl = options.frontCacheTtlSec;
155
160
  const size = options.frontCacheMaxBytes;
156
161
  serverOptions.cache = caches.withMemoryCache(serverOptions.cache, {
157
- ttlSeconds: ttl === undefined ? undefined : Number(ttl.trim() || NaN),
158
- maxBytes: size === undefined ? undefined : Number(size.trim() || NaN),
162
+ ttlSeconds: ttl === undefined
163
+ ? undefined
164
+ : Number(ttl.trim() || NaN),
165
+ maxBytes: size === undefined
166
+ ? undefined
167
+ : Number(size.trim() || NaN),
159
168
  });
160
169
  }
161
170
  if (serverOptions.debug) {
package/dist/gcs.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Storage } from '@google-cloud/storage';
2
- declare const getStorageClient: ({ projectId, keyFilename, apiEndpoint }: {
2
+ declare const getStorageClient: ({ projectId, keyFilename, apiEndpoint, }: {
3
3
  projectId?: string;
4
4
  keyFilename?: string;
5
5
  apiEndpoint?: string;
package/dist/gcs.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Storage } from '@google-cloud/storage';
2
2
  let storageClient; // singleton
3
- const getStorageClient = function ({ projectId, keyFilename, apiEndpoint }) {
3
+ const getStorageClient = ({ projectId, keyFilename, apiEndpoint, }) => {
4
4
  if (storageClient !== undefined)
5
5
  return storageClient;
6
6
  const storageOptions = {
7
7
  projectId,
8
8
  keyFilename,
9
- apiEndpoint
9
+ apiEndpoint,
10
10
  };
11
11
  storageClient = new Storage(storageOptions);
12
12
  return storageClient;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getRenderedClip, getRenderedTile, getRenderedCamera, GetRenderedClipOptions, GetRenderedTileOptions, GetRenderedCameraOptions } from './render/index.js';
1
+ import { getRenderedClip, getRenderedTile, getRenderedCamera, type GetRenderedClipOptions, type GetRenderedTileOptions, type GetRenderedCameraOptions } from './render/index.js';
2
2
  export declare function getRenderedClipBuffer(options: GetRenderedClipOptions): Promise<Buffer>;
3
3
  export { getRenderedClip as getRenderedClipStream };
4
4
  export declare function getRenderedCameraBuffer(options: GetRenderedCameraOptions): Promise<Buffer>;
package/dist/main.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import process from 'node:process';
2
2
  import { createProgram } from './cli.js';
3
- createProgram().parseAsync(process.argv).catch((error) => {
3
+ createProgram()
4
+ .parseAsync(process.argv)
5
+ .catch((error) => {
4
6
  console.error(error);
5
7
  process.exitCode = 1;
6
8
  });
@@ -20,7 +20,7 @@ async function getRenderPool(style, cache, mode) {
20
20
  const newPool = new Pool({
21
21
  create: () => {
22
22
  const map = new mbgl.Map({
23
- request: function (req, callback) {
23
+ request: (req, callback) => {
24
24
  getSource(req.url, cache)
25
25
  .then((buf) => {
26
26
  if (buf !== null && buf.length > 0) {
@@ -41,7 +41,7 @@ async function getRenderPool(style, cache, mode) {
41
41
  });
42
42
  },
43
43
  ratio: 1,
44
- // @ts-ignore
44
+ // @ts-expect-error
45
45
  mode,
46
46
  });
47
47
  map.load(style);
@@ -10,7 +10,7 @@ async function render(style, renderOptions, cache, mode) {
10
10
  const pool = await getRenderPool(style, cache, mode);
11
11
  const worker = await pool.acquire();
12
12
  const rendered = new Promise((resolve, reject) => {
13
- worker.render(renderOptions, function (err, buffer) {
13
+ worker.render(renderOptions, (err, buffer) => {
14
14
  pool.release(worker);
15
15
  if (err) {
16
16
  reject(err);
package/dist/s3.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { S3Client } from '@aws-sdk/client-s3';
2
2
  let s3Client; // singleton
3
- const getS3Client = function ({ region, endpoint, forcePathStyle, }) {
3
+ const getS3Client = ({ region, endpoint, forcePathStyle, }) => {
4
4
  if (s3Client !== undefined)
5
5
  return s3Client;
6
6
  const s3ClientConfig = {
@@ -1,4 +1,4 @@
1
- import { Context } from 'hono';
1
+ import type { Context } from 'hono';
2
2
  declare function getDebugPage(c: Context): Response;
3
3
  declare function getEditorPage(c: Context): Response;
4
4
  export { getDebugPage, getEditorPage };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from 'hono/quick';
2
- import { type Cache } from '../cache/index.js';
2
+ import type { Cache } from '../cache/index.js';
3
3
  type InitServerOptions = {
4
4
  cache: Cache;
5
5
  port: number;
@@ -1,4 +1,4 @@
1
- import { Cache } from '../../cache/index.js';
1
+ import type { Cache } from '../../cache/index.js';
2
2
  declare function createCameraRouter(options: {
3
3
  cache: Cache;
4
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
@@ -1,4 +1,4 @@
1
- import { Cache } from '../../cache/index.js';
1
+ import type { Cache } from '../../cache/index.js';
2
2
  declare function createClipRouter(options: {
3
3
  cache: Cache;
4
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
@@ -1,4 +1,4 @@
1
- import { Cache } from '../../cache/index.js';
1
+ import type { Cache } from '../../cache/index.js';
2
2
  declare function createTilesRouter(options: {
3
3
  cache: Cache;
4
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
@@ -17,7 +17,7 @@ function createTilesRouter(options) {
17
17
  // path params
18
18
  const z = Number(c.req.param('z'));
19
19
  const x = Number(c.req.param('x'));
20
- let [_y, ext] = c.req.param('y_ext').split('.');
20
+ const [_y, ext] = c.req.param('y_ext').split('.');
21
21
  const y = Number(_y);
22
22
  if (!isValidXyz(x, y, z))
23
23
  return c.body('invalid xyz', 400);
@@ -59,7 +59,7 @@ function createTilesRouter(options) {
59
59
  // path params
60
60
  const z = Number(c.req.param('z'));
61
61
  const x = Number(c.req.param('x'));
62
- let [_y, ext] = c.req.param('y_ext').split('.');
62
+ const [_y, ext] = c.req.param('y_ext').split('.');
63
63
  const y = Number(_y);
64
64
  if (!isValidXyz(x, y, z))
65
65
  return c.body('invalid xyz', 400);
@@ -1,4 +1,4 @@
1
- import { Cache } from '../cache/index.js';
1
+ import { type Cache } from '../cache/index.js';
2
2
  /**
3
3
  * retrieve sources from the uri.
4
4
  * Concurrent requests for the same uri share a single in-flight fetch
@@ -26,7 +26,7 @@ async function getMbtilesSource(uri) {
26
26
  const [z, x, y] = uri
27
27
  .replace(`mbtiles://${mbtilesFilepath}/`, '')
28
28
  .split('/');
29
- const ty = Math.pow(2, Number(z)) - 1 - Number(y);
29
+ const ty = 2 ** Number(z) - 1 - Number(y);
30
30
  const row = statement.get(z, x, ty);
31
31
  if (!row)
32
32
  return null;
@@ -51,7 +51,8 @@ class S3Source {
51
51
  }));
52
52
  }
53
53
  catch (e) {
54
- if (e instanceof Error && e.name === 'PreconditionFailed') {
54
+ if (e instanceof Error &&
55
+ e.name === 'PreconditionFailed') {
55
56
  throw new Error('etag mismatch');
56
57
  }
57
58
  throw e;
@@ -111,7 +112,9 @@ async function getPmtilesSource(uri, cache = noneCache()) {
111
112
  }
112
113
  }
113
114
  const [z, x, y] = uri.replace(`pmtiles://${pmtilesUri}/`, '').split('/');
114
- const tile = await pmtiles.getZxy(Number(z), Number(x), Number(y)).catch((err) => {
115
+ const tile = await pmtiles
116
+ .getZxy(Number(z), Number(x), Number(y))
117
+ .catch((err) => {
115
118
  // SharedPromiseCache retains rejected metadata promises. Rebuild after a
116
119
  // failure, without evicting a replacement created by a newer request.
117
120
  if (pmtilesCache.get(pmtilesUri) === pmtiles)
package/package.json CHANGED
@@ -1,21 +1,27 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chiitiler",
4
- "version": "1.25.2",
4
+ "version": "1.25.3",
5
5
  "description": "Tiny map rendering server for MapLibre Style Spec",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "engines": {
9
+ "node": ">=24.22.1"
10
+ },
8
11
  "files": [
9
12
  "dist"
10
13
  ],
11
14
  "scripts": {
12
- "build": "npx esbuild --bundle src/main.ts --minify --outfile=build/main.cjs --platform=node --external:@maplibre/maplibre-gl-native --external:sharp",
15
+ "build": "esbuild --bundle src/main.ts --minify --outfile=build/main.cjs --platform=node --external:@maplibre/maplibre-gl-native --external:sharp",
13
16
  "check": "tsc --noEmit",
14
17
  "dev": "tsx watch src/main.ts tile-server -D",
15
18
  "test:unit": "vitest src",
16
19
  "test:coverage": "vitest src --coverage --coverage.provider=v8",
17
20
  "test:integration": "vitest tests/integration.test.ts",
18
- "test:benchmark": "tsx tests/benchmark.ts"
21
+ "test:benchmark": "tsx tests/benchmark.ts",
22
+ "docs": "typedoc src/index.ts --out docs --includeVersion",
23
+ "lint": "biome check .",
24
+ "format": "biome check --write ."
19
25
  },
20
26
  "keywords": [],
21
27
  "author": "Kanahiro Iguchi",
@@ -24,6 +30,7 @@
24
30
  "url": "https://github.com/Kanahiro/chiitiler"
25
31
  },
26
32
  "devDependencies": {
33
+ "@biomejs/biome": "^2.6.6",
27
34
  "@tsconfig/node24": "^24.0.5",
28
35
  "@types/autocannon": "^7.12.7",
29
36
  "@types/node": "^25.9.6",
@@ -32,6 +39,7 @@
32
39
  "esbuild": "^0.28.2",
33
40
  "image-size": "^2.0.2",
34
41
  "tsx": "^4.23.13",
42
+ "typedoc": "^0.28.20",
35
43
  "typescript": "^6.0.3",
36
44
  "vitest": "^4.1.4"
37
45
  },
@@ -50,5 +58,6 @@
50
58
  "lru-cache": "^11.5.2",
51
59
  "pmtiles": "^4.5.0",
52
60
  "sharp": "^0.35.4"
53
- }
61
+ },
62
+ "packageManager": "pnpm@12.18.0"
54
63
  }