text-compress 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/dist/api/folder.d.ts +38 -0
  4. package/dist/api/folder.d.ts.map +1 -0
  5. package/dist/api/folder.js +50 -0
  6. package/dist/api/folder.js.map +1 -0
  7. package/dist/api/text.d.ts +30 -0
  8. package/dist/api/text.d.ts.map +1 -0
  9. package/dist/api/text.js +38 -0
  10. package/dist/api/text.js.map +1 -0
  11. package/dist/archive/collect.d.ts +26 -0
  12. package/dist/archive/collect.d.ts.map +1 -0
  13. package/dist/archive/collect.js +38 -0
  14. package/dist/archive/collect.js.map +1 -0
  15. package/dist/archive/format.d.ts +62 -0
  16. package/dist/archive/format.d.ts.map +1 -0
  17. package/dist/archive/format.js +143 -0
  18. package/dist/archive/format.js.map +1 -0
  19. package/dist/archive/types.d.ts +25 -0
  20. package/dist/archive/types.d.ts.map +1 -0
  21. package/dist/archive/types.js +10 -0
  22. package/dist/archive/types.js.map +1 -0
  23. package/dist/archive/unpack.d.ts +21 -0
  24. package/dist/archive/unpack.d.ts.map +1 -0
  25. package/dist/archive/unpack.js +41 -0
  26. package/dist/archive/unpack.js.map +1 -0
  27. package/dist/cli/analytics.d.ts +10 -0
  28. package/dist/cli/analytics.d.ts.map +1 -0
  29. package/dist/cli/analytics.js +22 -0
  30. package/dist/cli/analytics.js.map +1 -0
  31. package/dist/cli/args.d.ts +43 -0
  32. package/dist/cli/args.d.ts.map +1 -0
  33. package/dist/cli/args.js +152 -0
  34. package/dist/cli/args.js.map +1 -0
  35. package/dist/cli/commands/compress.d.ts +9 -0
  36. package/dist/cli/commands/compress.d.ts.map +1 -0
  37. package/dist/cli/commands/compress.js +62 -0
  38. package/dist/cli/commands/compress.js.map +1 -0
  39. package/dist/cli/commands/decompress.d.ts +9 -0
  40. package/dist/cli/commands/decompress.d.ts.map +1 -0
  41. package/dist/cli/commands/decompress.js +69 -0
  42. package/dist/cli/commands/decompress.js.map +1 -0
  43. package/dist/cli/detect.d.ts +25 -0
  44. package/dist/cli/detect.d.ts.map +1 -0
  45. package/dist/cli/detect.js +78 -0
  46. package/dist/cli/detect.js.map +1 -0
  47. package/dist/cli/main.d.ts +8 -0
  48. package/dist/cli/main.d.ts.map +1 -0
  49. package/dist/cli/main.js +74 -0
  50. package/dist/cli/main.js.map +1 -0
  51. package/dist/cli/output.d.ts +13 -0
  52. package/dist/cli/output.d.ts.map +1 -0
  53. package/dist/cli/output.js +26 -0
  54. package/dist/cli/output.js.map +1 -0
  55. package/dist/cli/paths.d.ts +13 -0
  56. package/dist/cli/paths.d.ts.map +1 -0
  57. package/dist/cli/paths.js +24 -0
  58. package/dist/cli/paths.js.map +1 -0
  59. package/dist/cli/usage.d.ts +8 -0
  60. package/dist/cli/usage.d.ts.map +1 -0
  61. package/dist/cli/usage.js +73 -0
  62. package/dist/cli/usage.js.map +1 -0
  63. package/dist/cli.d.ts +3 -0
  64. package/dist/cli.d.ts.map +1 -0
  65. package/dist/cli.js +10 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/compression/brotli.d.ts +43 -0
  68. package/dist/compression/brotli.d.ts.map +1 -0
  69. package/dist/compression/brotli.js +56 -0
  70. package/dist/compression/brotli.js.map +1 -0
  71. package/dist/crypto/password.d.ts +22 -0
  72. package/dist/crypto/password.d.ts.map +1 -0
  73. package/dist/crypto/password.js +62 -0
  74. package/dist/crypto/password.js.map +1 -0
  75. package/dist/encoding/base64.d.ts +17 -0
  76. package/dist/encoding/base64.d.ts.map +1 -0
  77. package/dist/encoding/base64.js +21 -0
  78. package/dist/encoding/base64.js.map +1 -0
  79. package/dist/encoding/index.d.ts +29 -0
  80. package/dist/encoding/index.d.ts.map +1 -0
  81. package/dist/encoding/index.js +47 -0
  82. package/dist/encoding/index.js.map +1 -0
  83. package/dist/encoding/z85.d.ts +64 -0
  84. package/dist/encoding/z85.d.ts.map +1 -0
  85. package/dist/encoding/z85.js +99 -0
  86. package/dist/encoding/z85.js.map +1 -0
  87. package/dist/fs/gitignore.d.ts +30 -0
  88. package/dist/fs/gitignore.d.ts.map +1 -0
  89. package/dist/fs/gitignore.js +104 -0
  90. package/dist/fs/gitignore.js.map +1 -0
  91. package/dist/fs/paths.d.ts +22 -0
  92. package/dist/fs/paths.d.ts.map +1 -0
  93. package/dist/fs/paths.js +55 -0
  94. package/dist/fs/paths.js.map +1 -0
  95. package/dist/fs/walk.d.ts +22 -0
  96. package/dist/fs/walk.d.ts.map +1 -0
  97. package/dist/fs/walk.js +49 -0
  98. package/dist/fs/walk.js.map +1 -0
  99. package/dist/index.d.ts +21 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +25 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/payload/tags.d.ts +60 -0
  104. package/dist/payload/tags.d.ts.map +1 -0
  105. package/dist/payload/tags.js +78 -0
  106. package/dist/payload/tags.js.map +1 -0
  107. package/dist/split/parts.d.ts +130 -0
  108. package/dist/split/parts.d.ts.map +1 -0
  109. package/dist/split/parts.js +290 -0
  110. package/dist/split/parts.js.map +1 -0
  111. package/dist/streaming/folder.d.ts +49 -0
  112. package/dist/streaming/folder.d.ts.map +1 -0
  113. package/dist/streaming/folder.js +220 -0
  114. package/dist/streaming/folder.js.map +1 -0
  115. package/dist/streaming.d.ts +16 -0
  116. package/dist/streaming.d.ts.map +1 -0
  117. package/dist/streaming.js +243 -0
  118. package/dist/streaming.js.map +1 -0
  119. package/dist/types.d.ts +18 -0
  120. package/dist/types.d.ts.map +1 -0
  121. package/dist/types.js +11 -0
  122. package/dist/types.js.map +1 -0
  123. package/package.json +60 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @module payload/tags
3
+ *
4
+ * Payload tagging — discriminated union over compressed blobs.
5
+ *
6
+ * After Brotli decompression the first byte tells the consumer what kind
7
+ * of payload follows. This is a lightweight **type tag** pattern (similar
8
+ * to MIME types or protobuf field numbers) that lets a single encoded
9
+ * string represent either UTF-8 text or a folder archive without external
10
+ * metadata.
11
+ *
12
+ * ## On-the-wire layout
13
+ *
14
+ * ```
15
+ * [tag: u8][payload bytes…]
16
+ * │ │
17
+ * │ └── Brotli-compressed body (text UTF-8 or archive binary)
18
+ * └── TAG_TEXT (0x01) or TAG_FOLDER (0x02)
19
+ * ```
20
+ *
21
+ * ## Full pipeline (compress)
22
+ *
23
+ * ```
24
+ * raw data → wrapPayload(tag, data) → brotliCompress → encodeBuffer
25
+ * ```
26
+ *
27
+ * ## Full pipeline (decompress)
28
+ *
29
+ * ```
30
+ * encoded string → decodeBuffer → brotliDecompress → read tag byte → route
31
+ * ```
32
+ */
33
+ import type { Encoding } from "../types.js";
34
+ /** Tag byte for UTF-8 text payloads. */
35
+ export declare const TAG_TEXT = 1;
36
+ /** Tag byte for folder archive payloads. */
37
+ export declare const TAG_FOLDER = 2;
38
+ /**
39
+ * Prepend a 1-byte type tag to raw payload bytes.
40
+ *
41
+ * @param tag - {@link TAG_TEXT} or {@link TAG_FOLDER}.
42
+ * @param data - Uncompressed payload body.
43
+ */
44
+ export declare function wrapPayload(tag: number, data: Buffer): Buffer;
45
+ /**
46
+ * Decode and decompress an encoded string, returning the tag and body.
47
+ *
48
+ * Low-level entry point used by both `decompress()` and `decompressToPath()`.
49
+ */
50
+ export declare function decompressPayload(encoded: string, encoding: Encoding, password?: string): {
51
+ tag: number;
52
+ data: Buffer;
53
+ };
54
+ /**
55
+ * Compress, tag, and encode a raw buffer in one step.
56
+ *
57
+ * Shared helper for text and folder compression paths.
58
+ */
59
+ export declare function compressTaggedPayload(tag: number, data: Buffer, encoding: Encoding, password?: string): string;
60
+ //# sourceMappingURL=tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/payload/tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,wCAAwC;AACxC,eAAO,MAAM,QAAQ,IAAO,CAAA;AAE5B,4CAA4C;AAC5C,eAAO,MAAM,UAAU,IAAO,CAAA;AAE9B;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAU/B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAMR"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @module payload/tags
3
+ *
4
+ * Payload tagging — discriminated union over compressed blobs.
5
+ *
6
+ * After Brotli decompression the first byte tells the consumer what kind
7
+ * of payload follows. This is a lightweight **type tag** pattern (similar
8
+ * to MIME types or protobuf field numbers) that lets a single encoded
9
+ * string represent either UTF-8 text or a folder archive without external
10
+ * metadata.
11
+ *
12
+ * ## On-the-wire layout
13
+ *
14
+ * ```
15
+ * [tag: u8][payload bytes…]
16
+ * │ │
17
+ * │ └── Brotli-compressed body (text UTF-8 or archive binary)
18
+ * └── TAG_TEXT (0x01) or TAG_FOLDER (0x02)
19
+ * ```
20
+ *
21
+ * ## Full pipeline (compress)
22
+ *
23
+ * ```
24
+ * raw data → wrapPayload(tag, data) → brotliCompress → encodeBuffer
25
+ * ```
26
+ *
27
+ * ## Full pipeline (decompress)
28
+ *
29
+ * ```
30
+ * encoded string → decodeBuffer → brotliDecompress → read tag byte → route
31
+ * ```
32
+ */
33
+ import { brotliDecompressSync } from "node:zlib";
34
+ import { brotliCompress } from "../compression/brotli.js";
35
+ import { decryptBuffer, encryptBuffer, isEncrypted } from "../crypto/password.js";
36
+ import { decodeBuffer, encodeBuffer } from "../encoding/index.js";
37
+ /** Tag byte for UTF-8 text payloads. */
38
+ export const TAG_TEXT = 0x01;
39
+ /** Tag byte for folder archive payloads. */
40
+ export const TAG_FOLDER = 0x02;
41
+ /**
42
+ * Prepend a 1-byte type tag to raw payload bytes.
43
+ *
44
+ * @param tag - {@link TAG_TEXT} or {@link TAG_FOLDER}.
45
+ * @param data - Uncompressed payload body.
46
+ */
47
+ export function wrapPayload(tag, data) {
48
+ return Buffer.concat([Buffer.from([tag]), data]);
49
+ }
50
+ /**
51
+ * Decode and decompress an encoded string, returning the tag and body.
52
+ *
53
+ * Low-level entry point used by both `decompress()` and `decompressToPath()`.
54
+ */
55
+ export function decompressPayload(encoded, encoding, password) {
56
+ let decoded = decodeBuffer(encoded, encoding);
57
+ if (isEncrypted(decoded)) {
58
+ if (!password) {
59
+ throw new Error("This payload is password-protected. Pass -p/--password to decompress.");
60
+ }
61
+ decoded = decryptBuffer(decoded, password);
62
+ }
63
+ const raw = brotliDecompressSync(decoded);
64
+ return { tag: raw[0], data: raw.subarray(1) };
65
+ }
66
+ /**
67
+ * Compress, tag, and encode a raw buffer in one step.
68
+ *
69
+ * Shared helper for text and folder compression paths.
70
+ */
71
+ export function compressTaggedPayload(tag, data, encoding, password) {
72
+ let compressed = brotliCompress(wrapPayload(tag, data));
73
+ if (password) {
74
+ compressed = encryptBuffer(compressed, password);
75
+ }
76
+ return encodeBuffer(compressed, encoding);
77
+ }
78
+ //# sourceMappingURL=tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/payload/tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAGjE,wCAAwC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAA;AAE5B,4CAA4C;AAC5C,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAA;AAE9B;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY;IACnD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,QAAkB,EAClB,QAAiB;IAEjB,IAAI,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC7C,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAA;QAC1F,CAAC;QACD,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC5C,CAAC;IACD,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACzC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAW,EACX,IAAY,EACZ,QAAkB,EAClB,QAAiB;IAEjB,IAAI,UAAU,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;IACvD,IAAI,QAAQ,EAAE,CAAC;QACb,UAAU,GAAG,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @module split/parts
3
+ *
4
+ * Split-file I/O for oversized compressed strings.
5
+ *
6
+ * Chat platforms and some editors impose character limits (~30 000–50 000).
7
+ * This module splits a long encoded string into numbered part files and
8
+ * reassembles them on read.
9
+ *
10
+ * ## v2 split format
11
+ *
12
+ * Each logical part is prefixed with a 12-byte header so order is encoded in
13
+ * file content, not filenames:
14
+ *
15
+ * ```
16
+ * [magic: "TCP\x02"][partIndex: u32le][totalParts: u32le][payload…]
17
+ * ```
18
+ *
19
+ * Every part carries `totalParts`, so any single valid part reveals how many
20
+ * logical parts exist. A physical file may contain one or more consecutive
21
+ * parts. Parts can be read in any file order, and adjacent part files can be
22
+ * merged into fewer files.
23
+ *
24
+ * ## Compress naming (unchanged)
25
+ *
26
+ * ```
27
+ * output.txt → output.1.txt, output.2.txt, … output.12.txt
28
+ * archive → archive.1, archive.2, …
29
+ * ```
30
+ *
31
+ * Zero-padding width matches the total part count so lexical sort equals
32
+ * numeric sort (`output.02.txt` before `output.10.txt`).
33
+ *
34
+ * ## Decompress discovery
35
+ *
36
+ * Pass **any** sibling file. All files sharing the same prefix — the basename
37
+ * segment before the **first** `.` — are scanned (`file.1.md`, `file.7.txt`,
38
+ * … → prefix `file`). Extension and the numeric segment in the filename are
39
+ * ignored. Files without valid split headers are skipped.
40
+ *
41
+ * ## Auto-split threshold
42
+ *
43
+ * When no explicit `-s` size is given, outputs longer than
44
+ * {@link AUTO_SPLIT_CHARS} are automatically split.
45
+ */
46
+ /** Default character threshold for automatic output splitting. */
47
+ export declare const AUTO_SPLIT_CHARS = 30000;
48
+ /** Magic bytes identifying a v2 split chunk header. */
49
+ export declare const SPLIT_MAGIC: Buffer<ArrayBuffer>;
50
+ /** One parsed logical part from split file content. */
51
+ export interface SplitChunk {
52
+ partIndex: number;
53
+ totalParts: number;
54
+ payload: string;
55
+ }
56
+ /**
57
+ * Decide whether and how to split encoded output.
58
+ *
59
+ * @param encodedLength - Total characters in the encoded string.
60
+ * @param explicitSplit - User-provided `-s` value, if any.
61
+ * @returns Chunk size, or `undefined` when no split is needed.
62
+ */
63
+ export declare function resolveSplitChunkSize(encodedLength: number, explicitSplit?: number): number | undefined;
64
+ /**
65
+ * Split a string into fixed-size chunks (last chunk may be shorter).
66
+ *
67
+ * @throws If `chunkSize` is not a positive integer.
68
+ */
69
+ export declare function splitString(value: string, chunkSize: number): string[];
70
+ /** Build the 12-byte v2 split chunk header. */
71
+ export declare function createSplitChunkHeader(partIndex: number, totalParts: number): Buffer;
72
+ /**
73
+ * Wrap one encoded payload chunk with a v2 split header.
74
+ */
75
+ export declare function wrapSplitChunk(partIndex: number, totalParts: number, payload: string): string;
76
+ /**
77
+ * Parse split chunks from a buffer when it starts with a split header.
78
+ *
79
+ * @returns `null` when the buffer is not split data or is malformed.
80
+ */
81
+ export declare function tryParseSplitChunks(buf: Buffer): SplitChunk[] | null;
82
+ /**
83
+ * Parse all logical parts from one file's bytes.
84
+ *
85
+ * Files without a leading split header are treated as a single unsplit payload.
86
+ */
87
+ export declare function parseSplitBuffer(buf: Buffer): SplitChunk[];
88
+ /**
89
+ * Reassemble logical parts into one encoded string.
90
+ *
91
+ * Uses `totalParts` from the chunk headers — any valid part reveals the full count.
92
+ *
93
+ * @throws If parts are missing, duplicated, or disagree on total count.
94
+ */
95
+ export declare function assembleSplitChunks(chunks: SplitChunk[]): string;
96
+ /**
97
+ * Build the filesystem path for one part of a split output.
98
+ *
99
+ * Inserts `.<paddedIndex>` before the file extension.
100
+ */
101
+ export declare function formatSplitOutputPath(outputPath: string, partIndex: number, totalParts: number): string;
102
+ /** Basename segment before the first `.` — used to group split siblings on read. */
103
+ export declare function extractFilenamePrefix(filePath: string): string;
104
+ /**
105
+ * Return the filename prefix used to discover split siblings.
106
+ *
107
+ * @deprecated Use {@link extractFilenamePrefix} instead.
108
+ */
109
+ export declare function parseSplitPartPath(filePath: string): {
110
+ prefix: string;
111
+ };
112
+ /**
113
+ * Resolve an input path to all prefix-sibling part files in the same directory.
114
+ *
115
+ * Any sibling may be passed; discovery uses the prefix before the first `.`.
116
+ * Falls back to the input file alone when no `prefix.*` siblings exist.
117
+ */
118
+ export declare function resolveSplitInputPaths(inputPath: string): string[];
119
+ /**
120
+ * Read and concatenate all parts for a split (or single) compressed input.
121
+ *
122
+ * Scans every prefix sibling, skips files without valid split headers, and
123
+ * reassembles using embedded part indices and `totalParts`. When no sibling
124
+ * contains split headers, reads the requested path as a single unsplit file.
125
+ */
126
+ export declare function readSplitInput(inputPath: string): {
127
+ content: string;
128
+ partPaths: string[];
129
+ };
130
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/split/parts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAKH,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC,uDAAuD;AACvD,eAAO,MAAM,WAAW,qBAAwC,CAAA;AAIhE,uDAAuD;AACvD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAIpB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAWtE;AAED,+CAA+C;AAC/C,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAMpF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAK7F;AA4BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,IAAI,CAQpE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAS1D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAmChE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAUR;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAEvE;AAkBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBlE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB,CAoBA"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @module split/parts
3
+ *
4
+ * Split-file I/O for oversized compressed strings.
5
+ *
6
+ * Chat platforms and some editors impose character limits (~30 000–50 000).
7
+ * This module splits a long encoded string into numbered part files and
8
+ * reassembles them on read.
9
+ *
10
+ * ## v2 split format
11
+ *
12
+ * Each logical part is prefixed with a 12-byte header so order is encoded in
13
+ * file content, not filenames:
14
+ *
15
+ * ```
16
+ * [magic: "TCP\x02"][partIndex: u32le][totalParts: u32le][payload…]
17
+ * ```
18
+ *
19
+ * Every part carries `totalParts`, so any single valid part reveals how many
20
+ * logical parts exist. A physical file may contain one or more consecutive
21
+ * parts. Parts can be read in any file order, and adjacent part files can be
22
+ * merged into fewer files.
23
+ *
24
+ * ## Compress naming (unchanged)
25
+ *
26
+ * ```
27
+ * output.txt → output.1.txt, output.2.txt, … output.12.txt
28
+ * archive → archive.1, archive.2, …
29
+ * ```
30
+ *
31
+ * Zero-padding width matches the total part count so lexical sort equals
32
+ * numeric sort (`output.02.txt` before `output.10.txt`).
33
+ *
34
+ * ## Decompress discovery
35
+ *
36
+ * Pass **any** sibling file. All files sharing the same prefix — the basename
37
+ * segment before the **first** `.` — are scanned (`file.1.md`, `file.7.txt`,
38
+ * … → prefix `file`). Extension and the numeric segment in the filename are
39
+ * ignored. Files without valid split headers are skipped.
40
+ *
41
+ * ## Auto-split threshold
42
+ *
43
+ * When no explicit `-s` size is given, outputs longer than
44
+ * {@link AUTO_SPLIT_CHARS} are automatically split.
45
+ */
46
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
47
+ import { dirname, join } from "node:path";
48
+ /** Default character threshold for automatic output splitting. */
49
+ export const AUTO_SPLIT_CHARS = 30_000;
50
+ /** Magic bytes identifying a v2 split chunk header. */
51
+ export const SPLIT_MAGIC = Buffer.from([0x54, 0x43, 0x50, 0x02]);
52
+ const SPLIT_HEADER_SIZE = 12;
53
+ /**
54
+ * Decide whether and how to split encoded output.
55
+ *
56
+ * @param encodedLength - Total characters in the encoded string.
57
+ * @param explicitSplit - User-provided `-s` value, if any.
58
+ * @returns Chunk size, or `undefined` when no split is needed.
59
+ */
60
+ export function resolveSplitChunkSize(encodedLength, explicitSplit) {
61
+ if (explicitSplit !== undefined)
62
+ return explicitSplit;
63
+ if (encodedLength > AUTO_SPLIT_CHARS)
64
+ return AUTO_SPLIT_CHARS;
65
+ return undefined;
66
+ }
67
+ /**
68
+ * Split a string into fixed-size chunks (last chunk may be shorter).
69
+ *
70
+ * @throws If `chunkSize` is not a positive integer.
71
+ */
72
+ export function splitString(value, chunkSize) {
73
+ if (!Number.isInteger(chunkSize) || chunkSize < 1) {
74
+ throw new Error(`Split size must be a positive integer, got ${chunkSize}.`);
75
+ }
76
+ if (value.length === 0)
77
+ return [""];
78
+ const chunks = [];
79
+ for (let i = 0; i < value.length; i += chunkSize) {
80
+ chunks.push(value.slice(i, i + chunkSize));
81
+ }
82
+ return chunks;
83
+ }
84
+ /** Build the 12-byte v2 split chunk header. */
85
+ export function createSplitChunkHeader(partIndex, totalParts) {
86
+ const header = Buffer.alloc(SPLIT_HEADER_SIZE);
87
+ SPLIT_MAGIC.copy(header, 0);
88
+ header.writeUInt32LE(partIndex, 4);
89
+ header.writeUInt32LE(totalParts, 8);
90
+ return header;
91
+ }
92
+ /**
93
+ * Wrap one encoded payload chunk with a v2 split header.
94
+ */
95
+ export function wrapSplitChunk(partIndex, totalParts, payload) {
96
+ return Buffer.concat([
97
+ createSplitChunkHeader(partIndex, totalParts),
98
+ Buffer.from(payload, "utf-8"),
99
+ ]).toString("utf-8");
100
+ }
101
+ function readSplitChunks(buf) {
102
+ const chunks = [];
103
+ let offset = 0;
104
+ while (offset < buf.length) {
105
+ if (buf.length - offset < SPLIT_HEADER_SIZE) {
106
+ throw new Error("Invalid split format: truncated chunk header.");
107
+ }
108
+ if (!buf.subarray(offset, offset + SPLIT_MAGIC.length).equals(SPLIT_MAGIC)) {
109
+ throw new Error("Invalid split format: unexpected data between split chunks.");
110
+ }
111
+ const partIndex = buf.readUInt32LE(offset + 4);
112
+ const totalParts = buf.readUInt32LE(offset + 8);
113
+ offset += SPLIT_HEADER_SIZE;
114
+ const nextMagic = buf.indexOf(SPLIT_MAGIC, offset);
115
+ const end = nextMagic === -1 ? buf.length : nextMagic;
116
+ const payload = buf.subarray(offset, end).toString("utf-8");
117
+ chunks.push({ partIndex, totalParts, payload });
118
+ offset = end;
119
+ }
120
+ return chunks;
121
+ }
122
+ /**
123
+ * Parse split chunks from a buffer when it starts with a split header.
124
+ *
125
+ * @returns `null` when the buffer is not split data or is malformed.
126
+ */
127
+ export function tryParseSplitChunks(buf) {
128
+ if (buf.length < SPLIT_HEADER_SIZE)
129
+ return null;
130
+ if (!buf.subarray(0, SPLIT_MAGIC.length).equals(SPLIT_MAGIC))
131
+ return null;
132
+ try {
133
+ return readSplitChunks(buf);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ /**
140
+ * Parse all logical parts from one file's bytes.
141
+ *
142
+ * Files without a leading split header are treated as a single unsplit payload.
143
+ */
144
+ export function parseSplitBuffer(buf) {
145
+ if (buf.length === 0) {
146
+ return [{ partIndex: 1, totalParts: 1, payload: "" }];
147
+ }
148
+ const splitChunks = tryParseSplitChunks(buf);
149
+ if (splitChunks)
150
+ return splitChunks;
151
+ return [{ partIndex: 1, totalParts: 1, payload: buf.toString("utf-8") }];
152
+ }
153
+ /**
154
+ * Reassemble logical parts into one encoded string.
155
+ *
156
+ * Uses `totalParts` from the chunk headers — any valid part reveals the full count.
157
+ *
158
+ * @throws If parts are missing, duplicated, or disagree on total count.
159
+ */
160
+ export function assembleSplitChunks(chunks) {
161
+ if (chunks.length === 0) {
162
+ throw new Error("No split chunks found.");
163
+ }
164
+ if (chunks.length === 1 && chunks[0].totalParts === 1) {
165
+ return chunks[0].payload;
166
+ }
167
+ const totalParts = chunks[0].totalParts;
168
+ const byIndex = new Map();
169
+ for (const chunk of chunks) {
170
+ if (chunk.totalParts !== totalParts) {
171
+ throw new Error("Split parts disagree on total part count.");
172
+ }
173
+ if (!Number.isInteger(chunk.partIndex) || chunk.partIndex < 1 || chunk.partIndex > totalParts) {
174
+ throw new Error(`Invalid split part index ${chunk.partIndex}.`);
175
+ }
176
+ if (byIndex.has(chunk.partIndex)) {
177
+ throw new Error(`Duplicate split part ${chunk.partIndex}.`);
178
+ }
179
+ byIndex.set(chunk.partIndex, chunk.payload);
180
+ }
181
+ const parts = [];
182
+ for (let i = 1; i <= totalParts; i++) {
183
+ const payload = byIndex.get(i);
184
+ if (payload === undefined) {
185
+ throw new Error(`Missing split part ${i}.`);
186
+ }
187
+ parts.push(payload);
188
+ }
189
+ return parts.join("");
190
+ }
191
+ /**
192
+ * Build the filesystem path for one part of a split output.
193
+ *
194
+ * Inserts `.<paddedIndex>` before the file extension.
195
+ */
196
+ export function formatSplitOutputPath(outputPath, partIndex, totalParts) {
197
+ const part = String(partIndex).padStart(String(totalParts).length, "0");
198
+ const slash = Math.max(outputPath.lastIndexOf("/"), outputPath.lastIndexOf("\\"));
199
+ const dot = outputPath.lastIndexOf(".");
200
+ const hasExtension = dot > slash;
201
+ if (hasExtension) {
202
+ return `${outputPath.slice(0, dot)}.${part}${outputPath.slice(dot)}`;
203
+ }
204
+ return `${outputPath}.${part}`;
205
+ }
206
+ /** Basename segment before the first `.` — used to group split siblings on read. */
207
+ export function extractFilenamePrefix(filePath) {
208
+ const slash = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
209
+ const name = filePath.slice(slash + 1);
210
+ const dot = name.indexOf(".");
211
+ if (dot <= 0)
212
+ return name;
213
+ return name.slice(0, dot);
214
+ }
215
+ /**
216
+ * Return the filename prefix used to discover split siblings.
217
+ *
218
+ * @deprecated Use {@link extractFilenamePrefix} instead.
219
+ */
220
+ export function parseSplitPartPath(filePath) {
221
+ return { prefix: extractFilenamePrefix(filePath) };
222
+ }
223
+ /** List regular files in `dir` whose names start with `prefix.`. */
224
+ function listPrefixSiblingPaths(dir, prefix) {
225
+ const marker = `${prefix}.`;
226
+ return readdirSync(dir)
227
+ .filter((name) => name.startsWith(marker))
228
+ .map((name) => join(dir, name))
229
+ .filter((path) => {
230
+ try {
231
+ return statSync(path).isFile();
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ })
237
+ .sort();
238
+ }
239
+ /**
240
+ * Resolve an input path to all prefix-sibling part files in the same directory.
241
+ *
242
+ * Any sibling may be passed; discovery uses the prefix before the first `.`.
243
+ * Falls back to the input file alone when no `prefix.*` siblings exist.
244
+ */
245
+ export function resolveSplitInputPaths(inputPath) {
246
+ if (existsSync(inputPath)) {
247
+ const stat = statSync(inputPath);
248
+ if (stat.isDirectory()) {
249
+ throw new Error(`"${inputPath}" is a directory, not a compressed file. Pass the compressed .txt file.`);
250
+ }
251
+ if (!stat.isFile()) {
252
+ throw new Error(`Cannot read "${inputPath}": not a regular file.`);
253
+ }
254
+ }
255
+ const dir = dirname(inputPath);
256
+ const prefix = extractFilenamePrefix(inputPath);
257
+ const siblings = listPrefixSiblingPaths(dir, prefix);
258
+ if (siblings.length > 0)
259
+ return siblings;
260
+ if (existsSync(inputPath) && statSync(inputPath).isFile()) {
261
+ return [inputPath];
262
+ }
263
+ throw new Error(`Input file not found: ${inputPath}`);
264
+ }
265
+ /**
266
+ * Read and concatenate all parts for a split (or single) compressed input.
267
+ *
268
+ * Scans every prefix sibling, skips files without valid split headers, and
269
+ * reassembles using embedded part indices and `totalParts`. When no sibling
270
+ * contains split headers, reads the requested path as a single unsplit file.
271
+ */
272
+ export function readSplitInput(inputPath) {
273
+ const partPaths = resolveSplitInputPaths(inputPath);
274
+ const chunks = [];
275
+ for (const path of partPaths) {
276
+ const parsed = tryParseSplitChunks(readFileSync(path));
277
+ if (parsed)
278
+ chunks.push(...parsed);
279
+ }
280
+ if (chunks.length > 0) {
281
+ const content = assembleSplitChunks(chunks);
282
+ return { content, partPaths };
283
+ }
284
+ if (!existsSync(inputPath) || !statSync(inputPath).isFile()) {
285
+ throw new Error(`No valid split parts found for prefix "${extractFilenamePrefix(inputPath)}".`);
286
+ }
287
+ const content = assembleSplitChunks(parseSplitBuffer(readFileSync(inputPath)));
288
+ return { content, partPaths: [inputPath] };
289
+ }
290
+ //# sourceMappingURL=parts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.js","sourceRoot":"","sources":["../../src/split/parts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @module streaming/folder
3
+ *
4
+ * Memory-efficient folder compression pipeline.
5
+ *
6
+ * Large directory trees may exceed available RAM if every file is loaded
7
+ * into a single buffer. This module streams each stage to disk:
8
+ *
9
+ * ```
10
+ * walk tree → temp archive file
11
+ * → stream Brotli → temp compressed file
12
+ * → stream encode → output .txt (possibly split)
13
+ * ```
14
+ *
15
+ * ## Pattern: staged pipeline with temp files
16
+ *
17
+ * Each stage reads from the previous stage's output file and writes to
18
+ * the next, keeping peak memory bounded by chunk buffer sizes rather than
19
+ * total archive size.
20
+ *
21
+ * ## Chunk sizes
22
+ *
23
+ * - `COPY_CHUNK` (1 MiB) — file content copied into the archive.
24
+ * - `ENCODE_READ_CHUNK` (3 MiB) — binary read buffer for Base64 streaming.
25
+ * Base64 encoding is applied per-chunk for `encoding === 64`; Z85 requires
26
+ * the full padded buffer and is loaded once (trade-off for correctness).
27
+ */
28
+ import type { Encoding } from "../types.js";
29
+ /** Statistics collected while building the archive. */
30
+ interface ArchiveStats {
31
+ fileCount: number;
32
+ dirCount: number;
33
+ originalBytes: number;
34
+ archiveBytes: number;
35
+ }
36
+ /** Result of {@link compressFolderToPath}. */
37
+ export interface CompressFolderToPathResult extends ArchiveStats {
38
+ outputPaths: string[];
39
+ compressedBytes: number;
40
+ splitChunkSize?: number;
41
+ }
42
+ /**
43
+ * Compress a folder to encoded text file(s) using the streaming pipeline.
44
+ *
45
+ * Creates a temporary working directory that is always cleaned up in `finally`.
46
+ */
47
+ export declare function compressFolderToPath(dirPath: string, outputPath: string, encoding?: Encoding, split?: number, password?: string): Promise<CompressFolderToPathResult>;
48
+ export {};
49
+ //# sourceMappingURL=folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../../src/streaming/folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAgCH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAQ3C,uDAAuD;AACvD,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;CACrB;AAuKD,8CAA8C;AAC9C,MAAM,WAAW,0BAA2B,SAAQ,YAAY;IAC9D,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,QAAa,EACvB,KAAK,CAAC,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,0BAA0B,CAAC,CA2BrC"}