@archildata/client 0.1.12 → 0.8.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/README.md CHANGED
@@ -1,168 +1,310 @@
1
1
  # @archildata/client
2
2
 
3
- High-performance Node.js bindings for the Archil distributed filesystem client.
3
+ Node.js client for [Archil](https://archil.com) — a high-performance distributed filesystem that turns your cloud storage (S3, GCS, Azure Blob, R2) into something you can read, write, and run commands against like a local disk.
4
4
 
5
- ## Overview
5
+ ## Quick Start: Connect an S3 Bucket
6
6
 
7
- This package provides low-level N-API bindings to the Archil client library, exposing the `ArchilService` trait methods directly to JavaScript/TypeScript. It's designed for use cases where you need direct protocol access without FUSE overhead.
7
+ This walks you through creating an Archil disk backed by your S3 bucket and running bash commands on it. The whole thing takes about two minutes.
8
8
 
9
- ## Installation
9
+ ### 1. Sign up
10
+
11
+ Go to [console.archil.com](https://console.archil.com) and create an account.
12
+
13
+ ### 2. Install
10
14
 
11
15
  ```bash
12
16
  npm install @archildata/client
13
17
  ```
14
18
 
15
- ## Supported Platforms
19
+ ### 3. Create a disk
20
+
21
+ An Archil "disk" is a filesystem that sits in front of your cloud storage. You create one by telling Archil which bucket to use. This doesn't move or copy your data — Archil reads and writes directly to your bucket.
22
+
23
+ You also need to authorize a mount token on the disk. This is a separate credential from your API key — it's what clients use to connect to the disk's data plane. Pick any string as the principal (it's just an identifier).
24
+
25
+ ```typescript
26
+ import { Archil } from '@archildata/client/api';
27
+
28
+ const archil = new Archil({
29
+ apiKey: 'your-api-key',
30
+ region: 'aws-us-east-1',
31
+ });
32
+
33
+ const MOUNT_TOKEN = 'my-secret-mount-token';
34
+
35
+ const disk = await archil.disks.create({
36
+ name: 'my-disk',
37
+ mounts: [{
38
+ type: 's3',
39
+ bucketName: 'my-bucket',
40
+ accessKeyId: 'AKIA...',
41
+ secretAccessKey: '...',
42
+ }],
43
+ authMethods: [{
44
+ type: 'token',
45
+ principal: MOUNT_TOKEN,
46
+ nickname: 'my-mount-token',
47
+ tokenSuffix: MOUNT_TOKEN.slice(-4),
48
+ }],
49
+ });
50
+
51
+ console.log(`Created disk: ${disk.organization}/${disk.name}`);
52
+ ```
53
+
54
+ You only do this once. After that, the disk is available by name whenever you want to connect.
55
+
56
+ ### 4. Run bash commands on it
57
+
58
+ Install `@archildata/just-bash` to get a shell that runs against your disk:
59
+
60
+ ```bash
61
+ npm install @archildata/just-bash
62
+ ```
63
+
64
+ Then connect using the mount token you authorized above:
16
65
 
17
- This package includes pre-built binaries for:
66
+ ```bash
67
+ ARCHIL_TOKEN=my-secret-mount-token npx @archildata/just-bash aws-us-east-1 myaccount/my-disk
68
+ ```
18
69
 
19
- - **macOS** (Apple Silicon) - `darwin-arm64`
20
- - **Linux** (x86_64) - `linux-x64-gnu`
21
- - **Linux** (ARM64) - `linux-arm64-gnu`
70
+ This drops you into an interactive shell. The files you see are the contents of your S3 bucket:
22
71
 
23
- Other platforms are not currently supported.
72
+ ```
73
+ $ ls
74
+ data/ logs/ config.json
24
75
 
25
- > **Note:** For best performance, run your application in the same region as your Archil disk (e.g., if your disk is in `aws-us-east-1`, deploy your app to AWS us-east-1).
76
+ $ cat config.json
77
+ {"version": 2, "debug": false}
26
78
 
27
- ## Usage
79
+ $ echo "hello from archil" > greeting.txt
80
+ ```
81
+
82
+ Everything you do here — reads, writes, renames, deletes — goes through Archil to your bucket.
83
+
84
+ ## Using just-bash Programmatically
85
+
86
+ The interactive shell is great for poking around, but you can also run bash commands from your own code. This is useful for scripts, CI pipelines, or anywhere you want to run shell commands against your disk without mounting it.
28
87
 
29
88
  ```typescript
30
89
  import { ArchilClient } from '@archildata/client';
90
+ import { ArchilFs, createArchilCommand } from '@archildata/just-bash';
91
+ import { Bash } from 'just-bash';
31
92
 
32
- // Connect to Archil
93
+ // Connect to your disk
33
94
  const client = await ArchilClient.connect({
34
95
  region: 'aws-us-east-1',
35
- diskName: 'myaccount/mydisk',
36
- authToken: process.env.ARCHIL_TOKEN, // optional, uses IAM if not provided
96
+ diskName: 'myaccount/my-disk',
97
+ authToken: 'my-secret-mount-token',
37
98
  });
38
99
 
39
- // Get root directory attributes
40
- const rootAttrs = await client.getAttributes(1);
41
- console.log('Root size:', rootAttrs.size);
100
+ // Create a filesystem adapter and a bash executor
101
+ const fs = await ArchilFs.create(client);
102
+ const bash = new Bash({
103
+ fs,
104
+ customCommands: [createArchilCommand(client, fs)],
105
+ });
42
106
 
43
- // List directory contents
44
- const entries = await client.readDirectory(1);
45
- for (const entry of entries) {
46
- console.log(`${entry.name} (inode: ${entry.inodeId})`);
47
- }
107
+ // Run commands just like you would in a terminal
108
+ const result = await bash.exec('ls -la /');
109
+ console.log(result.stdout);
110
+
111
+ // Write a file
112
+ await bash.exec('echo "hello world" > /greeting.txt');
48
113
 
49
- // Read a file
50
- const lookup = await client.lookupInode(1, 'myfile.txt');
51
- const data = await client.readInode(lookup.inodeId, 0, 1024);
52
- console.log('File content:', data.toString());
114
+ // Read it back
115
+ const cat = await bash.exec('cat /greeting.txt');
116
+ console.log(cat.stdout); // "hello world"
53
117
 
54
- // Close when done
118
+ // Clean up
55
119
  await client.close();
56
120
  ```
57
121
 
58
- ## API
122
+ You can also use the filesystem adapter directly without bash, if you just need standard file operations:
123
+
124
+ ```typescript
125
+ const fs = await ArchilFs.create(client);
126
+
127
+ // These work like their Node.js fs equivalents
128
+ await fs.writeFile('/notes.txt', 'some content');
129
+ const content = await fs.readFile('/notes.txt');
130
+ const entries = await fs.readdir('/');
131
+ const stats = await fs.stat('/notes.txt');
132
+ await fs.mkdir('/mydir', { recursive: true });
133
+ await fs.cp('/notes.txt', '/mydir/notes-copy.txt');
134
+ await fs.rm('/notes.txt');
135
+ ```
136
+
137
+ ## Using the Native Client Directly
138
+
139
+ For more control, you can use the `ArchilClient` directly. This gives you access to the full filesystem protocol — inodes, delegations, paginated directory reads, etc.
140
+
141
+ ### Connecting
142
+
143
+ ```typescript
144
+ import { ArchilClient } from '@archildata/client';
145
+
146
+ const client = await ArchilClient.connect({
147
+ region: 'aws-us-east-1',
148
+ diskName: 'myaccount/my-disk',
149
+ authToken: 'my-secret-mount-token',
150
+ });
151
+ ```
152
+
153
+ ### Reading files
154
+
155
+ Every file and directory on an Archil disk has an inode ID. The root directory is always inode `1`. You navigate by looking up names inside directories, then reading the inodes you find.
156
+
157
+ ```typescript
158
+ // Look up a file by name in the root directory
159
+ const entry = await client.lookupInode(1, 'config.json');
160
+
161
+ // Read its contents
162
+ const data = await client.readInode(entry.inodeId, 0, entry.attributes.size);
163
+ console.log(data.toString());
164
+ ```
165
+
166
+ ### Listing directories
167
+
168
+ ```typescript
169
+ const handle = await client.openDirectory(1);
170
+ const page = await client.readDirectory(1, handle, 100);
171
+
172
+ for (const entry of page.entries) {
173
+ console.log(`${entry.name} (${entry.inodeType})`);
174
+ }
59
175
 
60
- ### `ArchilClient`
176
+ client.closeDirectory(1, handle);
177
+ ```
61
178
 
62
- The main client class for interacting with Archil filesystems.
179
+ For large directories, pass the returned `page.nextCursor` back into `readDirectory` to get the next page. When `nextCursor` is `undefined`, you've seen everything.
63
180
 
64
- #### Connection Methods
181
+ ### Writing files
65
182
 
66
- - `connect(config)` - Connect using region and disk name (recommended)
67
- - `connectDirect(config)` - Connect directly to a server (for testing)
183
+ Archil uses a delegation model for writes. Before you can write to a file, you "check out" a delegation on it — this tells the server you intend to modify it and gives you exclusive access. When you're done, you "check in" to release it so other clients can write.
68
184
 
69
- #### Metadata Operations
185
+ ```typescript
186
+ // Check out the file to acquire a write delegation
187
+ await client.checkout(inodeId);
70
188
 
71
- - `getAttributes(inodeId, options?)` - Get inode attributes
72
- - `lookupInode(parentInodeId, name, options?)` - Lookup entry by name
73
- - `readDirectory(inodeId, options?)` - List directory entries
74
- - `getExtendedAttribute(inodeId, name, options?)` - Get xattr value
75
- - `listExtendedAttributes(inodeId, options?)` - List xattr names
189
+ // Write data
190
+ await client.writeData(inodeId, 0, Buffer.from('new contents'));
76
191
 
77
- #### Data Operations
192
+ // Make sure the write is durable on the server
193
+ await client.sync();
78
194
 
79
- - `readInode(inodeId, offset, length, options?)` - Read file data
80
- - `writeData(inodeId, offset, data, options?)` - Write file data (requires delegation)
81
- - `sync()` - Sync all pending writes to server
195
+ // Release the delegation so other clients can write
196
+ await client.checkin(inodeId);
197
+ ```
82
198
 
83
- #### Delegation Operations
199
+ ### Creating files and directories
84
200
 
85
- - `checkout(inodeId, options?)` - Acquire write delegation
86
- - `checkin(inodeId, options?)` - Release delegation
87
- - `checkinAll()` - Release all delegations
88
- - `listDelegations()` - List currently held delegations
201
+ ```typescript
202
+ // Create a directory in root (inode 1)
203
+ const dir = await client.create(1, 'mydir', {
204
+ inodeType: 'Directory',
205
+ uid: 1000,
206
+ gid: 1000,
207
+ mode: 0o755,
208
+ });
89
209
 
90
- #### Mutation Operations
210
+ // Create a file inside it
211
+ const file = await client.create(dir.inodeId, 'data.txt', {
212
+ inodeType: 'File',
213
+ uid: 1000,
214
+ gid: 1000,
215
+ mode: 0o644,
216
+ });
91
217
 
92
- - `create(parentInodeId, name, attributes, options?)` - Create file/directory
93
- - `unlink(parentInodeId, name, options?)` - Delete file or empty directory
94
- - `rename(parentInodeId, name, newParentInodeId, newName, options?)` - Move/rename
95
- - `setattr(inodeId, attributes, options)` - Update file attributes (user required in options)
96
- - `setImmutable(inodeId)` - Mark subtree as immutable
97
- - `setMutable(inodeId)` - Mark subtree as mutable
98
- - `listImmutableSubtrees()` - List immutable roots
218
+ // Write to the new file
219
+ await client.checkout(file.inodeId);
220
+ await client.writeData(file.inodeId, 0, Buffer.from('file contents'));
221
+ await client.checkin(file.inodeId);
222
+ ```
99
223
 
100
- ### Types
224
+ ### Renaming and deleting
101
225
 
102
226
  ```typescript
103
- interface SimpleConnectionConfig {
104
- region: string; // e.g., "aws-us-east-1"
105
- diskName: string; // e.g., "myaccount/mydisk"
106
- authToken?: string; // optional, uses IAM if not provided
107
- logLevel?: string; // optional: "trace", "debug", "info", "warn", "error"
108
- }
227
+ await client.rename(parentInodeId, 'old.txt', parentInodeId, 'new.txt');
228
+ await client.unlink(parentInodeId, 'new.txt');
229
+ ```
109
230
 
110
- interface UnixUser {
111
- uid: number;
112
- gid: number;
113
- }
231
+ ### Cleaning up
114
232
 
115
- interface InodeAttributes {
116
- inodeId: number;
117
- inodeType: 'File' | 'Directory' | 'Symlink' | ...;
118
- size: number;
119
- uid: number;
120
- gid: number;
121
- mode: number;
122
- nlink: number;
123
- ctimeMs: number;
124
- atimeMs: number;
125
- mtimeMs: number;
126
- btimeMs: number;
127
- rdev?: number;
128
- symlinkTarget?: string;
129
- }
233
+ Always close the client when you're done. This flushes pending writes and releases any delegations you're still holding.
130
234
 
131
- interface DirectoryEntry {
132
- name: string;
133
- inodeId: number;
134
- inodeType: string;
135
- }
235
+ ```typescript
236
+ await client.close();
237
+ ```
136
238
 
137
- interface OperationOptions {
138
- user?: UnixUser; // Unix user context for permission checks
139
- }
239
+ ## Managing Disks
140
240
 
141
- interface CheckoutOptions {
142
- force?: boolean; // Force revoke existing delegations (default: false)
143
- user?: UnixUser; // Unix user context for permission checks
144
- }
241
+ The control plane API lets you manage disks and access control after initial setup.
145
242
 
146
- interface SetAttrAttributes {
147
- mode?: number;
148
- uid?: number;
149
- gid?: number;
150
- size?: number;
151
- atimeMs?: number; // use -1 for current time
152
- mtimeMs?: number; // use -1 for current time
153
- }
243
+ ```typescript
244
+ import { Archil } from '@archildata/client/api';
245
+
246
+ const archil = new Archil({
247
+ apiKey: 'your-api-key',
248
+ region: 'aws-us-east-1',
249
+ });
250
+
251
+ // List your disks
252
+ const disks = await archil.disks.list();
253
+
254
+ // Get a specific disk by ID
255
+ const disk = await archil.disks.get('dsk-xxx');
256
+
257
+ // Authorize another mount token on an existing disk
258
+ const CI_TOKEN = 'ci-mount-token';
259
+ await disk.addUser({
260
+ type: 'token',
261
+ principal: CI_TOKEN,
262
+ nickname: 'ci-token',
263
+ tokenSuffix: CI_TOKEN.slice(-4),
264
+ });
265
+
266
+ // Remove access
267
+ await disk.removeUser('token', CI_TOKEN);
268
+
269
+ // Delete a disk (this does not delete your bucket data)
270
+ await disk.delete();
154
271
  ```
155
272
 
156
- ## Building
273
+ ### Going from a disk to a client
157
274
 
158
- This package uses napi-rs for native bindings. To build from source:
275
+ If you have a `Disk` object from the API, you can connect directly without specifying the region and name again:
159
276
 
160
- ```bash
161
- cd rust-libs/archil-node
162
- npm install
163
- npm run build
277
+ ```typescript
278
+ const disk = await archil.disks.get('dsk-xxx');
279
+ const client = await disk.mount({ authToken: '<your-token>' });
280
+
281
+ // client is an ArchilClient — use it for reads, writes, etc.
282
+ await client.close();
164
283
  ```
165
284
 
166
- ## License
285
+ ## Supported Regions
286
+
287
+ | Region | Provider |
288
+ | ------------------ | -------- |
289
+ | `aws-us-east-1` | AWS |
290
+ | `aws-us-west-2` | AWS |
291
+ | `aws-eu-west-1` | AWS |
292
+ | `gcp-us-central1` | GCP |
293
+
294
+ ## Supported Storage Backends
295
+
296
+ - **Amazon S3** (`type: 's3'`)
297
+ - **Google Cloud Storage** (`type: 'gcs'`)
298
+ - **Azure Blob Storage** (`type: 'azure-blob'`)
299
+ - **Cloudflare R2** (`type: 'r2'`)
300
+ - **S3-compatible services** (`type: 's3-compatible'`)
301
+
302
+ ## Platform Support
303
+
304
+ The control plane API (`@archildata/client/api`) works on any platform — macOS, Windows, Linux.
305
+
306
+ The native filesystem client (connecting to disks, reading/writing files) supports **Linux** (x64 or arm64, glibc) and **macOS** (Apple Silicon / arm64). On other platforms, the API-only imports still work.
307
+
308
+ ## Support
167
309
 
168
- Proprietary - Archil Inc.
310
+ Questions, feature requests, or issues? Reach us at **support@archil.com**.
Binary file
package/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './native';
2
+ export * from './dist/api/index';
package/main.js ADDED
@@ -0,0 +1,15 @@
1
+ let native = {};
2
+ try {
3
+ native = require('./native.js');
4
+ } catch (e) {
5
+ // Only swallow "unsupported platform" errors so the API-only client
6
+ // still works on macOS/Windows/Alpine. Re-throw install failures on
7
+ // supported platforms — those need loud errors, not silent undefined.
8
+ if (e.message && !e.message.includes('does not support') &&
9
+ !e.message.includes('only supports') &&
10
+ !e.message.includes('cannot be loaded through a bundler')) {
11
+ throw e;
12
+ }
13
+ }
14
+ const api = require('./dist/api/index.js');
15
+ module.exports = { ...native, ...api };
package/main.mjs ADDED
@@ -0,0 +1,17 @@
1
+ export * from './dist/api/index.mjs';
2
+
3
+ import { createRequire } from 'node:module';
4
+ const esmRequire = createRequire(import.meta.url);
5
+ let native = {};
6
+ try {
7
+ native = esmRequire('./native.js');
8
+ } catch (e) {
9
+ if (e.message && !e.message.includes('does not support') &&
10
+ !e.message.includes('only supports') &&
11
+ !e.message.includes('cannot be loaded through a bundler')) {
12
+ throw e;
13
+ }
14
+ }
15
+ export const ArchilClient = native.ArchilClient;
16
+ export const initLogging = native.initLogging;
17
+ export const JsInodeType = native.JsInodeType;
@@ -73,13 +73,49 @@ export interface DirectoryEntry {
73
73
  /** Type of the entry (File, Directory, etc.) */
74
74
  inodeType: string
75
75
  }
76
+ /** Parameters for creating a new inode */
77
+ export interface CreateInodeParams {
78
+ /** Type of inode to create (File, Directory, Symlink, etc.) */
79
+ inodeType: string
80
+ /** Owner user ID */
81
+ uid: number
82
+ /** Owner group ID */
83
+ gid: number
84
+ /** Unix permission mode */
85
+ mode: number
86
+ /** Device ID for block/char devices */
87
+ rdev?: number
88
+ /** Symlink target if creating a symlink */
89
+ symlinkTarget?: string
90
+ }
91
+ /** Result of creating a new inode */
92
+ export interface CreateResult {
93
+ /** Inode ID of the created entry */
94
+ inodeId: number
95
+ /** Final attributes of the created inode (includes SGID inheritance, etc.) */
96
+ attributes: InodeAttributes
97
+ }
98
+ /** A page of directory entries for paginated reading */
99
+ export interface DirectoryPage {
100
+ /** Entries in this page */
101
+ entries: Array<DirectoryEntry>
102
+ /** Opaque cursor for the next page, or undefined if this is the last page */
103
+ nextCursor?: string
104
+ }
76
105
  /** Lookup response containing inode info */
77
106
  export interface LookupResponse {
78
- /** Inode ID of the found entry (-1 if not found) */
107
+ /** Inode ID of the found entry */
79
108
  inodeId: number
80
109
  /** Attributes of the found inode */
81
110
  attributes: InodeAttributes
82
111
  }
112
+ /** Result of opening a file */
113
+ export interface OpenFileResult {
114
+ /** File handle to use for subsequent operations */
115
+ fileHandle: number
116
+ /** Whether auto-checkout occurred (delegation will be checked in on release) */
117
+ needsCheckin: boolean
118
+ }
83
119
  /** Connection configuration for direct server connections */
84
120
  export interface DirectConnectionConfig {
85
121
  /** Server address (e.g., "localhost:8080") */
@@ -218,16 +254,14 @@ export declare class ArchilClient {
218
254
  close(): Promise<number>
219
255
  /** Get attributes for an inode (uses cache). */
220
256
  getAttributes(inodeId: number, options?: OperationOptions | undefined | null): Promise<InodeAttributes>
221
- /** Lookup an entry by name in a directory (uses cache). */
222
- lookupInode(parentInodeId: number, name: string, options?: OperationOptions | undefined | null): Promise<LookupResponse>
223
- /**
224
- * Read directory entries.
225
- *
226
- * Uses the cache layer via snapshot_and_synchronize_directory to ensure
227
- * the dirent cache is properly populated. This ensures that subsequent
228
- * lookup_inode calls can find the entries returned here.
229
- */
230
- readDirectory(inodeId: number, options?: OperationOptions | undefined | null): Promise<Array<DirectoryEntry>>
257
+ /** Lookup an entry by name in a directory (uses cache). Returns null if not found. */
258
+ lookupInode(parentInodeId: number, name: string, options?: OperationOptions | undefined | null): Promise<LookupResponse | null>
259
+ /** Open a directory for paginated reading. Returns a file handle. */
260
+ openDirectory(inodeId: number, options?: OperationOptions | undefined | null): Promise<number>
261
+ /** Read a page of directory entries using a handle from openDirectory. */
262
+ readDirectory(inodeId: number, fileHandle: number, limit: number, cursor?: string | undefined | null, options?: OperationOptions | undefined | null): Promise<DirectoryPage>
263
+ /** Close a directory handle opened with openDirectory. */
264
+ closeDirectory(inodeId: number, fileHandle: number): void
231
265
  /** Get an extended attribute value. */
232
266
  getExtendedAttribute(inodeId: number, name: string, options?: OperationOptions | undefined | null): Promise<Buffer | null>
233
267
  /** List all extended attribute names. */
@@ -336,10 +370,20 @@ export declare class ArchilClient {
336
370
  *
337
371
  * Automatically chooses between unconditional create (if we have a delegation
338
372
  * on the parent) and conditional create (if we need the server to issue a delegation).
373
+ */
374
+ create(parentInodeId: number, name: string, params: CreateInodeParams, options?: OperationOptions | undefined | null): Promise<CreateResult>
375
+ /**
376
+ * Open a file for reading or writing.
339
377
  *
340
- * Returns the inode ID of the created file.
378
+ * @param accessType - 0 for read, 1 for write, 2 for read-write
341
379
  */
342
- create(parentInodeId: number, name: string, attributes: InodeAttributes, options?: OperationOptions | undefined | null): Promise<number>
380
+ openFile(inodeId: number, accessType: number, options?: OperationOptions | undefined | null): Promise<OpenFileResult>
381
+ /** Release (close) a file handle. */
382
+ releaseFile(inodeId: number, fileHandle: number, options?: OperationOptions | undefined | null): Promise<void>
383
+ /** Read the target of a symlink. */
384
+ readlink(inodeId: number, options?: OperationOptions | undefined | null): Promise<string>
385
+ /** Check access permissions for an inode. */
386
+ checkAccess(inodeId: number, mask: number, options?: OperationOptions | undefined | null): Promise<void>
343
387
  /** Mark an inode subtree as immutable. */
344
388
  setImmutable(inodeId: number): Promise<void>
345
389
  /** Mark an inode subtree as mutable (reverse of set_immutable). */
package/native.js ADDED
@@ -0,0 +1,99 @@
1
+ /* eslint-disable */
2
+
3
+ const { existsSync } = require('fs')
4
+ const { join } = require('path')
5
+
6
+ const { platform, arch } = process
7
+
8
+ // Bundlers (tsup, webpack, esbuild, etc.) cannot handle native .node addons.
9
+ // They either silently drop the binary or replace require() with their own
10
+ // resolution, which breaks at runtime. Detect this early with a clear message.
11
+ if (typeof __dirname === 'undefined' || !existsSync(__dirname)) {
12
+ throw new Error(
13
+ '@archildata/client contains a native Node.js addon (.node binary) that cannot be loaded ' +
14
+ 'through a bundler (tsup, webpack, esbuild, etc.). Import @archildata/client from ' +
15
+ 'unbundled code only — for example, mark it as external in your bundler config.'
16
+ )
17
+ }
18
+
19
+ const SUPPORTED_PLATFORMS = {
20
+ 'linux-x64-gnu': {
21
+ localFile: 'archildata-client.linux-x64-gnu.node',
22
+ package: '@archildata/client-linux-x64-gnu',
23
+ },
24
+ 'linux-arm64-gnu': {
25
+ localFile: 'archildata-client.linux-arm64-gnu.node',
26
+ package: '@archildata/client-linux-arm64-gnu',
27
+ },
28
+ 'darwin-arm64': {
29
+ localFile: 'archildata-client.darwin-arm64.node',
30
+ package: '@archildata/client-darwin-arm64',
31
+ },
32
+ }
33
+
34
+ function isMusl() {
35
+ const report = process.report && process.report.getReport && process.report.getReport()
36
+ if (report) {
37
+ return !report.header.glibcVersionRuntime
38
+ }
39
+ try {
40
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
41
+ return require('fs').readFileSync(lddPath, 'utf8').includes('musl')
42
+ } catch {
43
+ return true
44
+ }
45
+ }
46
+
47
+ let platformKey
48
+ if (platform === 'darwin') {
49
+ platformKey = `darwin-${arch}`
50
+ } else if (platform === 'linux') {
51
+ if (isMusl()) {
52
+ throw new Error(
53
+ `@archildata/client does not support musl-based Linux distros (e.g. Alpine). ` +
54
+ `Current platform: ${platform}/${arch}/musl.`
55
+ )
56
+ }
57
+ platformKey = `linux-${arch}-gnu`
58
+ } else {
59
+ throw new Error(
60
+ `@archildata/client only supports Linux (x64, arm64) and macOS (arm64). ` +
61
+ `Current platform: ${platform}/${arch}.`
62
+ )
63
+ }
64
+
65
+ const target = SUPPORTED_PLATFORMS[platformKey]
66
+
67
+ if (!target) {
68
+ throw new Error(
69
+ `@archildata/client does not support ${platform}/${arch}. ` +
70
+ `Supported platforms: Linux x64, Linux arm64, macOS arm64.`
71
+ )
72
+ }
73
+
74
+ let nativeBinding
75
+
76
+ // Local .node file takes precedence (development builds)
77
+ const localPath = join(__dirname, target.localFile)
78
+ if (existsSync(localPath)) {
79
+ nativeBinding = require(localPath)
80
+ } else {
81
+ try {
82
+ nativeBinding = require(target.package)
83
+ } catch (e) {
84
+ throw new Error(
85
+ `Failed to load native binding for ${platformKey}. ` +
86
+ `Tried platform package '${target.package}' but it was not installed.\n\n` +
87
+ `If you're using npm, reinstall with: npm install @archildata/client\n` +
88
+ `If you're using a bundler (tsup, webpack, esbuild), mark @archildata/client ` +
89
+ `as external — native .node addons cannot be bundled.\n\n` +
90
+ `Original error: ${e.message}`
91
+ )
92
+ }
93
+ }
94
+
95
+ const { initLogging, ArchilClient, JsInodeType } = nativeBinding
96
+
97
+ module.exports.initLogging = initLogging
98
+ module.exports.ArchilClient = ArchilClient
99
+ module.exports.JsInodeType = JsInodeType
package/package.json CHANGED
@@ -1,24 +1,46 @@
1
1
  {
2
2
  "name": "@archildata/client",
3
- "version": "0.1.12",
3
+ "version": "0.8.1",
4
4
  "description": "High-performance Node.js client for Archil distributed filesystem",
5
- "main": "index.js",
6
- "types": "index.d.ts",
5
+ "main": "main.js",
6
+ "types": "main.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./main.d.ts",
10
+ "import": "./main.mjs",
11
+ "require": "./main.js"
12
+ },
13
+ "./api": {
14
+ "types": "./dist/api/index.d.ts",
15
+ "import": "./dist/api/index.mjs",
16
+ "require": "./dist/api/index.js"
17
+ },
18
+ "./native": {
19
+ "types": "./native.d.ts",
20
+ "require": "./native.js"
21
+ }
22
+ },
7
23
  "napi": {
8
24
  "name": "archildata-client",
9
25
  "triples": {
10
- "defaults": true,
26
+ "defaults": false,
11
27
  "additional": [
12
- "aarch64-apple-darwin",
13
28
  "aarch64-unknown-linux-gnu",
14
- "x86_64-apple-darwin",
15
- "x86_64-unknown-linux-gnu"
29
+ "x86_64-unknown-linux-gnu",
30
+ "aarch64-apple-darwin"
16
31
  ]
17
32
  }
18
33
  },
19
34
  "license": "UNLICENSED",
35
+ "dependencies": {
36
+ "openapi-fetch": "^0.13.5"
37
+ },
20
38
  "devDependencies": {
21
- "@napi-rs/cli": "^2.18.0"
39
+ "@napi-rs/cli": "^2.18.0",
40
+ "@types/node": "^20.19.33",
41
+ "openapi-typescript": "^7.6.1",
42
+ "tsup": "^8.4.0",
43
+ "typescript": "^5.9.3"
22
44
  },
23
45
  "engines": {
24
46
  "node": ">= 18"
@@ -27,29 +49,17 @@
27
49
  "artifacts": "napi artifacts",
28
50
  "build": "napi build --platform --release",
29
51
  "build:debug": "napi build --platform",
30
- "prepublishOnly": "napi prepublish -t npm",
31
- "test": "node --test test/*.js",
32
- "universal": "napi universal",
33
- "version": "napi version"
52
+ "build:api": "npm run generate && tsup src/api/index.ts --format cjs,esm --dts --outDir dist/api",
53
+ "generate": "openapi-typescript ../../api/controlplane/openapi.yaml -o src/api/generated/openapi.d.ts"
34
54
  },
35
55
  "files": [
36
- "index.js",
37
- "index.d.ts",
56
+ "native.js",
57
+ "native.d.ts",
58
+ "main.js",
59
+ "main.mjs",
60
+ "main.d.ts",
61
+ "dist/",
38
62
  "*.node",
39
63
  "README.md"
40
- ],
41
- "keywords": [
42
- "archil",
43
- "filesystem",
44
- "distributed",
45
- "napi",
46
- "native"
47
- ],
48
- "optionalDependencies": {
49
- "@archildata/client-win32-x64-msvc": "0.1.12",
50
- "@archildata/client-darwin-x64": "0.1.12",
51
- "@archildata/client-linux-x64-gnu": "0.1.12",
52
- "@archildata/client-darwin-arm64": "0.1.12",
53
- "@archildata/client-linux-arm64-gnu": "0.1.12"
54
- }
55
- }
64
+ ]
65
+ }
Binary file
package/index.js DELETED
@@ -1,317 +0,0 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
- /* prettier-ignore */
4
-
5
- /* auto-generated by NAPI-RS */
6
-
7
- const { existsSync, readFileSync } = require('fs')
8
- const { join } = require('path')
9
-
10
- const { platform, arch } = process
11
-
12
- let nativeBinding = null
13
- let localFileExisted = false
14
- let loadError = null
15
-
16
- function isMusl() {
17
- // For Node 10
18
- if (!process.report || typeof process.report.getReport !== 'function') {
19
- try {
20
- const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
- return readFileSync(lddPath, 'utf8').includes('musl')
22
- } catch (e) {
23
- return true
24
- }
25
- } else {
26
- const { glibcVersionRuntime } = process.report.getReport().header
27
- return !glibcVersionRuntime
28
- }
29
- }
30
-
31
- switch (platform) {
32
- case 'android':
33
- switch (arch) {
34
- case 'arm64':
35
- localFileExisted = existsSync(join(__dirname, 'archildata-client.android-arm64.node'))
36
- try {
37
- if (localFileExisted) {
38
- nativeBinding = require('./archildata-client.android-arm64.node')
39
- } else {
40
- nativeBinding = require('@archildata/client-android-arm64')
41
- }
42
- } catch (e) {
43
- loadError = e
44
- }
45
- break
46
- case 'arm':
47
- localFileExisted = existsSync(join(__dirname, 'archildata-client.android-arm-eabi.node'))
48
- try {
49
- if (localFileExisted) {
50
- nativeBinding = require('./archildata-client.android-arm-eabi.node')
51
- } else {
52
- nativeBinding = require('@archildata/client-android-arm-eabi')
53
- }
54
- } catch (e) {
55
- loadError = e
56
- }
57
- break
58
- default:
59
- throw new Error(`Unsupported architecture on Android ${arch}`)
60
- }
61
- break
62
- case 'win32':
63
- switch (arch) {
64
- case 'x64':
65
- localFileExisted = existsSync(
66
- join(__dirname, 'archildata-client.win32-x64-msvc.node')
67
- )
68
- try {
69
- if (localFileExisted) {
70
- nativeBinding = require('./archildata-client.win32-x64-msvc.node')
71
- } else {
72
- nativeBinding = require('@archildata/client-win32-x64-msvc')
73
- }
74
- } catch (e) {
75
- loadError = e
76
- }
77
- break
78
- case 'ia32':
79
- localFileExisted = existsSync(
80
- join(__dirname, 'archildata-client.win32-ia32-msvc.node')
81
- )
82
- try {
83
- if (localFileExisted) {
84
- nativeBinding = require('./archildata-client.win32-ia32-msvc.node')
85
- } else {
86
- nativeBinding = require('@archildata/client-win32-ia32-msvc')
87
- }
88
- } catch (e) {
89
- loadError = e
90
- }
91
- break
92
- case 'arm64':
93
- localFileExisted = existsSync(
94
- join(__dirname, 'archildata-client.win32-arm64-msvc.node')
95
- )
96
- try {
97
- if (localFileExisted) {
98
- nativeBinding = require('./archildata-client.win32-arm64-msvc.node')
99
- } else {
100
- nativeBinding = require('@archildata/client-win32-arm64-msvc')
101
- }
102
- } catch (e) {
103
- loadError = e
104
- }
105
- break
106
- default:
107
- throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
- }
109
- break
110
- case 'darwin':
111
- localFileExisted = existsSync(join(__dirname, 'archildata-client.darwin-universal.node'))
112
- try {
113
- if (localFileExisted) {
114
- nativeBinding = require('./archildata-client.darwin-universal.node')
115
- } else {
116
- nativeBinding = require('@archildata/client-darwin-universal')
117
- }
118
- break
119
- } catch {}
120
- switch (arch) {
121
- case 'x64':
122
- localFileExisted = existsSync(join(__dirname, 'archildata-client.darwin-x64.node'))
123
- try {
124
- if (localFileExisted) {
125
- nativeBinding = require('./archildata-client.darwin-x64.node')
126
- } else {
127
- nativeBinding = require('@archildata/client-darwin-x64')
128
- }
129
- } catch (e) {
130
- loadError = e
131
- }
132
- break
133
- case 'arm64':
134
- localFileExisted = existsSync(
135
- join(__dirname, 'archildata-client.darwin-arm64.node')
136
- )
137
- try {
138
- if (localFileExisted) {
139
- nativeBinding = require('./archildata-client.darwin-arm64.node')
140
- } else {
141
- nativeBinding = require('@archildata/client-darwin-arm64')
142
- }
143
- } catch (e) {
144
- loadError = e
145
- }
146
- break
147
- default:
148
- throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
- }
150
- break
151
- case 'freebsd':
152
- if (arch !== 'x64') {
153
- throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
- }
155
- localFileExisted = existsSync(join(__dirname, 'archildata-client.freebsd-x64.node'))
156
- try {
157
- if (localFileExisted) {
158
- nativeBinding = require('./archildata-client.freebsd-x64.node')
159
- } else {
160
- nativeBinding = require('@archildata/client-freebsd-x64')
161
- }
162
- } catch (e) {
163
- loadError = e
164
- }
165
- break
166
- case 'linux':
167
- switch (arch) {
168
- case 'x64':
169
- if (isMusl()) {
170
- localFileExisted = existsSync(
171
- join(__dirname, 'archildata-client.linux-x64-musl.node')
172
- )
173
- try {
174
- if (localFileExisted) {
175
- nativeBinding = require('./archildata-client.linux-x64-musl.node')
176
- } else {
177
- nativeBinding = require('@archildata/client-linux-x64-musl')
178
- }
179
- } catch (e) {
180
- loadError = e
181
- }
182
- } else {
183
- localFileExisted = existsSync(
184
- join(__dirname, 'archildata-client.linux-x64-gnu.node')
185
- )
186
- try {
187
- if (localFileExisted) {
188
- nativeBinding = require('./archildata-client.linux-x64-gnu.node')
189
- } else {
190
- nativeBinding = require('@archildata/client-linux-x64-gnu')
191
- }
192
- } catch (e) {
193
- loadError = e
194
- }
195
- }
196
- break
197
- case 'arm64':
198
- if (isMusl()) {
199
- localFileExisted = existsSync(
200
- join(__dirname, 'archildata-client.linux-arm64-musl.node')
201
- )
202
- try {
203
- if (localFileExisted) {
204
- nativeBinding = require('./archildata-client.linux-arm64-musl.node')
205
- } else {
206
- nativeBinding = require('@archildata/client-linux-arm64-musl')
207
- }
208
- } catch (e) {
209
- loadError = e
210
- }
211
- } else {
212
- localFileExisted = existsSync(
213
- join(__dirname, 'archildata-client.linux-arm64-gnu.node')
214
- )
215
- try {
216
- if (localFileExisted) {
217
- nativeBinding = require('./archildata-client.linux-arm64-gnu.node')
218
- } else {
219
- nativeBinding = require('@archildata/client-linux-arm64-gnu')
220
- }
221
- } catch (e) {
222
- loadError = e
223
- }
224
- }
225
- break
226
- case 'arm':
227
- if (isMusl()) {
228
- localFileExisted = existsSync(
229
- join(__dirname, 'archildata-client.linux-arm-musleabihf.node')
230
- )
231
- try {
232
- if (localFileExisted) {
233
- nativeBinding = require('./archildata-client.linux-arm-musleabihf.node')
234
- } else {
235
- nativeBinding = require('@archildata/client-linux-arm-musleabihf')
236
- }
237
- } catch (e) {
238
- loadError = e
239
- }
240
- } else {
241
- localFileExisted = existsSync(
242
- join(__dirname, 'archildata-client.linux-arm-gnueabihf.node')
243
- )
244
- try {
245
- if (localFileExisted) {
246
- nativeBinding = require('./archildata-client.linux-arm-gnueabihf.node')
247
- } else {
248
- nativeBinding = require('@archildata/client-linux-arm-gnueabihf')
249
- }
250
- } catch (e) {
251
- loadError = e
252
- }
253
- }
254
- break
255
- case 'riscv64':
256
- if (isMusl()) {
257
- localFileExisted = existsSync(
258
- join(__dirname, 'archildata-client.linux-riscv64-musl.node')
259
- )
260
- try {
261
- if (localFileExisted) {
262
- nativeBinding = require('./archildata-client.linux-riscv64-musl.node')
263
- } else {
264
- nativeBinding = require('@archildata/client-linux-riscv64-musl')
265
- }
266
- } catch (e) {
267
- loadError = e
268
- }
269
- } else {
270
- localFileExisted = existsSync(
271
- join(__dirname, 'archildata-client.linux-riscv64-gnu.node')
272
- )
273
- try {
274
- if (localFileExisted) {
275
- nativeBinding = require('./archildata-client.linux-riscv64-gnu.node')
276
- } else {
277
- nativeBinding = require('@archildata/client-linux-riscv64-gnu')
278
- }
279
- } catch (e) {
280
- loadError = e
281
- }
282
- }
283
- break
284
- case 's390x':
285
- localFileExisted = existsSync(
286
- join(__dirname, 'archildata-client.linux-s390x-gnu.node')
287
- )
288
- try {
289
- if (localFileExisted) {
290
- nativeBinding = require('./archildata-client.linux-s390x-gnu.node')
291
- } else {
292
- nativeBinding = require('@archildata/client-linux-s390x-gnu')
293
- }
294
- } catch (e) {
295
- loadError = e
296
- }
297
- break
298
- default:
299
- throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
- }
301
- break
302
- default:
303
- throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
- }
305
-
306
- if (!nativeBinding) {
307
- if (loadError) {
308
- throw loadError
309
- }
310
- throw new Error(`Failed to load native binding`)
311
- }
312
-
313
- const { initLogging, ArchilClient, JsInodeType } = nativeBinding
314
-
315
- module.exports.initLogging = initLogging
316
- module.exports.ArchilClient = ArchilClient
317
- module.exports.JsInodeType = JsInodeType