@archildata/client 0.1.13 → 0.8.2

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