marcattacks 1.0.0 → 1.0.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/dist/httpstream.d.ts +3 -0
- package/dist/httpstream.d.ts.map +1 -0
- package/{src/httpstream.ts → dist/httpstream.js} +3 -7
- package/dist/httpstream.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/input/alephseq.d.ts +3 -0
- package/dist/input/alephseq.d.ts.map +1 -0
- package/{src/input/alephseq.ts → dist/input/alephseq.js} +20 -34
- package/dist/input/alephseq.js.map +1 -0
- package/dist/input/json.d.ts +3 -0
- package/dist/input/json.d.ts.map +1 -0
- package/{src/input/json.ts → dist/input/json.js} +8 -18
- package/dist/input/json.js.map +1 -0
- package/dist/input/jsonl.d.ts +3 -0
- package/dist/input/jsonl.d.ts.map +1 -0
- package/{src/input/jsonl.ts → dist/input/jsonl.js} +11 -21
- package/dist/input/jsonl.js.map +1 -0
- package/dist/input/xml.d.ts +3 -0
- package/dist/input/xml.d.ts.map +1 -0
- package/{src/input/xml.ts → dist/input/xml.js} +29 -50
- package/dist/input/xml.js.map +1 -0
- package/dist/marcmap.d.ts +31 -0
- package/dist/marcmap.d.ts.map +1 -0
- package/{src/marcmap.ts → dist/marcmap.js} +25 -38
- package/dist/marcmap.js.map +1 -0
- package/dist/output/alephseq.d.ts +3 -0
- package/dist/output/alephseq.d.ts.map +1 -0
- package/{src/output/alephseq.ts → dist/output/alephseq.js} +17 -25
- package/dist/output/alephseq.js.map +1 -0
- package/dist/output/json.d.ts +3 -0
- package/dist/output/json.d.ts.map +1 -0
- package/{src/output/json.ts → dist/output/json.js} +4 -13
- package/dist/output/json.js.map +1 -0
- package/dist/output/jsonl.d.ts +3 -0
- package/dist/output/jsonl.d.ts.map +1 -0
- package/{src/output/jsonl.ts → dist/output/jsonl.js} +5 -8
- package/dist/output/jsonl.js.map +1 -0
- package/dist/output/rdf.d.ts +3 -0
- package/dist/output/rdf.d.ts.map +1 -0
- package/dist/output/rdf.js +44 -0
- package/dist/output/rdf.js.map +1 -0
- package/dist/output/xml.d.ts +6 -0
- package/dist/output/xml.d.ts.map +1 -0
- package/{src/output/xml.ts → dist/output/xml.js} +18 -35
- package/dist/output/xml.js.map +1 -0
- package/dist/plugin-loader.d.ts +2 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +24 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/s3stream.d.ts +8 -0
- package/dist/s3stream.d.ts.map +1 -0
- package/{src/s3stream.ts → dist/s3stream.js} +72 -115
- package/dist/s3stream.js.map +1 -0
- package/dist/sftpstream.d.ts +12 -0
- package/dist/sftpstream.d.ts.map +1 -0
- package/{src/sftpstream.ts → dist/sftpstream.js} +9 -39
- package/dist/sftpstream.js.map +1 -0
- package/dist/slow-writable.d.ts +38 -0
- package/dist/slow-writable.d.ts.map +1 -0
- package/dist/slow-writable.js +126 -0
- package/dist/slow-writable.js.map +1 -0
- package/dist/transform/json.d.ts +3 -0
- package/dist/transform/json.d.ts.map +1 -0
- package/{src/transform/json.ts → dist/transform/json.js} +8 -12
- package/dist/transform/json.js.map +1 -0
- package/dist/transform/rdf.d.ts +3 -0
- package/dist/transform/rdf.d.ts.map +1 -0
- package/{src/transform/rdf.ts → dist/transform/rdf.js} +82 -110
- package/dist/transform/rdf.js.map +1 -0
- package/package.json +6 -2
- package/Dockerfile +0 -23
- package/README-docker.md +0 -39
- package/TYPESCRIPT.txt +0 -6
- package/data/output.rdf +0 -12425
- package/data/sample.xml +0 -2
- package/demo/demo.jsonata +0 -44
- package/docker-compose.yaml +0 -37
- package/logo.jpg +0 -0
- package/plugin/demo.js +0 -12
- package/src/index.ts +0 -177
- package/src/output/rdf.ts +0 -63
- package/src/plugin-loader.ts +0 -27
- package/src/slow-writable.ts +0 -165
- package/tsconfig.json +0 -46
|
@@ -1,147 +1,119 @@
|
|
|
1
|
-
import {
|
|
2
|
-
S3Client,
|
|
3
|
-
GetObjectCommand,
|
|
4
|
-
PutObjectCommand,
|
|
5
|
-
UploadPartCommand,
|
|
6
|
-
CreateMultipartUploadCommand,
|
|
7
|
-
CompleteMultipartUploadCommand,
|
|
8
|
-
type S3ClientConfig
|
|
9
|
-
} from "@aws-sdk/client-s3";
|
|
1
|
+
import { S3Client, GetObjectCommand, PutObjectCommand, UploadPartCommand, CreateMultipartUploadCommand, CompleteMultipartUploadCommand } from "@aws-sdk/client-s3";
|
|
10
2
|
import { Readable, Writable } from "stream";
|
|
11
3
|
import log4js from 'log4js';
|
|
12
|
-
|
|
13
4
|
const logger = log4js.getLogger();
|
|
14
|
-
|
|
15
|
-
region: string;
|
|
16
|
-
endpoint: string;
|
|
17
|
-
bucket: string;
|
|
18
|
-
key: string;
|
|
19
|
-
accessKeyId?: string;
|
|
20
|
-
secretAccessKey?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export async function s3ReaderStream(url: URL, options: { range?: string }): Promise<Readable> {
|
|
5
|
+
export async function s3ReaderStream(url, options) {
|
|
24
6
|
const config = parseURL(url);
|
|
25
|
-
|
|
26
|
-
logger.debug(`s3 config:`,config);
|
|
27
|
-
|
|
7
|
+
logger.debug(`s3 config:`, config);
|
|
28
8
|
const bucket = config.bucket;
|
|
29
|
-
const key
|
|
30
|
-
const range
|
|
9
|
+
const key = config.key;
|
|
10
|
+
const range = options.range;
|
|
31
11
|
const s3 = makeClient(config);
|
|
32
|
-
|
|
33
12
|
const res = await s3.send(new GetObjectCommand({
|
|
34
13
|
Bucket: bucket,
|
|
35
14
|
Key: key,
|
|
36
15
|
Range: range,
|
|
37
16
|
}));
|
|
38
|
-
|
|
39
17
|
const body = res.Body;
|
|
40
|
-
|
|
41
18
|
if (!body) {
|
|
42
19
|
throw new Error("S3 GetObject returned an empty body");
|
|
43
20
|
}
|
|
44
|
-
|
|
45
21
|
// 1) If SDK returned a Node.js readable stream (typical in Node)
|
|
46
22
|
if (isNodeReadable(body)) {
|
|
47
|
-
return body
|
|
23
|
+
return body;
|
|
48
24
|
}
|
|
49
|
-
|
|
50
25
|
// 2) If SDK returned a WHATWG ReadableStream (browser-ish or newer runtimes)
|
|
51
26
|
if (isReadableStream(body)) {
|
|
52
27
|
// Node.js v17+ has Readable.fromWeb
|
|
53
28
|
// Fallback: wrap async iterator
|
|
54
|
-
if (typeof
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
29
|
+
if (typeof Readable.fromWeb === "function") {
|
|
30
|
+
return Readable.fromWeb(body);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Convert using async iterator produced by the stream
|
|
34
|
+
const reader = body.getReader();
|
|
35
|
+
const nodeStream = new Readable({
|
|
36
|
+
read() {
|
|
37
|
+
// no-op. We'll push from async loop below
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
(async () => {
|
|
41
|
+
try {
|
|
42
|
+
while (true) {
|
|
43
|
+
const { done, value } = await reader.read();
|
|
44
|
+
if (done)
|
|
45
|
+
break;
|
|
46
|
+
nodeStream.push(Buffer.from(value));
|
|
47
|
+
}
|
|
48
|
+
nodeStream.push(null);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
nodeStream.destroy(err);
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
return nodeStream;
|
|
77
55
|
}
|
|
78
56
|
}
|
|
79
|
-
|
|
80
57
|
// 3) If SDK returned a Blob (browsers)
|
|
81
58
|
if (typeof Blob !== "undefined" && body instanceof Blob) {
|
|
82
|
-
const stream =
|
|
83
|
-
if (typeof
|
|
84
|
-
|
|
59
|
+
const stream = body.stream();
|
|
60
|
+
if (typeof Readable.fromWeb === "function") {
|
|
61
|
+
return Readable.fromWeb(stream);
|
|
85
62
|
}
|
|
86
63
|
// fallback same as above
|
|
87
64
|
const reader = stream.getReader();
|
|
88
65
|
const nodeStream = new Readable({
|
|
89
|
-
|
|
66
|
+
read() { }
|
|
90
67
|
});
|
|
91
68
|
(async () => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
69
|
+
try {
|
|
70
|
+
while (true) {
|
|
71
|
+
const { done, value } = await reader.read();
|
|
72
|
+
if (done)
|
|
73
|
+
break;
|
|
74
|
+
nodeStream.push(Buffer.from(value));
|
|
75
|
+
}
|
|
76
|
+
nodeStream.push(null);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
nodeStream.destroy(err);
|
|
97
80
|
}
|
|
98
|
-
nodeStream.push(null);
|
|
99
|
-
} catch (err) {
|
|
100
|
-
nodeStream.destroy(err as Error);
|
|
101
|
-
}
|
|
102
81
|
})();
|
|
103
82
|
return nodeStream;
|
|
104
83
|
}
|
|
105
|
-
|
|
106
84
|
// 4) If it's an async iterable (some runtimes)
|
|
107
85
|
if (isAsyncIterable(body)) {
|
|
108
|
-
return Readable.from(body
|
|
86
|
+
return Readable.from(body);
|
|
109
87
|
}
|
|
110
|
-
|
|
111
88
|
// Unknown body shape
|
|
112
89
|
throw new Error("Unsupported S3 GetObject body type");
|
|
113
90
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return new Promise<Writable>( (resolve) => {
|
|
91
|
+
export function s3WriterStream(url, options) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
117
93
|
const config = parseURL(url);
|
|
118
|
-
|
|
119
94
|
logger.debug(`s3 config:`, config);
|
|
120
95
|
const bucket = config.bucket;
|
|
121
96
|
const key = config.key;
|
|
122
97
|
const s3 = makeClient(config);
|
|
123
98
|
const partSize = options.partSize ?? 5 * 1024 * 1024;
|
|
124
|
-
|
|
125
|
-
let
|
|
126
|
-
let parts: Array<{ ETag: string | undefined; PartNumber: number }> = [];
|
|
99
|
+
let uploadId = null;
|
|
100
|
+
let parts = [];
|
|
127
101
|
let buffer = Buffer.alloc(0);
|
|
128
102
|
let partNumber = 1;
|
|
129
|
-
|
|
130
103
|
const writer = new Writable({
|
|
131
104
|
async write(chunk, _encoding, callback) {
|
|
132
105
|
logger.debug("write chunk...");
|
|
133
106
|
try {
|
|
134
107
|
buffer = Buffer.concat([buffer, chunk]);
|
|
135
|
-
|
|
136
108
|
if (buffer.length >= partSize) {
|
|
137
109
|
await flushPart();
|
|
138
110
|
}
|
|
139
111
|
callback();
|
|
140
|
-
}
|
|
141
|
-
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
callback(err);
|
|
142
115
|
}
|
|
143
116
|
},
|
|
144
|
-
|
|
145
117
|
async final(callback) {
|
|
146
118
|
logger.debug("final...");
|
|
147
119
|
try {
|
|
@@ -150,42 +122,38 @@ export function s3WriterStream(url: URL, options: { partSize?: number;}) : Promi
|
|
|
150
122
|
logger.debug("finishUpload...");
|
|
151
123
|
await finishUpload();
|
|
152
124
|
callback();
|
|
153
|
-
}
|
|
154
|
-
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
callback(err);
|
|
155
128
|
}
|
|
156
129
|
}
|
|
157
130
|
});
|
|
158
|
-
|
|
159
131
|
async function ensureUpload() {
|
|
160
132
|
if (!uploadId) {
|
|
161
133
|
const res = await s3.send(new CreateMultipartUploadCommand({
|
|
162
134
|
Bucket: bucket,
|
|
163
135
|
Key: key
|
|
164
136
|
}));
|
|
165
|
-
uploadId = res.UploadId
|
|
137
|
+
uploadId = res.UploadId;
|
|
166
138
|
}
|
|
167
139
|
}
|
|
168
|
-
|
|
169
140
|
async function flushPart(isLast = false) {
|
|
170
|
-
if (buffer.length === 0 && !isLast)
|
|
171
|
-
|
|
141
|
+
if (buffer.length === 0 && !isLast)
|
|
142
|
+
return;
|
|
172
143
|
logger.debug("ensureUpload...");
|
|
173
144
|
await ensureUpload();
|
|
174
|
-
|
|
175
145
|
logger.debug("s3.send...");
|
|
176
146
|
const res = await s3.send(new UploadPartCommand({
|
|
177
147
|
Bucket: bucket,
|
|
178
148
|
Key: key,
|
|
179
149
|
PartNumber: partNumber,
|
|
180
|
-
UploadId: uploadId
|
|
150
|
+
UploadId: uploadId,
|
|
181
151
|
Body: buffer
|
|
182
152
|
}));
|
|
183
|
-
|
|
184
153
|
parts.push({ ETag: res.ETag, PartNumber: partNumber });
|
|
185
154
|
buffer = Buffer.alloc(0);
|
|
186
155
|
partNumber++;
|
|
187
156
|
}
|
|
188
|
-
|
|
189
157
|
async function finishUpload() {
|
|
190
158
|
if (!uploadId) {
|
|
191
159
|
// No parts written, upload empty object
|
|
@@ -196,39 +164,32 @@ export function s3WriterStream(url: URL, options: { partSize?: number;}) : Promi
|
|
|
196
164
|
}));
|
|
197
165
|
return;
|
|
198
166
|
}
|
|
199
|
-
|
|
200
167
|
await s3.send(new CompleteMultipartUploadCommand({
|
|
201
168
|
Bucket: bucket,
|
|
202
169
|
Key: key,
|
|
203
|
-
UploadId: uploadId
|
|
170
|
+
UploadId: uploadId,
|
|
204
171
|
MultipartUpload: { Parts: parts }
|
|
205
172
|
}));
|
|
206
173
|
}
|
|
207
|
-
|
|
208
174
|
resolve(writer);
|
|
209
175
|
});
|
|
210
176
|
}
|
|
211
|
-
|
|
212
|
-
function isNodeReadable(x: any): x is Readable {
|
|
177
|
+
function isNodeReadable(x) {
|
|
213
178
|
return x && typeof x.pipe === "function" && typeof x.read === "function";
|
|
214
179
|
}
|
|
215
|
-
|
|
216
|
-
function isReadableStream(x: any): x is ReadableStream {
|
|
180
|
+
function isReadableStream(x) {
|
|
217
181
|
return typeof x?.getReader === "function";
|
|
218
182
|
}
|
|
219
|
-
|
|
220
|
-
function isAsyncIterable(x: any): x is AsyncIterable<any> {
|
|
183
|
+
function isAsyncIterable(x) {
|
|
221
184
|
return x && typeof x[Symbol.asyncIterator] === "function";
|
|
222
185
|
}
|
|
223
|
-
|
|
224
|
-
function makeClient(config: S3Config) : S3Client {
|
|
186
|
+
function makeClient(config) {
|
|
225
187
|
logger.debug(config);
|
|
226
|
-
const myConfig
|
|
188
|
+
const myConfig = {
|
|
227
189
|
endpoint: config.endpoint,
|
|
228
190
|
forcePathStyle: true,
|
|
229
191
|
region: config.region,
|
|
230
192
|
};
|
|
231
|
-
|
|
232
193
|
if (config.accessKeyId && config.secretAccessKey) {
|
|
233
194
|
myConfig.credentials = {
|
|
234
195
|
accessKeyId: config.accessKeyId,
|
|
@@ -237,15 +198,13 @@ function makeClient(config: S3Config) : S3Client {
|
|
|
237
198
|
}
|
|
238
199
|
return new S3Client(myConfig);
|
|
239
200
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const config : S3Config = {
|
|
201
|
+
function parseURL(url) {
|
|
202
|
+
const config = {
|
|
243
203
|
region: "us-east-1",
|
|
244
204
|
endpoint: "http://localhost:3371",
|
|
245
205
|
bucket: "bbl",
|
|
246
206
|
key: "test.txt"
|
|
247
207
|
};
|
|
248
|
-
|
|
249
208
|
const scheme = url.protocol.startsWith("s3s") ? "https" : "http";
|
|
250
209
|
config.endpoint = `${scheme}://${url.hostname}`;
|
|
251
210
|
if (url.port) {
|
|
@@ -253,14 +212,12 @@ function parseURL(url: URL) : S3Config {
|
|
|
253
212
|
}
|
|
254
213
|
config.bucket = url.pathname.split("/")[1] ?? "";
|
|
255
214
|
config.key = url.pathname.split("/").splice(2).join("/");
|
|
256
|
-
|
|
257
215
|
if (url.username) {
|
|
258
216
|
config.accessKeyId = url.username;
|
|
259
217
|
}
|
|
260
|
-
|
|
261
218
|
if (url.password) {
|
|
262
219
|
config.secretAccessKey = url.password;
|
|
263
220
|
}
|
|
264
|
-
|
|
265
221
|
return config;
|
|
266
|
-
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=s3stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3stream.js","sourceRoot":"","sources":["../src/s3stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Readable, Writable } from "stream";
|
|
2
|
+
export interface SftpConfig {
|
|
3
|
+
host: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
username: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
privateKey?: Buffer | string;
|
|
8
|
+
}
|
|
9
|
+
export declare function sftpReadStream(remotePath: string, config: SftpConfig): Promise<Readable>;
|
|
10
|
+
export declare function sftpWriteStream(remotePath: string, config: SftpConfig): Promise<Writable>;
|
|
11
|
+
export declare function sftpLatestFile(config: SftpConfig, remoteDir: string, extension: string): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=sftpstream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sftpstream.d.ts","sourceRoot":"","sources":["../src/sftpstream.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAG,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2B9F;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2B/F;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C9G"}
|
|
@@ -1,114 +1,84 @@
|
|
|
1
1
|
import { Client } from "ssh2";
|
|
2
|
-
import { Readable
|
|
3
|
-
|
|
4
|
-
export interface SftpConfig {
|
|
5
|
-
host: string;
|
|
6
|
-
port?: number;
|
|
7
|
-
username: string;
|
|
8
|
-
password?: string;
|
|
9
|
-
privateKey?: Buffer | string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function sftpReadStream(remotePath: string, config: SftpConfig): Promise<Readable> {
|
|
2
|
+
import { Readable, Writable } from "stream";
|
|
3
|
+
export async function sftpReadStream(remotePath, config) {
|
|
13
4
|
return new Promise((resolve, reject) => {
|
|
14
5
|
const conn = new Client();
|
|
15
|
-
|
|
16
6
|
conn.on("ready", () => {
|
|
17
7
|
conn.sftp((err, sftp) => {
|
|
18
8
|
if (err) {
|
|
19
9
|
conn.end();
|
|
20
10
|
return reject(err);
|
|
21
11
|
}
|
|
22
|
-
|
|
23
12
|
const stream = sftp.createReadStream(remotePath);
|
|
24
|
-
|
|
25
13
|
// Close SSH connection when stream ends or errors
|
|
26
14
|
stream.on("close", () => conn.end());
|
|
27
|
-
stream.on("error", (err
|
|
15
|
+
stream.on("error", (err) => {
|
|
28
16
|
conn.end();
|
|
29
17
|
reject(err);
|
|
30
18
|
});
|
|
31
|
-
|
|
32
19
|
resolve(stream);
|
|
33
20
|
});
|
|
34
21
|
});
|
|
35
|
-
|
|
36
22
|
conn.on("error", (err) => reject(err));
|
|
37
23
|
conn.connect(config);
|
|
38
24
|
});
|
|
39
25
|
}
|
|
40
|
-
|
|
41
|
-
export async function sftpWriteStream(remotePath: string, config: SftpConfig): Promise<Writable> {
|
|
26
|
+
export async function sftpWriteStream(remotePath, config) {
|
|
42
27
|
return new Promise((resolve, reject) => {
|
|
43
28
|
const conn = new Client();
|
|
44
|
-
|
|
45
29
|
conn.on("ready", () => {
|
|
46
30
|
conn.sftp((err, sftp) => {
|
|
47
31
|
if (err) {
|
|
48
32
|
conn.end();
|
|
49
33
|
return reject(err);
|
|
50
34
|
}
|
|
51
|
-
|
|
52
35
|
const stream = sftp.createWriteStream(remotePath, { encoding: "utf-8" });
|
|
53
|
-
|
|
54
36
|
// Close SSH connection when stream ends or errors
|
|
55
37
|
stream.on("close", () => conn.end());
|
|
56
|
-
stream.on("error", (err
|
|
38
|
+
stream.on("error", (err) => {
|
|
57
39
|
conn.end();
|
|
58
40
|
reject(err);
|
|
59
41
|
});
|
|
60
|
-
|
|
61
42
|
resolve(stream);
|
|
62
43
|
});
|
|
63
44
|
});
|
|
64
|
-
|
|
65
45
|
conn.on("error", (err) => reject(err));
|
|
66
46
|
conn.connect(config);
|
|
67
47
|
});
|
|
68
48
|
}
|
|
69
|
-
|
|
70
|
-
export async function sftpLatestFile(config: SftpConfig, remoteDir: string, extension: string): Promise<string> {
|
|
49
|
+
export async function sftpLatestFile(config, remoteDir, extension) {
|
|
71
50
|
return new Promise((resolve, reject) => {
|
|
72
51
|
const conn = new Client();
|
|
73
|
-
|
|
74
52
|
conn.on("ready", () => {
|
|
75
53
|
conn.sftp((err, sftp) => {
|
|
76
54
|
if (err) {
|
|
77
55
|
conn.end();
|
|
78
56
|
return reject(err);
|
|
79
57
|
}
|
|
80
|
-
|
|
81
58
|
sftp.readdir(remoteDir, (err, list) => {
|
|
82
59
|
if (err) {
|
|
83
60
|
conn.end();
|
|
84
61
|
return reject(err);
|
|
85
62
|
}
|
|
86
|
-
|
|
87
63
|
if (!list || list.length === 0) {
|
|
88
64
|
conn.end();
|
|
89
65
|
return reject(new Error("No files found in directory"));
|
|
90
66
|
}
|
|
91
|
-
|
|
92
67
|
// Filter only .xml files
|
|
93
68
|
const myFiles = list.filter(f => f.filename.toLowerCase().endsWith(extension));
|
|
94
|
-
|
|
95
69
|
if (myFiles.length === 0) {
|
|
96
70
|
conn.end();
|
|
97
71
|
return reject(new Error(`No ${extension} files found in directory`));
|
|
98
72
|
}
|
|
99
|
-
|
|
100
|
-
const latest = myFiles.reduce((prev, curr) =>
|
|
101
|
-
(prev.attrs.mtime > curr.attrs.mtime) ? prev : curr
|
|
102
|
-
);
|
|
103
|
-
|
|
73
|
+
const latest = myFiles.reduce((prev, curr) => (prev.attrs.mtime > curr.attrs.mtime) ? prev : curr);
|
|
104
74
|
const latestPath = `${remoteDir}/${latest.filename}`;
|
|
105
75
|
conn.end();
|
|
106
76
|
resolve(latestPath);
|
|
107
77
|
});
|
|
108
78
|
});
|
|
109
79
|
});
|
|
110
|
-
|
|
111
80
|
conn.on("error", (err) => reject(err));
|
|
112
81
|
conn.connect(config);
|
|
113
82
|
});
|
|
114
|
-
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=sftpstream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sftpstream.js","sourceRoot":"","sources":["../src/sftpstream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Writable, type WritableOptions } from "stream";
|
|
2
|
+
export interface SlowWritableOptions extends WritableOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Delay per chunk (ms). Default 200 ms.
|
|
5
|
+
*/
|
|
6
|
+
delayMs?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Maximum number of concurrent "in-flight" asynchronous writes.
|
|
9
|
+
* While more writes may be queued, only up to this number will be processed in parallel.
|
|
10
|
+
* Default 1.
|
|
11
|
+
*/
|
|
12
|
+
maxConcurrency?: number;
|
|
13
|
+
/**
|
|
14
|
+
* If set to a positive integer n, every nth chunk will produce an error (for testing).
|
|
15
|
+
* Default 0 (never error).
|
|
16
|
+
*/
|
|
17
|
+
simulateErrorEveryN?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* SlowWritable: a Writable stream that processes writes asynchronously
|
|
21
|
+
* with an artificial delay and optional concurrency control.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SlowWritable extends Writable {
|
|
24
|
+
private delayMs;
|
|
25
|
+
private maxConcurrency;
|
|
26
|
+
private simulateErrorEveryN;
|
|
27
|
+
private inFlight;
|
|
28
|
+
private queue;
|
|
29
|
+
private seqCounter;
|
|
30
|
+
private destroyedFlag;
|
|
31
|
+
constructor(opts?: SlowWritableOptions);
|
|
32
|
+
_write(chunk: any, encoding: BufferEncoding, callback: (err?: Error | null) => void): void;
|
|
33
|
+
private processQueue;
|
|
34
|
+
private performAsyncWrite;
|
|
35
|
+
_final(callback: (err?: Error | null) => void): void;
|
|
36
|
+
_destroy(err: Error | null, callback: (error?: Error | null) => void): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=slow-writable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slow-writable.d.ts","sourceRoot":"","sources":["../src/slow-writable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAKxD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAKL;IACR,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAS;gBAElB,IAAI,GAAE,mBAAwB;IAU1C,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IAY1F,OAAO,CAAC,YAAY;YAwBN,iBAAiB;IA6C/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IAgBpD,QAAQ,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;CAa5E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// slow-writable.ts
|
|
2
|
+
import { Writable } from "stream";
|
|
3
|
+
import log4js from 'log4js';
|
|
4
|
+
const logger = log4js.getLogger();
|
|
5
|
+
/**
|
|
6
|
+
* SlowWritable: a Writable stream that processes writes asynchronously
|
|
7
|
+
* with an artificial delay and optional concurrency control.
|
|
8
|
+
*/
|
|
9
|
+
export class SlowWritable extends Writable {
|
|
10
|
+
delayMs;
|
|
11
|
+
maxConcurrency;
|
|
12
|
+
simulateErrorEveryN;
|
|
13
|
+
inFlight = 0;
|
|
14
|
+
queue = [];
|
|
15
|
+
seqCounter = 0;
|
|
16
|
+
destroyedFlag = false;
|
|
17
|
+
constructor(opts = {}) {
|
|
18
|
+
// Keep objectMode/encoding behavior from user but default to object mode false
|
|
19
|
+
const { delayMs = 200, maxConcurrency = 1, simulateErrorEveryN = 0, ...writableOpts } = opts;
|
|
20
|
+
super(writableOpts);
|
|
21
|
+
this.delayMs = delayMs;
|
|
22
|
+
this.maxConcurrency = Math.max(1, Math.floor(maxConcurrency));
|
|
23
|
+
this.simulateErrorEveryN = Math.max(0, Math.floor(simulateErrorEveryN));
|
|
24
|
+
}
|
|
25
|
+
// Node will call _write for each chunk
|
|
26
|
+
_write(chunk, encoding, callback) {
|
|
27
|
+
if (this.destroyedFlag) {
|
|
28
|
+
callback(new Error("Stream is destroyed"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const seq = ++this.seqCounter;
|
|
32
|
+
this.queue.push({ chunk, encoding, callback, seq });
|
|
33
|
+
this.processQueue();
|
|
34
|
+
}
|
|
35
|
+
// Process queued writes honoring maxConcurrency
|
|
36
|
+
processQueue() {
|
|
37
|
+
// If nothing to do or already at concurrency limit, return
|
|
38
|
+
while (this.inFlight < this.maxConcurrency && this.queue.length > 0 && !this.destroyedFlag) {
|
|
39
|
+
const item = this.queue.shift();
|
|
40
|
+
this.inFlight++;
|
|
41
|
+
this.performAsyncWrite(item)
|
|
42
|
+
.then(() => {
|
|
43
|
+
this.inFlight--;
|
|
44
|
+
// After finishing one, try to process more
|
|
45
|
+
// Use nextTick to avoid deep recursion
|
|
46
|
+
process.nextTick(() => this.processQueue());
|
|
47
|
+
})
|
|
48
|
+
.catch((err) => {
|
|
49
|
+
this.inFlight--;
|
|
50
|
+
// propagate error via callback; stream will emit 'error' as well
|
|
51
|
+
item.callback(err);
|
|
52
|
+
this.emit("error", err);
|
|
53
|
+
// continue processing queue
|
|
54
|
+
process.nextTick(() => this.processQueue());
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Simulate an asynchronous write that takes `delayMs` ms
|
|
59
|
+
async performAsyncWrite(item) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const maybeError = this.simulateErrorEveryN > 0 && item.seq % this.simulateErrorEveryN === 0;
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
// simulate processing chunk here. For demonstration we just log.
|
|
64
|
+
// In real use, replace with actual async I/O.
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
logger.info(`SlowWritable processed seq=${item.seq}`);
|
|
67
|
+
if (maybeError) {
|
|
68
|
+
const err = new Error(`Simulated error at seq ${item.seq}`);
|
|
69
|
+
item.callback(err);
|
|
70
|
+
reject(err);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
item.callback();
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
}, this.delayMs);
|
|
77
|
+
// If stream was destroyed meantime, cancel timer and callback with error
|
|
78
|
+
const onDestroy = () => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
const err = new Error("Stream destroyed while writing");
|
|
81
|
+
try {
|
|
82
|
+
item.callback(err);
|
|
83
|
+
}
|
|
84
|
+
catch (_) {
|
|
85
|
+
// ignore
|
|
86
|
+
}
|
|
87
|
+
reject(err);
|
|
88
|
+
};
|
|
89
|
+
// Ensure we don't leak listeners. If destroyedFlag becomes true quickly, call onDestroy.
|
|
90
|
+
if (this.destroyedFlag) {
|
|
91
|
+
onDestroy();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
_final(callback) {
|
|
96
|
+
// Wait until queue emptied and inFlight is zero
|
|
97
|
+
const check = () => {
|
|
98
|
+
if (this.destroyedFlag) {
|
|
99
|
+
callback(new Error("Stream destroyed before finalizing"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (this.queue.length === 0 && this.inFlight === 0) {
|
|
103
|
+
callback();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
setTimeout(check, 10);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
check();
|
|
110
|
+
}
|
|
111
|
+
_destroy(err, callback) {
|
|
112
|
+
this.destroyedFlag = true;
|
|
113
|
+
// flush callbacks in queue with error
|
|
114
|
+
while (this.queue.length > 0) {
|
|
115
|
+
const item = this.queue.shift();
|
|
116
|
+
try {
|
|
117
|
+
item.callback(err ?? new Error("Stream destroyed"));
|
|
118
|
+
}
|
|
119
|
+
catch (_) {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
callback(err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=slow-writable.js.map
|