@basemaps/lambda-tiler 7.6.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/build/__tests__/config.data.js +3 -3
  3. package/build/__tests__/config.data.js.map +1 -1
  4. package/build/__tests__/tile.style.json.test.js +0 -5
  5. package/build/__tests__/tile.style.json.test.js.map +1 -1
  6. package/build/routes/__tests__/imagery.test.js +39 -1
  7. package/build/routes/__tests__/imagery.test.js.map +1 -1
  8. package/build/routes/__tests__/tile.style.json.test.js +24 -0
  9. package/build/routes/__tests__/tile.style.json.test.js.map +1 -1
  10. package/build/routes/imagery.js +4 -0
  11. package/build/routes/imagery.js.map +1 -1
  12. package/build/routes/tile.style.json.d.ts +8 -2
  13. package/build/routes/tile.style.json.js +59 -27
  14. package/build/routes/tile.style.json.js.map +1 -1
  15. package/build/routes/tile.xyz.raster.js.map +1 -1
  16. package/build/util/__test__/cache.test.d.ts +1 -0
  17. package/build/util/__test__/cache.test.js +29 -0
  18. package/build/util/__test__/cache.test.js.map +1 -0
  19. package/build/util/source.cache.d.ts +2 -8
  20. package/build/util/source.cache.js +6 -24
  21. package/build/util/source.cache.js.map +1 -1
  22. package/build/util/swapping.lru.d.ts +1 -0
  23. package/build/util/swapping.lru.js +4 -0
  24. package/build/util/swapping.lru.js.map +1 -1
  25. package/package.json +6 -6
  26. package/src/__tests__/config.data.ts +3 -3
  27. package/src/__tests__/tile.style.json.test.ts +0 -7
  28. package/src/routes/__tests__/imagery.test.ts +51 -1
  29. package/src/routes/__tests__/tile.style.json.test.ts +31 -0
  30. package/src/routes/imagery.ts +4 -0
  31. package/src/routes/tile.style.json.ts +63 -25
  32. package/src/routes/tile.xyz.raster.ts +0 -1
  33. package/src/util/__test__/cache.test.ts +36 -0
  34. package/src/util/source.cache.ts +10 -20
  35. package/src/util/swapping.lru.ts +5 -0
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,29 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { fsa, FsMemory } from '@chunkd/fs';
4
+ import { SourceCache } from '../source.cache.js';
5
+ describe('CoSourceCache', () => {
6
+ it('should not exit if a promise rejection happens for tiff', async () => {
7
+ const cache = new SourceCache(5);
8
+ const mem = new FsMemory();
9
+ const tiffLoc = new URL('memory://foo/bar.tiff');
10
+ await mem.write(tiffLoc, Buffer.from('ABC123'));
11
+ fsa.register('memory://', mem);
12
+ let failCount = 0;
13
+ await cache.getCog(tiffLoc).catch(() => failCount++);
14
+ assert.equal(cache.cache.currentSize, 0);
15
+ assert.equal(failCount, 1);
16
+ });
17
+ it('should not exit if a promise rejection happens for tar', async () => {
18
+ const cache = new SourceCache(5);
19
+ const mem = new FsMemory();
20
+ const tiffLoc = new URL('memory://foo/bar.tar');
21
+ await mem.write(tiffLoc, Buffer.from('ABC123'));
22
+ fsa.register('memory://', mem);
23
+ let failCount = 0;
24
+ await cache.getCotar(tiffLoc).catch(() => failCount++);
25
+ assert.equal(cache.cache.currentSize, 0);
26
+ assert.equal(failCount, 1);
27
+ });
28
+ });
29
+ //# sourceMappingURL=cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../../../src/util/__test__/cache.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,MAAM,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACjD,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAE/B,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;QACrD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,MAAM,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,sBAAsB,CAAC,CAAC;QAChD,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAE/B,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -4,24 +4,18 @@ export type LruStrut = LruStrutCotar | LruStrutCog;
4
4
  export interface LruStrutCotar {
5
5
  type: 'cotar';
6
6
  value: Promise<Cotar>;
7
- _value?: Cotar;
7
+ size: number;
8
8
  }
9
9
  export interface LruStrutCog {
10
10
  type: 'cog';
11
11
  value: Promise<Tiff>;
12
- _value?: Tiff;
13
- }
14
- declare class LruStrutObj<T extends LruStrut> {
15
- ob: T;
16
- constructor(ob: T);
17
12
  size: number;
18
13
  }
19
14
  export declare class SourceCache {
20
- cache: SwappingLru<LruStrutObj<LruStrutCotar | LruStrutCog>>;
15
+ cache: SwappingLru<LruStrut>;
21
16
  constructor(maxSize: number);
22
17
  getCog(location: URL): Promise<Tiff>;
23
18
  getCotar(location: URL): Promise<Cotar>;
24
19
  }
25
20
  /** Cache the last 5,000 tiff/tar files accessed, generally it is only <100KB of memory used per tiff file so approx 50MB of cache */
26
21
  export declare const CoSources: SourceCache;
27
- export {};
@@ -1,25 +1,5 @@
1
1
  import { Cotar, fsa, Tiff } from '@basemaps/shared';
2
2
  import { SwappingLru } from './swapping.lru.js';
3
- class LruStrutObj {
4
- constructor(ob) {
5
- Object.defineProperty(this, "ob", {
6
- enumerable: true,
7
- configurable: true,
8
- writable: true,
9
- value: void 0
10
- });
11
- Object.defineProperty(this, "size", {
12
- enumerable: true,
13
- configurable: true,
14
- writable: true,
15
- value: 1
16
- });
17
- this.ob = ob;
18
- if (this.ob._value == null) {
19
- void this.ob.value.then((c) => (this.ob._value = c));
20
- }
21
- }
22
- }
23
3
  export class SourceCache {
24
4
  constructor(maxSize) {
25
5
  Object.defineProperty(this, "cache", {
@@ -31,25 +11,27 @@ export class SourceCache {
31
11
  this.cache = new SwappingLru(maxSize);
32
12
  }
33
13
  getCog(location) {
34
- const existing = this.cache.get(location.href)?.ob;
14
+ const existing = this.cache.get(location.href);
35
15
  if (existing != null) {
36
16
  if (existing.type === 'cog')
37
17
  return existing.value;
38
18
  throw new Error(`Existing object of type: ${existing.type} made for location: ${location.href}`);
39
19
  }
40
20
  const value = Tiff.create(fsa.source(location));
41
- this.cache.set(location.href, new LruStrutObj({ type: 'cog', value }));
21
+ this.cache.set(location.href, { type: 'cog', value, size: 1 });
22
+ value.catch(() => this.cache.delete(location.href));
42
23
  return value;
43
24
  }
44
25
  getCotar(location) {
45
- const existing = this.cache.get(location.href)?.ob;
26
+ const existing = this.cache.get(location.href);
46
27
  if (existing != null) {
47
28
  if (existing.type === 'cotar')
48
29
  return existing.value;
49
30
  throw new Error(`Existing object of type: ${existing.type} made for location: ${location.href}`);
50
31
  }
51
32
  const value = Cotar.fromTar(fsa.source(location));
52
- this.cache.set(location.href, new LruStrutObj({ type: 'cotar', value }));
33
+ this.cache.set(location.href, { type: 'cotar', value, size: 1 });
34
+ value.catch(() => this.cache.delete(location.href));
53
35
  return value;
54
36
  }
55
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"source.cache.js","sourceRoot":"","sources":["../../src/util/source.cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBhD,MAAM,WAAW;IAEf,YAAY,EAAK;QADjB;;;;;WAAM;QAQN;;;;mBAAO,CAAC;WAAC;QANP,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YAC3B,KAAK,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CAGF;AAED,MAAM,OAAO,WAAW;IAEtB,YAAY,OAAe;QAD3B;;;;;WAA6D;QAE3D,IAAI,CAAC,KAAK,GAAG,IAAI,WAAW,CAAwB,OAAO,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,CAAC,QAAa;QAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAEnD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,uBAAuB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ,CAAC,QAAa;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAEnD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;YACrD,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,uBAAuB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,qIAAqI;AACrI,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"source.cache.js","sourceRoot":"","sources":["../../src/util/source.cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBhD,MAAM,OAAO,WAAW;IAEtB,YAAY,OAAe;QAD3B;;;;;WAA6B;QAE3B,IAAI,CAAC,KAAK,GAAG,IAAI,WAAW,CAAW,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,CAAC,QAAa;QAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAE/C,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,uBAAuB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/D,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ,CAAC,QAAa;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAE/C,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;YACrD,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,IAAI,uBAAuB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACjE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,qIAAqI;AACrI,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC"}
@@ -13,6 +13,7 @@ export declare class SwappingLru<T extends {
13
13
  get(id: string): T | null;
14
14
  /** Reset the cache */
15
15
  clear(): void;
16
+ delete(id: string): void;
16
17
  set(id: string, tiff: T): void;
17
18
  /** Validate the size of the cache has not exploded */
18
19
  check(): void;
@@ -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.6.0",
3
+ "version": "7.9.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/linz/basemaps.git",
@@ -22,12 +22,12 @@
22
22
  "types": "./build/index.d.ts",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "@basemaps/config": "^7.6.0",
26
- "@basemaps/config-loader": "^7.6.0",
25
+ "@basemaps/config": "^7.7.0",
26
+ "@basemaps/config-loader": "^7.9.0",
27
27
  "@basemaps/geo": "^7.5.0",
28
- "@basemaps/shared": "^7.6.0",
28
+ "@basemaps/shared": "^7.9.0",
29
29
  "@basemaps/tiler": "^7.5.0",
30
- "@basemaps/tiler-sharp": "^7.6.0",
30
+ "@basemaps/tiler-sharp": "^7.7.0",
31
31
  "@linzjs/geojson": "^7.5.0",
32
32
  "@linzjs/lambda": "^4.0.0",
33
33
  "p-limit": "^4.0.0",
@@ -55,5 +55,5 @@
55
55
  "@types/pixelmatch": "^5.0.0",
56
56
  "pretty-json-log": "^1.0.0"
57
57
  },
58
- "gitHead": "b97ded866e18b19410cb3effd5c67240e8980da4"
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);
@@ -1,9 +1,27 @@
1
1
  import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
2
+ import { before, beforeEach, describe, it } from 'node:test';
3
3
 
4
+ import { ConfigProviderMemory } from '@basemaps/config';
5
+ import { fsa, FsMemory } from '@chunkd/fs';
6
+
7
+ import { Imagery3857 } from '../../__tests__/config.data.js';
8
+ import { mockRequest } from '../../__tests__/xyz.util.js';
9
+ import { handler } from '../../index.js';
10
+ import { ConfigLoader } from '../../util/config.loader.js';
4
11
  import { isAllowedFile } from '../imagery.js';
5
12
 
6
13
  describe('ImageryRoute', () => {
14
+ const memory = new FsMemory();
15
+ const config = new ConfigProviderMemory();
16
+
17
+ before(() => {
18
+ fsa.register('memory://imagery/', memory);
19
+ });
20
+
21
+ beforeEach(() => {
22
+ config.assets = 'fake-s3://assets/';
23
+ });
24
+
7
25
  it('should allow geojson and json files only', () => {
8
26
  assert.equal(isAllowedFile('foo.geojson'), true);
9
27
  assert.equal(isAllowedFile('foo.json'), true);
@@ -12,4 +30,36 @@ describe('ImageryRoute', () => {
12
30
  assert.equal(isAllowedFile(''), false);
13
31
  assert.equal(isAllowedFile(null as unknown as string), false);
14
32
  });
33
+
34
+ it('should force download geojson files', async (t) => {
35
+ const fakeImagery = { ...Imagery3857 };
36
+ config.put(fakeImagery);
37
+ fakeImagery.uri = 'memory://imagery/';
38
+
39
+ t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
40
+ const res404 = await handler.router.handle(mockRequest(`/v1/imagery/${fakeImagery.id}/capture-area.geojson`));
41
+ assert.equal(res404.status, 404);
42
+
43
+ await memory.write(fsa.toUrl('memory://imagery/capture-area.geojson'), JSON.stringify({ hello: 'world' }));
44
+
45
+ const res200 = await handler.router.handle(mockRequest(`/v1/imagery/${fakeImagery.id}/capture-area.geojson`));
46
+ assert.equal(res200.status, 200);
47
+ assert.equal(res200.headers.get('content-disposition'), 'attachment');
48
+ });
49
+
50
+ it('should fetch stac files', async (t) => {
51
+ const fakeImagery = { ...Imagery3857 };
52
+ config.put(fakeImagery);
53
+ fakeImagery.uri = 'memory://imagery/';
54
+
55
+ t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
56
+ const res404 = await handler.router.handle(mockRequest(`/v1/imagery/${fakeImagery.id}/collection.json`));
57
+ assert.equal(res404.status, 404);
58
+
59
+ await memory.write(fsa.toUrl('memory://imagery/collection.json'), JSON.stringify({ hello: 'world' }));
60
+
61
+ const res200 = await handler.router.handle(mockRequest(`/v1/imagery/${fakeImagery.id}/collection.json`));
62
+ assert.equal(res200.status, 200);
63
+ assert.equal(res200.headers.get('content-disposition'), undefined);
64
+ });
15
65
  });
@@ -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
  });
@@ -51,6 +51,10 @@ export async function imageryGet(req: LambdaHttpRequest<ImageryGet>): Promise<La
51
51
  response.header(HttpHeader.ETag, cacheKey);
52
52
  response.header(HttpHeader.ContentEncoding, 'gzip');
53
53
  response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
54
+ // Force geojson files to be downloaded
55
+ if (requestedFile.endsWith('.geojson')) {
56
+ response.header('Content-Disposition', 'attachment');
57
+ }
54
58
  response.buffer(isGzip(buf) ? buf : await gzipP(buf), 'application/json');
55
59
  req.set('bytes', buf.byteLength);
56
60
  return response;
@@ -1,4 +1,5 @@
1
1
  import { ConfigId, ConfigPrefix, ConfigTileSetRaster, Layer, Sources, StyleJson, TileSetType } from '@basemaps/config';
2
+ import { DefaultExaggeration } from '@basemaps/config/build/config/vector.style.js';
2
3
  import { GoogleTms, TileMatrixSet, TileMatrixSets } from '@basemaps/geo';
3
4
  import { Env, toQueryString } from '@basemaps/shared';
4
5
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
@@ -47,9 +48,6 @@ export function convertStyleJson(
47
48
  const sources = JSON.parse(JSON.stringify(style.sources)) as Sources;
48
49
  for (const [key, value] of Object.entries(sources)) {
49
50
  if (value.type === 'vector') {
50
- if (tileMatrix !== GoogleTms) {
51
- throw new LambdaHttpResponse(400, `TileMatrix is not supported for the vector source ${value.url}.`);
52
- }
53
51
  value.url = convertRelativeUrl(value.url, tileMatrix, apiKey, config);
54
52
  } else if ((value.type === 'raster' || value.type === 'raster-dem') && Array.isArray(value.tiles)) {
55
53
  for (let i = 0; i < value.tiles.length; i++) {
@@ -71,6 +69,7 @@ export function convertStyleJson(
71
69
  if (style.glyphs) styleJson.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
72
70
  if (style.sprite) styleJson.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
73
71
  if (style.sky) styleJson.sky = style.sky;
72
+ if (style.terrain) styleJson.terrain = style.terrain;
74
73
 
75
74
  return styleJson;
76
75
  }
@@ -81,15 +80,48 @@ export interface StyleGet {
81
80
  };
82
81
  }
83
82
 
84
- function setStyleTerrain(style: StyleJson, terrain: string): void {
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
+
90
+ function setStyleTerrain(style: StyleJson, terrain: string, tileMatrix: TileMatrixSet): void {
85
91
  const source = Object.keys(style.sources).find((s) => s === terrain);
86
92
  if (source == null) throw new LambdaHttpResponse(400, `Terrain: ${terrain} is not exists in the style source.`);
87
93
  style.terrain = {
88
94
  source,
89
- exaggeration: 1.2,
95
+ exaggeration: DefaultExaggeration[tileMatrix.identifier] ?? DefaultExaggeration[GoogleTms.identifier],
90
96
  };
91
97
  }
92
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
+
93
125
  async function ensureTerrain(
94
126
  req: LambdaHttpRequest<StyleGet>,
95
127
  tileMatrix: TileMatrixSet,
@@ -97,17 +129,16 @@ async function ensureTerrain(
97
129
  style: StyleJson,
98
130
  ): Promise<void> {
99
131
  const config = await ConfigLoader.load(req);
100
- const terrain = await config.TileSet.get('ts_elevation');
101
- if (terrain) {
102
- const configLocation = ConfigLoader.extract(req);
103
- const elevationQuery = toQueryString({ config: configLocation, api: apiKey, pipeline: 'terrain-rgb' });
104
- style.sources['LINZ-Terrain'] = {
105
- type: 'raster-dem',
106
- tileSize: 256,
107
- maxzoom: 18,
108
- tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
109
- };
110
- }
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
+ };
111
142
  }
112
143
 
113
144
  export async function tileSetToStyle(
@@ -115,7 +146,7 @@ export async function tileSetToStyle(
115
146
  tileSet: ConfigTileSetRaster,
116
147
  tileMatrix: TileMatrixSet,
117
148
  apiKey: string,
118
- terrain?: string,
149
+ cfg: StyleConfig,
119
150
  ): Promise<LambdaHttpResponse> {
120
151
  const [tileFormat] = Validate.getRequestedFormats(req) ?? ['webp'];
121
152
  if (tileFormat == null) return new LambdaHttpResponse(400, 'Invalid image format');
@@ -143,9 +174,10 @@ export async function tileSetToStyle(
143
174
  await ensureTerrain(req, tileMatrix, apiKey, style);
144
175
 
145
176
  // Add terrain in style
146
- if (terrain) setStyleTerrain(style, terrain);
177
+ if (cfg.terrain) setStyleTerrain(style, cfg.terrain, tileMatrix);
178
+ if (cfg.labels) await setStyleLabels(req, style);
147
179
 
148
- const data = Buffer.from(JSON.stringify(style));
180
+ const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
149
181
 
150
182
  const cacheKey = Etag.key(data);
151
183
  if (Etag.isNotModified(req, cacheKey)) return NotModified();
@@ -163,7 +195,7 @@ export async function tileSetOutputToStyle(
163
195
  tileSet: ConfigTileSetRaster,
164
196
  tileMatrix: TileMatrixSet,
165
197
  apiKey: string,
166
- terrain?: string,
198
+ cfg: StyleConfig,
167
199
  ): Promise<LambdaHttpResponse> {
168
200
  const configLocation = ConfigLoader.extract(req);
169
201
  const query = toQueryString({ config: configLocation, api: apiKey });
@@ -226,9 +258,10 @@ export async function tileSetOutputToStyle(
226
258
  await ensureTerrain(req, tileMatrix, apiKey, style);
227
259
 
228
260
  // Add terrain in style
229
- if (terrain) setStyleTerrain(style, terrain);
261
+ if (cfg.terrain) setStyleTerrain(style, cfg.terrain, tileMatrix);
262
+ if (cfg.labels) await setStyleLabels(req, style);
230
263
 
231
- const data = Buffer.from(JSON.stringify(style));
264
+ const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
232
265
 
233
266
  const cacheKey = Etag.key(data);
234
267
  if (Etag.isNotModified(req, cacheKey)) return Promise.resolve(NotModified());
@@ -249,18 +282,22 @@ export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<La
249
282
  const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier);
250
283
  if (tileMatrix == null) return new LambdaHttpResponse(400, 'Invalid tile matrix');
251
284
  const terrain = req.query.get('terrain') ?? undefined;
285
+ const labels = Boolean(req.query.get('labels') ?? false);
252
286
 
253
287
  // Get style Config from db
254
288
  const config = await ConfigLoader.load(req);
255
289
  const dbId = config.Style.id(styleName);
256
290
  const styleConfig = await config.Style.get(dbId);
291
+
292
+ req.set('styleConfig', { terrain, labels });
293
+
257
294
  if (styleConfig == null) {
258
295
  // Were we given a tileset name instead, generated
259
296
  const tileSet = await config.TileSet.get(config.TileSet.id(styleName));
260
297
  if (tileSet == null) return NotFound();
261
298
  if (tileSet.type !== TileSetType.Raster) return NotFound();
262
- if (tileSet.outputs) return await tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, terrain);
263
- 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 });
264
301
  }
265
302
 
266
303
  // Prepare sources and add linz source
@@ -277,7 +314,8 @@ export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<La
277
314
  await ensureTerrain(req, tileMatrix, apiKey, style);
278
315
 
279
316
  // Add terrain in style
280
- if (terrain) setStyleTerrain(style, terrain);
317
+ if (terrain) setStyleTerrain(style, terrain, tileMatrix);
318
+ if (labels) await setStyleLabels(req, style);
281
319
 
282
320
  const data = Buffer.from(JSON.stringify(style));
283
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();