@storyteller-platform/epub 0.5.1 → 0.6.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.
- package/README.md +1483 -276
- package/dist/adapters/interface.cjs +16 -0
- package/dist/adapters/interface.d.cts +69 -0
- package/dist/adapters/interface.d.ts +69 -0
- package/dist/adapters/interface.js +0 -0
- package/dist/adapters/memory.cjs +103 -0
- package/dist/adapters/memory.d.cts +37 -0
- package/dist/adapters/memory.d.ts +37 -0
- package/dist/adapters/memory.js +83 -0
- package/dist/adapters/tmpfs.cjs +235 -0
- package/dist/adapters/tmpfs.d.cts +29 -0
- package/dist/adapters/tmpfs.d.ts +29 -0
- package/dist/adapters/tmpfs.js +178 -0
- package/dist/index.cjs +285 -280
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +283 -241
- package/dist/upgrade.d.cts +143 -20
- package/dist/upgrade.d.ts +143 -20
- package/package.json +3 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var interface_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(interface_exports);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable storage backend for an Epub instance
|
|
3
|
+
*
|
|
4
|
+
* Built-in adapters:
|
|
5
|
+
* - {@link TmpFsAdapter} extracts the archive to a tmp directory and
|
|
6
|
+
* supports the full read/write/save/upgrade
|
|
7
|
+
* - {@link MemoryAdapter} keeps an in-memory zip handle and supports
|
|
8
|
+
* reads only
|
|
9
|
+
*/
|
|
10
|
+
type EpubStorageKind = "tmp-dir" | "in-memory" | (string & {});
|
|
11
|
+
interface EpubStorageCapabilities {
|
|
12
|
+
/** when false, the Epub class refuses every mutation method at runtime */
|
|
13
|
+
readonly writable: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface EpubListEntry {
|
|
16
|
+
/** absolute path under {@link EpubStorageAdapter.rootPath} */
|
|
17
|
+
absolutePath: string;
|
|
18
|
+
/** path relative to {@link EpubStorageAdapter.rootPath}, slash-separated */
|
|
19
|
+
relativePath: string;
|
|
20
|
+
}
|
|
21
|
+
interface EpubStorageAdapter {
|
|
22
|
+
/**
|
|
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
|
|
26
|
+
*/
|
|
27
|
+
readonly rootPath: string;
|
|
28
|
+
read(path: string): Promise<Uint8Array>;
|
|
29
|
+
read(path: string, encoding: "utf-8"): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Length of an entry in bytes, for the readium page-count heuristic
|
|
32
|
+
* which expects compressed size when available
|
|
33
|
+
*/
|
|
34
|
+
archiveLength(path: string): Promise<number>;
|
|
35
|
+
/** 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>;
|
|
38
|
+
/** Required for removeManifestItem / setCoverImage replacement */
|
|
39
|
+
remove?(path: string): Promise<void>;
|
|
40
|
+
/** Required for Epub.saveAndClose to walk the contents */
|
|
41
|
+
list?(): AsyncIterable<EpubListEntry>;
|
|
42
|
+
/** Required for Epub.copy */
|
|
43
|
+
duplicate?(): Promise<EpubStorageAdapter>;
|
|
44
|
+
/** Required for Epub.saveAndClose */
|
|
45
|
+
serialize?(targetPath: string): Promise<void>;
|
|
46
|
+
/** Always called on close or error */
|
|
47
|
+
dispose(): void | Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
interface EpubStorageAdapterClass<Opts extends object = object> {
|
|
50
|
+
readonly kind: EpubStorageKind;
|
|
51
|
+
readonly capabilities: EpubStorageCapabilities;
|
|
52
|
+
/**
|
|
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.
|
|
56
|
+
*/
|
|
57
|
+
init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
|
|
58
|
+
/**
|
|
59
|
+
* 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.
|
|
64
|
+
*/
|
|
65
|
+
initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
|
|
66
|
+
}
|
|
67
|
+
type AdapterOptions<A> = A extends EpubStorageAdapterClass<infer Opts> ? Opts : object;
|
|
68
|
+
|
|
69
|
+
export type { AdapterOptions, EpubListEntry, EpubStorageAdapter, EpubStorageAdapterClass, EpubStorageCapabilities, EpubStorageKind };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable storage backend for an Epub instance
|
|
3
|
+
*
|
|
4
|
+
* Built-in adapters:
|
|
5
|
+
* - {@link TmpFsAdapter} extracts the archive to a tmp directory and
|
|
6
|
+
* supports the full read/write/save/upgrade
|
|
7
|
+
* - {@link MemoryAdapter} keeps an in-memory zip handle and supports
|
|
8
|
+
* reads only
|
|
9
|
+
*/
|
|
10
|
+
type EpubStorageKind = "tmp-dir" | "in-memory" | (string & {});
|
|
11
|
+
interface EpubStorageCapabilities {
|
|
12
|
+
/** when false, the Epub class refuses every mutation method at runtime */
|
|
13
|
+
readonly writable: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface EpubListEntry {
|
|
16
|
+
/** absolute path under {@link EpubStorageAdapter.rootPath} */
|
|
17
|
+
absolutePath: string;
|
|
18
|
+
/** path relative to {@link EpubStorageAdapter.rootPath}, slash-separated */
|
|
19
|
+
relativePath: string;
|
|
20
|
+
}
|
|
21
|
+
interface EpubStorageAdapter {
|
|
22
|
+
/**
|
|
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
|
|
26
|
+
*/
|
|
27
|
+
readonly rootPath: string;
|
|
28
|
+
read(path: string): Promise<Uint8Array>;
|
|
29
|
+
read(path: string, encoding: "utf-8"): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Length of an entry in bytes, for the readium page-count heuristic
|
|
32
|
+
* which expects compressed size when available
|
|
33
|
+
*/
|
|
34
|
+
archiveLength(path: string): Promise<number>;
|
|
35
|
+
/** 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>;
|
|
38
|
+
/** Required for removeManifestItem / setCoverImage replacement */
|
|
39
|
+
remove?(path: string): Promise<void>;
|
|
40
|
+
/** Required for Epub.saveAndClose to walk the contents */
|
|
41
|
+
list?(): AsyncIterable<EpubListEntry>;
|
|
42
|
+
/** Required for Epub.copy */
|
|
43
|
+
duplicate?(): Promise<EpubStorageAdapter>;
|
|
44
|
+
/** Required for Epub.saveAndClose */
|
|
45
|
+
serialize?(targetPath: string): Promise<void>;
|
|
46
|
+
/** Always called on close or error */
|
|
47
|
+
dispose(): void | Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
interface EpubStorageAdapterClass<Opts extends object = object> {
|
|
50
|
+
readonly kind: EpubStorageKind;
|
|
51
|
+
readonly capabilities: EpubStorageCapabilities;
|
|
52
|
+
/**
|
|
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.
|
|
56
|
+
*/
|
|
57
|
+
init(source: string | Uint8Array, opts?: Opts): Promise<EpubStorageAdapter>;
|
|
58
|
+
/**
|
|
59
|
+
* 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.
|
|
64
|
+
*/
|
|
65
|
+
initEmpty?(opts?: Opts): Promise<EpubStorageAdapter>;
|
|
66
|
+
}
|
|
67
|
+
type AdapterOptions<A> = A extends EpubStorageAdapterClass<infer Opts> ? Opts : object;
|
|
68
|
+
|
|
69
|
+
export type { AdapterOptions, EpubListEntry, EpubStorageAdapter, EpubStorageAdapterClass, EpubStorageCapabilities, EpubStorageKind };
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var memory_exports = {};
|
|
20
|
+
__export(memory_exports, {
|
|
21
|
+
MemoryAdapter: () => MemoryAdapter
|
|
22
|
+
});
|
|
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");
|
|
28
|
+
class MemoryAdapter {
|
|
29
|
+
constructor(rootPath, zip, entries, cache) {
|
|
30
|
+
this.rootPath = rootPath;
|
|
31
|
+
this.zip = zip;
|
|
32
|
+
this.entries = entries;
|
|
33
|
+
this.cache = cache;
|
|
34
|
+
}
|
|
35
|
+
static kind = "in-memory";
|
|
36
|
+
static capabilities = { writable: false };
|
|
37
|
+
static async init(source, opts = {}) {
|
|
38
|
+
const { cache = true, signal } = opts;
|
|
39
|
+
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));
|
|
41
|
+
const entries = /* @__PURE__ */ new Map();
|
|
42
|
+
try {
|
|
43
|
+
for await (const entry of zip) {
|
|
44
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
45
|
+
if (entry.filename.endsWith("/")) continue;
|
|
46
|
+
entries.set(entry.filename, entry);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
await zip.close();
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
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);
|
|
57
|
+
}
|
|
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) {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
const key = this.toZipEntryKey(path);
|
|
66
|
+
const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
|
|
67
|
+
if (cached) {
|
|
68
|
+
return encoding ? cached.toString(encoding) : new Uint8Array(cached);
|
|
69
|
+
}
|
|
70
|
+
const entry = this.entries.get(key);
|
|
71
|
+
if (!entry) {
|
|
72
|
+
const err = new Error(
|
|
73
|
+
`ENOENT: zip entry not found in EPUB archive: ${key}`
|
|
74
|
+
);
|
|
75
|
+
err.code = "ENOENT";
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
const stream = await entry.openReadStream();
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for await (const chunk of stream) {
|
|
81
|
+
chunks.push(chunk);
|
|
82
|
+
}
|
|
83
|
+
const buf = Buffer.concat(chunks);
|
|
84
|
+
(_b = this.cache) == null ? void 0 : _b.set(key, buf);
|
|
85
|
+
return encoding ? buf.toString(encoding) : new Uint8Array(buf);
|
|
86
|
+
}
|
|
87
|
+
// 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;
|
|
92
|
+
}
|
|
93
|
+
dispose() {
|
|
94
|
+
var _a;
|
|
95
|
+
this.entries.clear();
|
|
96
|
+
(_a = this.cache) == null ? void 0 : _a.clear();
|
|
97
|
+
this.zip.close().catch(() => void 0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
101
|
+
0 && (module.exports = {
|
|
102
|
+
MemoryAdapter
|
|
103
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EpubStorageAdapter } from './interface.cjs';
|
|
2
|
+
|
|
3
|
+
interface MemoryAdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* per-entry decompressed buffer cache
|
|
6
|
+
* turn it off when opening many EPUBs and reading each entry at most once
|
|
7
|
+
* keeps resident size bounded by the entry index, not the decompressed payload sum
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
cache?: boolean;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads an EPUB archive into memory and serves all I/O off the in-memory zip handle.
|
|
15
|
+
*
|
|
16
|
+
* read only
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
declare class MemoryAdapter implements EpubStorageAdapter {
|
|
20
|
+
readonly rootPath: string;
|
|
21
|
+
private zip;
|
|
22
|
+
private entries;
|
|
23
|
+
private cache;
|
|
24
|
+
static readonly kind: "in-memory";
|
|
25
|
+
static readonly capabilities: {
|
|
26
|
+
readonly writable: false;
|
|
27
|
+
};
|
|
28
|
+
static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
|
|
29
|
+
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>;
|
|
34
|
+
dispose(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { MemoryAdapter, type MemoryAdapterOptions };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EpubStorageAdapter } from './interface.js';
|
|
2
|
+
|
|
3
|
+
interface MemoryAdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* per-entry decompressed buffer cache
|
|
6
|
+
* turn it off when opening many EPUBs and reading each entry at most once
|
|
7
|
+
* keeps resident size bounded by the entry index, not the decompressed payload sum
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
cache?: boolean;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads an EPUB archive into memory and serves all I/O off the in-memory zip handle.
|
|
15
|
+
*
|
|
16
|
+
* read only
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
declare class MemoryAdapter implements EpubStorageAdapter {
|
|
20
|
+
readonly rootPath: string;
|
|
21
|
+
private zip;
|
|
22
|
+
private entries;
|
|
23
|
+
private cache;
|
|
24
|
+
static readonly kind: "in-memory";
|
|
25
|
+
static readonly capabilities: {
|
|
26
|
+
readonly writable: false;
|
|
27
|
+
};
|
|
28
|
+
static init(source: string | Uint8Array, opts?: MemoryAdapterOptions): Promise<MemoryAdapter>;
|
|
29
|
+
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>;
|
|
34
|
+
dispose(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { MemoryAdapter, type MemoryAdapterOptions };
|
|
@@ -0,0 +1,83 @@
|
|
|
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";
|
|
9
|
+
class MemoryAdapter {
|
|
10
|
+
constructor(rootPath, zip, entries, cache) {
|
|
11
|
+
this.rootPath = rootPath;
|
|
12
|
+
this.zip = zip;
|
|
13
|
+
this.entries = entries;
|
|
14
|
+
this.cache = cache;
|
|
15
|
+
}
|
|
16
|
+
static kind = "in-memory";
|
|
17
|
+
static capabilities = { writable: false };
|
|
18
|
+
static async init(source, opts = {}) {
|
|
19
|
+
const { cache = true, signal } = opts;
|
|
20
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
21
|
+
const zip = typeof source === "string" ? await open(source) : await fromBuffer(Buffer.from(source));
|
|
22
|
+
const entries = /* @__PURE__ */ new Map();
|
|
23
|
+
try {
|
|
24
|
+
for await (const entry of zip) {
|
|
25
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
26
|
+
if (entry.filename.endsWith("/")) continue;
|
|
27
|
+
entries.set(entry.filename, entry);
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
await zip.close();
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
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);
|
|
38
|
+
}
|
|
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) {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
const key = this.toZipEntryKey(path);
|
|
47
|
+
const cached = (_a = this.cache) == null ? void 0 : _a.get(key);
|
|
48
|
+
if (cached) {
|
|
49
|
+
return encoding ? cached.toString(encoding) : new Uint8Array(cached);
|
|
50
|
+
}
|
|
51
|
+
const entry = this.entries.get(key);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
const err = new Error(
|
|
54
|
+
`ENOENT: zip entry not found in EPUB archive: ${key}`
|
|
55
|
+
);
|
|
56
|
+
err.code = "ENOENT";
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
const stream = await entry.openReadStream();
|
|
60
|
+
const chunks = [];
|
|
61
|
+
for await (const chunk of stream) {
|
|
62
|
+
chunks.push(chunk);
|
|
63
|
+
}
|
|
64
|
+
const buf = Buffer.concat(chunks);
|
|
65
|
+
(_b = this.cache) == null ? void 0 : _b.set(key, buf);
|
|
66
|
+
return encoding ? buf.toString(encoding) : new Uint8Array(buf);
|
|
67
|
+
}
|
|
68
|
+
// 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;
|
|
73
|
+
}
|
|
74
|
+
dispose() {
|
|
75
|
+
var _a;
|
|
76
|
+
this.entries.clear();
|
|
77
|
+
(_a = this.cache) == null ? void 0 : _a.clear();
|
|
78
|
+
this.zip.close().catch(() => void 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
MemoryAdapter
|
|
83
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
var __using = (stack, value, async) => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
28
|
+
if (dispose === void 0) {
|
|
29
|
+
dispose = value[__knownSymbol("dispose")];
|
|
30
|
+
if (async) inner = dispose;
|
|
31
|
+
}
|
|
32
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
33
|
+
if (inner) dispose = function() {
|
|
34
|
+
try {
|
|
35
|
+
inner.call(this);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return Promise.reject(e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
stack.push([async, dispose, value]);
|
|
41
|
+
} else if (async) {
|
|
42
|
+
stack.push([async]);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
};
|
|
46
|
+
var __callDispose = (stack, error, hasError) => {
|
|
47
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
48
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
49
|
+
};
|
|
50
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
51
|
+
var next = (it) => {
|
|
52
|
+
while (it = stack.pop()) {
|
|
53
|
+
try {
|
|
54
|
+
var result = it[1] && it[1].call(it[2]);
|
|
55
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasError) throw error;
|
|
61
|
+
};
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
64
|
+
var tmpfs_exports = {};
|
|
65
|
+
__export(tmpfs_exports, {
|
|
66
|
+
TmpFsAdapter: () => TmpFsAdapter
|
|
67
|
+
});
|
|
68
|
+
module.exports = __toCommonJS(tmpfs_exports);
|
|
69
|
+
var import_node_crypto = require("node:crypto");
|
|
70
|
+
var import_node_fs = require("node:fs");
|
|
71
|
+
var import_promises = require("node:fs/promises");
|
|
72
|
+
var import_node_os = require("node:os");
|
|
73
|
+
var import_promises2 = require("node:stream/promises");
|
|
74
|
+
var import_yauzl_promise = require("yauzl-promise");
|
|
75
|
+
var import_yazl = require("yazl");
|
|
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
|
+
return AUDIO_FILE_EXTENSIONS.some((ext) => filenameOrExt.endsWith(ext));
|
|
101
|
+
}
|
|
102
|
+
function mintRootPath() {
|
|
103
|
+
return (0, import_path.join)((0, import_node_os.tmpdir)(), `storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`);
|
|
104
|
+
}
|
|
105
|
+
class TmpFsAdapter {
|
|
106
|
+
constructor(rootPath) {
|
|
107
|
+
this.rootPath = rootPath;
|
|
108
|
+
}
|
|
109
|
+
static kind = "extracted-dir";
|
|
110
|
+
static capabilities = { writable: true };
|
|
111
|
+
static async init(source) {
|
|
112
|
+
const rootPath = mintRootPath();
|
|
113
|
+
try {
|
|
114
|
+
var _stack = [];
|
|
115
|
+
try {
|
|
116
|
+
const zipfile = typeof source === "string" ? await (0, import_yauzl_promise.open)(source) : await (0, import_yauzl_promise.fromBuffer)(Buffer.from(source));
|
|
117
|
+
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
118
|
+
stack.defer(async () => {
|
|
119
|
+
await zipfile.close();
|
|
120
|
+
});
|
|
121
|
+
for await (const entry of zipfile) {
|
|
122
|
+
if (entry.filename.endsWith("/")) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const writePath = (0, import_path.join)(rootPath, entry.filename);
|
|
126
|
+
const readStream = await entry.openReadStream();
|
|
127
|
+
await (0, import_promises.mkdir)((0, import_path.dirname)(writePath), { recursive: true });
|
|
128
|
+
const writeStream = (0, import_node_fs.createWriteStream)(writePath);
|
|
129
|
+
await (0, import_promises2.pipeline)(readStream, writeStream);
|
|
130
|
+
}
|
|
131
|
+
} catch (_) {
|
|
132
|
+
var _error = _, _hasError = true;
|
|
133
|
+
} finally {
|
|
134
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
135
|
+
_promise && await _promise;
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
(0, import_node_fs.rmSync)(rootPath, { force: true, recursive: true });
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
return new TmpFsAdapter(rootPath);
|
|
142
|
+
}
|
|
143
|
+
static async initEmpty() {
|
|
144
|
+
const rootPath = mintRootPath();
|
|
145
|
+
await (0, import_promises.mkdir)(rootPath, { recursive: true });
|
|
146
|
+
return new TmpFsAdapter(rootPath);
|
|
147
|
+
}
|
|
148
|
+
async read(path, encoding) {
|
|
149
|
+
return await (0, import_promises.readFile)(path, encoding);
|
|
150
|
+
}
|
|
151
|
+
async write(path, data, encoding) {
|
|
152
|
+
await (0, import_promises.mkdir)((0, import_path.dirname)(path), { recursive: true });
|
|
153
|
+
await (0, import_promises.writeFile)(path, data, encoding);
|
|
154
|
+
}
|
|
155
|
+
async remove(path) {
|
|
156
|
+
await (0, import_promises.rm)(path);
|
|
157
|
+
}
|
|
158
|
+
async archiveLength(path) {
|
|
159
|
+
try {
|
|
160
|
+
const st = await (0, import_promises.stat)(path);
|
|
161
|
+
return st.size;
|
|
162
|
+
} catch {
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async *list() {
|
|
167
|
+
const entries = await (0, import_promises.readdir)(this.rootPath, {
|
|
168
|
+
recursive: true,
|
|
169
|
+
withFileTypes: true
|
|
170
|
+
});
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (entry.isDirectory()) continue;
|
|
173
|
+
const absolutePath = (0, import_path.join)(entry.parentPath, entry.name);
|
|
174
|
+
yield {
|
|
175
|
+
absolutePath,
|
|
176
|
+
relativePath: absolutePath.replace(`${this.rootPath}${import_path.sep}`, "")
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async duplicate() {
|
|
181
|
+
const newRoot = mintRootPath();
|
|
182
|
+
try {
|
|
183
|
+
await (0, import_promises.cp)(this.rootPath, newRoot, { recursive: true });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
(0, import_node_fs.rmSync)(newRoot, { force: true, recursive: true });
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
return new TmpFsAdapter(newRoot);
|
|
189
|
+
}
|
|
190
|
+
async serialize(targetPath) {
|
|
191
|
+
var _stack = [];
|
|
192
|
+
try {
|
|
193
|
+
const tmpArchivePath = (0, import_path.join)(
|
|
194
|
+
(0, import_node_os.tmpdir)(),
|
|
195
|
+
`storyteller-platform-epub-${(0, import_node_crypto.randomUUID)()}`
|
|
196
|
+
);
|
|
197
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
198
|
+
const zipfile = new import_yazl.ZipFile();
|
|
199
|
+
const writeStream = (0, import_node_fs.createWriteStream)(tmpArchivePath);
|
|
200
|
+
writeStream.on("close", () => {
|
|
201
|
+
resolve();
|
|
202
|
+
});
|
|
203
|
+
const stack = __using(_stack, new AsyncDisposableStack(), true);
|
|
204
|
+
stack.defer(async () => {
|
|
205
|
+
writeStream.close();
|
|
206
|
+
await (0, import_promises.rm)(tmpArchivePath, { force: true });
|
|
207
|
+
});
|
|
208
|
+
zipfile.outputStream.pipe(writeStream);
|
|
209
|
+
zipfile.addBuffer(Buffer.from("application/epub+zip"), "mimetype", {
|
|
210
|
+
compress: false
|
|
211
|
+
});
|
|
212
|
+
for await (const entry of this.list()) {
|
|
213
|
+
if (entry.relativePath === "mimetype") continue;
|
|
214
|
+
zipfile.addFile(entry.absolutePath, entry.relativePath, {
|
|
215
|
+
compress: !isAudioFile(entry.absolutePath)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
zipfile.end();
|
|
219
|
+
await promise;
|
|
220
|
+
await (0, import_promises.cp)(tmpArchivePath, targetPath);
|
|
221
|
+
} catch (_) {
|
|
222
|
+
var _error = _, _hasError = true;
|
|
223
|
+
} finally {
|
|
224
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
225
|
+
_promise && await _promise;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
dispose() {
|
|
229
|
+
(0, import_node_fs.rmSync)(this.rootPath, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
233
|
+
0 && (module.exports = {
|
|
234
|
+
TmpFsAdapter
|
|
235
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EpubStorageAdapter, EpubListEntry } from './interface.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts an EPUB archive to a temp directory and serves all I/O off
|
|
5
|
+
* the real filesystem.
|
|
6
|
+
* `Epub.using(TmpFsAdapter).from(...)` returns a writable {@link Epub}.
|
|
7
|
+
*/
|
|
8
|
+
declare class TmpFsAdapter implements EpubStorageAdapter {
|
|
9
|
+
readonly rootPath: string;
|
|
10
|
+
static readonly kind: "extracted-dir";
|
|
11
|
+
static readonly capabilities: {
|
|
12
|
+
readonly writable: true;
|
|
13
|
+
};
|
|
14
|
+
static init(source: string | Uint8Array): Promise<TmpFsAdapter>;
|
|
15
|
+
static initEmpty(): Promise<TmpFsAdapter>;
|
|
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>;
|
|
23
|
+
list(): AsyncIterable<EpubListEntry>;
|
|
24
|
+
duplicate(): Promise<TmpFsAdapter>;
|
|
25
|
+
serialize(targetPath: string): Promise<void>;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { TmpFsAdapter };
|