xrootd 0.1.11 → 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 CHANGED
@@ -1,23 +1,144 @@
1
- # XRootD Typescript Support
1
+ # 🚀 XRootD TypeScript Support (`xrootd`)
2
2
 
3
- > [!WARNING]
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
- - now complete: FileSystem/Url/Env
7
- - in progress: File/CopyProcess/ErrorProcess/And others
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
- minimium gcc version for linux: GLIBCXX_3.4.26 (GCC 9 or newer) <br> minimium node version: node20
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
- License
129
+ ## Licensing
130
+
131
+ This project is released under a **Dual License** strategy to balance open-source compatibility and developer freedom:
16
132
 
17
- This project follows the dual license: [GNU GPLv3](LICENSE-GPLv3) and [MIT](LICENSE-MIT).
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
- The XRootD project is GPL, so this project is also GPL. However, the API of this project is provided under MIT license, so you can use this project in your project without any restriction.
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
- This project is not affiliated with the XRootD project. It is a third-party project that provides a Node.js interface to the XRootD library.
144
+ *Contribution guidelines coming soon.*
package/dist/index.cjs CHANGED
@@ -144,13 +144,20 @@ var File = class {
144
144
  async read(offset, size) {
145
145
  return this._internal.Read(BigInt(offset), size);
146
146
  }
147
+ async write(offset, arg1, arg2, arg3) {
148
+ if (Buffer.isBuffer(arg1)) return this._internal.Write(BigInt(offset), arg1);
149
+ else if (typeof arg1 === "number" && typeof arg2 === "number") return this._internal.WriteFd(BigInt(offset), arg1, arg2, arg3 !== void 0 ? BigInt(arg3) : void 0);
150
+ else throw new TypeError("Invalid arguments for write");
151
+ }
147
152
  /**
148
- * 写入文件块
149
- * @param offset 偏移量
150
- * @param buffer 要写入的数据
153
+ * 从本地 fd 写入文件块 (直接映射)
154
+ * @param offset 写入的起始字节偏移量。
155
+ * @param size 写入大小。
156
+ * @param fd 本地文件描述符 (fd)。
157
+ * @param fdoff 可选,从本地 fd 中读取的起始偏移。
151
158
  */
152
- async write(offset, buffer) {
153
- return this._internal.Write(BigInt(offset), buffer);
159
+ async writeFd(offset, size, fd, fdoff) {
160
+ return this._internal.WriteFd(BigInt(offset), size, fd, fdoff !== void 0 ? BigInt(fdoff) : void 0);
154
161
  }
155
162
  /**
156
163
  * 同步文件缓冲区到磁盘
@@ -172,7 +179,9 @@ var File = class {
172
179
  return this._internal.IsOpen();
173
180
  }
174
181
  async getProperty(name) {
175
- return this._internal.GetProperty(name);
182
+ const ret = this._internal.GetProperty(name);
183
+ if (ret.success) return ret.value;
184
+ throw new Error("TODO");
176
185
  }
177
186
  async setProperty(name, value) {
178
187
  return this._internal.SetProperty(name, value);
@@ -204,25 +213,37 @@ var File = class {
204
213
  /**
205
214
  * 设置文件的扩展属性
206
215
  */
207
- async setXAttr(name, value) {
208
- return this._internal.SetXAttr(name);
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;
209
224
  }
210
225
  /**
211
226
  * 获取文件的扩展属性
212
227
  */
213
- async getXAttr(name) {
214
- return this._internal.GetXAttr(name);
228
+ async getXAttrs(keys) {
229
+ return this._internal.GetXAttr(keys);
230
+ }
231
+ async getXAttr(key) {
232
+ return (await this._internal.GetXAttr([key]))[key];
215
233
  }
216
234
  /**
217
235
  * 删除文件的扩展属性
218
236
  */
219
- async delXAttr(name) {
220
- return this._internal.DelXAttr(name);
237
+ async delXAttrs(keys) {
238
+ return this._internal.DelXAttr(keys);
239
+ }
240
+ async delXAttr(key) {
241
+ return (await this._internal.DelXAttr([key]))[0].ok;
221
242
  }
222
243
  /**
223
- * 列出文件所有的扩展属性名称
244
+ * 列出文件所有的扩展属性和内容
224
245
  */
225
- async listXAttr() {
246
+ async listXAttrs() {
226
247
  return this._internal.ListXAttr();
227
248
  }
228
249
  /**
@@ -528,25 +549,34 @@ var FileSystem = class {
528
549
  * @param targetPath 目标路径
529
550
  * @param attrs 扩展属性键值对记录
530
551
  */
531
- async setXAttr(targetPath, attrs) {
552
+ async setXAttrs(targetPath, attrs) {
532
553
  return this._internal.SetXAttr(this._normalize(targetPath), attrs);
533
554
  }
555
+ async setXAttr(targetPath, key, value) {
556
+ return (await this.setXAttrs(targetPath, { [key]: value }))[0].ok;
557
+ }
534
558
  /**
535
559
  * 获取扩展属性
536
560
  * @param targetPath 目标路径
537
561
  * @param keys 需要获取的属性名数组
538
562
  */
539
- async getXAttr(targetPath, keys) {
563
+ async getXAttrs(targetPath, keys) {
540
564
  return this._internal.GetXAttr(this._normalize(targetPath), keys);
541
565
  }
566
+ async getXAttr(targetPath, key) {
567
+ return (await this.getXAttrs(targetPath, [key]))[key];
568
+ }
542
569
  /**
543
570
  * 删除指定的扩展属性
544
571
  * @param targetPath 目标路径
545
572
  * @param keys 需要删除的属性名数组
546
573
  */
547
- async delXAttr(targetPath, keys) {
574
+ async delXAttrs(targetPath, keys) {
548
575
  return this._internal.DelXAttr(this._normalize(targetPath), keys);
549
576
  }
577
+ async delXAttr(targetPath, key) {
578
+ return (await this.delXAttrs(targetPath, [key]))[0].ok;
579
+ }
550
580
  /**
551
581
  * 列出目标文件或目录的所有扩展属性
552
582
  */
@@ -555,6 +585,47 @@ var FileSystem = class {
555
585
  }
556
586
  };
557
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
558
629
  //#region lib/url.ts
559
630
  /**
560
631
  * 纯 TypeScript 实现的 XRootD URL 解析器
@@ -656,14 +727,91 @@ var XRootDUrl = class XRootDUrl {
656
727
  };
657
728
  //#endregion
658
729
  //#region lib/env.ts
659
- const Env = {
660
- putInt: nativeAddon.Env.PutInt,
661
- putString: nativeAddon.Env.PutString,
662
- getInt: nativeAddon.Env.GetInt,
663
- getString: nativeAddon.Env.GetString
730
+ function checkIntRange(key, value) {
731
+ const num = Number(value);
732
+ if (num < -2147483648 || num > 2147483647) console.warn(`[xrootd] Warning: value ${value} for key "${key}" is out of 32-bit signed integer range [-2147483648, 2147483647]. It will be truncated.`);
733
+ }
734
+ var XRootDEnvironment = class {
735
+ constructor() {}
736
+ /**
737
+ * 设置整数配置项,返回是否设置成功。
738
+ */
739
+ putInt(key, intVal) {
740
+ checkIntRange(key, intVal);
741
+ const success = nativeAddon.Env.PutInt(key, Number(intVal));
742
+ if (!success) console.warn(`[xrootd] Warning: Failed to set integer configuration "${key}"=${intVal}. It might have been overridden by a system environment variable.`);
743
+ return success;
744
+ }
745
+ /**
746
+ * 设置字符串配置项,返回是否设置成功。
747
+ */
748
+ putString(key, strVal) {
749
+ if (key === "SecProtocol") {
750
+ process.env.XrdSecPROTOCOL = strVal;
751
+ process.env.XRD_SECPROTOCOL = strVal;
752
+ return true;
753
+ }
754
+ const success = nativeAddon.Env.PutString(key, strVal);
755
+ if (!success) console.warn(`[xrootd] Warning: Failed to set string configuration "${key}"="${strVal}". It might have been overridden by a system environment variable.`);
756
+ return success;
757
+ }
758
+ /**
759
+ * 设置布尔配置项,返回是否设置成功。
760
+ */
761
+ putBoolean(key, boolVal) {
762
+ if (typeof boolVal === "boolean") boolVal = boolVal ? "true" : "false";
763
+ return this.putString(key, boolVal);
764
+ }
765
+ /**
766
+ * 获取整数配置项。
767
+ */
768
+ getInt(key) {
769
+ return nativeAddon.Env.GetInt(key) ?? void 0;
770
+ }
771
+ /**
772
+ * 获取字符串配置项。
773
+ */
774
+ getString(key) {
775
+ if (key === "SecProtocol") return process.env.XrdSecPROTOCOL ?? process.env.XRD_SECPROTOCOL ?? void 0;
776
+ return nativeAddon.Env.GetString(key) ?? void 0;
777
+ }
778
+ /**
779
+ * 批量安全地设置 XRootD 底层参数
780
+ */
781
+ configure(config) {
782
+ for (const [key, value] of Object.entries(config)) {
783
+ if (value === void 0 || value === null) continue;
784
+ let success = true;
785
+ if (key === "SecProtocol") {
786
+ process.env.XrdSecPROTOCOL = String(value);
787
+ process.env.XRD_SECPROTOCOL = String(value);
788
+ continue;
789
+ }
790
+ if (typeof value === "number" || typeof value === "bigint") {
791
+ checkIntRange(key, value);
792
+ success = nativeAddon.Env.PutInt(key, Number(value));
793
+ } else if (typeof value === "string") success = nativeAddon.Env.PutString(key, value);
794
+ else if (typeof value === "boolean") success = nativeAddon.Env.PutString(key, value ? "true" : "false");
795
+ else {
796
+ console.warn(`[xrootd] Unhandled type ${typeof value} for key ${key}.`);
797
+ success = nativeAddon.Env.PutString(key, value.toString());
798
+ }
799
+ if (!success) console.warn(`[xrootd] Warning: Failed to set configuration "${key}"=${value}. It might have been overridden by a system environment variable.`);
800
+ }
801
+ }
802
+ get(key) {
803
+ if (key === "SecProtocol") return process.env.XrdSecPROTOCOL ?? process.env.XRD_SECPROTOCOL ?? void 0;
804
+ return nativeAddon.Env.GetInt(key) ?? nativeAddon.Env.GetString(key) ?? void 0;
805
+ }
664
806
  };
807
+ const Env = new XRootDEnvironment();
808
+ Env.configure({
809
+ RequestTimeout: 30,
810
+ WorkerThreads: 4
811
+ });
665
812
  //#endregion
666
813
  exports.AccessMode = AccessMode;
814
+ exports.CopyProcess = CopyProcess;
667
815
  exports.Env = Env;
668
816
  exports.File = File;
669
817
  exports.FileSystem = FileSystem;