styled-map-package-api 5.0.0-pre.1 → 5.0.0-pre.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
@@ -58,10 +58,17 @@ const stream = download({
58
58
 
59
59
  ### Converting from MBTiles
60
60
 
61
+ > **Note:** MBTiles conversion requires Node >= 20 (uses `better-sqlite3` which dropped Node 18 support).
62
+
61
63
  ```js
62
64
  import { fromMBTiles } from 'styled-map-package-api/from-mbtiles'
63
65
 
66
+ // From a file path (Node.js)
64
67
  const stream = fromMBTiles('path/to/tiles.mbtiles')
68
+
69
+ // From an ArrayBuffer or Uint8Array (Node.js and browsers)
70
+ const stream = fromMBTiles(buffer)
71
+
65
72
  // Pipe the ReadableStream to an .smp file
66
73
  ```
67
74
 
@@ -1,6 +1,6 @@
1
1
  import { GlyphDownloadStats } from './style-downloader.cjs';
2
2
  import { TileDownloadStats } from './tile-downloader.cjs';
3
- import { D as DownloadStream } from './types-DPei6PMF.cjs';
3
+ import { D as DownloadStream } from './types-Bhn0-Ldk.cjs';
4
4
  import { BBox } from './utils/geo.cjs';
5
5
  import 'ky';
6
6
  import '@maplibre/maplibre-gl-style-spec';
@@ -1,6 +1,6 @@
1
1
  import { GlyphDownloadStats } from './style-downloader.js';
2
2
  import { TileDownloadStats } from './tile-downloader.js';
3
- import { D as DownloadStream } from './types-DPei6PMF.js';
3
+ import { D as DownloadStream } from './types-Bhn0-Ldk.js';
4
4
  import { BBox } from './utils/geo.js';
5
5
  import 'ky';
6
6
  import '@maplibre/maplibre-gl-style-spec';
@@ -22,53 +22,99 @@ __export(from_mbtiles_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(from_mbtiles_exports);
24
24
  var import_mbtiles_reader = require("mbtiles-reader");
25
+ var import_misc = require('./utils/misc.cjs');
25
26
  var import_streams = require('./utils/streams.cjs');
26
27
  var import_writer = require('./writer.cjs');
27
28
  const SOURCE_ID = "mbtiles-source";
28
- function fromMBTiles(mbtilesPath) {
29
- const reader = new import_mbtiles_reader.MBTiles(mbtilesPath);
30
- if (reader.metadata.format === "pbf") {
31
- throw new Error("Vector MBTiles are not yet supported");
32
- }
33
- const style = {
34
- version: 8,
35
- name: reader.metadata.name,
36
- sources: {
37
- [SOURCE_ID]: {
38
- ...reader.metadata,
39
- type: "raster"
29
+ function fromMBTiles(source) {
30
+ let outputReader;
31
+ let conversionDone;
32
+ const pipeAbort = new AbortController();
33
+ return new ReadableStream({
34
+ async start() {
35
+ const reader = await import_mbtiles_reader.MBTiles.open(source);
36
+ if (reader.metadata.format === "pbf") {
37
+ throw new Error("Vector MBTiles are not yet supported");
40
38
  }
41
- },
42
- layers: [
43
- {
44
- id: "background",
45
- type: "background",
46
- paint: {
47
- "background-color": "white"
48
- }
49
- },
50
- {
51
- id: "raster",
52
- type: "raster",
53
- source: SOURCE_ID,
54
- paint: {
55
- "raster-opacity": 1
39
+ const {
40
+ name,
41
+ minzoom,
42
+ maxzoom,
43
+ scheme,
44
+ attribution,
45
+ description,
46
+ version: tilesetVersion
47
+ } = reader.metadata;
48
+ const style = {
49
+ version: 8,
50
+ name,
51
+ sources: {
52
+ [SOURCE_ID]: {
53
+ type: "raster",
54
+ tileSize: 256,
55
+ minzoom,
56
+ maxzoom,
57
+ scheme,
58
+ attribution
59
+ }
60
+ },
61
+ layers: [
62
+ {
63
+ id: "background",
64
+ type: "background",
65
+ paint: {
66
+ "background-color": "white"
67
+ }
68
+ },
69
+ {
70
+ id: "raster",
71
+ type: "raster",
72
+ source: SOURCE_ID,
73
+ paint: {
74
+ "raster-opacity": 1
75
+ }
76
+ }
77
+ ]
78
+ };
79
+ if (description || tilesetVersion) {
80
+ style.metadata = {
81
+ "mbtiles:description": description,
82
+ "mbtiles:version": tilesetVersion
83
+ };
84
+ }
85
+ const writer = new import_writer.Writer(style);
86
+ outputReader = writer.outputStream.getReader();
87
+ conversionDone = (async () => {
88
+ try {
89
+ await (0, import_streams.readableFromAsync)(mbtilesToTileArgs(reader)).pipeTo(
90
+ writer.createTileWriteStream(),
91
+ { signal: pipeAbort.signal }
92
+ );
93
+ writer.finish();
94
+ } catch (err) {
95
+ try {
96
+ writer.abort(err instanceof Error ? err : new Error(String(err)));
97
+ } catch {
98
+ }
56
99
  }
100
+ })();
101
+ },
102
+ async pull(controller) {
103
+ const { done, value } = await /** @type {ReadableStreamDefaultReader<Uint8Array>} */
104
+ outputReader.read();
105
+ if (done) {
106
+ controller.close();
107
+ } else {
108
+ controller.enqueue(value);
57
109
  }
58
- ]
59
- };
60
- const writer = new import_writer.Writer(style);
61
- (async () => {
62
- try {
63
- await (0, import_streams.readableFromAsync)(mbtilesToTileArgs(reader)).pipeTo(
64
- writer.createTileWriteStream()
65
- );
66
- writer.finish();
67
- } catch (err) {
68
- writer.abort(err instanceof Error ? err : new Error(String(err)));
110
+ },
111
+ async cancel(reason) {
112
+ pipeAbort.abort(reason);
113
+ await conversionDone;
114
+ await /** @type {ReadableStreamDefaultReader<Uint8Array>} */
115
+ outputReader.cancel(reason).catch(import_misc.noop);
69
116
  }
70
- })();
71
- return writer.outputStream;
117
+ });
72
118
  }
73
119
  async function* mbtilesToTileArgs(mbtiles) {
74
120
  for (const { z, x, y, data, format } of mbtiles) {
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Convert a MBTiles file to a styled map package, returned as a web
3
- * ReadableStream.
3
+ * ReadableStream. The async MBTiles.open() happens lazily inside the
4
+ * stream's start(), so this function is synchronous.
4
5
  *
5
- * @param {string} mbtilesPath
6
+ * Requires Node >= 20 (uses better-sqlite3 which dropped Node 18 support).
7
+ *
8
+ * @param {string | ArrayBuffer | Uint8Array} source MBTiles source — file path
9
+ * (Node), OPFS path (browser Worker), or in-memory buffer.
6
10
  * @returns {ReadableStream<Uint8Array>}
7
11
  */
8
- declare function fromMBTiles(mbtilesPath: string): ReadableStream<Uint8Array>;
12
+ declare function fromMBTiles(source: string | ArrayBuffer | Uint8Array): ReadableStream<Uint8Array>;
9
13
 
10
14
  export { fromMBTiles };
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Convert a MBTiles file to a styled map package, returned as a web
3
- * ReadableStream.
3
+ * ReadableStream. The async MBTiles.open() happens lazily inside the
4
+ * stream's start(), so this function is synchronous.
4
5
  *
5
- * @param {string} mbtilesPath
6
+ * Requires Node >= 20 (uses better-sqlite3 which dropped Node 18 support).
7
+ *
8
+ * @param {string | ArrayBuffer | Uint8Array} source MBTiles source — file path
9
+ * (Node), OPFS path (browser Worker), or in-memory buffer.
6
10
  * @returns {ReadableStream<Uint8Array>}
7
11
  */
8
- declare function fromMBTiles(mbtilesPath: string): ReadableStream<Uint8Array>;
12
+ declare function fromMBTiles(source: string | ArrayBuffer | Uint8Array): ReadableStream<Uint8Array>;
9
13
 
10
14
  export { fromMBTiles };
@@ -1,51 +1,97 @@
1
1
  import { MBTiles } from "mbtiles-reader";
2
+ import { noop } from "./utils/misc.js";
2
3
  import { readableFromAsync } from "./utils/streams.js";
3
4
  import { Writer } from "./writer.js";
4
5
  const SOURCE_ID = "mbtiles-source";
5
- function fromMBTiles(mbtilesPath) {
6
- const reader = new MBTiles(mbtilesPath);
7
- if (reader.metadata.format === "pbf") {
8
- throw new Error("Vector MBTiles are not yet supported");
9
- }
10
- const style = {
11
- version: 8,
12
- name: reader.metadata.name,
13
- sources: {
14
- [SOURCE_ID]: {
15
- ...reader.metadata,
16
- type: "raster"
6
+ function fromMBTiles(source) {
7
+ let outputReader;
8
+ let conversionDone;
9
+ const pipeAbort = new AbortController();
10
+ return new ReadableStream({
11
+ async start() {
12
+ const reader = await MBTiles.open(source);
13
+ if (reader.metadata.format === "pbf") {
14
+ throw new Error("Vector MBTiles are not yet supported");
17
15
  }
18
- },
19
- layers: [
20
- {
21
- id: "background",
22
- type: "background",
23
- paint: {
24
- "background-color": "white"
25
- }
26
- },
27
- {
28
- id: "raster",
29
- type: "raster",
30
- source: SOURCE_ID,
31
- paint: {
32
- "raster-opacity": 1
16
+ const {
17
+ name,
18
+ minzoom,
19
+ maxzoom,
20
+ scheme,
21
+ attribution,
22
+ description,
23
+ version: tilesetVersion
24
+ } = reader.metadata;
25
+ const style = {
26
+ version: 8,
27
+ name,
28
+ sources: {
29
+ [SOURCE_ID]: {
30
+ type: "raster",
31
+ tileSize: 256,
32
+ minzoom,
33
+ maxzoom,
34
+ scheme,
35
+ attribution
36
+ }
37
+ },
38
+ layers: [
39
+ {
40
+ id: "background",
41
+ type: "background",
42
+ paint: {
43
+ "background-color": "white"
44
+ }
45
+ },
46
+ {
47
+ id: "raster",
48
+ type: "raster",
49
+ source: SOURCE_ID,
50
+ paint: {
51
+ "raster-opacity": 1
52
+ }
53
+ }
54
+ ]
55
+ };
56
+ if (description || tilesetVersion) {
57
+ style.metadata = {
58
+ "mbtiles:description": description,
59
+ "mbtiles:version": tilesetVersion
60
+ };
61
+ }
62
+ const writer = new Writer(style);
63
+ outputReader = writer.outputStream.getReader();
64
+ conversionDone = (async () => {
65
+ try {
66
+ await readableFromAsync(mbtilesToTileArgs(reader)).pipeTo(
67
+ writer.createTileWriteStream(),
68
+ { signal: pipeAbort.signal }
69
+ );
70
+ writer.finish();
71
+ } catch (err) {
72
+ try {
73
+ writer.abort(err instanceof Error ? err : new Error(String(err)));
74
+ } catch {
75
+ }
33
76
  }
77
+ })();
78
+ },
79
+ async pull(controller) {
80
+ const { done, value } = await /** @type {ReadableStreamDefaultReader<Uint8Array>} */
81
+ outputReader.read();
82
+ if (done) {
83
+ controller.close();
84
+ } else {
85
+ controller.enqueue(value);
34
86
  }
35
- ]
36
- };
37
- const writer = new Writer(style);
38
- (async () => {
39
- try {
40
- await readableFromAsync(mbtilesToTileArgs(reader)).pipeTo(
41
- writer.createTileWriteStream()
42
- );
43
- writer.finish();
44
- } catch (err) {
45
- writer.abort(err instanceof Error ? err : new Error(String(err)));
87
+ },
88
+ async cancel(reason) {
89
+ pipeAbort.abort(reason);
90
+ await conversionDone;
91
+ await /** @type {ReadableStreamDefaultReader<Uint8Array>} */
92
+ outputReader.cancel(reason).catch(noop);
46
93
  }
47
- })();
48
- return writer.outputStream;
94
+ });
49
95
  }
50
96
  async function* mbtilesToTileArgs(mbtiles) {
51
97
  for (const { z, x, y, data, format } of mbtiles) {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SMPSource$1, a as SMPStyle$1 } from './types-DPei6PMF.cjs';
2
- export { W as Writer } from './types-DPei6PMF.cjs';
1
+ import { S as SMPSource$1, a as SMPStyle$1 } from './types-Bhn0-Ldk.cjs';
2
+ export { W as Writer } from './types-Bhn0-Ldk.cjs';
3
3
  export { Reader } from './reader.cjs';
4
4
  export { createServer } from './server.cjs';
5
5
  export { StyleDownloader } from './style-downloader.cjs';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SMPSource$1, a as SMPStyle$1 } from './types-DPei6PMF.js';
2
- export { W as Writer } from './types-DPei6PMF.js';
1
+ import { S as SMPSource$1, a as SMPStyle$1 } from './types-Bhn0-Ldk.js';
2
+ export { W as Writer } from './types-Bhn0-Ldk.js';
3
3
  export { Reader } from './reader.js';
4
4
  export { createServer } from './server.js';
5
5
  export { StyleDownloader } from './style-downloader.js';
package/dist/reader.cjs CHANGED
@@ -155,7 +155,7 @@ class Reader {
155
155
  sourcePromise.catch(import_misc.noop);
156
156
  zipPromise = sourcePromise.then((source) => {
157
157
  this.#fileSource = source;
158
- return import_zip_reader.ZipReader.from(source);
158
+ return import_zip_reader.ZipReader.from(source, { skipUniqueEntryCheck: true });
159
159
  });
160
160
  } else {
161
161
  zipPromise = Promise.resolve(filepathOrZip);
package/dist/reader.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SMPStyle } from './types-DPei6PMF.cjs';
1
+ import { a as SMPStyle } from './types-Bhn0-Ldk.cjs';
2
2
  import * as _gmaclennan_zip_reader from '@gmaclennan/zip-reader';
3
3
  import '@maplibre/maplibre-gl-style-spec';
4
4
  import 'geojson';
package/dist/reader.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SMPStyle } from './types-DPei6PMF.js';
1
+ import { a as SMPStyle } from './types-Bhn0-Ldk.js';
2
2
  import * as _gmaclennan_zip_reader from '@gmaclennan/zip-reader';
3
3
  import '@maplibre/maplibre-gl-style-spec';
4
4
  import 'geojson';
package/dist/reader.js CHANGED
@@ -128,7 +128,7 @@ class Reader {
128
128
  sourcePromise.catch(noop);
129
129
  zipPromise = sourcePromise.then((source) => {
130
130
  this.#fileSource = source;
131
- return ZipReader.from(source);
131
+ return ZipReader.from(source, { skipUniqueEntryCheck: true });
132
132
  });
133
133
  } else {
134
134
  zipPromise = Promise.resolve(filepathOrZip);
package/dist/server.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RequestLike } from 'itty-router';
2
2
  import { Reader } from './reader.cjs';
3
3
  import { IttyRouter } from 'itty-router/IttyRouter';
4
- import './types-DPei6PMF.cjs';
4
+ import './types-Bhn0-Ldk.cjs';
5
5
  import '@maplibre/maplibre-gl-style-spec';
6
6
  import 'geojson';
7
7
  import 'type-fest';
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RequestLike } from 'itty-router';
2
2
  import { Reader } from './reader.js';
3
3
  import { IttyRouter } from 'itty-router/IttyRouter';
4
- import './types-DPei6PMF.js';
4
+ import './types-Bhn0-Ldk.js';
5
5
  import '@maplibre/maplibre-gl-style-spec';
6
6
  import 'geojson';
7
7
  import 'type-fest';
@@ -1,6 +1,6 @@
1
1
  import * as ky from 'ky';
2
2
  import { BBox } from './utils/geo.cjs';
3
- import { b as StyleInlinedSources, G as GlyphInfo, T as TileInfo } from './types-DPei6PMF.cjs';
3
+ import { b as StyleInlinedSources, G as GlyphInfo, T as TileInfo } from './types-Bhn0-Ldk.cjs';
4
4
  import { TileDownloadStats } from './tile-downloader.cjs';
5
5
  import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
6
6
  import 'geojson';
@@ -1,6 +1,6 @@
1
1
  import * as ky from 'ky';
2
2
  import { BBox } from './utils/geo.js';
3
- import { b as StyleInlinedSources, G as GlyphInfo, T as TileInfo } from './types-DPei6PMF.js';
3
+ import { b as StyleInlinedSources, G as GlyphInfo, T as TileInfo } from './types-Bhn0-Ldk.js';
4
4
  import { TileDownloadStats } from './tile-downloader.js';
5
5
  import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
6
6
  import 'geojson';
@@ -1,4 +1,4 @@
1
- import { T as TileInfo$1 } from './types-DPei6PMF.cjs';
1
+ import { T as TileInfo$1 } from './types-Bhn0-Ldk.cjs';
2
2
  import { BBox } from './utils/geo.cjs';
3
3
  import { FetchQueue } from './utils/fetch.cjs';
4
4
  import '@maplibre/maplibre-gl-style-spec';
@@ -1,4 +1,4 @@
1
- import { T as TileInfo$1 } from './types-DPei6PMF.js';
1
+ import { T as TileInfo$1 } from './types-Bhn0-Ldk.js';
2
2
  import { BBox } from './utils/geo.js';
3
3
  import { FetchQueue } from './utils/fetch.js';
4
4
  import '@maplibre/maplibre-gl-style-spec';
@@ -27,6 +27,13 @@ import { SetRequired } from 'type-fest';
27
27
  /** @import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec' */
28
28
  /** @import { InputSource, SMPSource } from './types.js' */
29
29
  declare const SUPPORTED_SOURCE_TYPES: readonly ["raster", "vector", "geojson"];
30
+ /**
31
+ * @typedef {object} WriterOptions
32
+ * @property {boolean} [dedupe] When true, duplicate tiles (with identical
33
+ * content) are stored only once in the archive. Additional entries in the
34
+ * central directory point to the same data. This reduces file size for
35
+ * tilesets with many repeated tiles (e.g. ocean tiles).
36
+ */
30
37
  /**
31
38
  * Write a styled map package to a stream. Stream `writer.outputStream` to a
32
39
  * destination, e.g. `fs.createWriteStream('my-map.styledmap')`. You must call
@@ -39,8 +46,9 @@ declare class Writer {
39
46
  * @param {any} style A v7 or v8 MapLibre style. v7 styles will be migrated to
40
47
  * v8. (There are currently no typescript declarations for v7 styles, hence
41
48
  * this is typed as `any` and validated internally)
49
+ * @param {WriterOptions} [options]
42
50
  */
43
- constructor(style: any);
51
+ constructor(style: any, { dedupe }?: WriterOptions);
44
52
  /**
45
53
  * @returns {ReadableStream<Uint8Array>} Readable stream of the styled map package
46
54
  */
@@ -131,6 +139,15 @@ type GlyphInfo = {
131
139
  font: string;
132
140
  range: GlyphRange;
133
141
  };
142
+ type WriterOptions = {
143
+ /**
144
+ * When true, duplicate tiles (with identical
145
+ * content) are stored only once in the archive. Additional entries in the
146
+ * central directory point to the same data. This reduces file size for
147
+ * tilesets with many repeated tiles (e.g. ocean tiles).
148
+ */
149
+ dedupe?: boolean | undefined;
150
+ };
134
151
 
135
152
  type TransformInlinedSource<T extends SourceSpecification> = T extends GeoJSONSourceSpecification ? OmitUnion<T, 'data'> & {
136
153
  data: GeoJSON;
@@ -172,6 +189,7 @@ type TransformSMPStyle<T extends StyleSpecification> = Omit<T, 'sources'> & {
172
189
  'smp:sourceFolders': {
173
190
  [_: string]: string;
174
191
  };
192
+ [key: string]: unknown;
175
193
  };
176
194
  sources: {
177
195
  [_: string]: SMPSource;
@@ -180,4 +198,4 @@ type TransformSMPStyle<T extends StyleSpecification> = Omit<T, 'sources'> & {
180
198
  type DownloadStream = ReadableStream<Uint8Array>;
181
199
  type OmitUnion<T, K extends keyof any> = T extends unknown ? Omit<T, K> : never;
182
200
 
183
- export { type DownloadStream as D, type GlyphInfo as G, type InlinedSource as I, type SMPSource as S, type TileInfo as T, Writer as W, type SMPStyle as a, type StyleInlinedSources as b, type TileFormat as c, type GlyphRange as d, SUPPORTED_SOURCE_TYPES as e, type Source as f, type SourceInfo as g };
201
+ export { type DownloadStream as D, type GlyphInfo as G, type InlinedSource as I, type SMPSource as S, type TileInfo as T, Writer as W, type SMPStyle as a, type StyleInlinedSources as b, type TileFormat as c, type GlyphRange as d, SUPPORTED_SOURCE_TYPES as e, type Source as f, type SourceInfo as g, type WriterOptions as h };
@@ -27,6 +27,13 @@ import { SetRequired } from 'type-fest';
27
27
  /** @import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec' */
28
28
  /** @import { InputSource, SMPSource } from './types.js' */
29
29
  declare const SUPPORTED_SOURCE_TYPES: readonly ["raster", "vector", "geojson"];
30
+ /**
31
+ * @typedef {object} WriterOptions
32
+ * @property {boolean} [dedupe] When true, duplicate tiles (with identical
33
+ * content) are stored only once in the archive. Additional entries in the
34
+ * central directory point to the same data. This reduces file size for
35
+ * tilesets with many repeated tiles (e.g. ocean tiles).
36
+ */
30
37
  /**
31
38
  * Write a styled map package to a stream. Stream `writer.outputStream` to a
32
39
  * destination, e.g. `fs.createWriteStream('my-map.styledmap')`. You must call
@@ -39,8 +46,9 @@ declare class Writer {
39
46
  * @param {any} style A v7 or v8 MapLibre style. v7 styles will be migrated to
40
47
  * v8. (There are currently no typescript declarations for v7 styles, hence
41
48
  * this is typed as `any` and validated internally)
49
+ * @param {WriterOptions} [options]
42
50
  */
43
- constructor(style: any);
51
+ constructor(style: any, { dedupe }?: WriterOptions);
44
52
  /**
45
53
  * @returns {ReadableStream<Uint8Array>} Readable stream of the styled map package
46
54
  */
@@ -131,6 +139,15 @@ type GlyphInfo = {
131
139
  font: string;
132
140
  range: GlyphRange;
133
141
  };
142
+ type WriterOptions = {
143
+ /**
144
+ * When true, duplicate tiles (with identical
145
+ * content) are stored only once in the archive. Additional entries in the
146
+ * central directory point to the same data. This reduces file size for
147
+ * tilesets with many repeated tiles (e.g. ocean tiles).
148
+ */
149
+ dedupe?: boolean | undefined;
150
+ };
134
151
 
135
152
  type TransformInlinedSource<T extends SourceSpecification> = T extends GeoJSONSourceSpecification ? OmitUnion<T, 'data'> & {
136
153
  data: GeoJSON;
@@ -172,6 +189,7 @@ type TransformSMPStyle<T extends StyleSpecification> = Omit<T, 'sources'> & {
172
189
  'smp:sourceFolders': {
173
190
  [_: string]: string;
174
191
  };
192
+ [key: string]: unknown;
175
193
  };
176
194
  sources: {
177
195
  [_: string]: SMPSource;
@@ -180,4 +198,4 @@ type TransformSMPStyle<T extends StyleSpecification> = Omit<T, 'sources'> & {
180
198
  type DownloadStream = ReadableStream<Uint8Array>;
181
199
  type OmitUnion<T, K extends keyof any> = T extends unknown ? Omit<T, K> : never;
182
200
 
183
- export { type DownloadStream as D, type GlyphInfo as G, type InlinedSource as I, type SMPSource as S, type TileInfo as T, Writer as W, type SMPStyle as a, type StyleInlinedSources as b, type TileFormat as c, type GlyphRange as d, SUPPORTED_SOURCE_TYPES as e, type Source as f, type SourceInfo as g };
201
+ export { type DownloadStream as D, type GlyphInfo as G, type InlinedSource as I, type SMPSource as S, type TileInfo as T, Writer as W, type SMPStyle as a, type StyleInlinedSources as b, type TileFormat as c, type GlyphRange as d, SUPPORTED_SOURCE_TYPES as e, type Source as f, type SourceInfo as g, type WriterOptions as h };
@@ -1,4 +1,4 @@
1
- import { c as TileFormat } from '../types-DPei6PMF.cjs';
1
+ import { c as TileFormat } from '../types-Bhn0-Ldk.cjs';
2
2
  import '@maplibre/maplibre-gl-style-spec';
3
3
  import 'geojson';
4
4
  import 'type-fest';
@@ -1,4 +1,4 @@
1
- import { c as TileFormat } from '../types-DPei6PMF.js';
1
+ import { c as TileFormat } from '../types-Bhn0-Ldk.js';
2
2
  import '@maplibre/maplibre-gl-style-spec';
3
3
  import 'geojson';
4
4
  import 'type-fest';
@@ -1,5 +1,5 @@
1
1
  import { BBox } from './geo.cjs';
2
- import { I as InlinedSource } from '../types-DPei6PMF.cjs';
2
+ import { I as InlinedSource } from '../types-Bhn0-Ldk.cjs';
3
3
  import * as _maplibre_maplibre_gl_style_spec from '@maplibre/maplibre-gl-style-spec';
4
4
  import { StyleSpecification, ValidationError } from '@maplibre/maplibre-gl-style-spec';
5
5
  import 'geojson';
@@ -1,5 +1,5 @@
1
1
  import { BBox } from './geo.js';
2
- import { I as InlinedSource } from '../types-DPei6PMF.js';
2
+ import { I as InlinedSource } from '../types-Bhn0-Ldk.js';
3
3
  import * as _maplibre_maplibre_gl_style_spec from '@maplibre/maplibre-gl-style-spec';
4
4
  import { StyleSpecification, ValidationError } from '@maplibre/maplibre-gl-style-spec';
5
5
  import 'geojson';
@@ -1,5 +1,5 @@
1
1
  import * as type_fest from 'type-fest';
2
- import { d as GlyphRange, T as TileInfo, c as TileFormat } from '../types-DPei6PMF.cjs';
2
+ import { d as GlyphRange, T as TileInfo, c as TileFormat } from '../types-Bhn0-Ldk.cjs';
3
3
  import '@maplibre/maplibre-gl-style-spec';
4
4
  import 'geojson';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as type_fest from 'type-fest';
2
- import { d as GlyphRange, T as TileInfo, c as TileFormat } from '../types-DPei6PMF.js';
2
+ import { d as GlyphRange, T as TileInfo, c as TileFormat } from '../types-Bhn0-Ldk.js';
3
3
  import '@maplibre/maplibre-gl-style-spec';
4
4
  import 'geojson';
5
5
 
package/dist/writer.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var writer_exports = {};
20
30
  __export(writer_exports, {
@@ -56,13 +66,20 @@ class Writer {
56
66
  #outputStream;
57
67
  /** @type {ReadableStreamDefaultController<Uint8Array>} */
58
68
  #outputController;
69
+ /** @type {boolean} */
70
+ #dedupe;
71
+ /** @type {Map<string, string>} hash → first entry name */
72
+ #tileHashes = /* @__PURE__ */ new Map();
73
+ /** @type {Array<{ name: string, originalName: string }>} */
74
+ #duplicateEntries = [];
59
75
  static SUPPORTED_SOURCE_TYPES = SUPPORTED_SOURCE_TYPES;
60
76
  /**
61
77
  * @param {any} style A v7 or v8 MapLibre style. v7 styles will be migrated to
62
78
  * v8. (There are currently no typescript declarations for v7 styles, hence
63
79
  * this is typed as `any` and validated internally)
80
+ * @param {WriterOptions} [options]
64
81
  */
65
- constructor(style) {
82
+ constructor(style, { dedupe = false } = {}) {
66
83
  if (!style || !("version" in style)) {
67
84
  throw new Error("Invalid style");
68
85
  }
@@ -79,6 +96,7 @@ class Writer {
79
96
  throw new AggregateError(errors, "Invalid style");
80
97
  }
81
98
  this.#style = styleCopy;
99
+ this.#dedupe = dedupe;
82
100
  for (const [sourceId, source] of Object.entries(this.#style.sources)) {
83
101
  if (source.type !== "geojson") continue;
84
102
  this.#addSource(sourceId, source);
@@ -252,6 +270,23 @@ class Writer {
252
270
  source.bounds = (0, import_geo.unionBBox)([source.bounds, bbox2]);
253
271
  }
254
272
  const name = (0, import_templates.getTileFilename)({ sourceId: encodedSourceId, z, x, y, format });
273
+ if (this.#dedupe) {
274
+ const data = await toUint8Array(tileData);
275
+ const hash = await hashData(data);
276
+ if (this.#addedFiles.has(name)) {
277
+ throw new Error(`${name} already added`);
278
+ }
279
+ this.#addedFiles.add(name);
280
+ const existingName = this.#tileHashes.get(hash);
281
+ if (existingName) {
282
+ this.#duplicateEntries.push({ name, originalName: existingName });
283
+ return;
284
+ }
285
+ this.#tileHashes.set(hash, name);
286
+ const readable = toWebStream(data);
287
+ await this.#zipWriter.addEntry({ readable, name, store: true });
288
+ return;
289
+ }
255
290
  return this.#append(tileData, { name, store: true });
256
291
  }
257
292
  /**
@@ -316,7 +351,17 @@ class Writer {
316
351
  this.#prepareStyle();
317
352
  const style = JSON.stringify(this.#style);
318
353
  await this.#append(style, { name: import_templates.STYLE_FILE });
319
- const entries = await this.#zipWriter.entries();
354
+ let entries = await this.#zipWriter.entries();
355
+ if (this.#duplicateEntries.length > 0) {
356
+ const entriesByName = new Map(entries.map((e) => [e.name, e]));
357
+ for (const { name, originalName } of this.#duplicateEntries) {
358
+ const original = entriesByName.get(originalName);
359
+ if (!original) {
360
+ throw new Error(`Original entry ${originalName} not found`);
361
+ }
362
+ entries.push({ ...original, name });
363
+ }
364
+ }
320
365
  const sortedEntries = sortEntries(entries);
321
366
  await this.#zipWriter.finalize({ entries: sortedEntries });
322
367
  }
@@ -430,6 +475,37 @@ function get2DBBox(bbox2) {
430
475
  if (bbox2.length === 4) return bbox2;
431
476
  return [bbox2[0], bbox2[1], bbox2[3], bbox2[4]];
432
477
  }
478
+ async function toUint8Array(source) {
479
+ if (source instanceof Uint8Array) return source;
480
+ if (typeof source === "string") return new TextEncoder().encode(source);
481
+ const reader = (
482
+ /** @type {ReadableStream<Uint8Array>} */
483
+ source.getReader()
484
+ );
485
+ const chunks = [];
486
+ let totalLength = 0;
487
+ while (true) {
488
+ const { done, value } = await reader.read();
489
+ if (done) break;
490
+ chunks.push(value);
491
+ totalLength += value.byteLength;
492
+ }
493
+ const result = new Uint8Array(totalLength);
494
+ let offset = 0;
495
+ for (const chunk of chunks) {
496
+ result.set(chunk, offset);
497
+ offset += chunk.byteLength;
498
+ }
499
+ return result;
500
+ }
501
+ async function hashData(data) {
502
+ let c = globalThis.crypto;
503
+ if (!c) {
504
+ c = (await import("node:crypto")).webcrypto;
505
+ }
506
+ const buf = await c.subtle.digest("SHA-256", data);
507
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
508
+ }
433
509
  function sortEntries(entries) {
434
510
  return [...entries].sort((a, b) => {
435
511
  if (a.name === import_templates.VERSION_FILE) return -1;
package/dist/writer.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { G as GlyphInfo, d as GlyphRange, e as SUPPORTED_SOURCE_TYPES, f as Source, g as SourceInfo, c as TileFormat, T as TileInfo, W as Writer } from './types-DPei6PMF.cjs';
1
+ export { G as GlyphInfo, d as GlyphRange, e as SUPPORTED_SOURCE_TYPES, f as Source, g as SourceInfo, c as TileFormat, T as TileInfo, W as Writer, h as WriterOptions } from './types-Bhn0-Ldk.cjs';
2
2
  import '@maplibre/maplibre-gl-style-spec';
3
3
  import 'geojson';
4
4
  import 'type-fest';
package/dist/writer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { G as GlyphInfo, d as GlyphRange, e as SUPPORTED_SOURCE_TYPES, f as Source, g as SourceInfo, c as TileFormat, T as TileInfo, W as Writer } from './types-DPei6PMF.js';
1
+ export { G as GlyphInfo, d as GlyphRange, e as SUPPORTED_SOURCE_TYPES, f as Source, g as SourceInfo, c as TileFormat, T as TileInfo, W as Writer, h as WriterOptions } from './types-Bhn0-Ldk.js';
2
2
  import '@maplibre/maplibre-gl-style-spec';
3
3
  import 'geojson';
4
4
  import 'type-fest';
package/dist/writer.js CHANGED
@@ -44,13 +44,20 @@ class Writer {
44
44
  #outputStream;
45
45
  /** @type {ReadableStreamDefaultController<Uint8Array>} */
46
46
  #outputController;
47
+ /** @type {boolean} */
48
+ #dedupe;
49
+ /** @type {Map<string, string>} hash → first entry name */
50
+ #tileHashes = /* @__PURE__ */ new Map();
51
+ /** @type {Array<{ name: string, originalName: string }>} */
52
+ #duplicateEntries = [];
47
53
  static SUPPORTED_SOURCE_TYPES = SUPPORTED_SOURCE_TYPES;
48
54
  /**
49
55
  * @param {any} style A v7 or v8 MapLibre style. v7 styles will be migrated to
50
56
  * v8. (There are currently no typescript declarations for v7 styles, hence
51
57
  * this is typed as `any` and validated internally)
58
+ * @param {WriterOptions} [options]
52
59
  */
53
- constructor(style) {
60
+ constructor(style, { dedupe = false } = {}) {
54
61
  if (!style || !("version" in style)) {
55
62
  throw new Error("Invalid style");
56
63
  }
@@ -67,6 +74,7 @@ class Writer {
67
74
  throw new AggregateError(errors, "Invalid style");
68
75
  }
69
76
  this.#style = styleCopy;
77
+ this.#dedupe = dedupe;
70
78
  for (const [sourceId, source] of Object.entries(this.#style.sources)) {
71
79
  if (source.type !== "geojson") continue;
72
80
  this.#addSource(sourceId, source);
@@ -240,6 +248,23 @@ class Writer {
240
248
  source.bounds = unionBBox([source.bounds, bbox2]);
241
249
  }
242
250
  const name = getTileFilename({ sourceId: encodedSourceId, z, x, y, format });
251
+ if (this.#dedupe) {
252
+ const data = await toUint8Array(tileData);
253
+ const hash = await hashData(data);
254
+ if (this.#addedFiles.has(name)) {
255
+ throw new Error(`${name} already added`);
256
+ }
257
+ this.#addedFiles.add(name);
258
+ const existingName = this.#tileHashes.get(hash);
259
+ if (existingName) {
260
+ this.#duplicateEntries.push({ name, originalName: existingName });
261
+ return;
262
+ }
263
+ this.#tileHashes.set(hash, name);
264
+ const readable = toWebStream(data);
265
+ await this.#zipWriter.addEntry({ readable, name, store: true });
266
+ return;
267
+ }
243
268
  return this.#append(tileData, { name, store: true });
244
269
  }
245
270
  /**
@@ -304,7 +329,17 @@ class Writer {
304
329
  this.#prepareStyle();
305
330
  const style = JSON.stringify(this.#style);
306
331
  await this.#append(style, { name: STYLE_FILE });
307
- const entries = await this.#zipWriter.entries();
332
+ let entries = await this.#zipWriter.entries();
333
+ if (this.#duplicateEntries.length > 0) {
334
+ const entriesByName = new Map(entries.map((e) => [e.name, e]));
335
+ for (const { name, originalName } of this.#duplicateEntries) {
336
+ const original = entriesByName.get(originalName);
337
+ if (!original) {
338
+ throw new Error(`Original entry ${originalName} not found`);
339
+ }
340
+ entries.push({ ...original, name });
341
+ }
342
+ }
308
343
  const sortedEntries = sortEntries(entries);
309
344
  await this.#zipWriter.finalize({ entries: sortedEntries });
310
345
  }
@@ -418,6 +453,37 @@ function get2DBBox(bbox2) {
418
453
  if (bbox2.length === 4) return bbox2;
419
454
  return [bbox2[0], bbox2[1], bbox2[3], bbox2[4]];
420
455
  }
456
+ async function toUint8Array(source) {
457
+ if (source instanceof Uint8Array) return source;
458
+ if (typeof source === "string") return new TextEncoder().encode(source);
459
+ const reader = (
460
+ /** @type {ReadableStream<Uint8Array>} */
461
+ source.getReader()
462
+ );
463
+ const chunks = [];
464
+ let totalLength = 0;
465
+ while (true) {
466
+ const { done, value } = await reader.read();
467
+ if (done) break;
468
+ chunks.push(value);
469
+ totalLength += value.byteLength;
470
+ }
471
+ const result = new Uint8Array(totalLength);
472
+ let offset = 0;
473
+ for (const chunk of chunks) {
474
+ result.set(chunk, offset);
475
+ offset += chunk.byteLength;
476
+ }
477
+ return result;
478
+ }
479
+ async function hashData(data) {
480
+ let c = globalThis.crypto;
481
+ if (!c) {
482
+ c = (await import("node:crypto")).webcrypto;
483
+ }
484
+ const buf = await c.subtle.digest("SHA-256", data);
485
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
486
+ }
421
487
  function sortEntries(entries) {
422
488
  return [...entries].sort((a, b) => {
423
489
  if (a.name === VERSION_FILE) return -1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styled-map-package-api",
3
- "version": "5.0.0-pre.1",
3
+ "version": "5.0.0-pre.3",
4
4
  "description": "JavaScript API for reading, writing, and serving Styled Map Package (.smp) files",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -114,7 +114,7 @@
114
114
  "keywords": [],
115
115
  "license": "MIT",
116
116
  "dependencies": {
117
- "@gmaclennan/zip-reader": "^1.0.0-pre.2",
117
+ "@gmaclennan/zip-reader": "^1.0.0",
118
118
  "@mapbox/sphericalmercator": "^1.2.0",
119
119
  "@maplibre/maplibre-gl-style-spec": "^20.3.1",
120
120
  "@placemarkio/check-geojson": "^0.1.12",
@@ -124,12 +124,15 @@
124
124
  "itty-router": "^5.0.22",
125
125
  "ky": "^1.7.5",
126
126
  "map-obj": "^5.0.2",
127
- "mbtiles-reader": "^1.0.0",
127
+ "mbtiles-reader": "^2.0.1",
128
128
  "p-limit": "^6.2.0",
129
129
  "readable-stream": "^4.7.0",
130
130
  "yocto-queue": "^1.1.1",
131
131
  "zip-writer": "^2.2.0"
132
132
  },
133
+ "optionalDependencies": {
134
+ "better-sqlite3": "^12.8.0"
135
+ },
133
136
  "devDependencies": {
134
137
  "@commander-js/extra-typings": "^12.1.0",
135
138
  "@types/geojson": "^7946.0.16",