@storyteller-platform/epub 0.2.1 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Entry } from '@zip.js/zip.js';
1
+ import { NativePath } from '@yarnpkg/fslib';
2
+ import { ZipFS } from '@yarnpkg/libzip';
2
3
  import { XMLParser, XMLBuilder } from 'fast-xml-parser';
3
4
 
4
5
  declare global {
@@ -14,9 +15,10 @@ type Letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" |
14
15
  type QuestionMark = "?";
15
16
  /** A valid name for an XML element (must start with a letter) */
16
17
  type ElementName = `${Letter | Uppercase<Letter> | QuestionMark}${string}`;
18
+ type PropertyPrefix = "@_";
17
19
  /** An XML element */
18
20
  type XmlElement<Name extends ElementName = ElementName> = {
19
- ":@"?: Record<string, string>;
21
+ ":@"?: Record<`${PropertyPrefix}${string}`, string>;
20
22
  } & {
21
23
  [key in Name]: ParsedXml;
22
24
  };
@@ -36,17 +38,6 @@ type ManifestItem = {
36
38
  mediaOverlay?: string | undefined;
37
39
  properties?: string[] | undefined;
38
40
  };
39
- declare class EpubEntry {
40
- filename: string;
41
- private entry;
42
- private data;
43
- getData(): Promise<Uint8Array<ArrayBufferLike>>;
44
- setData(data: Uint8Array): void;
45
- constructor(entry: Entry | {
46
- filename: string;
47
- data: Uint8Array;
48
- });
49
- }
50
41
  type MetadataEntry = {
51
42
  id?: string | undefined;
52
43
  type: ElementName;
@@ -94,7 +85,7 @@ type PackageElement = XmlElement<"package"> | XmlElement<"opf:package">;
94
85
  * Example usage:
95
86
  *
96
87
  * ```ts
97
- * import { Epub, getBody, findByName, textContent } from '@smoores/epub';
88
+ * import { Epub, getBody, findByName, textContent } from '@storyteller-platform/epub';
98
89
  *
99
90
  * const epub = await Epub.from('./path/to/book.epub');
100
91
  * const title = await epub.getTitle();
@@ -114,8 +105,9 @@ type PackageElement = XmlElement<"package"> | XmlElement<"opf:package">;
114
105
  * @link https://www.w3.org/TR/epub-33/
115
106
  */
116
107
  declare class Epub {
117
- private entries;
118
- private onClose?;
108
+ protected zipFs: ZipFS;
109
+ protected zipPath: NativePath;
110
+ protected inputPath: string | undefined;
119
111
  static xmlParser: XMLParser;
120
112
  static xhtmlParser: XMLParser;
121
113
  static xmlBuilder: XMLBuilder;
@@ -171,18 +163,11 @@ declare class Epub {
171
163
  * a text node or an XML element.
172
164
  */
173
165
  static isXmlTextNode(node: XmlNode): node is XmlTextNode;
174
- private zipWriter;
175
- private dataWriter;
176
166
  private rootfile;
177
167
  private manifest;
178
168
  private spine;
179
169
  private packageMutex;
180
- protected constructor(entries: EpubEntry[], onClose?: (() => Promise<void> | void) | undefined);
181
- /**
182
- * Close the Epub. Must be called before the Epub goes out
183
- * of scope/is garbage collected.
184
- */
185
- close(): Promise<void>;
170
+ protected constructor(zipFs: ZipFS, zipPath: NativePath, inputPath: string | undefined);
186
171
  /**
187
172
  * Construct an Epub instance, optionally beginning
188
173
  * with the provided metadata.
@@ -190,7 +175,7 @@ declare class Epub {
190
175
  * @param dublinCore Core metadata terms
191
176
  * @param additionalMetadata An array of additional metadata entries
192
177
  */
193
- static create({ title, language, identifier, date, subjects, type, creators, contributors, }: DublinCore, additionalMetadata?: EpubMetadata): Promise<Epub>;
178
+ static create(path: string, { title, language, identifier, date, subjects, type, creators, contributors, }: DublinCore, additionalMetadata?: EpubMetadata): Promise<Epub>;
194
179
  /**
195
180
  * Construct an Epub instance by reading an existing EPUB
196
181
  * publication.
@@ -200,7 +185,6 @@ declare class Epub {
200
185
  * the data of the EPUB publication.
201
186
  */
202
187
  static from(pathOrData: string | Uint8Array): Promise<Epub>;
203
- private getEntry;
204
188
  private removeEntry;
205
189
  private getFileData;
206
190
  private getRootfile;
@@ -277,6 +261,24 @@ declare class Epub {
277
261
  * @link https://www.w3.org/TR/epub-33/#sec-pkg-metadata
278
262
  */
279
263
  getMetadata(): Promise<EpubMetadata>;
264
+ /**
265
+ * Retrieve the identifier from the dc:identifier element
266
+ * in the EPUB metadata.
267
+ *
268
+ * If there is no dc:identifier element, returns null.
269
+ *
270
+ * @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
271
+ */
272
+ getIdentifier(): Promise<string | null>;
273
+ /**
274
+ * Set the dc:identifier metadata element with the provided string.
275
+ *
276
+ * Updates the existing dc:identifier element if one exists.
277
+ * Otherwise creates a new element
278
+ *
279
+ * @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
280
+ */
281
+ setIdentifier(identifier: string): Promise<void>;
280
282
  /**
281
283
  * Even "EPUB 3" publications sometimes still only use the
282
284
  * EPUB 2 specification for identifying the cover image.
@@ -601,7 +603,7 @@ declare class Epub {
601
603
  * @param head Optional - the XMl nodes to place in the head
602
604
  * @param language Optional - defaults to the EPUB's language
603
605
  */
604
- createXhtmlDocument(body: ParsedXml, head?: ParsedXml, language?: Intl.Locale): Promise<(XmlElement<"?xml"> | XmlElement<"html">)[]>;
606
+ createXhtmlDocument(body: ParsedXml, head?: ParsedXml, language?: Intl.Locale): Promise<(XmlElement<"html"> | XmlElement<"?xml">)[]>;
605
607
  /**
606
608
  * Retrieves the contents of an XHTML item, given its manifest id.
607
609
  *
@@ -697,18 +699,29 @@ declare class Epub {
697
699
  */
698
700
  replaceMetadata(predicate: (entry: MetadataEntry) => boolean, entry: MetadataEntry): Promise<void>;
699
701
  /**
700
- * Write the current contents of the Epub to a new
701
- * Uint8Array.
702
+ * Remove one or more metadata entries.
703
+ *
704
+ * The `predicate` argument will be used to determine which entries
705
+ * to remove. The all metadata entries that match the
706
+ * predicate will be removed.
707
+ *
708
+ * @param predicate Calls predicate once for each metadata entry,
709
+ * removing any for which it returns true
702
710
  *
703
- * This _does not_ close the Epub. It can continue to
704
- * be modified after it has been written to disk. Use
705
- * `epub.close()` to close the Epub.
711
+ * @link https://www.w3.org/TR/epub-33/#sec-pkg-metadata
712
+ */
713
+ removeMetadata(predicate: (entry: MetadataEntry) => boolean): Promise<void>;
714
+ discardAndClose(): void;
715
+ /**
716
+ * Write the current contents of the Epub to a new
717
+ * EPUB archive on disk.
706
718
  *
707
719
  * When this method is called, the "dcterms:modified"
708
720
  * meta tag is automatically updated to the current UTC
709
721
  * timestamp.
710
722
  */
711
- writeToArray(): Promise<Uint8Array<ArrayBufferLike>>;
723
+ saveAndClose(): Promise<void>;
724
+ [Symbol.dispose](): void;
712
725
  }
713
726
 
714
727
  export { type AlternateScript, type Collection, type DcCreator, type DcSubject, type DublinCore, type ElementName, Epub, type EpubMetadata, type ManifestItem, type MetadataEntry, type PackageElement, type ParsedXml, type XmlElement, type XmlNode, type XmlTextNode };
package/dist/index.js CHANGED
@@ -1,45 +1,19 @@
1
- import {
2
- ERR_DUPLICATED_NAME,
3
- Uint8ArrayReader,
4
- Uint8ArrayWriter,
5
- ZipReader,
6
- ZipWriter
7
- } from "@zip.js/zip.js";
1
+ import { randomUUID } from "node:crypto";
2
+ import { rmSync } from "node:fs";
3
+ import { cp, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { PortablePath, npath, ppath } from "@yarnpkg/fslib";
6
+ import { DEFAULT_COMPRESSION_LEVEL, ZipFS } from "@yarnpkg/libzip";
8
7
  import { Mutex } from "async-mutex";
9
8
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
10
9
  import memoize from "mem";
11
10
  import { lookup } from "mime-types";
12
11
  import { nanoid } from "nanoid";
13
- import { dirname, resolve } from "@storyteller-platform/path";
14
- class EpubEntry {
15
- filename;
16
- entry = null;
17
- data = null;
18
- async getData() {
19
- if (this.data) return this.data;
20
- const writer = new Uint8ArrayWriter();
21
- const data = await this.entry.getData(writer);
22
- this.data = data;
23
- return this.data;
24
- }
25
- setData(data) {
26
- this.data = data;
27
- }
28
- constructor(entry) {
29
- this.filename = entry.filename;
30
- if ("data" in entry) {
31
- this.data = entry.data;
32
- } else {
33
- this.entry = entry;
34
- }
35
- }
36
- }
37
12
  class Epub {
38
- constructor(entries, onClose) {
39
- this.entries = entries;
40
- this.onClose = onClose;
41
- this.dataWriter = new Uint8ArrayWriter();
42
- this.zipWriter = new ZipWriter(this.dataWriter);
13
+ constructor(zipFs, zipPath, inputPath) {
14
+ this.zipFs = zipFs;
15
+ this.zipPath = zipPath;
16
+ this.inputPath = inputPath;
43
17
  this.readXhtmlItemContents = memoize(
44
18
  this.readXhtmlItemContents.bind(this),
45
19
  // This isn't unnecessary, the generic here just isn't handling the
@@ -194,21 +168,10 @@ ${JSON.stringify(element, null, 2)}`
194
168
  static isXmlTextNode(node) {
195
169
  return "#text" in node;
196
170
  }
197
- zipWriter;
198
- dataWriter;
199
171
  rootfile = null;
200
172
  manifest = null;
201
173
  spine = null;
202
174
  packageMutex = new Mutex();
203
- /**
204
- * Close the Epub. Must be called before the Epub goes out
205
- * of scope/is garbage collected.
206
- */
207
- async close() {
208
- var _a;
209
- await ((_a = this.onClose) == null ? void 0 : _a.call(this));
210
- await this.zipWriter.close();
211
- }
212
175
  /**
213
176
  * Construct an Epub instance, optionally beginning
214
177
  * with the provided metadata.
@@ -216,7 +179,7 @@ ${JSON.stringify(element, null, 2)}`
216
179
  * @param dublinCore Core metadata terms
217
180
  * @param additionalMetadata An array of additional metadata entries
218
181
  */
219
- static async create({
182
+ static async create(path, {
220
183
  title,
221
184
  language,
222
185
  identifier,
@@ -226,7 +189,11 @@ ${JSON.stringify(element, null, 2)}`
226
189
  creators,
227
190
  contributors
228
191
  }, additionalMetadata = []) {
229
- const entries = [];
192
+ const tmp = npath.join(
193
+ tmpdir(),
194
+ `storyteller-platform-epub-${randomUUID()}.epub`
195
+ );
196
+ const zipFs = new ZipFS(npath.toPortablePath(tmp), { create: true });
230
197
  const encoder = new TextEncoder();
231
198
  const container = encoder.encode(`<?xml version="1.0"?>
232
199
  <container>
@@ -235,8 +202,10 @@ ${JSON.stringify(element, null, 2)}`
235
202
  </rootfiles>
236
203
  </container>
237
204
  `);
238
- entries.push(
239
- new EpubEntry({ filename: "META-INF/container.xml", data: container })
205
+ await zipFs.mkdirpPromise(ppath.join(PortablePath.root, "META-INF"));
206
+ await zipFs.writeFilePromise(
207
+ ppath.join(PortablePath.root, "META-INF", "container.xml"),
208
+ container
240
209
  );
241
210
  const packageDocument = encoder.encode(`<?xml version="1.0"?>
242
211
  <package unique-identifier="pub-id" dir="${language.textInfo.direction}" xml:lang=${language.toString()} version="3.0">
@@ -248,10 +217,12 @@ ${JSON.stringify(element, null, 2)}`
248
217
  </spine>
249
218
  </package>
250
219
  `);
251
- entries.push(
252
- new EpubEntry({ filename: "OEBPS/content.opf", data: packageDocument })
220
+ await zipFs.mkdirpPromise(ppath.join(PortablePath.root, "OEBPS"));
221
+ await zipFs.writeFilePromise(
222
+ ppath.join(PortablePath.root, "OEBPS", "content.opf"),
223
+ packageDocument
253
224
  );
254
- const epub = new this(entries);
225
+ const epub = new this(zipFs, tmp, path);
255
226
  const metadata = [
256
227
  {
257
228
  id: "pub-id",
@@ -287,42 +258,37 @@ ${JSON.stringify(element, null, 2)}`
287
258
  * path to an EPUB file on disk, or a Uint8Array representing
288
259
  * the data of the EPUB publication.
289
260
  */
261
+ // eslint-disable-next-line @typescript-eslint/require-await
290
262
  static async from(pathOrData) {
291
- if (typeof pathOrData === "string") {
292
- throw new Error("Import from /node to construct from a file");
263
+ const tmp = npath.join(
264
+ tmpdir(),
265
+ `storyteller-platform-epub-${randomUUID()}.epub`
266
+ );
267
+ if (typeof pathOrData !== "string") {
268
+ await writeFile(tmp, pathOrData);
269
+ } else {
270
+ await cp(pathOrData, tmp);
293
271
  }
294
- const fileData = pathOrData;
295
- const dataReader = new Uint8ArrayReader(fileData);
296
- const zipReader = new ZipReader(dataReader);
297
- const zipEntries = await zipReader.getEntries();
298
- const epubEntries = zipEntries.map((entry) => new EpubEntry(entry));
299
- const epub = new this(epubEntries, () => zipReader.close());
300
- return epub;
301
- }
302
- getEntry(path) {
303
- return this.entries.find((entry) => entry.filename === path);
272
+ const zipFs = new ZipFS(npath.toPortablePath(tmp));
273
+ return new this(
274
+ zipFs,
275
+ tmp,
276
+ typeof pathOrData === "string" ? pathOrData : void 0
277
+ );
304
278
  }
305
279
  async removeEntry(href) {
306
280
  const rootfile = await this.getRootfile();
307
281
  const filename = this.resolveHref(rootfile, href);
308
- const index = this.entries.findIndex((entry) => entry.filename === filename);
309
- if (index === -1) return;
310
- this.entries.splice(index, 1);
282
+ await this.zipFs.removePromise(filename);
311
283
  }
312
284
  async getFileData(path, encoding) {
313
- const containerEntry = this.getEntry(path);
314
- if (!containerEntry)
315
- throw new Error(
316
- `Could not get file data for entry ${path}: entry not found`
317
- );
318
- const containerContents = await containerEntry.getData();
319
- return encoding === "utf-8" ? new TextDecoder("utf-8").decode(containerContents) : containerContents;
285
+ return await this.zipFs.readFilePromise(path, encoding);
320
286
  }
321
287
  async getRootfile() {
322
288
  var _a;
323
289
  if (this.rootfile !== null) return this.rootfile;
324
290
  const containerString = await this.getFileData(
325
- "META-INF/container.xml",
291
+ ppath.join(PortablePath.root, "META-INF", "container.xml"),
326
292
  "utf-8"
327
293
  );
328
294
  if (!containerString)
@@ -349,11 +315,12 @@ ${JSON.stringify(element, null, 2)}`
349
315
  return !Epub.isXmlTextNode(node) && ((_a2 = node[":@"]) == null ? void 0 : _a2["@_media-type"]) === "application/oebps-package+xml";
350
316
  }
351
317
  );
352
- if (!((_a = rootfile == null ? void 0 : rootfile[":@"]) == null ? void 0 : _a["@_full-path"]))
318
+ const fullPath = (_a = rootfile == null ? void 0 : rootfile[":@"]) == null ? void 0 : _a["@_full-path"];
319
+ if (!fullPath)
353
320
  throw new Error(
354
321
  "Failed to parse EPUB container.xml: Found no rootfile element"
355
322
  );
356
- this.rootfile = rootfile[":@"]["@_full-path"];
323
+ this.rootfile = ppath.resolve(PortablePath.root, fullPath);
357
324
  return this.rootfile;
358
325
  }
359
326
  migratePackageDocument(packageDocument) {
@@ -416,7 +383,7 @@ ${JSON.stringify(element, null, 2)}`
416
383
  produced ?? packageDocument
417
384
  );
418
385
  const rootfile = await this.getRootfile();
419
- this.writeEntryContents(rootfile, updatedPackageDocument, "utf-8");
386
+ await this.writeEntryContents(rootfile, updatedPackageDocument, "utf-8");
420
387
  });
421
388
  }
422
389
  /**
@@ -555,6 +522,34 @@ ${JSON.stringify(element, null, 2)}`
555
522
  const metadata = metadataElement.metadata.map((node) => Epub.parseMetadataItem(node)).filter((node) => !!node);
556
523
  return metadata;
557
524
  }
525
+ /**
526
+ * Retrieve the identifier from the dc:identifier element
527
+ * in the EPUB metadata.
528
+ *
529
+ * If there is no dc:identifier element, returns null.
530
+ *
531
+ * @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
532
+ */
533
+ async getIdentifier() {
534
+ const metadata = await this.getMetadata();
535
+ const entry = metadata.find(({ type }) => type === "dc:identifier");
536
+ return (entry == null ? void 0 : entry.value) ?? null;
537
+ }
538
+ /**
539
+ * Set the dc:identifier metadata element with the provided string.
540
+ *
541
+ * Updates the existing dc:identifier element if one exists.
542
+ * Otherwise creates a new element
543
+ *
544
+ * @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
545
+ */
546
+ async setIdentifier(identifier) {
547
+ await this.replaceMetadata(({ type }) => type === "dc:identifier", {
548
+ type: "dc:identifier",
549
+ properties: {},
550
+ value: identifier
551
+ });
552
+ }
558
553
  /**
559
554
  * Even "EPUB 3" publications sometimes still only use the
560
555
  * EPUB 2 specification for identifying the cover image.
@@ -1448,9 +1443,8 @@ ${JSON.stringify(element, null, 2)}`
1448
1443
  * Returns a Zip Entry path for an HREF
1449
1444
  */
1450
1445
  resolveHref(from, href) {
1451
- const startPath = dirname(from);
1452
- const absoluteStartPath = startPath.startsWith("/") ? startPath : `/${startPath}`;
1453
- return resolve(absoluteStartPath, href).slice(1);
1446
+ const startPath = ppath.dirname(from);
1447
+ return ppath.resolve(PortablePath.root, startPath, href);
1454
1448
  }
1455
1449
  async readItemContents(id, encoding) {
1456
1450
  const rootfile = await this.getRootfile();
@@ -1495,11 +1489,9 @@ ${JSON.stringify(element, null, 2)}`
1495
1489
  const body = Epub.getXhtmlBody(xml);
1496
1490
  return Epub.getXhtmlTextContent(body);
1497
1491
  }
1498
- writeEntryContents(path, contents, encoding) {
1499
- const data = encoding === "utf-8" ? new TextEncoder().encode(contents) : contents;
1500
- const entry = this.getEntry(path);
1501
- if (!entry) throw new Error(`Could not find file at ${path} in EPUB`);
1502
- entry.setData(data);
1492
+ async writeEntryContents(path, contents, encoding) {
1493
+ await this.zipFs.mkdirpPromise(ppath.dirname(path));
1494
+ await this.zipFs.writeFilePromise(path, contents, encoding);
1503
1495
  }
1504
1496
  async writeItemContents(id, contents, encoding) {
1505
1497
  const rootfile = await this.getRootfile();
@@ -1510,9 +1502,9 @@ ${JSON.stringify(element, null, 2)}`
1510
1502
  memoize.clear(this.readXhtmlItemContents);
1511
1503
  const href = this.resolveHref(rootfile, manifestItem.href);
1512
1504
  if (encoding === "utf-8") {
1513
- this.writeEntryContents(href, contents, encoding);
1505
+ await this.writeEntryContents(href, contents, encoding);
1514
1506
  } else {
1515
- this.writeEntryContents(href, contents);
1507
+ await this.writeEntryContents(href, contents);
1516
1508
  }
1517
1509
  }
1518
1510
  /**
@@ -1575,7 +1567,9 @@ ${JSON.stringify(element, null, 2)}`
1575
1567
  ...item.mediaType && { "media-type": item.mediaType },
1576
1568
  ...item.fallback && { fallback: item.fallback },
1577
1569
  ...item.mediaOverlay && { "media-overlay": item.mediaOverlay },
1578
- ...item.properties && { properties: item.properties.join(" ") }
1570
+ ...item.properties && {
1571
+ properties: item.properties.join(" ")
1572
+ }
1579
1573
  })
1580
1574
  );
1581
1575
  });
@@ -1587,7 +1581,8 @@ ${JSON.stringify(element, null, 2)}`
1587
1581
  contents
1588
1582
  )
1589
1583
  ) : contents;
1590
- this.entries.push(new EpubEntry({ filename, data }));
1584
+ await this.zipFs.mkdirpPromise(ppath.dirname(filename));
1585
+ await this.zipFs.writeFilePromise(filename, data);
1591
1586
  }
1592
1587
  /**
1593
1588
  * Update the manifest entry for an existing item.
@@ -1737,19 +1732,25 @@ ${JSON.stringify(element, null, 2)}`
1737
1732
  }
1738
1733
  });
1739
1734
  }
1735
+ discardAndClose() {
1736
+ this.zipFs.discardAndClose();
1737
+ this.rootfile = null;
1738
+ this.manifest = null;
1739
+ this.spine = null;
1740
+ rmSync(this.zipPath, { recursive: true, force: true });
1741
+ }
1740
1742
  /**
1741
1743
  * Write the current contents of the Epub to a new
1742
- * Uint8Array.
1743
- *
1744
- * This _does not_ close the Epub. It can continue to
1745
- * be modified after it has been written to disk. Use
1746
- * `epub.close()` to close the Epub.
1744
+ * EPUB archive on disk.
1747
1745
  *
1748
1746
  * When this method is called, the "dcterms:modified"
1749
1747
  * meta tag is automatically updated to the current UTC
1750
1748
  * timestamp.
1751
1749
  */
1752
- async writeToArray() {
1750
+ async saveAndClose() {
1751
+ if (!this.inputPath) {
1752
+ throw new Error("In-memory EPUB files cannot be saved to disk");
1753
+ }
1753
1754
  await this.replaceMetadata(
1754
1755
  (entry) => entry.properties["property"] === "dcterms:modified",
1755
1756
  {
@@ -1759,48 +1760,20 @@ ${JSON.stringify(element, null, 2)}`
1759
1760
  value: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+/, "")
1760
1761
  }
1761
1762
  );
1762
- let mimetypeEntry = this.getEntry("mimetype");
1763
- if (!mimetypeEntry) {
1764
- mimetypeEntry = new EpubEntry({
1765
- filename: "mimetype",
1766
- data: new TextEncoder().encode("application/epub+zip")
1767
- });
1768
- this.entries.push(mimetypeEntry);
1769
- }
1770
- const mimetypeReader = new Uint8ArrayReader(await mimetypeEntry.getData());
1771
- try {
1772
- await this.zipWriter.add(mimetypeEntry.filename, mimetypeReader, {
1773
- level: 0,
1774
- extendedTimestamp: false
1775
- });
1776
- } catch (e) {
1777
- if (e instanceof Error && e.message === ERR_DUPLICATED_NAME) {
1778
- throw new Error(
1779
- `Failed to add file "${mimetypeEntry.filename}" to zip archive: ${e.message}`
1780
- );
1781
- }
1782
- throw e;
1783
- }
1784
- await Promise.all(
1785
- this.entries.map(async (entry) => {
1786
- if (entry.filename === "mimetype") return;
1787
- const reader = new Uint8ArrayReader(await entry.getData());
1788
- try {
1789
- return await this.zipWriter.add(entry.filename, reader);
1790
- } catch (e) {
1791
- if (e instanceof Error && e.message === ERR_DUPLICATED_NAME) {
1792
- throw new Error(
1793
- `Failed to add file "${entry.filename}" to zip archive: ${e.message}`
1794
- );
1795
- }
1796
- throw e;
1797
- }
1798
- })
1763
+ this.zipFs.level = 0;
1764
+ await this.zipFs.writeFilePromise(
1765
+ ppath.join(PortablePath.root, "mimetype"),
1766
+ "application/epub+zip",
1767
+ "utf-8"
1799
1768
  );
1800
- const data = await this.zipWriter.close();
1801
- this.dataWriter = new Uint8ArrayWriter();
1802
- this.zipWriter = new ZipWriter(this.dataWriter);
1803
- return data;
1769
+ this.zipFs.level = DEFAULT_COMPRESSION_LEVEL;
1770
+ this.zipFs.saveAndClose();
1771
+ await cp(this.zipPath, this.inputPath, { force: true });
1772
+ }
1773
+ [Symbol.dispose]() {
1774
+ if (this.zipFs.ready) {
1775
+ this.discardAndClose();
1776
+ }
1804
1777
  }
1805
1778
  }
1806
1779
  export {
package/package.json CHANGED
@@ -1,34 +1,18 @@
1
1
  {
2
2
  "name": "@storyteller-platform/epub",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "MIT",
8
8
  "exports": {
9
- ".": {
10
- "@storyteller": "./index.ts",
11
- "@storyteller-node": "./node.ts",
12
- "import": {
13
- "types": "./dist/index.d.ts",
14
- "default": "./dist/index.js"
15
- },
16
- "require": {
17
- "types": "./dist/index.d.cts",
18
- "default": "./dist/index.cjs"
19
- }
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
20
12
  },
21
- "./node": {
22
- "@storyteller": "./node.ts",
23
- "@storyteller-node": "./node.ts",
24
- "import": {
25
- "types": "./dist/node.d.ts",
26
- "default": "./dist/node.js"
27
- },
28
- "require": {
29
- "types": "./dist/node.d.cts",
30
- "default": "./dist/node.cjs"
31
- }
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "default": "./dist/index.cjs"
32
16
  }
33
17
  },
34
18
  "files": [
@@ -47,19 +31,21 @@
47
31
  "@storyteller-platform/tsup": "^0.1.0",
48
32
  "@tsconfig/strictest": "^2.0.5",
49
33
  "@types/mime-types": "^2",
50
- "@types/node": "^22.10.1",
34
+ "@types/node": "^24.0.0",
51
35
  "markdown-toc": "^1.2.0",
52
36
  "remark-toc": "^9.0.0",
53
37
  "tsup": "^8.5.0",
54
38
  "tsx": "^4.19.2",
55
- "typedoc": "^0.28.4",
39
+ "typedoc": "^0.28.13",
56
40
  "typedoc-plugin-markdown": "^4.6.3",
57
41
  "typedoc-plugin-remark": "^1.2.0",
58
- "typescript": "^5.8.3"
42
+ "typescript": "^5.9.2"
59
43
  },
60
44
  "dependencies": {
61
- "@storyteller-platform/fs": "^0.1.2",
62
- "@storyteller-platform/path": "^0.1.0",
45
+ "@storyteller-platform/fs": "^0.1.3",
46
+ "@storyteller-platform/path": "^0.1.1",
47
+ "@yarnpkg/fslib": "^3.1.3",
48
+ "@yarnpkg/libzip": "^3.2.2",
63
49
  "@zip.js/zip.js": "^2.0.0",
64
50
  "async-mutex": "^0.5.0",
65
51
  "fast-xml-parser": "^4.0.0",
@@ -68,6 +54,16 @@
68
54
  "nanoid": "^5.1.5"
69
55
  },
70
56
  "publishConfig": {
71
- "access": "public"
57
+ "access": "public",
58
+ "exports": {
59
+ "import": {
60
+ "types": "./dist/index.d.ts",
61
+ "default": "./dist/index.js"
62
+ },
63
+ "require": {
64
+ "types": "./dist/index.d.cts",
65
+ "default": "./dist/index.cjs"
66
+ }
67
+ }
72
68
  }
73
69
  }