@storyteller-platform/epub 0.7.0 → 0.7.1

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.
@@ -13,30 +13,40 @@ interface EpubStorageCapabilities {
13
13
  readonly writable: boolean;
14
14
  }
15
15
  interface EpubListEntry {
16
- /** absolute path under {@link EpubStorageAdapter.rootPath} */
16
+ /** location the adapter can read the entry back from, in whatever form it stores */
17
17
  absolutePath: string;
18
- /** path relative to {@link EpubStorageAdapter.rootPath}, slash-separated */
18
+ /** the entry name, as it appears in the archive */
19
19
  relativePath: string;
20
20
  }
21
+ /**
22
+ * Adapters only receive entry names, which is:
23
+ * - the name a file has in the archive itself
24
+ * - relative to the container root, slash-separated,
25
+ * - already percent-decoded (eg `OEBPS/a b.xhtml`, not `OEBPS/a%20b.xhtml`)
26
+ *
27
+ * Adapter decides where to store files under what name
28
+ * In previous versions of the EPUB lib, this was a little bit more muddled, leading to
29
+ * eg the TmpFsAdapter trying to read `OEBPS/a%20b.xhtml`, whereas it was stored as `OEBPS/a b.xhtml`
30
+ */
21
31
  interface EpubStorageAdapter {
32
+ read(entry: string): Promise<Uint8Array>;
33
+ read(entry: string, encoding: "utf-8"): Promise<string>;
22
34
  /**
23
- * Opaque path prefix used by `resolveInternalHref` to anchor absolute
24
- * paths within the archive. For TmpFsAdapter this is the tmp dir;
25
- * for MemoryAdapter it's a virtual prefix that's never written to disk
35
+ * Whether the archive holds an entry under exactly this name.
36
+ * Used to choose between different canditates for an href
37
+ * so should be exact
26
38
  */
27
- readonly rootPath: string;
28
- read(path: string): Promise<Uint8Array>;
29
- read(path: string, encoding: "utf-8"): Promise<string>;
39
+ exists(entry: string): Promise<boolean>;
30
40
  /**
31
41
  * Length of an entry in bytes, for the readium page-count heuristic
32
42
  * which expects compressed size when available
33
43
  */
34
- archiveLength(path: string): Promise<number>;
44
+ archiveLength(entry: string): Promise<number>;
35
45
  /** Required for any mutation method on Epub */
36
- write?(path: string, data: Uint8Array): Promise<void>;
37
- write?(path: string, data: string, encoding: "utf-8"): Promise<void>;
46
+ write?(entry: string, data: Uint8Array): Promise<void>;
47
+ write?(entry: string, data: string, encoding: "utf-8"): Promise<void>;
38
48
  /** Required for removeManifestItem / setCoverImage replacement */
39
- remove?(path: string): Promise<void>;
49
+ remove?(entry: string): Promise<void>;
40
50
  /** Required for Epub.saveAndClose to walk the contents */
41
51
  list?(): AsyncIterable<EpubListEntry>;
42
52
  /** Required for Epub.copy */
@@ -50,17 +60,13 @@ interface EpubStorageAdapterClass<Opts extends object = object> {
50
60
  readonly kind: EpubStorageKind;
51
61
  readonly capabilities: EpubStorageCapabilities;
52
62
  /**
53
- * Open an archive from a path or buffer. The returned adapter is
54
- * ready to read; the Epub class will assert it's a valid EPUB 3
55
- * afterwards.
63
+ * Open an archive from a path or buffer.
64
+ * Will throw if the archive is not a valid EPUB 3
56
65
  */
57
66
  init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
58
67
  /**
59
68
  * Initialize an empty extract root for {@link Epub.create}.
60
- *
61
- * Optional — adapters that can't be written to from scratch
62
- * (e.g. read-only zip handles) should leave this off, and
63
- * `Epub.using(...).create(...)` will throw.
69
+ * Only necessary for adapters that can write
64
70
  */
65
71
  initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
66
72
  }
@@ -13,30 +13,40 @@ interface EpubStorageCapabilities {
13
13
  readonly writable: boolean;
14
14
  }
15
15
  interface EpubListEntry {
16
- /** absolute path under {@link EpubStorageAdapter.rootPath} */
16
+ /** location the adapter can read the entry back from, in whatever form it stores */
17
17
  absolutePath: string;
18
- /** path relative to {@link EpubStorageAdapter.rootPath}, slash-separated */
18
+ /** the entry name, as it appears in the archive */
19
19
  relativePath: string;
20
20
  }
21
+ /**
22
+ * Adapters only receive entry names, which is:
23
+ * - the name a file has in the archive itself
24
+ * - relative to the container root, slash-separated,
25
+ * - already percent-decoded (eg `OEBPS/a b.xhtml`, not `OEBPS/a%20b.xhtml`)
26
+ *
27
+ * Adapter decides where to store files under what name
28
+ * In previous versions of the EPUB lib, this was a little bit more muddled, leading to
29
+ * eg the TmpFsAdapter trying to read `OEBPS/a%20b.xhtml`, whereas it was stored as `OEBPS/a b.xhtml`
30
+ */
21
31
  interface EpubStorageAdapter {
32
+ read(entry: string): Promise<Uint8Array>;
33
+ read(entry: string, encoding: "utf-8"): Promise<string>;
22
34
  /**
23
- * Opaque path prefix used by `resolveInternalHref` to anchor absolute
24
- * paths within the archive. For TmpFsAdapter this is the tmp dir;
25
- * for MemoryAdapter it's a virtual prefix that's never written to disk
35
+ * Whether the archive holds an entry under exactly this name.
36
+ * Used to choose between different canditates for an href
37
+ * so should be exact
26
38
  */
27
- readonly rootPath: string;
28
- read(path: string): Promise<Uint8Array>;
29
- read(path: string, encoding: "utf-8"): Promise<string>;
39
+ exists(entry: string): Promise<boolean>;
30
40
  /**
31
41
  * Length of an entry in bytes, for the readium page-count heuristic
32
42
  * which expects compressed size when available
33
43
  */
34
- archiveLength(path: string): Promise<number>;
44
+ archiveLength(entry: string): Promise<number>;
35
45
  /** Required for any mutation method on Epub */
36
- write?(path: string, data: Uint8Array): Promise<void>;
37
- write?(path: string, data: string, encoding: "utf-8"): Promise<void>;
46
+ write?(entry: string, data: Uint8Array): Promise<void>;
47
+ write?(entry: string, data: string, encoding: "utf-8"): Promise<void>;
38
48
  /** Required for removeManifestItem / setCoverImage replacement */
39
- remove?(path: string): Promise<void>;
49
+ remove?(entry: string): Promise<void>;
40
50
  /** Required for Epub.saveAndClose to walk the contents */
41
51
  list?(): AsyncIterable<EpubListEntry>;
42
52
  /** Required for Epub.copy */
@@ -50,17 +60,13 @@ interface EpubStorageAdapterClass<Opts extends object = object> {
50
60
  readonly kind: EpubStorageKind;
51
61
  readonly capabilities: EpubStorageCapabilities;
52
62
  /**
53
- * Open an archive from a path or buffer. The returned adapter is
54
- * ready to read; the Epub class will assert it's a valid EPUB 3
55
- * afterwards.
63
+ * Open an archive from a path or buffer.
64
+ * Will throw if the archive is not a valid EPUB 3
56
65
  */
57
66
  init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
58
67
  /**
59
68
  * Initialize an empty extract root for {@link Epub.create}.
60
- *
61
- * Optional — adapters that can't be written to from scratch
62
- * (e.g. read-only zip handles) should leave this off, and
63
- * `Epub.using(...).create(...)` will throw.
69
+ * Only necessary for adapters that can write
64
70
  */
65
71
  initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
66
72
  }
@@ -21,13 +21,9 @@ __export(memory_exports, {
21
21
  MemoryAdapter: () => MemoryAdapter
22
22
  });
23
23
  module.exports = __toCommonJS(memory_exports);
24
- var import_node_crypto = require("node:crypto");
25
- var import_node_os = require("node:os");
26
- var import_yauzl_promise = require("yauzl-promise");
27
- var import_path = require("@storyteller-platform/path");
24
+ var import_zip = require("../zip.cjs");
28
25
  class MemoryAdapter {
29
- constructor(rootPath, zip, entries, cache) {
30
- this.rootPath = rootPath;
26
+ constructor(zip, entries, cache) {
31
27
  this.zip = zip;
32
28
  this.entries = entries;
33
29
  this.cache = cache;
@@ -37,7 +33,7 @@ class MemoryAdapter {
37
33
  static async init(source, opts = {}) {
38
34
  const { cache = true, signal } = opts;
39
35
  signal == null ? void 0 : signal.throwIfAborted();
40
- const zip = typeof source === "string" ? await (0, import_yauzl_promise.open)(source) : await (0, import_yauzl_promise.fromBuffer)(Buffer.from(source));
36
+ const zip = await (0, import_zip.openZip)(source);
41
37
  const entries = /* @__PURE__ */ new Map();
42
38
  try {
43
39
  for await (const entry of zip) {
@@ -49,46 +45,40 @@ class MemoryAdapter {
49
45
  await zip.close();
50
46
  throw error;
51
47
  }
52
- const rootPath = (0, import_path.join)(
53
- (0, import_node_os.tmpdir)(),
54
- `storyteller-platform-epub-zip-${(0, import_node_crypto.randomUUID)()}.epub`
55
- );
56
- return new MemoryAdapter(rootPath, zip, entries, cache ? /* @__PURE__ */ new Map() : null);
48
+ return new MemoryAdapter(zip, entries, cache ? /* @__PURE__ */ new Map() : null);
57
49
  }
58
- toZipEntryKey(path) {
59
- const prefix = this.rootPath.endsWith(import_path.sep) ? this.rootPath : this.rootPath + import_path.sep;
60
- const rel = path.startsWith(prefix) ? path.slice(prefix.length) : path;
61
- return import_path.sep === "/" ? rel : rel.split(import_path.sep).join("/");
62
- }
63
- async read(path, encoding) {
50
+ async read(entry, encoding) {
64
51
  var _a, _b;
65
- const key = this.toZipEntryKey(path);
66
- const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
52
+ const cached = (_a = this.cache) == null ? void 0 : _a.get(entry);
67
53
  if (cached) {
68
54
  return encoding ? cached.toString(encoding) : new Uint8Array(cached);
69
55
  }
70
- const entry = this.entries.get(key);
71
- if (!entry) {
56
+ const zipEntry = this.entries.get(entry);
57
+ if (!zipEntry) {
72
58
  const err = new Error(
73
- `ENOENT: zip entry not found in EPUB archive: ${key}`
59
+ `ENOENT: zip entry not found in EPUB archive: ${entry}`
74
60
  );
75
61
  err.code = "ENOENT";
76
62
  throw err;
77
63
  }
78
- const stream = await entry.openReadStream();
64
+ const stream = await zipEntry.openReadStream();
79
65
  const chunks = [];
80
66
  for await (const chunk of stream) {
81
67
  chunks.push(chunk);
82
68
  }
83
69
  const buf = Buffer.concat(chunks);
84
- (_b = this.cache) == null ? void 0 : _b.set(key, buf);
70
+ (_b = this.cache) == null ? void 0 : _b.set(entry, buf);
85
71
  return encoding ? buf.toString(encoding) : new Uint8Array(buf);
86
72
  }
87
73
  // eslint-disable-next-line @typescript-eslint/require-await
88
- async archiveLength(path) {
89
- const entry = this.entries.get(this.toZipEntryKey(path));
90
- if (!entry) return 0;
91
- return entry.compressedSize || entry.uncompressedSize;
74
+ async exists(entry) {
75
+ return this.entries.has(entry);
76
+ }
77
+ // eslint-disable-next-line @typescript-eslint/require-await
78
+ async archiveLength(entry) {
79
+ const zipEntry = this.entries.get(entry);
80
+ if (!zipEntry) return 0;
81
+ return zipEntry.compressedSize || zipEntry.uncompressedSize;
92
82
  }
93
83
  dispose() {
94
84
  var _a;
@@ -17,7 +17,6 @@ interface MemoryAdapterOptions {
17
17
  *
18
18
  */
19
19
  declare class MemoryAdapter implements EpubStorageAdapter {
20
- readonly rootPath: string;
21
20
  private zip;
22
21
  private entries;
23
22
  private cache;
@@ -27,10 +26,10 @@ declare class MemoryAdapter implements EpubStorageAdapter {
27
26
  };
28
27
  static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
29
28
  private constructor();
30
- private toZipEntryKey;
31
- read(path: string): Promise<Uint8Array>;
32
- read(path: string, encoding: "utf-8"): Promise<string>;
33
- archiveLength(path: string): Promise<number>;
29
+ read(entry: string): Promise<Uint8Array>;
30
+ read(entry: string, encoding: "utf-8"): Promise<string>;
31
+ exists(entry: string): Promise<boolean>;
32
+ archiveLength(entry: string): Promise<number>;
34
33
  dispose(): void;
35
34
  }
36
35
 
@@ -17,7 +17,6 @@ interface MemoryAdapterOptions {
17
17
  *
18
18
  */
19
19
  declare class MemoryAdapter implements EpubStorageAdapter {
20
- readonly rootPath: string;
21
20
  private zip;
22
21
  private entries;
23
22
  private cache;
@@ -27,10 +26,10 @@ declare class MemoryAdapter implements EpubStorageAdapter {
27
26
  };
28
27
  static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
29
28
  private constructor();
30
- private toZipEntryKey;
31
- read(path: string): Promise<Uint8Array>;
32
- read(path: string, encoding: "utf-8"): Promise<string>;
33
- archiveLength(path: string): Promise<number>;
29
+ read(entry: string): Promise<Uint8Array>;
30
+ read(entry: string, encoding: "utf-8"): Promise<string>;
31
+ exists(entry: string): Promise<boolean>;
32
+ archiveLength(entry: string): Promise<number>;
34
33
  dispose(): void;
35
34
  }
36
35
 
@@ -1,14 +1,7 @@
1
1
  import "../chunk-BIEQXUOY.js";
2
- import { randomUUID } from "node:crypto";
3
- import { tmpdir } from "node:os";
4
- import {
5
- fromBuffer,
6
- open
7
- } from "yauzl-promise";
8
- import { join, sep } from "@storyteller-platform/path";
2
+ import { openZip } from "../zip.js";
9
3
  class MemoryAdapter {
10
- constructor(rootPath, zip, entries, cache) {
11
- this.rootPath = rootPath;
4
+ constructor(zip, entries, cache) {
12
5
  this.zip = zip;
13
6
  this.entries = entries;
14
7
  this.cache = cache;
@@ -18,7 +11,7 @@ class MemoryAdapter {
18
11
  static async init(source, opts = {}) {
19
12
  const { cache = true, signal } = opts;
20
13
  signal == null ? void 0 : signal.throwIfAborted();
21
- const zip = typeof source === "string" ? await open(source) : await fromBuffer(Buffer.from(source));
14
+ const zip = await openZip(source);
22
15
  const entries = /* @__PURE__ */ new Map();
23
16
  try {
24
17
  for await (const entry of zip) {
@@ -30,46 +23,40 @@ class MemoryAdapter {
30
23
  await zip.close();
31
24
  throw error;
32
25
  }
33
- const rootPath = join(
34
- tmpdir(),
35
- `storyteller-platform-epub-zip-${randomUUID()}.epub`
36
- );
37
- return new MemoryAdapter(rootPath, zip, entries, cache ? /* @__PURE__ */ new Map() : null);
26
+ return new MemoryAdapter(zip, entries, cache ? /* @__PURE__ */ new Map() : null);
38
27
  }
39
- toZipEntryKey(path) {
40
- const prefix = this.rootPath.endsWith(sep) ? this.rootPath : this.rootPath + sep;
41
- const rel = path.startsWith(prefix) ? path.slice(prefix.length) : path;
42
- return sep === "/" ? rel : rel.split(sep).join("/");
43
- }
44
- async read(path, encoding) {
28
+ async read(entry, encoding) {
45
29
  var _a, _b;
46
- const key = this.toZipEntryKey(path);
47
- const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
30
+ const cached = (_a = this.cache) == null ? void 0 : _a.get(entry);
48
31
  if (cached) {
49
32
  return encoding ? cached.toString(encoding) : new Uint8Array(cached);
50
33
  }
51
- const entry = this.entries.get(key);
52
- if (!entry) {
34
+ const zipEntry = this.entries.get(entry);
35
+ if (!zipEntry) {
53
36
  const err = new Error(
54
- `ENOENT: zip entry not found in EPUB archive: ${key}`
37
+ `ENOENT: zip entry not found in EPUB archive: ${entry}`
55
38
  );
56
39
  err.code = "ENOENT";
57
40
  throw err;
58
41
  }
59
- const stream = await entry.openReadStream();
42
+ const stream = await zipEntry.openReadStream();
60
43
  const chunks = [];
61
44
  for await (const chunk of stream) {
62
45
  chunks.push(chunk);
63
46
  }
64
47
  const buf = Buffer.concat(chunks);
65
- (_b = this.cache) == null ? void 0 : _b.set(key, buf);
48
+ (_b = this.cache) == null ? void 0 : _b.set(entry, buf);
66
49
  return encoding ? buf.toString(encoding) : new Uint8Array(buf);
67
50
  }
68
51
  // eslint-disable-next-line @typescript-eslint/require-await
69
- async archiveLength(path) {
70
- const entry = this.entries.get(this.toZipEntryKey(path));
71
- if (!entry) return 0;
72
- return entry.compressedSize || entry.uncompressedSize;
52
+ async exists(entry) {
53
+ return this.entries.has(entry);
54
+ }
55
+ // eslint-disable-next-line @typescript-eslint/require-await
56
+ async archiveLength(entry) {
57
+ const zipEntry = this.entries.get(entry);
58
+ if (!zipEntry) return 0;
59
+ return zipEntry.compressedSize || zipEntry.uncompressedSize;
73
60
  }
74
61
  dispose() {
75
62
  var _a;
@@ -69,39 +69,23 @@ module.exports = __toCommonJS(tmpfs_exports);
69
69
  var import_node_crypto = require("node:crypto");
70
70
  var import_node_fs = require("node:fs");
71
71
  var import_promises = require("node:fs/promises");
72
- var import_node_os = require("node:os");
73
72
  var import_promises2 = require("node:stream/promises");
74
- var import_yauzl_promise = require("yauzl-promise");
75
73
  var import_yazl = require("yazl");
74
+ var import_fs = require("@storyteller-platform/fs");
75
+ var import_media_types = require("@storyteller-platform/media-types");
76
76
  var import_path = require("@storyteller-platform/path");
77
- const MP3_FILE_EXTENSIONS = [".mp3"];
78
- const MPEG4_FILE_EXTENSIONS = [".mp4", ".m4a", ".m4b"];
79
- const AAC_FILE_EXTENSIONS = [".aac"];
80
- const OGG_FILE_EXTENSIONS = [".ogg", ".oga", ".mogg"];
81
- const OPUS_FILE_EXTENSIONS = [".opus"];
82
- const WAVE_FILE_EXTENSIONS = [".wav"];
83
- const AIFF_FILE_EXTENSIONS = [".aiff"];
84
- const FLAC_FILE_EXTENSIONS = [".flac"];
85
- const ALAC_FILE_EXTENSIONS = [".alac"];
86
- const WEBM_FILE_EXTENSIONS = [".weba"];
87
- const AUDIO_FILE_EXTENSIONS = [
88
- ...MP3_FILE_EXTENSIONS,
89
- ...AAC_FILE_EXTENSIONS,
90
- ...MPEG4_FILE_EXTENSIONS,
91
- ...OPUS_FILE_EXTENSIONS,
92
- ...OGG_FILE_EXTENSIONS,
93
- ...WAVE_FILE_EXTENSIONS,
94
- ...AIFF_FILE_EXTENSIONS,
95
- ...FLAC_FILE_EXTENSIONS,
96
- ...ALAC_FILE_EXTENSIONS,
97
- ...WEBM_FILE_EXTENSIONS
98
- ];
99
- function isAudioFile(filenameOrExt) {
100
- const lowered = filenameOrExt.toLowerCase();
101
- return AUDIO_FILE_EXTENSIONS.some((ext) => lowered.endsWith(ext));
102
- }
77
+ var import_zip = require("../zip.cjs");
78
+ const PRECOMPRESSED_KINDS = /* @__PURE__ */ new Set([
79
+ "audio",
80
+ "image",
81
+ "archive",
82
+ "package"
83
+ ]);
103
84
  function mintRootPath() {
104
- return (0, import_path.join)((0, import_node_os.tmpdir)(), `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`);
85
+ return (0, import_fs.scratchPath)(`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`);
86
+ }
87
+ function isStorableEntryName(entry) {
88
+ return !entry.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
105
89
  }
106
90
  class TmpFsAdapter {
107
91
  constructor(rootPath) {
@@ -114,7 +98,7 @@ class TmpFsAdapter {
114
98
  try {
115
99
  var _stack = [];
116
100
  try {
117
- const zipfile = typeof source === "string" ? await (0, import_yauzl_promise.open)(source) : await (0, import_yauzl_promise.fromBuffer)(Buffer.from(source));
101
+ const zipfile = await (0, import_zip.openZip)(source);
118
102
  const stack = __using(_stack, new AsyncDisposableStack(), true);
119
103
  stack.defer(async () => {
120
104
  await zipfile.close();
@@ -123,7 +107,8 @@ class TmpFsAdapter {
123
107
  if (entry.filename.endsWith("/")) {
124
108
  continue;
125
109
  }
126
- const writePath = (0, import_path.join)(rootPath, entry.filename);
110
+ if (!isStorableEntryName(entry.filename)) continue;
111
+ const writePath = (0, import_path.join)(rootPath, ...entry.filename.split("/"));
127
112
  const readStream = await entry.openReadStream();
128
113
  await (0, import_promises.mkdir)((0, import_path.dirname)(writePath), { recursive: true });
129
114
  const writeStream = (0, import_node_fs.createWriteStream)(writePath);
@@ -146,19 +131,36 @@ class TmpFsAdapter {
146
131
  await (0, import_promises.mkdir)(rootPath, { recursive: true });
147
132
  return new TmpFsAdapter(rootPath);
148
133
  }
149
- async read(path, encoding) {
150
- return await (0, import_promises.readFile)(path, encoding);
134
+ diskPath(entry) {
135
+ if (!isStorableEntryName(entry)) {
136
+ const err = new Error(`ENOENT: no such entry in EPUB archive: ${entry}`);
137
+ err.code = "ENOENT";
138
+ throw err;
139
+ }
140
+ return (0, import_path.join)(this.rootPath, ...entry.split("/"));
141
+ }
142
+ async read(entry, encoding) {
143
+ return await (0, import_promises.readFile)(this.diskPath(entry), encoding);
151
144
  }
152
- async write(path, data, encoding) {
145
+ async write(entry, data, encoding) {
146
+ const path = this.diskPath(entry);
153
147
  await (0, import_promises.mkdir)((0, import_path.dirname)(path), { recursive: true });
154
148
  await (0, import_promises.writeFile)(path, data, encoding);
155
149
  }
156
- async remove(path) {
157
- await (0, import_promises.rm)(path);
150
+ async remove(entry) {
151
+ await (0, import_promises.rm)(this.diskPath(entry));
152
+ }
153
+ async exists(entry) {
154
+ try {
155
+ await (0, import_promises.stat)(this.diskPath(entry));
156
+ return true;
157
+ } catch {
158
+ return false;
159
+ }
158
160
  }
159
- async archiveLength(path) {
161
+ async archiveLength(entry) {
160
162
  try {
161
- const st = await (0, import_promises.stat)(path);
163
+ const st = await (0, import_promises.stat)(this.diskPath(entry));
162
164
  return st.size;
163
165
  } catch {
164
166
  return 0;
@@ -174,7 +176,7 @@ class TmpFsAdapter {
174
176
  const absolutePath = (0, import_path.join)(entry.parentPath, entry.name);
175
177
  yield {
176
178
  absolutePath,
177
- relativePath: absolutePath.replace(`${this.rootPath}${import_path.sep}`, "")
179
+ relativePath: absolutePath.slice(this.rootPath.length + 1).split(import_path.sep).join("/")
178
180
  };
179
181
  }
180
182
  }
@@ -189,31 +191,33 @@ class TmpFsAdapter {
189
191
  return new TmpFsAdapter(newRoot);
190
192
  }
191
193
  async serialize(targetPath) {
194
+ var _a;
192
195
  var _stack = [];
193
196
  try {
194
- const tmpArchivePath = (0, import_path.join)(
195
- (0, import_node_os.tmpdir)(),
196
- `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
197
- );
198
- const { promise, resolve } = Promise.withResolvers();
197
+ const tmpArchivePath = mintRootPath();
198
+ const { promise, resolve, reject } = Promise.withResolvers();
199
199
  const zipfile = new import_yazl.ZipFile();
200
200
  const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
201
201
  writeStream.on("close", () => {
202
202
  resolve();
203
203
  });
204
+ writeStream.on("error", reject);
205
+ zipfile.outputStream.on("error", reject);
204
206
  const stack = __using(_stack, new AsyncDisposableStack(), true);
205
207
  stack.defer(async () => {
206
208
  writeStream.close();
207
209
  await (0, import_promises.rm)(tmpArchivePath, { force: true });
208
210
  });
209
211
  zipfile.outputStream.pipe(writeStream);
210
- zipfile.addBuffer(Buffer.from("application/epub+zip"), "mimetype", {
212
+ zipfile.addBuffer(Buffer.from(import_media_types.MediaType.EPUB.mime), "mimetype", {
211
213
  compress: false
212
214
  });
213
215
  for await (const entry of this.list()) {
214
216
  if (entry.relativePath === "mimetype") continue;
215
217
  zipfile.addFile(entry.absolutePath, entry.relativePath, {
216
- compress: !isAudioFile(entry.absolutePath)
218
+ compress: !PRECOMPRESSED_KINDS.has(
219
+ (_a = import_media_types.MediaType.fromPath(entry.absolutePath)) == null ? void 0 : _a.kind
220
+ )
217
221
  });
218
222
  }
219
223
  zipfile.end();
@@ -6,7 +6,7 @@ import { EpubStorageAdapter, EpubListEntry } from './interface.cjs';
6
6
  * `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
7
7
  */
8
8
  declare class TmpFsAdapter implements EpubStorageAdapter {
9
- readonly rootPath: string;
9
+ private readonly rootPath;
10
10
  static readonly kind: "extracted-dir";
11
11
  static readonly capabilities: {
12
12
  readonly writable: true;
@@ -14,12 +14,14 @@ declare class TmpFsAdapter implements EpubStorageAdapter {
14
14
  static init(source: string | Uint8Array): Promise<TmpFsAdapter>;
15
15
  static initEmpty(): Promise<TmpFsAdapter>;
16
16
  private constructor();
17
- read(path: string): Promise<Uint8Array>;
18
- read(path: string, encoding: "utf-8"): Promise<string>;
19
- write(path: string, data: Uint8Array): Promise<void>;
20
- write(path: string, data: string, encoding: "utf-8"): Promise<void>;
21
- remove(path: string): Promise<void>;
22
- archiveLength(path: string): Promise<number>;
17
+ private diskPath;
18
+ read(entry: string): Promise<Uint8Array>;
19
+ read(entry: string, encoding: "utf-8"): Promise<string>;
20
+ write(entry: string, data: Uint8Array): Promise<void>;
21
+ write(entry: string, data: string, encoding: "utf-8"): Promise<void>;
22
+ remove(entry: string): Promise<void>;
23
+ exists(entry: string): Promise<boolean>;
24
+ archiveLength(entry: string): Promise<number>;
23
25
  list(): AsyncIterable<EpubListEntry>;
24
26
  duplicate(): Promise<TmpFsAdapter>;
25
27
  serialize(targetPath: string): Promise<void>;
@@ -6,7 +6,7 @@ import { EpubStorageAdapter, EpubListEntry } from './interface.js';
6
6
  * `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
7
7
  */
8
8
  declare class TmpFsAdapter implements EpubStorageAdapter {
9
- readonly rootPath: string;
9
+ private readonly rootPath;
10
10
  static readonly kind: "extracted-dir";
11
11
  static readonly capabilities: {
12
12
  readonly writable: true;
@@ -14,12 +14,14 @@ declare class TmpFsAdapter implements EpubStorageAdapter {
14
14
  static init(source: string | Uint8Array): Promise<TmpFsAdapter>;
15
15
  static initEmpty(): Promise<TmpFsAdapter>;
16
16
  private constructor();
17
- read(path: string): Promise<Uint8Array>;
18
- read(path: string, encoding: "utf-8"): Promise<string>;
19
- write(path: string, data: Uint8Array): Promise<void>;
20
- write(path: string, data: string, encoding: "utf-8"): Promise<void>;
21
- remove(path: string): Promise<void>;
22
- archiveLength(path: string): Promise<number>;
17
+ private diskPath;
18
+ read(entry: string): Promise<Uint8Array>;
19
+ read(entry: string, encoding: "utf-8"): Promise<string>;
20
+ write(entry: string, data: Uint8Array): Promise<void>;
21
+ write(entry: string, data: string, encoding: "utf-8"): Promise<void>;
22
+ remove(entry: string): Promise<void>;
23
+ exists(entry: string): Promise<boolean>;
24
+ archiveLength(entry: string): Promise<number>;
23
25
  list(): AsyncIterable<EpubListEntry>;
24
26
  duplicate(): Promise<TmpFsAdapter>;
25
27
  serialize(targetPath: string): Promise<void>;