@basemaps/lambda-tiler 7.7.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,6 +73,10 @@ export class SwappingLru {
73
73
  this.cacheB.clear();
74
74
  this.clears++;
75
75
  }
76
+ delete(id) {
77
+ this.cacheA.delete(id);
78
+ this.cacheB.delete(id);
79
+ }
76
80
  set(id, tiff) {
77
81
  this.cacheA.set(id, tiff);
78
82
  this.check();
@@ -1 +1 @@
1
- {"version":3,"file":"swapping.lru.js","sourceRoot":"","sources":["../../src/util/swapping.lru.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAW;IAYtB,YAAY,OAAe;QAX3B;;;;mBAAyB,IAAI,GAAG,EAAE;WAAC;QACnC;;;;mBAAyB,IAAI,GAAG,EAAE;WAAC;QACnC;;;;;WAAgB;QAEhB;;;;mBAAO,CAAC;WAAC;QACT;;;;mBAAS,CAAC;WAAC;QACX;;;;mBAAS,CAAC;WAAC;QACX;;;;mBAAS,CAAC;WAAC;QAEX;;;;mBAAiB,CAAC,CAAC;WAAC;QAGlB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,sBAAsB;IACtB,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,GAAG,CAAC,EAAU,EAAE,IAAO;QACrB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,sDAAsD;IACtD,KAAK;QACH,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC;YAAE,OAAO;QAC9B,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,qFAAqF;QACrF,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAChB,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,6DAA6D;IAC7D,IAAI,WAAW;QACb,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
1
+ {"version":3,"file":"swapping.lru.js","sourceRoot":"","sources":["../../src/util/swapping.lru.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAW;IAYtB,YAAY,OAAe;QAX3B;;;;mBAAyB,IAAI,GAAG,EAAE;WAAC;QACnC;;;;mBAAyB,IAAI,GAAG,EAAE;WAAC;QACnC;;;;;WAAgB;QAEhB;;;;mBAAO,CAAC;WAAC;QACT;;;;mBAAS,CAAC;WAAC;QACX;;;;mBAAS,CAAC;WAAC;QACX;;;;mBAAS,CAAC;WAAC;QAEX;;;;mBAAiB,CAAC,CAAC;WAAC;QAGlB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,sBAAsB;IACtB,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAED,GAAG,CAAC,EAAU,EAAE,IAAO;QACrB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAED,sDAAsD;IACtD,KAAK;QACH,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC;YAAE,OAAO;QAC9B,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,qFAAqF;QACrF,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAChB,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,6DAA6D;IAC7D,IAAI,WAAW;QACb,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basemaps/lambda-tiler",
3
- "version": "7.7.0",
3
+ "version": "7.9.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/linz/basemaps.git",
@@ -23,9 +23,9 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "@basemaps/config": "^7.7.0",
26
- "@basemaps/config-loader": "^7.7.0",
26
+ "@basemaps/config-loader": "^7.9.0",
27
27
  "@basemaps/geo": "^7.5.0",
28
- "@basemaps/shared": "^7.7.0",
28
+ "@basemaps/shared": "^7.9.0",
29
29
  "@basemaps/tiler": "^7.5.0",
30
30
  "@basemaps/tiler-sharp": "^7.7.0",
31
31
  "@linzjs/geojson": "^7.5.0",
@@ -55,5 +55,5 @@
55
55
  "@types/pixelmatch": "^5.0.0",
56
56
  "pretty-json-log": "^1.0.0"
57
57
  },
58
- "gitHead": "c8752c8791d1e2bab45f52cb10dc2d7df3c2a5a2"
58
+ "gitHead": "5dc4332d74563f6ca6ac644258728eb64ceacf6a"
59
59
  }
@@ -33,9 +33,9 @@ export const TileSetAerial: ConfigTileSetRaster = {
33
33
  export const TileSetVector: ConfigTileSetVector = {
34
34
  id: 'ts_topographic',
35
35
  type: TileSetType.Vector,
36
- name: 'topotgrpahic',
37
- description: 'topotgrpahic__description',
38
- title: 'topotgrpahic Imagery',
36
+ name: 'topographic',
37
+ description: 'topographic__description',
38
+ title: 'topographic Imagery',
39
39
  category: 'Basemap',
40
40
  layers: [
41
41
  {
@@ -4,7 +4,6 @@ import { afterEach, beforeEach, describe, it } from 'node:test';
4
4
  import { StyleJson } from '@basemaps/config';
5
5
  import { GoogleTms, Nztm2000QuadTms } from '@basemaps/geo';
6
6
  import { Env } from '@basemaps/shared';
7
- import { LambdaHttpResponse } from '@linzjs/lambda';
8
7
 
9
8
  import { convertRelativeUrl, convertStyleJson } from '../routes/tile.style.json.js';
10
9
 
@@ -153,12 +152,6 @@ describe('TileStyleJson', () => {
153
152
  });
154
153
  });
155
154
 
156
- it('should thrown error for NZTM2000Quad with vector source', () => {
157
- const converted = (): StyleJson => convertStyleJson(baseStyleJson, Nztm2000QuadTms, 'abc123', null);
158
-
159
- assert.throws(converted, LambdaHttpResponse);
160
- });
161
-
162
155
  it('should convert relative glyphs and sprites', () => {
163
156
  const apiKey = '0x9f9f';
164
157
  const converted = convertStyleJson(baseStyleJson, GoogleTms, apiKey, null);
@@ -380,4 +380,35 @@ describe('/v1/styles', () => {
380
380
  assert.deepEqual(terrain.source, 'LINZ-Terrain');
381
381
  assert.deepEqual(terrain.exaggeration, 1.2);
382
382
  });
383
+
384
+ it('should set labels via parameter', async () => {
385
+ config.put(TileSetAerial);
386
+ config.put(TileSetElevation);
387
+
388
+ const fakeStyle = { id: 'st_labels', name: 'labels', style: fakeVectorStyleConfig };
389
+ config.put(fakeStyle);
390
+
391
+ const request = mockUrlRequest('/v1/styles/aerial.json', `?terrain=LINZ-Terrain&labels=true`, Api.header);
392
+ const res = await handler.router.handle(request);
393
+ assert.equal(res.status, 200, res.statusDescription);
394
+
395
+ const body = JSON.parse(Buffer.from(res.body, 'base64').toString()) as StyleJson;
396
+ const terrain = body.terrain as unknown as Terrain;
397
+ assert.deepEqual(terrain.source, 'LINZ-Terrain');
398
+ assert.deepEqual(terrain.exaggeration, 1.2);
399
+
400
+ assert.equal(body.sources['basemaps_vector']?.type, 'vector');
401
+ assert.equal(body.layers.length, 2);
402
+ });
403
+
404
+ it('should error when joining layers with duplicate ids', async () => {
405
+ const fakeStyle = { id: 'st_labels', name: 'labels', style: fakeVectorStyleConfig };
406
+ config.put(fakeStyle);
407
+ config.put(TileSetAerial);
408
+
409
+ const request = mockUrlRequest('/v1/styles/labels.json', `?labels=true`, Api.header);
410
+ const res = await handler.router.handle(request);
411
+ assert.equal(res.status, 400, res.statusDescription);
412
+ assert.equal(res.statusDescription.includes('Background1'), true);
413
+ });
383
414
  });
@@ -48,9 +48,6 @@ export function convertStyleJson(
48
48
  const sources = JSON.parse(JSON.stringify(style.sources)) as Sources;
49
49
  for (const [key, value] of Object.entries(sources)) {
50
50
  if (value.type === 'vector') {
51
- if (tileMatrix !== GoogleTms) {
52
- throw new LambdaHttpResponse(400, `TileMatrix is not supported for the vector source ${value.url}.`);
53
- }
54
51
  value.url = convertRelativeUrl(value.url, tileMatrix, apiKey, config);
55
52
  } else if ((value.type === 'raster' || value.type === 'raster-dem') && Array.isArray(value.tiles)) {
56
53
  for (let i = 0; i < value.tiles.length; i++) {
@@ -72,6 +69,7 @@ export function convertStyleJson(
72
69
  if (style.glyphs) styleJson.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
73
70
  if (style.sprite) styleJson.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
74
71
  if (style.sky) styleJson.sky = style.sky;
72
+ if (style.terrain) styleJson.terrain = style.terrain;
75
73
 
76
74
  return styleJson;
77
75
  }
@@ -82,6 +80,13 @@ export interface StyleGet {
82
80
  };
83
81
  }
84
82
 
83
+ export interface StyleConfig {
84
+ /** Name of the terrain layer */
85
+ terrain?: string | null;
86
+ /** Combine layer with the labels layer */
87
+ labels: boolean;
88
+ }
89
+
85
90
  function setStyleTerrain(style: StyleJson, terrain: string, tileMatrix: TileMatrixSet): void {
86
91
  const source = Object.keys(style.sources).find((s) => s === terrain);
87
92
  if (source == null) throw new LambdaHttpResponse(400, `Terrain: ${terrain} is not exists in the style source.`);
@@ -91,6 +96,32 @@ function setStyleTerrain(style: StyleJson, terrain: string, tileMatrix: TileMatr
91
96
  };
92
97
  }
93
98
 
99
+ async function setStyleLabels(req: LambdaHttpRequest<StyleGet>, style: StyleJson): Promise<void> {
100
+ const config = await ConfigLoader.load(req);
101
+ const labels = await config.Style.get('labels');
102
+
103
+ if (labels == null) {
104
+ req.log.warn('LabelsStyle:Missing');
105
+ return;
106
+ }
107
+
108
+ const layerId = new Set<string>();
109
+ for (const l of style.layers) layerId.add(l.id);
110
+
111
+ for (const newLayers of labels.style.layers) {
112
+ if (layerId.has(newLayers.id)) {
113
+ throw new LambdaHttpResponse(400, 'Cannot merge styles with duplicate layerIds: ' + newLayers.id);
114
+ }
115
+ }
116
+
117
+ if (style.glyphs == null) style.glyphs = labels.style.glyphs;
118
+ if (style.sprite == null) style.sprite = labels.style.sprite;
119
+ if (style.sky == null) style.sky = labels.style.sky;
120
+
121
+ Object.assign(style.sources, labels.style.sources);
122
+ style.layers = style.layers.concat(labels.style.layers);
123
+ }
124
+
94
125
  async function ensureTerrain(
95
126
  req: LambdaHttpRequest<StyleGet>,
96
127
  tileMatrix: TileMatrixSet,
@@ -98,17 +129,16 @@ async function ensureTerrain(
98
129
  style: StyleJson,
99
130
  ): Promise<void> {
100
131
  const config = await ConfigLoader.load(req);
101
- const terrain = await config.TileSet.get('ts_elevation');
102
- if (terrain) {
103
- const configLocation = ConfigLoader.extract(req);
104
- const elevationQuery = toQueryString({ config: configLocation, api: apiKey, pipeline: 'terrain-rgb' });
105
- style.sources['LINZ-Terrain'] = {
106
- type: 'raster-dem',
107
- tileSize: 256,
108
- maxzoom: 18,
109
- tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
110
- };
111
- }
132
+ const terrain = await config.TileSet.get('elevation');
133
+ if (terrain == null) return;
134
+ const configLocation = ConfigLoader.extract(req);
135
+ const elevationQuery = toQueryString({ config: configLocation, api: apiKey, pipeline: 'terrain-rgb' });
136
+ style.sources['LINZ-Terrain'] = {
137
+ type: 'raster-dem',
138
+ tileSize: 256,
139
+ maxzoom: 18,
140
+ tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
141
+ };
112
142
  }
113
143
 
114
144
  export async function tileSetToStyle(
@@ -116,7 +146,7 @@ export async function tileSetToStyle(
116
146
  tileSet: ConfigTileSetRaster,
117
147
  tileMatrix: TileMatrixSet,
118
148
  apiKey: string,
119
- terrain?: string,
149
+ cfg: StyleConfig,
120
150
  ): Promise<LambdaHttpResponse> {
121
151
  const [tileFormat] = Validate.getRequestedFormats(req) ?? ['webp'];
122
152
  if (tileFormat == null) return new LambdaHttpResponse(400, 'Invalid image format');
@@ -144,9 +174,10 @@ export async function tileSetToStyle(
144
174
  await ensureTerrain(req, tileMatrix, apiKey, style);
145
175
 
146
176
  // Add terrain in style
147
- if (terrain) setStyleTerrain(style, terrain, tileMatrix);
177
+ if (cfg.terrain) setStyleTerrain(style, cfg.terrain, tileMatrix);
178
+ if (cfg.labels) await setStyleLabels(req, style);
148
179
 
149
- const data = Buffer.from(JSON.stringify(style));
180
+ const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
150
181
 
151
182
  const cacheKey = Etag.key(data);
152
183
  if (Etag.isNotModified(req, cacheKey)) return NotModified();
@@ -164,7 +195,7 @@ export async function tileSetOutputToStyle(
164
195
  tileSet: ConfigTileSetRaster,
165
196
  tileMatrix: TileMatrixSet,
166
197
  apiKey: string,
167
- terrain?: string,
198
+ cfg: StyleConfig,
168
199
  ): Promise<LambdaHttpResponse> {
169
200
  const configLocation = ConfigLoader.extract(req);
170
201
  const query = toQueryString({ config: configLocation, api: apiKey });
@@ -227,9 +258,10 @@ export async function tileSetOutputToStyle(
227
258
  await ensureTerrain(req, tileMatrix, apiKey, style);
228
259
 
229
260
  // Add terrain in style
230
- if (terrain) setStyleTerrain(style, terrain, tileMatrix);
261
+ if (cfg.terrain) setStyleTerrain(style, cfg.terrain, tileMatrix);
262
+ if (cfg.labels) await setStyleLabels(req, style);
231
263
 
232
- const data = Buffer.from(JSON.stringify(style));
264
+ const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
233
265
 
234
266
  const cacheKey = Etag.key(data);
235
267
  if (Etag.isNotModified(req, cacheKey)) return Promise.resolve(NotModified());
@@ -250,18 +282,22 @@ export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<La
250
282
  const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier);
251
283
  if (tileMatrix == null) return new LambdaHttpResponse(400, 'Invalid tile matrix');
252
284
  const terrain = req.query.get('terrain') ?? undefined;
285
+ const labels = Boolean(req.query.get('labels') ?? false);
253
286
 
254
287
  // Get style Config from db
255
288
  const config = await ConfigLoader.load(req);
256
289
  const dbId = config.Style.id(styleName);
257
290
  const styleConfig = await config.Style.get(dbId);
291
+
292
+ req.set('styleConfig', { terrain, labels });
293
+
258
294
  if (styleConfig == null) {
259
295
  // Were we given a tileset name instead, generated
260
296
  const tileSet = await config.TileSet.get(config.TileSet.id(styleName));
261
297
  if (tileSet == null) return NotFound();
262
298
  if (tileSet.type !== TileSetType.Raster) return NotFound();
263
- if (tileSet.outputs) return await tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, terrain);
264
- else return await tileSetToStyle(req, tileSet, tileMatrix, apiKey, terrain);
299
+ if (tileSet.outputs) return await tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, { terrain, labels });
300
+ else return await tileSetToStyle(req, tileSet, tileMatrix, apiKey, { terrain, labels });
265
301
  }
266
302
 
267
303
  // Prepare sources and add linz source
@@ -279,6 +315,7 @@ export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<La
279
315
 
280
316
  // Add terrain in style
281
317
  if (terrain) setStyleTerrain(style, terrain, tileMatrix);
318
+ if (labels) await setStyleLabels(req, style);
282
319
 
283
320
  const data = Buffer.from(JSON.stringify(style));
284
321
 
@@ -109,7 +109,6 @@ export const TileXyzRaster = {
109
109
  }
110
110
 
111
111
  // Remove with typescript >=5.5.0
112
-
113
112
  return (await Promise.all(toLoad)).filter((f) => f != null);
114
113
  },
115
114
 
@@ -0,0 +1,36 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { fsa, FsMemory } from '@chunkd/fs';
5
+
6
+ import { SourceCache } from '../source.cache.js';
7
+
8
+ describe('CoSourceCache', () => {
9
+ it('should not exit if a promise rejection happens for tiff', async () => {
10
+ const cache = new SourceCache(5);
11
+
12
+ const mem = new FsMemory();
13
+ const tiffLoc = new URL('memory://foo/bar.tiff');
14
+ await mem.write(tiffLoc, Buffer.from('ABC123'));
15
+ fsa.register('memory://', mem);
16
+
17
+ let failCount = 0;
18
+ await cache.getCog(tiffLoc).catch(() => failCount++);
19
+ assert.equal(cache.cache.currentSize, 0);
20
+ assert.equal(failCount, 1);
21
+ });
22
+
23
+ it('should not exit if a promise rejection happens for tar', async () => {
24
+ const cache = new SourceCache(5);
25
+
26
+ const mem = new FsMemory();
27
+ const tiffLoc = new URL('memory://foo/bar.tar');
28
+ await mem.write(tiffLoc, Buffer.from('ABC123'));
29
+ fsa.register('memory://', mem);
30
+
31
+ let failCount = 0;
32
+ await cache.getCotar(tiffLoc).catch(() => failCount++);
33
+ assert.equal(cache.cache.currentSize, 0);
34
+ assert.equal(failCount, 1);
35
+ });
36
+ });
@@ -7,54 +7,44 @@ export type LruStrut = LruStrutCotar | LruStrutCog;
7
7
  export interface LruStrutCotar {
8
8
  type: 'cotar';
9
9
  value: Promise<Cotar>;
10
- _value?: Cotar;
10
+ size: number;
11
11
  }
12
12
 
13
13
  export interface LruStrutCog {
14
14
  type: 'cog';
15
15
  value: Promise<Tiff>;
16
- _value?: Tiff;
17
- }
18
-
19
- class LruStrutObj<T extends LruStrut> {
20
- ob: T;
21
- constructor(ob: T) {
22
- this.ob = ob;
23
- if (this.ob._value == null) {
24
- void this.ob.value.then((c) => (this.ob._value = c));
25
- }
26
- }
27
-
28
- size = 1;
16
+ size: number;
29
17
  }
30
18
 
31
19
  export class SourceCache {
32
- cache: SwappingLru<LruStrutObj<LruStrutCotar | LruStrutCog>>;
20
+ cache: SwappingLru<LruStrut>;
33
21
  constructor(maxSize: number) {
34
- this.cache = new SwappingLru<LruStrutObj<LruStrut>>(maxSize);
22
+ this.cache = new SwappingLru<LruStrut>(maxSize);
35
23
  }
36
24
 
37
25
  getCog(location: URL): Promise<Tiff> {
38
- const existing = this.cache.get(location.href)?.ob;
26
+ const existing = this.cache.get(location.href);
39
27
 
40
28
  if (existing != null) {
41
29
  if (existing.type === 'cog') return existing.value;
42
30
  throw new Error(`Existing object of type: ${existing.type} made for location: ${location.href}`);
43
31
  }
44
32
  const value = Tiff.create(fsa.source(location));
45
- this.cache.set(location.href, new LruStrutObj({ type: 'cog', value }));
33
+ this.cache.set(location.href, { type: 'cog', value, size: 1 });
34
+ value.catch(() => this.cache.delete(location.href));
46
35
  return value;
47
36
  }
48
37
 
49
38
  getCotar(location: URL): Promise<Cotar> {
50
- const existing = this.cache.get(location.href)?.ob;
39
+ const existing = this.cache.get(location.href);
51
40
 
52
41
  if (existing != null) {
53
42
  if (existing.type === 'cotar') return existing.value;
54
43
  throw new Error(`Existing object of type: ${existing.type} made for location: ${location.href}`);
55
44
  }
56
45
  const value = Cotar.fromTar(fsa.source(location));
57
- this.cache.set(location.href, new LruStrutObj({ type: 'cotar', value }));
46
+ this.cache.set(location.href, { type: 'cotar', value, size: 1 });
47
+ value.catch(() => this.cache.delete(location.href));
58
48
  return value;
59
49
  }
60
50
  }
@@ -41,6 +41,11 @@ export class SwappingLru<T extends { size: number }> {
41
41
  this.clears++;
42
42
  }
43
43
 
44
+ delete(id: string): void {
45
+ this.cacheA.delete(id);
46
+ this.cacheB.delete(id);
47
+ }
48
+
44
49
  set(id: string, tiff: T): void {
45
50
  this.cacheA.set(id, tiff);
46
51
  this.check();