@vertesia/memory 0.43.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 (95) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +421 -0
  3. package/lib/cjs/Builder.js +186 -0
  4. package/lib/cjs/Builder.js.map +1 -0
  5. package/lib/cjs/ContentObject.js +114 -0
  6. package/lib/cjs/ContentObject.js.map +1 -0
  7. package/lib/cjs/ContentSource.js +82 -0
  8. package/lib/cjs/ContentSource.js.map +1 -0
  9. package/lib/cjs/MemoryPack.js +228 -0
  10. package/lib/cjs/MemoryPack.js.map +1 -0
  11. package/lib/cjs/MemoryPackBuilder.js +47 -0
  12. package/lib/cjs/MemoryPackBuilder.js.map +1 -0
  13. package/lib/cjs/commands/copy.js +53 -0
  14. package/lib/cjs/commands/copy.js.map +1 -0
  15. package/lib/cjs/commands/exec.js +82 -0
  16. package/lib/cjs/commands/exec.js.map +1 -0
  17. package/lib/cjs/index.js +28 -0
  18. package/lib/cjs/index.js.map +1 -0
  19. package/lib/cjs/package.json +3 -0
  20. package/lib/cjs/utils/cmdline.js +90 -0
  21. package/lib/cjs/utils/cmdline.js.map +1 -0
  22. package/lib/cjs/utils/rewrite.js +166 -0
  23. package/lib/cjs/utils/rewrite.js.map +1 -0
  24. package/lib/cjs/utils/stream.js +27 -0
  25. package/lib/cjs/utils/stream.js.map +1 -0
  26. package/lib/cjs/utils/tar.js +185 -0
  27. package/lib/cjs/utils/tar.js.map +1 -0
  28. package/lib/esm/Builder.js +178 -0
  29. package/lib/esm/Builder.js.map +1 -0
  30. package/lib/esm/ContentObject.js +103 -0
  31. package/lib/esm/ContentObject.js.map +1 -0
  32. package/lib/esm/ContentSource.js +75 -0
  33. package/lib/esm/ContentSource.js.map +1 -0
  34. package/lib/esm/MemoryPack.js +218 -0
  35. package/lib/esm/MemoryPack.js.map +1 -0
  36. package/lib/esm/MemoryPackBuilder.js +43 -0
  37. package/lib/esm/MemoryPackBuilder.js.map +1 -0
  38. package/lib/esm/commands/copy.js +50 -0
  39. package/lib/esm/commands/copy.js.map +1 -0
  40. package/lib/esm/commands/exec.js +75 -0
  41. package/lib/esm/commands/exec.js.map +1 -0
  42. package/lib/esm/index.js +7 -0
  43. package/lib/esm/index.js.map +1 -0
  44. package/lib/esm/utils/cmdline.js +86 -0
  45. package/lib/esm/utils/cmdline.js.map +1 -0
  46. package/lib/esm/utils/rewrite.js +161 -0
  47. package/lib/esm/utils/rewrite.js.map +1 -0
  48. package/lib/esm/utils/stream.js +23 -0
  49. package/lib/esm/utils/stream.js.map +1 -0
  50. package/lib/esm/utils/tar.js +175 -0
  51. package/lib/esm/utils/tar.js.map +1 -0
  52. package/lib/tsconfig.tsbuildinfo +1 -0
  53. package/lib/types/Builder.d.ts +72 -0
  54. package/lib/types/Builder.d.ts.map +1 -0
  55. package/lib/types/ContentObject.d.ts +43 -0
  56. package/lib/types/ContentObject.d.ts.map +1 -0
  57. package/lib/types/ContentSource.d.ts +32 -0
  58. package/lib/types/ContentSource.d.ts.map +1 -0
  59. package/lib/types/MemoryPack.d.ts +46 -0
  60. package/lib/types/MemoryPack.d.ts.map +1 -0
  61. package/lib/types/MemoryPackBuilder.d.ts +18 -0
  62. package/lib/types/MemoryPackBuilder.d.ts.map +1 -0
  63. package/lib/types/commands/copy.d.ts +8 -0
  64. package/lib/types/commands/copy.d.ts.map +1 -0
  65. package/lib/types/commands/exec.d.ts +7 -0
  66. package/lib/types/commands/exec.d.ts.map +1 -0
  67. package/lib/types/index.d.ts +14 -0
  68. package/lib/types/index.d.ts.map +1 -0
  69. package/lib/types/utils/cmdline.d.ts +10 -0
  70. package/lib/types/utils/cmdline.d.ts.map +1 -0
  71. package/lib/types/utils/rewrite.d.ts +38 -0
  72. package/lib/types/utils/rewrite.d.ts.map +1 -0
  73. package/lib/types/utils/stream.d.ts +9 -0
  74. package/lib/types/utils/stream.d.ts.map +1 -0
  75. package/lib/types/utils/tar.d.ts +40 -0
  76. package/lib/types/utils/tar.d.ts.map +1 -0
  77. package/package.json +40 -0
  78. package/src/Builder.ts +239 -0
  79. package/src/ContentObject.ts +114 -0
  80. package/src/ContentSource.ts +88 -0
  81. package/src/MemoryPack.ts +233 -0
  82. package/src/MemoryPackBuilder.ts +55 -0
  83. package/src/builder.test.ts +214 -0
  84. package/src/commands/copy.ts +53 -0
  85. package/src/commands/exec.test.ts +22 -0
  86. package/src/commands/exec.ts +83 -0
  87. package/src/index.ts +14 -0
  88. package/src/utils/cmdline.test.ts +32 -0
  89. package/src/utils/cmdline.ts +92 -0
  90. package/src/utils/rewrite.test.ts +65 -0
  91. package/src/utils/rewrite.ts +167 -0
  92. package/src/utils/stream.test.ts +13 -0
  93. package/src/utils/stream.ts +27 -0
  94. package/src/utils/tar.test.ts +48 -0
  95. package/src/utils/tar.ts +203 -0
@@ -0,0 +1,203 @@
1
+ import fs from "fs";
2
+ import { FileHandle, open } from "fs/promises";
3
+ import { pipeline } from "stream/promises";
4
+ import tar from "tar-stream";
5
+ import zlib from "zlib";
6
+
7
+ export interface TarEntry {
8
+ name: string;
9
+ getContent(): Promise<Buffer>;
10
+ }
11
+
12
+ export class TarBuilder {
13
+ pack: tar.Pack;
14
+ indexData: string[] = [];
15
+ currentOffset = 0;
16
+ tarPromise: Promise<unknown>;
17
+
18
+ constructor(file: string) {
19
+ const pack = tar.pack(); // Create a new tar stream
20
+ this.pack = pack;
21
+ // Open the output file as a write stream
22
+ const outputStream = fs.createWriteStream(file);
23
+ if (file.endsWith('.gz')) {
24
+ this.tarPromise = pipeline(pack, zlib.createGzip(), outputStream);
25
+ } else {
26
+ this.tarPromise = pipeline(pack, outputStream);
27
+ }
28
+ }
29
+
30
+
31
+ async add(name: string, content?: Buffer) {
32
+ name = normalizePath(name);
33
+ // Calculate header size, 512 bytes for tar headers
34
+ const headerSize = 512;
35
+ const contentSize = content ? Buffer.byteLength(content) : 0;
36
+ const entryHeaderOffset = this.currentOffset;
37
+
38
+ // Store the index entry
39
+ // entry data offset is always at header offset + 512 bytes
40
+ if (contentSize > 0) { // do not index directories
41
+ this.indexData.push(`${name}:${entryHeaderOffset},${contentSize}`);
42
+ }
43
+
44
+ // Add the file entry to the tar stream
45
+ this.pack.entry({ name, size: contentSize }, content);
46
+
47
+ // Update the offset
48
+ this.currentOffset += headerSize + contentSize;
49
+ // Tar files are padded to 512-byte boundaries
50
+ if (contentSize % 512 !== 0) {
51
+ this.currentOffset += 512 - (contentSize % 512);
52
+ }
53
+ }
54
+
55
+ async build() {
56
+ const pack = this.pack;
57
+ // Convert index data to string and calculate its size
58
+ const indexContent = this.indexData.join('\n') + '\n';
59
+ const indexContentSize = Buffer.byteLength(indexContent);
60
+
61
+ // Add the .index entry to the tar
62
+ pack.entry({ name: '.index', size: indexContentSize }, indexContent);
63
+
64
+ pack.finalize(); // Finalize the tar stream
65
+
66
+ await this.tarPromise;
67
+ }
68
+
69
+ destroy() {
70
+ this.pack.destroy();
71
+ }
72
+
73
+ }
74
+
75
+ export async function loadTarIndex(tarFile: string) {
76
+ const fd = await open(tarFile, 'r');
77
+ try {
78
+ return await readTarIndex(fd);
79
+ } catch (err) {
80
+ await fd.close();
81
+ throw err;
82
+ }
83
+ }
84
+
85
+ async function readTarIndex(fd: FileHandle) {
86
+ const stats = await fd.stat();
87
+ const size = stats.size;
88
+ // we want to find the index header.
89
+ // we read the last chunks of 512 until we find the file name followed by a 0 char.
90
+ // the tar file ends with a segment of 1024 bytes of 0 so we need to skip that.
91
+ // we pick a size for the buffer to also include the file size entry from the header. So the buffer should be
92
+ // of 100 + 8 + 8 + 8 + 12 bytes = 124 + 12 bytes = 136 bytes
93
+ // the file size will be located at offset 124 and is 12 bytes long
94
+ // skip 1024 0 bytes then skip another 1024 bytes to find the first possible location of the index header (512 bytes for content and 512 bytes for the header)
95
+ let offset = size - 1024 - 1024;
96
+ const buffer = Buffer.alloc(512);
97
+ while (offset >= 0) {
98
+ await fd.read(buffer, 0, 512, offset);
99
+ // remove the 0 byte padding
100
+ const fileName = buffer.toString('utf-8', 0, 100);
101
+ if (fileName.startsWith('.index\0')) {
102
+ // we found the index header
103
+ const indexSize = getHeaderFileSize(buffer);
104
+ const indexDataOffset = offset + 512;
105
+ const indexDataEnd = indexDataOffset + indexSize;
106
+ if (indexDataEnd > size - 1024) {
107
+ throw new Error('Invalid index data offsets: [' + indexDataOffset + ':' + indexDataEnd + ']');
108
+ }
109
+ const dataBbuffer = Buffer.alloc(indexSize);
110
+ await fd.read(dataBbuffer, 0, indexSize, indexDataOffset);
111
+ const indexContent = dataBbuffer.toString('utf-8');
112
+ return new TarIndex(fd, indexContent);
113
+ }
114
+ offset -= 512;
115
+ }
116
+ return null;
117
+ }
118
+
119
+ export interface TarEntryIndex {
120
+ offset: number,
121
+ size: number
122
+ }
123
+ export class TarIndex {
124
+ entries: Record<string, TarEntryIndex> = {};
125
+ headerBuffer = Buffer.alloc(512);
126
+ /**
127
+ * @param fd the tar file descriptor
128
+ * @param content the index content
129
+ */
130
+ constructor(public fd: FileHandle, content: string) {
131
+ const lines = content.split('\n');
132
+ for (const line of lines) {
133
+ if (line) {
134
+ const [name, value] = line.split(':');
135
+ const [offsetStr, sizeStr] = value.split(',');
136
+ const offset = parseInt(offsetStr);
137
+ const size = parseInt(sizeStr);
138
+ this.entries[name] = { offset, size };
139
+ }
140
+ }
141
+ }
142
+
143
+ getPaths() {
144
+ return Object.keys(this.entries);
145
+ }
146
+
147
+ getSortedPaths() {
148
+ return Object.keys(this.entries).sort();
149
+ }
150
+
151
+ get(name: string) {
152
+ return this.entries[name];
153
+ }
154
+
155
+ async getContentAt(offset: number, size: number) {
156
+ const buffer = Buffer.alloc(size);
157
+ await this.fd.read(buffer, 0, size, offset + 512);
158
+ return buffer;
159
+ }
160
+ async getContent(name: string) {
161
+ const entry = this.entries[name];
162
+ if (entry) {
163
+ return this.getContentAt(entry.offset, entry.size);
164
+ } else {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ getReadStream(name: string, encoding?: BufferEncoding) {
170
+ const entry = this.entries[name];
171
+ if (entry) {
172
+ const offset = entry.offset + 512;
173
+ return this.fd.createReadStream({
174
+ encoding,
175
+ start: entry.offset,
176
+ end: offset + entry.size
177
+ })
178
+ } else {
179
+ return null;
180
+ }
181
+ }
182
+
183
+ async close() {
184
+ await this.fd.close();
185
+ }
186
+
187
+ }
188
+
189
+
190
+ function getHeaderFileSize(buffer: Buffer) {
191
+ const octalSize = buffer.toString('ascii', 124, 136).trim();
192
+ return parseInt(octalSize, 8);
193
+ }
194
+
195
+ export function normalizePath(path: string) {
196
+ if (path.startsWith('/')) {
197
+ path = path.slice(1);
198
+ }
199
+ if (path.endsWith('/')) {
200
+ path = path.slice(-1);
201
+ }
202
+ return path;
203
+ }