xrootd 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -10
- package/dist/index.cjs +77 -12
- package/dist/index.d.cts +119 -17
- package/dist/index.d.mts +119 -17
- package/dist/index.mjs +77 -13
- package/package.json +3 -3
- package/prebuilds/darwin-arm64/xrootd.node +0 -0
- package/prebuilds/darwin-x64/xrootd.node +0 -0
- package/prebuilds/linux-arm64/xrootd.node +0 -0
- package/prebuilds/linux-x64/xrootd.node +0 -0
package/README.md
CHANGED
|
@@ -1,23 +1,144 @@
|
|
|
1
|
-
# XRootD
|
|
1
|
+
# 🚀 XRootD TypeScript Support (`xrootd`)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> **Working in process**. This package is currently under active development and not ready for production use. Stay tuned!
|
|
3
|
+
A high-performance, enterprise-grade Node.js binding for the [XRootD](https://github.com/xrootd/xrootd) client library. Designed to bring seamless, extreme-throughput data access to the TypeScript ecosystem.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> **Work in Progress**: This package is currently under active development. While core features are highly functional, the API may undergo minor refinements.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Supported Capabilities
|
|
10
|
+
|
|
11
|
+
* **`FileSystem`**: Cluster-level operations (`stat`, `dirList`, `rm`, `mkdir`, `locate`).
|
|
12
|
+
* **`File`**: High-performance I/O (`open`, `read`, `write`, `vectorRead`, server-side `clone`).
|
|
13
|
+
* **`CopyProcess`**: Asynchronous, highly-parallel data transfers with real-time progress callbacks.
|
|
14
|
+
* **`Env`**: Type-safe configuration management and OS-level auth protocol routing.
|
|
15
|
+
* **`Url`**: XRootD-specific URI scheme parsing and validation.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
To ensure compatibility with the pre-compiled native bindings, your environment must meet the following requirements:
|
|
21
|
+
|
|
22
|
+
* **Node.js**: Version 20.x or higher.
|
|
23
|
+
* **System Dependencies (Linux)**: Requires `libstdc++` providing `GLIBCXX_3.4.26` or newer (typically GCC 9+, e.g., CentOS 9 / Ubuntu 20.04+).
|
|
24
|
+
* **System Dependencies (Macos)**: Requires macos 14 or later for arm64 architecture, macos 15 or later for intel architecture.
|
|
25
|
+
* **Authentication**: Ensure your host machine has valid tickets (e.g., `kinit` for Kerberos) if accessing a secured cluster.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install xrootd
|
|
32
|
+
# or
|
|
33
|
+
yarn add xrootd
|
|
34
|
+
|
|
35
|
+
```
|
|
8
36
|
|
|
9
37
|
---
|
|
10
38
|
|
|
11
|
-
|
|
39
|
+
## Usage Examples
|
|
40
|
+
|
|
41
|
+
### 1. Configuration & Basic FileSystem
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Env, FileSystem } from 'xrootd';
|
|
45
|
+
|
|
46
|
+
// Safely configure underlying XRootD environment
|
|
47
|
+
Env.configure({
|
|
48
|
+
RequestTimeout: 30, // Prevent 30-min infinite hangs (Node.js friendly)
|
|
49
|
+
WorkerThreads: 4, // Boost underlying multiplexing
|
|
50
|
+
SecProtocol: 'krb5,unix' // Force Kerberos or Unix socket auth
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const fs = new FileSystem('root://eos01.ihep.ac.cn/');
|
|
54
|
+
|
|
55
|
+
async function checkCluster() {
|
|
56
|
+
try {
|
|
57
|
+
const stat = await fs.stat('/eos/lhaaso/data');
|
|
58
|
+
console.log(`Directory size: ${stat.size} bytes`);
|
|
59
|
+
console.log(`Modified at: ${stat.modTimeAsString}`);
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
if (err.code === 'ENOENT') {
|
|
62
|
+
console.error('File not found in cluster.');
|
|
63
|
+
} else {
|
|
64
|
+
console.error(err.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. Zero-Copy File Reading
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { File, OpenFlags } from 'xrootd';
|
|
75
|
+
|
|
76
|
+
async function readHeader() {
|
|
77
|
+
const file = new File();
|
|
78
|
+
|
|
79
|
+
// Auto-translates and handles C++ lifecycle
|
|
80
|
+
await file.open('root://eos01.ihep.ac.cn//eos/test.dat', OpenFlags.Read);
|
|
81
|
+
|
|
82
|
+
// The returned buffer is a zero-copy mapping of C++ allocated memory
|
|
83
|
+
const buffer = await file.read(0n, 1024);
|
|
84
|
+
console.log(buffer.toString('utf-8'));
|
|
85
|
+
|
|
86
|
+
await file.close();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. High-Performance CopyProcess with Progress
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { CopyProcess } from 'xrootd';
|
|
95
|
+
|
|
96
|
+
async function syncData() {
|
|
97
|
+
const cp = new CopyProcess();
|
|
98
|
+
|
|
99
|
+
cp.addJob({
|
|
100
|
+
source: 'root://eos01.ihep.ac.cn//eos/data.raw',
|
|
101
|
+
target: '/local/disk/data.raw',
|
|
102
|
+
force: true,
|
|
103
|
+
parallelChunks: 4
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await cp.prepare();
|
|
107
|
+
|
|
108
|
+
const results = await cp.run((jobNum, processed, total) => {
|
|
109
|
+
const percent = ((processed / total) * 100).toFixed(2);
|
|
110
|
+
console.log(`Job ${jobNum} Progress: ${percent}%`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log('Copy completed:', results);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ✨ Why `xrootd`? (Core Architecture)
|
|
119
|
+
|
|
120
|
+
Unlike traditional Node.js C++ addons, `xrootd` is architected for **High Energy Physics (HEP)** and **Big Data** workloads:
|
|
121
|
+
|
|
122
|
+
* **True Native Async**: Bypasses the notoriously limited Node.js `libuv` thread pool (default 4 threads). It hooks directly into XRootD's underlying C++ event loop via N-API `ThreadSafeFunction`, allowing thousands of concurrent requests without blocking the V8 engine.
|
|
123
|
+
* **Absolute Zero-Copy I/O**: Implements direct memory handoffs. Data read from the EOS cluster is mounted directly as V8 `Buffer` objects without a single byte of internal memory copying, completely eliminating GC (Garbage Collection) pauses during heavy I/O.
|
|
124
|
+
* **Idiomatic Node.js Experience**: "Thin C++, Thick TS". Complex XRootD protocol errors (e.g., `3011`) are smartly translated into standard Node.js exceptions (e.g., `ENOENT`, `EACCES`), complete with system call contexts and retryable flags.
|
|
125
|
+
* **Zero-Config Authentication**: Bundles essential security plugins (Kerberos, SSS, Unix, Token) natively. Path injection is handled automatically under the hood—no more `[FATAL] Auth failed` or missing `.so` nightmares.
|
|
12
126
|
|
|
13
127
|
---
|
|
14
128
|
|
|
15
|
-
|
|
129
|
+
## Licensing
|
|
130
|
+
|
|
131
|
+
This project is released under a **Dual License** strategy to balance open-source compatibility and developer freedom:
|
|
16
132
|
|
|
17
|
-
|
|
133
|
+
1. **[GNU GPLv3](https://www.google.com/search?q=https://www.gnu.org/licenses/gpl-3.0.en.html)**: The native binding codebase adheres to the GPLv3 license to remain fully compatible with the upstream C++ XRootD project.
|
|
134
|
+
2. **[MIT](https://www.google.com/search?q=https://opensource.org/licenses/MIT)**: The core TypeScript APIs, interface definitions, and glue layers are provided under the MIT license, allowing you to integrate the TS components into your own software architectures without viral restrictive requirements.
|
|
18
135
|
|
|
19
|
-
|
|
136
|
+
*Disclaimer: This project is a third-party community initiative built for modern web ecosystems and is not affiliated with, officially endorsed by, or sponsored by the core XRootD project.*
|
|
137
|
+
|
|
138
|
+
---
|
|
20
139
|
|
|
140
|
+
## Contributing
|
|
21
141
|
|
|
142
|
+
We welcome contributions from the high-energy physics, astrophysics, and Node.js communities! Whether it's reporting bugs, improving documentation, or adding support for advanced XRootD features.
|
|
22
143
|
|
|
23
|
-
|
|
144
|
+
*Contribution guidelines coming soon.*
|
package/dist/index.cjs
CHANGED
|
@@ -179,7 +179,9 @@ var File = class {
|
|
|
179
179
|
return this._internal.IsOpen();
|
|
180
180
|
}
|
|
181
181
|
async getProperty(name) {
|
|
182
|
-
|
|
182
|
+
const ret = this._internal.GetProperty(name);
|
|
183
|
+
if (ret.success) return ret.value;
|
|
184
|
+
throw new Error("TODO");
|
|
183
185
|
}
|
|
184
186
|
async setProperty(name, value) {
|
|
185
187
|
return this._internal.SetProperty(name, value);
|
|
@@ -211,25 +213,37 @@ var File = class {
|
|
|
211
213
|
/**
|
|
212
214
|
* 设置文件的扩展属性
|
|
213
215
|
*/
|
|
214
|
-
async
|
|
215
|
-
return this._internal.SetXAttr(
|
|
216
|
+
async setXAttrs(attrs) {
|
|
217
|
+
return this._internal.SetXAttr(attrs);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 设置文件的扩展属性
|
|
221
|
+
*/
|
|
222
|
+
async setXAttr(key, value) {
|
|
223
|
+
return (await this._internal.SetXAttr({ [key]: value }))[0].ok;
|
|
216
224
|
}
|
|
217
225
|
/**
|
|
218
226
|
* 获取文件的扩展属性
|
|
219
227
|
*/
|
|
220
|
-
async
|
|
221
|
-
return this._internal.GetXAttr(
|
|
228
|
+
async getXAttrs(keys) {
|
|
229
|
+
return this._internal.GetXAttr(keys);
|
|
230
|
+
}
|
|
231
|
+
async getXAttr(key) {
|
|
232
|
+
return (await this._internal.GetXAttr([key]))[key];
|
|
222
233
|
}
|
|
223
234
|
/**
|
|
224
235
|
* 删除文件的扩展属性
|
|
225
236
|
*/
|
|
226
|
-
async
|
|
227
|
-
return this._internal.DelXAttr(
|
|
237
|
+
async delXAttrs(keys) {
|
|
238
|
+
return this._internal.DelXAttr(keys);
|
|
239
|
+
}
|
|
240
|
+
async delXAttr(key) {
|
|
241
|
+
return (await this._internal.DelXAttr([key]))[0].ok;
|
|
228
242
|
}
|
|
229
243
|
/**
|
|
230
|
-
*
|
|
244
|
+
* 列出文件所有的扩展属性和内容
|
|
231
245
|
*/
|
|
232
|
-
async
|
|
246
|
+
async listXAttrs() {
|
|
233
247
|
return this._internal.ListXAttr();
|
|
234
248
|
}
|
|
235
249
|
/**
|
|
@@ -535,25 +549,34 @@ var FileSystem = class {
|
|
|
535
549
|
* @param targetPath 目标路径
|
|
536
550
|
* @param attrs 扩展属性键值对记录
|
|
537
551
|
*/
|
|
538
|
-
async
|
|
552
|
+
async setXAttrs(targetPath, attrs) {
|
|
539
553
|
return this._internal.SetXAttr(this._normalize(targetPath), attrs);
|
|
540
554
|
}
|
|
555
|
+
async setXAttr(targetPath, key, value) {
|
|
556
|
+
return (await this.setXAttrs(targetPath, { [key]: value }))[0].ok;
|
|
557
|
+
}
|
|
541
558
|
/**
|
|
542
559
|
* 获取扩展属性
|
|
543
560
|
* @param targetPath 目标路径
|
|
544
561
|
* @param keys 需要获取的属性名数组
|
|
545
562
|
*/
|
|
546
|
-
async
|
|
563
|
+
async getXAttrs(targetPath, keys) {
|
|
547
564
|
return this._internal.GetXAttr(this._normalize(targetPath), keys);
|
|
548
565
|
}
|
|
566
|
+
async getXAttr(targetPath, key) {
|
|
567
|
+
return (await this.getXAttrs(targetPath, [key]))[key];
|
|
568
|
+
}
|
|
549
569
|
/**
|
|
550
570
|
* 删除指定的扩展属性
|
|
551
571
|
* @param targetPath 目标路径
|
|
552
572
|
* @param keys 需要删除的属性名数组
|
|
553
573
|
*/
|
|
554
|
-
async
|
|
574
|
+
async delXAttrs(targetPath, keys) {
|
|
555
575
|
return this._internal.DelXAttr(this._normalize(targetPath), keys);
|
|
556
576
|
}
|
|
577
|
+
async delXAttr(targetPath, key) {
|
|
578
|
+
return (await this.delXAttrs(targetPath, [key]))[0].ok;
|
|
579
|
+
}
|
|
557
580
|
/**
|
|
558
581
|
* 列出目标文件或目录的所有扩展属性
|
|
559
582
|
*/
|
|
@@ -562,6 +585,47 @@ var FileSystem = class {
|
|
|
562
585
|
}
|
|
563
586
|
};
|
|
564
587
|
//#endregion
|
|
588
|
+
//#region lib/copy.ts
|
|
589
|
+
var CopyProcess = class {
|
|
590
|
+
nativeCp;
|
|
591
|
+
constructor() {
|
|
592
|
+
this.nativeCp = new nativeAddon.CopyProcess();
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Add a job to the copy process synchronously
|
|
596
|
+
*/
|
|
597
|
+
addJob(config) {
|
|
598
|
+
this.nativeCp.AddJob(config);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Asynchronously prepare the jobs (resolve DNS, check endpoints, etc.)
|
|
602
|
+
*/
|
|
603
|
+
async prepare() {
|
|
604
|
+
return this.nativeCp.Prepare();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Run the copy process.
|
|
608
|
+
* @param onProgress Optional progress callback
|
|
609
|
+
* @returns Array of results corresponding to the jobs added
|
|
610
|
+
*/
|
|
611
|
+
async run(onProgress) {
|
|
612
|
+
if (onProgress) this.nativeCp.SetEventListener("progress", onProgress);
|
|
613
|
+
return this.nativeCp.Run();
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Abort the running copy process.
|
|
617
|
+
*/
|
|
618
|
+
abort() {
|
|
619
|
+
this.nativeCp.CancelJob();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Abort the running copy process. (alias for abort)
|
|
623
|
+
*/
|
|
624
|
+
cancelJob() {
|
|
625
|
+
this.nativeCp.CancelJob();
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
//#endregion
|
|
565
629
|
//#region lib/url.ts
|
|
566
630
|
/**
|
|
567
631
|
* 纯 TypeScript 实现的 XRootD URL 解析器
|
|
@@ -747,6 +811,7 @@ Env.configure({
|
|
|
747
811
|
});
|
|
748
812
|
//#endregion
|
|
749
813
|
exports.AccessMode = AccessMode;
|
|
814
|
+
exports.CopyProcess = CopyProcess;
|
|
750
815
|
exports.Env = Env;
|
|
751
816
|
exports.File = File;
|
|
752
817
|
exports.FileSystem = FileSystem;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,18 @@ interface IXRootDError extends Error {
|
|
|
8
8
|
xrdErrNo: number;
|
|
9
9
|
xrdErrMsg: string;
|
|
10
10
|
}
|
|
11
|
+
type XRootDOkError = {
|
|
12
|
+
ok: true;
|
|
13
|
+
} | ({
|
|
14
|
+
ok: false;
|
|
15
|
+
} & IXRootDError);
|
|
16
|
+
type XAttrStatusResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
name: string;
|
|
19
|
+
} | ({
|
|
20
|
+
ok: false;
|
|
21
|
+
name: string;
|
|
22
|
+
} & IXRootDError);
|
|
11
23
|
interface StatInfo {
|
|
12
24
|
id: string;
|
|
13
25
|
size: bigint;
|
|
@@ -56,16 +68,10 @@ interface DirListEntry {
|
|
|
56
68
|
hostAddress: string;
|
|
57
69
|
stat: StatInfo | null;
|
|
58
70
|
}
|
|
59
|
-
interface XAttrStatusResult {
|
|
60
|
-
name: string;
|
|
61
|
-
isOk: boolean;
|
|
62
|
-
code: number;
|
|
63
|
-
message: string;
|
|
64
|
-
}
|
|
65
71
|
declare class XRootDError extends Error {
|
|
66
72
|
code: number;
|
|
67
73
|
status: number;
|
|
68
|
-
constructor(status:
|
|
74
|
+
constructor(status: XRootDOkError);
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* 对应 src/core/XrdNodeFile.cc 中 Init 暴露的类
|
|
@@ -93,6 +99,65 @@ interface INativeFile {
|
|
|
93
99
|
ListXAttr(): Promise<Record<string, string>>;
|
|
94
100
|
Clone(list: CloneLocationRequest[]): Promise<void>;
|
|
95
101
|
}
|
|
102
|
+
interface CopyJobConfig {
|
|
103
|
+
/** original source URL */
|
|
104
|
+
source: string;
|
|
105
|
+
/** target directory or file */
|
|
106
|
+
target: string;
|
|
107
|
+
/** maximum number sources */
|
|
108
|
+
sourceLimit?: number;
|
|
109
|
+
/** overwrite target if exists */
|
|
110
|
+
force?: boolean;
|
|
111
|
+
/** persistify only on successful close */
|
|
112
|
+
posc?: boolean;
|
|
113
|
+
/** ignore locking semantics on destination */
|
|
114
|
+
coerce?: boolean;
|
|
115
|
+
/** create path to the file if it doesn't exist */
|
|
116
|
+
makeDir?: boolean;
|
|
117
|
+
/** "first" try third party copy, if it fails try normal copy; "only" only try third party copy */
|
|
118
|
+
thirdParty?: string;
|
|
119
|
+
/** "none" - no checksumming
|
|
120
|
+
"end2end" - end to end checksumming
|
|
121
|
+
"source" - calculate checksum at source
|
|
122
|
+
"target" - calculate checksum at target */
|
|
123
|
+
checkSumMode?: "none" | "end2end" | "source" | "target";
|
|
124
|
+
/** type of the checksum to be used */
|
|
125
|
+
checkSumType?: string;
|
|
126
|
+
/** checksum preset */
|
|
127
|
+
checkSumPreset?: string;
|
|
128
|
+
/** size of a copy chunks in bytes */
|
|
129
|
+
chunkSize?: number;
|
|
130
|
+
/** number of chunks that should be requested in parallel
|
|
131
|
+
*
|
|
132
|
+
* [uint8_t]
|
|
133
|
+
*/
|
|
134
|
+
parallelChunks?: number;
|
|
135
|
+
/** time limit for successfull initialization of the copy job
|
|
136
|
+
* [time_t]
|
|
137
|
+
*/
|
|
138
|
+
initTimeout?: number;
|
|
139
|
+
/** time limit for the actual copy to finish
|
|
140
|
+
* [time_t]
|
|
141
|
+
*/
|
|
142
|
+
tpcTimeout?: number;
|
|
143
|
+
/** support for the case where the size source file may change during reading process */
|
|
144
|
+
dynamicSource?: boolean;
|
|
145
|
+
}
|
|
146
|
+
interface CopyJobResult {
|
|
147
|
+
/** checksum at source, if requested */
|
|
148
|
+
sourceCheckSum?: string;
|
|
149
|
+
/** checksum at target, if requested */
|
|
150
|
+
targetCheckSum?: string;
|
|
151
|
+
/** file size */
|
|
152
|
+
size: bigint;
|
|
153
|
+
/** status of the copy operation */
|
|
154
|
+
status?: XRootDOkError;
|
|
155
|
+
/** all sources used */
|
|
156
|
+
sources?: string[];
|
|
157
|
+
/** the actual disk server target */
|
|
158
|
+
realTarget?: string;
|
|
159
|
+
}
|
|
160
|
+
type ProgressCallback = (jobNum: number, processed: number, total: number) => void;
|
|
96
161
|
interface ReadStreamOptions {
|
|
97
162
|
start?: bigint;
|
|
98
163
|
end?: bigint;
|
|
@@ -232,7 +297,7 @@ declare class File$1 {
|
|
|
232
297
|
*/
|
|
233
298
|
isOpen(): boolean;
|
|
234
299
|
getProperty(name: string): Promise<string>;
|
|
235
|
-
setProperty(name: string, value: string): Promise<
|
|
300
|
+
setProperty(name: string, value: string): Promise<boolean>;
|
|
236
301
|
/**
|
|
237
302
|
* 向量化读取 (Vector Read)
|
|
238
303
|
* 在单个请求中从文件的多个非连续区域读取数据,极大地减少网络往返开销。
|
|
@@ -248,19 +313,25 @@ declare class File$1 {
|
|
|
248
313
|
/**
|
|
249
314
|
* 设置文件的扩展属性
|
|
250
315
|
*/
|
|
251
|
-
|
|
316
|
+
setXAttrs(attrs: Record<string, string>): Promise<XAttrStatusResult[]>;
|
|
317
|
+
/**
|
|
318
|
+
* 设置文件的扩展属性
|
|
319
|
+
*/
|
|
320
|
+
setXAttr(key: string, value: string): Promise<boolean>;
|
|
252
321
|
/**
|
|
253
322
|
* 获取文件的扩展属性
|
|
254
323
|
*/
|
|
255
|
-
|
|
324
|
+
getXAttrs(keys: string[]): Promise<Record<string, string>>;
|
|
325
|
+
getXAttr(key: string): Promise<string>;
|
|
256
326
|
/**
|
|
257
327
|
* 删除文件的扩展属性
|
|
258
328
|
*/
|
|
259
|
-
|
|
329
|
+
delXAttrs(keys: string[]): Promise<XAttrStatusResult[]>;
|
|
330
|
+
delXAttr(key: string): Promise<boolean>;
|
|
260
331
|
/**
|
|
261
|
-
*
|
|
332
|
+
* 列出文件所有的扩展属性和内容
|
|
262
333
|
*/
|
|
263
|
-
|
|
334
|
+
listXAttrs(): Promise<Record<string, string>>;
|
|
264
335
|
/**
|
|
265
336
|
* 将其他文件的指定区间在服务器端克隆到当前文件
|
|
266
337
|
* 当前文件必须以写入/更新模式打开
|
|
@@ -408,25 +479,56 @@ declare class FileSystem {
|
|
|
408
479
|
* @param targetPath 目标路径
|
|
409
480
|
* @param attrs 扩展属性键值对记录
|
|
410
481
|
*/
|
|
411
|
-
|
|
482
|
+
setXAttrs(targetPath: string, attrs: Record<string, string>): Promise<XAttrStatusResult[]>;
|
|
483
|
+
setXAttr(targetPath: string, key: string, value: string): Promise<boolean>;
|
|
412
484
|
/**
|
|
413
485
|
* 获取扩展属性
|
|
414
486
|
* @param targetPath 目标路径
|
|
415
487
|
* @param keys 需要获取的属性名数组
|
|
416
488
|
*/
|
|
417
|
-
|
|
489
|
+
getXAttrs(targetPath: string, keys: string[]): Promise<Record<string, string>>;
|
|
490
|
+
getXAttr(targetPath: string, key: string): Promise<string>;
|
|
418
491
|
/**
|
|
419
492
|
* 删除指定的扩展属性
|
|
420
493
|
* @param targetPath 目标路径
|
|
421
494
|
* @param keys 需要删除的属性名数组
|
|
422
495
|
*/
|
|
423
|
-
|
|
496
|
+
delXAttrs(targetPath: string, keys: string[]): Promise<XAttrStatusResult[]>;
|
|
497
|
+
delXAttr(targetPath: string, key: string): Promise<boolean>;
|
|
424
498
|
/**
|
|
425
499
|
* 列出目标文件或目录的所有扩展属性
|
|
426
500
|
*/
|
|
427
501
|
listXAttr(targetPath: string): Promise<Record<string, string>>;
|
|
428
502
|
}
|
|
429
503
|
//#endregion
|
|
504
|
+
//#region lib/copy.d.ts
|
|
505
|
+
declare class CopyProcess {
|
|
506
|
+
private nativeCp;
|
|
507
|
+
constructor();
|
|
508
|
+
/**
|
|
509
|
+
* Add a job to the copy process synchronously
|
|
510
|
+
*/
|
|
511
|
+
addJob(config: CopyJobConfig): void;
|
|
512
|
+
/**
|
|
513
|
+
* Asynchronously prepare the jobs (resolve DNS, check endpoints, etc.)
|
|
514
|
+
*/
|
|
515
|
+
prepare(): Promise<void>;
|
|
516
|
+
/**
|
|
517
|
+
* Run the copy process.
|
|
518
|
+
* @param onProgress Optional progress callback
|
|
519
|
+
* @returns Array of results corresponding to the jobs added
|
|
520
|
+
*/
|
|
521
|
+
run(onProgress?: ProgressCallback): Promise<CopyJobResult[]>;
|
|
522
|
+
/**
|
|
523
|
+
* Abort the running copy process.
|
|
524
|
+
*/
|
|
525
|
+
abort(): void;
|
|
526
|
+
/**
|
|
527
|
+
* Abort the running copy process. (alias for abort)
|
|
528
|
+
*/
|
|
529
|
+
cancelJob(): void;
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
430
532
|
//#region lib/url.d.ts
|
|
431
533
|
/**
|
|
432
534
|
* 纯 TypeScript 实现的 XRootD URL 解析器
|
|
@@ -758,4 +860,4 @@ declare class XRootDEnvironment {
|
|
|
758
860
|
}
|
|
759
861
|
declare const Env: XRootDEnvironment;
|
|
760
862
|
//#endregion
|
|
761
|
-
export { AccessMode, Env, File$1 as File, FileSystem, type LocationInfo, MkDirFlags, OpenFlags, type PropertyList, type ReadChunkRequest, type ReadStreamOptions, type StatInfo, type StatVFSInfo, XRootDUrl as URL, type WriteStreamOptions, type XRootDError };
|
|
863
|
+
export { AccessMode, CopyProcess, Env, File$1 as File, FileSystem, type LocationInfo, MkDirFlags, OpenFlags, type PropertyList, type ReadChunkRequest, type ReadStreamOptions, type StatInfo, type StatVFSInfo, XRootDUrl as URL, type WriteStreamOptions, type XRootDError };
|
package/dist/index.d.mts
CHANGED
|
@@ -8,6 +8,18 @@ interface IXRootDError extends Error {
|
|
|
8
8
|
xrdErrNo: number;
|
|
9
9
|
xrdErrMsg: string;
|
|
10
10
|
}
|
|
11
|
+
type XRootDOkError = {
|
|
12
|
+
ok: true;
|
|
13
|
+
} | ({
|
|
14
|
+
ok: false;
|
|
15
|
+
} & IXRootDError);
|
|
16
|
+
type XAttrStatusResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
name: string;
|
|
19
|
+
} | ({
|
|
20
|
+
ok: false;
|
|
21
|
+
name: string;
|
|
22
|
+
} & IXRootDError);
|
|
11
23
|
interface StatInfo {
|
|
12
24
|
id: string;
|
|
13
25
|
size: bigint;
|
|
@@ -56,16 +68,10 @@ interface DirListEntry {
|
|
|
56
68
|
hostAddress: string;
|
|
57
69
|
stat: StatInfo | null;
|
|
58
70
|
}
|
|
59
|
-
interface XAttrStatusResult {
|
|
60
|
-
name: string;
|
|
61
|
-
isOk: boolean;
|
|
62
|
-
code: number;
|
|
63
|
-
message: string;
|
|
64
|
-
}
|
|
65
71
|
declare class XRootDError extends Error {
|
|
66
72
|
code: number;
|
|
67
73
|
status: number;
|
|
68
|
-
constructor(status:
|
|
74
|
+
constructor(status: XRootDOkError);
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* 对应 src/core/XrdNodeFile.cc 中 Init 暴露的类
|
|
@@ -93,6 +99,65 @@ interface INativeFile {
|
|
|
93
99
|
ListXAttr(): Promise<Record<string, string>>;
|
|
94
100
|
Clone(list: CloneLocationRequest[]): Promise<void>;
|
|
95
101
|
}
|
|
102
|
+
interface CopyJobConfig {
|
|
103
|
+
/** original source URL */
|
|
104
|
+
source: string;
|
|
105
|
+
/** target directory or file */
|
|
106
|
+
target: string;
|
|
107
|
+
/** maximum number sources */
|
|
108
|
+
sourceLimit?: number;
|
|
109
|
+
/** overwrite target if exists */
|
|
110
|
+
force?: boolean;
|
|
111
|
+
/** persistify only on successful close */
|
|
112
|
+
posc?: boolean;
|
|
113
|
+
/** ignore locking semantics on destination */
|
|
114
|
+
coerce?: boolean;
|
|
115
|
+
/** create path to the file if it doesn't exist */
|
|
116
|
+
makeDir?: boolean;
|
|
117
|
+
/** "first" try third party copy, if it fails try normal copy; "only" only try third party copy */
|
|
118
|
+
thirdParty?: string;
|
|
119
|
+
/** "none" - no checksumming
|
|
120
|
+
"end2end" - end to end checksumming
|
|
121
|
+
"source" - calculate checksum at source
|
|
122
|
+
"target" - calculate checksum at target */
|
|
123
|
+
checkSumMode?: "none" | "end2end" | "source" | "target";
|
|
124
|
+
/** type of the checksum to be used */
|
|
125
|
+
checkSumType?: string;
|
|
126
|
+
/** checksum preset */
|
|
127
|
+
checkSumPreset?: string;
|
|
128
|
+
/** size of a copy chunks in bytes */
|
|
129
|
+
chunkSize?: number;
|
|
130
|
+
/** number of chunks that should be requested in parallel
|
|
131
|
+
*
|
|
132
|
+
* [uint8_t]
|
|
133
|
+
*/
|
|
134
|
+
parallelChunks?: number;
|
|
135
|
+
/** time limit for successfull initialization of the copy job
|
|
136
|
+
* [time_t]
|
|
137
|
+
*/
|
|
138
|
+
initTimeout?: number;
|
|
139
|
+
/** time limit for the actual copy to finish
|
|
140
|
+
* [time_t]
|
|
141
|
+
*/
|
|
142
|
+
tpcTimeout?: number;
|
|
143
|
+
/** support for the case where the size source file may change during reading process */
|
|
144
|
+
dynamicSource?: boolean;
|
|
145
|
+
}
|
|
146
|
+
interface CopyJobResult {
|
|
147
|
+
/** checksum at source, if requested */
|
|
148
|
+
sourceCheckSum?: string;
|
|
149
|
+
/** checksum at target, if requested */
|
|
150
|
+
targetCheckSum?: string;
|
|
151
|
+
/** file size */
|
|
152
|
+
size: bigint;
|
|
153
|
+
/** status of the copy operation */
|
|
154
|
+
status?: XRootDOkError;
|
|
155
|
+
/** all sources used */
|
|
156
|
+
sources?: string[];
|
|
157
|
+
/** the actual disk server target */
|
|
158
|
+
realTarget?: string;
|
|
159
|
+
}
|
|
160
|
+
type ProgressCallback = (jobNum: number, processed: number, total: number) => void;
|
|
96
161
|
interface ReadStreamOptions {
|
|
97
162
|
start?: bigint;
|
|
98
163
|
end?: bigint;
|
|
@@ -232,7 +297,7 @@ declare class File$1 {
|
|
|
232
297
|
*/
|
|
233
298
|
isOpen(): boolean;
|
|
234
299
|
getProperty(name: string): Promise<string>;
|
|
235
|
-
setProperty(name: string, value: string): Promise<
|
|
300
|
+
setProperty(name: string, value: string): Promise<boolean>;
|
|
236
301
|
/**
|
|
237
302
|
* 向量化读取 (Vector Read)
|
|
238
303
|
* 在单个请求中从文件的多个非连续区域读取数据,极大地减少网络往返开销。
|
|
@@ -248,19 +313,25 @@ declare class File$1 {
|
|
|
248
313
|
/**
|
|
249
314
|
* 设置文件的扩展属性
|
|
250
315
|
*/
|
|
251
|
-
|
|
316
|
+
setXAttrs(attrs: Record<string, string>): Promise<XAttrStatusResult[]>;
|
|
317
|
+
/**
|
|
318
|
+
* 设置文件的扩展属性
|
|
319
|
+
*/
|
|
320
|
+
setXAttr(key: string, value: string): Promise<boolean>;
|
|
252
321
|
/**
|
|
253
322
|
* 获取文件的扩展属性
|
|
254
323
|
*/
|
|
255
|
-
|
|
324
|
+
getXAttrs(keys: string[]): Promise<Record<string, string>>;
|
|
325
|
+
getXAttr(key: string): Promise<string>;
|
|
256
326
|
/**
|
|
257
327
|
* 删除文件的扩展属性
|
|
258
328
|
*/
|
|
259
|
-
|
|
329
|
+
delXAttrs(keys: string[]): Promise<XAttrStatusResult[]>;
|
|
330
|
+
delXAttr(key: string): Promise<boolean>;
|
|
260
331
|
/**
|
|
261
|
-
*
|
|
332
|
+
* 列出文件所有的扩展属性和内容
|
|
262
333
|
*/
|
|
263
|
-
|
|
334
|
+
listXAttrs(): Promise<Record<string, string>>;
|
|
264
335
|
/**
|
|
265
336
|
* 将其他文件的指定区间在服务器端克隆到当前文件
|
|
266
337
|
* 当前文件必须以写入/更新模式打开
|
|
@@ -408,25 +479,56 @@ declare class FileSystem {
|
|
|
408
479
|
* @param targetPath 目标路径
|
|
409
480
|
* @param attrs 扩展属性键值对记录
|
|
410
481
|
*/
|
|
411
|
-
|
|
482
|
+
setXAttrs(targetPath: string, attrs: Record<string, string>): Promise<XAttrStatusResult[]>;
|
|
483
|
+
setXAttr(targetPath: string, key: string, value: string): Promise<boolean>;
|
|
412
484
|
/**
|
|
413
485
|
* 获取扩展属性
|
|
414
486
|
* @param targetPath 目标路径
|
|
415
487
|
* @param keys 需要获取的属性名数组
|
|
416
488
|
*/
|
|
417
|
-
|
|
489
|
+
getXAttrs(targetPath: string, keys: string[]): Promise<Record<string, string>>;
|
|
490
|
+
getXAttr(targetPath: string, key: string): Promise<string>;
|
|
418
491
|
/**
|
|
419
492
|
* 删除指定的扩展属性
|
|
420
493
|
* @param targetPath 目标路径
|
|
421
494
|
* @param keys 需要删除的属性名数组
|
|
422
495
|
*/
|
|
423
|
-
|
|
496
|
+
delXAttrs(targetPath: string, keys: string[]): Promise<XAttrStatusResult[]>;
|
|
497
|
+
delXAttr(targetPath: string, key: string): Promise<boolean>;
|
|
424
498
|
/**
|
|
425
499
|
* 列出目标文件或目录的所有扩展属性
|
|
426
500
|
*/
|
|
427
501
|
listXAttr(targetPath: string): Promise<Record<string, string>>;
|
|
428
502
|
}
|
|
429
503
|
//#endregion
|
|
504
|
+
//#region lib/copy.d.ts
|
|
505
|
+
declare class CopyProcess {
|
|
506
|
+
private nativeCp;
|
|
507
|
+
constructor();
|
|
508
|
+
/**
|
|
509
|
+
* Add a job to the copy process synchronously
|
|
510
|
+
*/
|
|
511
|
+
addJob(config: CopyJobConfig): void;
|
|
512
|
+
/**
|
|
513
|
+
* Asynchronously prepare the jobs (resolve DNS, check endpoints, etc.)
|
|
514
|
+
*/
|
|
515
|
+
prepare(): Promise<void>;
|
|
516
|
+
/**
|
|
517
|
+
* Run the copy process.
|
|
518
|
+
* @param onProgress Optional progress callback
|
|
519
|
+
* @returns Array of results corresponding to the jobs added
|
|
520
|
+
*/
|
|
521
|
+
run(onProgress?: ProgressCallback): Promise<CopyJobResult[]>;
|
|
522
|
+
/**
|
|
523
|
+
* Abort the running copy process.
|
|
524
|
+
*/
|
|
525
|
+
abort(): void;
|
|
526
|
+
/**
|
|
527
|
+
* Abort the running copy process. (alias for abort)
|
|
528
|
+
*/
|
|
529
|
+
cancelJob(): void;
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
430
532
|
//#region lib/url.d.ts
|
|
431
533
|
/**
|
|
432
534
|
* 纯 TypeScript 实现的 XRootD URL 解析器
|
|
@@ -758,4 +860,4 @@ declare class XRootDEnvironment {
|
|
|
758
860
|
}
|
|
759
861
|
declare const Env: XRootDEnvironment;
|
|
760
862
|
//#endregion
|
|
761
|
-
export { AccessMode, Env, File$1 as File, FileSystem, type LocationInfo, MkDirFlags, OpenFlags, type PropertyList, type ReadChunkRequest, type ReadStreamOptions, type StatInfo, type StatVFSInfo, XRootDUrl as URL, type WriteStreamOptions, type XRootDError };
|
|
863
|
+
export { AccessMode, CopyProcess, Env, File$1 as File, FileSystem, type LocationInfo, MkDirFlags, OpenFlags, type PropertyList, type ReadChunkRequest, type ReadStreamOptions, type StatInfo, type StatVFSInfo, XRootDUrl as URL, type WriteStreamOptions, type XRootDError };
|
package/dist/index.mjs
CHANGED
|
@@ -154,7 +154,9 @@ var File = class {
|
|
|
154
154
|
return this._internal.IsOpen();
|
|
155
155
|
}
|
|
156
156
|
async getProperty(name) {
|
|
157
|
-
|
|
157
|
+
const ret = this._internal.GetProperty(name);
|
|
158
|
+
if (ret.success) return ret.value;
|
|
159
|
+
throw new Error("TODO");
|
|
158
160
|
}
|
|
159
161
|
async setProperty(name, value) {
|
|
160
162
|
return this._internal.SetProperty(name, value);
|
|
@@ -186,25 +188,37 @@ var File = class {
|
|
|
186
188
|
/**
|
|
187
189
|
* 设置文件的扩展属性
|
|
188
190
|
*/
|
|
189
|
-
async
|
|
190
|
-
return this._internal.SetXAttr(
|
|
191
|
+
async setXAttrs(attrs) {
|
|
192
|
+
return this._internal.SetXAttr(attrs);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 设置文件的扩展属性
|
|
196
|
+
*/
|
|
197
|
+
async setXAttr(key, value) {
|
|
198
|
+
return (await this._internal.SetXAttr({ [key]: value }))[0].ok;
|
|
191
199
|
}
|
|
192
200
|
/**
|
|
193
201
|
* 获取文件的扩展属性
|
|
194
202
|
*/
|
|
195
|
-
async
|
|
196
|
-
return this._internal.GetXAttr(
|
|
203
|
+
async getXAttrs(keys) {
|
|
204
|
+
return this._internal.GetXAttr(keys);
|
|
205
|
+
}
|
|
206
|
+
async getXAttr(key) {
|
|
207
|
+
return (await this._internal.GetXAttr([key]))[key];
|
|
197
208
|
}
|
|
198
209
|
/**
|
|
199
210
|
* 删除文件的扩展属性
|
|
200
211
|
*/
|
|
201
|
-
async
|
|
202
|
-
return this._internal.DelXAttr(
|
|
212
|
+
async delXAttrs(keys) {
|
|
213
|
+
return this._internal.DelXAttr(keys);
|
|
214
|
+
}
|
|
215
|
+
async delXAttr(key) {
|
|
216
|
+
return (await this._internal.DelXAttr([key]))[0].ok;
|
|
203
217
|
}
|
|
204
218
|
/**
|
|
205
|
-
*
|
|
219
|
+
* 列出文件所有的扩展属性和内容
|
|
206
220
|
*/
|
|
207
|
-
async
|
|
221
|
+
async listXAttrs() {
|
|
208
222
|
return this._internal.ListXAttr();
|
|
209
223
|
}
|
|
210
224
|
/**
|
|
@@ -510,25 +524,34 @@ var FileSystem = class {
|
|
|
510
524
|
* @param targetPath 目标路径
|
|
511
525
|
* @param attrs 扩展属性键值对记录
|
|
512
526
|
*/
|
|
513
|
-
async
|
|
527
|
+
async setXAttrs(targetPath, attrs) {
|
|
514
528
|
return this._internal.SetXAttr(this._normalize(targetPath), attrs);
|
|
515
529
|
}
|
|
530
|
+
async setXAttr(targetPath, key, value) {
|
|
531
|
+
return (await this.setXAttrs(targetPath, { [key]: value }))[0].ok;
|
|
532
|
+
}
|
|
516
533
|
/**
|
|
517
534
|
* 获取扩展属性
|
|
518
535
|
* @param targetPath 目标路径
|
|
519
536
|
* @param keys 需要获取的属性名数组
|
|
520
537
|
*/
|
|
521
|
-
async
|
|
538
|
+
async getXAttrs(targetPath, keys) {
|
|
522
539
|
return this._internal.GetXAttr(this._normalize(targetPath), keys);
|
|
523
540
|
}
|
|
541
|
+
async getXAttr(targetPath, key) {
|
|
542
|
+
return (await this.getXAttrs(targetPath, [key]))[key];
|
|
543
|
+
}
|
|
524
544
|
/**
|
|
525
545
|
* 删除指定的扩展属性
|
|
526
546
|
* @param targetPath 目标路径
|
|
527
547
|
* @param keys 需要删除的属性名数组
|
|
528
548
|
*/
|
|
529
|
-
async
|
|
549
|
+
async delXAttrs(targetPath, keys) {
|
|
530
550
|
return this._internal.DelXAttr(this._normalize(targetPath), keys);
|
|
531
551
|
}
|
|
552
|
+
async delXAttr(targetPath, key) {
|
|
553
|
+
return (await this.delXAttrs(targetPath, [key]))[0].ok;
|
|
554
|
+
}
|
|
532
555
|
/**
|
|
533
556
|
* 列出目标文件或目录的所有扩展属性
|
|
534
557
|
*/
|
|
@@ -537,6 +560,47 @@ var FileSystem = class {
|
|
|
537
560
|
}
|
|
538
561
|
};
|
|
539
562
|
//#endregion
|
|
563
|
+
//#region lib/copy.ts
|
|
564
|
+
var CopyProcess = class {
|
|
565
|
+
nativeCp;
|
|
566
|
+
constructor() {
|
|
567
|
+
this.nativeCp = new nativeAddon.CopyProcess();
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Add a job to the copy process synchronously
|
|
571
|
+
*/
|
|
572
|
+
addJob(config) {
|
|
573
|
+
this.nativeCp.AddJob(config);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Asynchronously prepare the jobs (resolve DNS, check endpoints, etc.)
|
|
577
|
+
*/
|
|
578
|
+
async prepare() {
|
|
579
|
+
return this.nativeCp.Prepare();
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Run the copy process.
|
|
583
|
+
* @param onProgress Optional progress callback
|
|
584
|
+
* @returns Array of results corresponding to the jobs added
|
|
585
|
+
*/
|
|
586
|
+
async run(onProgress) {
|
|
587
|
+
if (onProgress) this.nativeCp.SetEventListener("progress", onProgress);
|
|
588
|
+
return this.nativeCp.Run();
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Abort the running copy process.
|
|
592
|
+
*/
|
|
593
|
+
abort() {
|
|
594
|
+
this.nativeCp.CancelJob();
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Abort the running copy process. (alias for abort)
|
|
598
|
+
*/
|
|
599
|
+
cancelJob() {
|
|
600
|
+
this.nativeCp.CancelJob();
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
//#endregion
|
|
540
604
|
//#region lib/url.ts
|
|
541
605
|
/**
|
|
542
606
|
* 纯 TypeScript 实现的 XRootD URL 解析器
|
|
@@ -721,4 +785,4 @@ Env.configure({
|
|
|
721
785
|
WorkerThreads: 4
|
|
722
786
|
});
|
|
723
787
|
//#endregion
|
|
724
|
-
export { AccessMode, Env, File, FileSystem, MkDirFlags, OpenFlags, XRootDUrl as URL };
|
|
788
|
+
export { AccessMode, CopyProcess, Env, File, FileSystem, MkDirFlags, OpenFlags, XRootDUrl as URL };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xrootd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Prebuilt Node.js Native Addon for XRootD, allowing JS access to EOS file systems",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"build": "pnpm run build:addon && pnpm run build:binaries && pnpm run build:tsc && pnpm run build:ts",
|
|
78
78
|
"build:clean": "rm -r ./build && pnpm run build",
|
|
79
79
|
"pack": "mkdir -p packages && pnpm pack --pack-destination ./packages",
|
|
80
|
-
"test:prep": "unrun ./
|
|
81
|
-
"test": "node --test
|
|
80
|
+
"test:prep": "unrun ./tests/prepare.ts",
|
|
81
|
+
"test": "node --test tests/*.mjs"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|