@rljson/fs-agent 0.0.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/LICENSE +21 -0
- package/README.architecture.md +449 -0
- package/README.blog.md +11 -0
- package/README.contributors.md +32 -0
- package/README.md +24 -0
- package/README.public.md +781 -0
- package/README.trouble.md +23 -0
- package/dist/README.architecture.md +449 -0
- package/dist/README.blog.md +11 -0
- package/dist/README.contributors.md +32 -0
- package/dist/README.md +24 -0
- package/dist/README.public.md +781 -0
- package/dist/README.trouble.md +23 -0
- package/dist/client-server/client-server-setup.d.ts +17 -0
- package/dist/client-server/live-client-server.d.ts +1 -0
- package/dist/example.d.ts +1 -0
- package/dist/fs-agent.d.ts +178 -0
- package/dist/fs-agent.js +4049 -0
- package/dist/fs-blob-adapter.d.ts +92 -0
- package/dist/fs-db-adapter.d.ts +34 -0
- package/dist/fs-scanner.d.ts +104 -0
- package/dist/index.d.ts +5 -0
- package/dist/src/example.ts +101 -0
- package/package.json +59 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Bs } from '@rljson/bs';
|
|
2
|
+
import { Json } from '@rljson/json';
|
|
3
|
+
/**
|
|
4
|
+
* File metadata returned when storing a file as a blob
|
|
5
|
+
*/
|
|
6
|
+
export interface FileBlobMeta extends Json {
|
|
7
|
+
/** Name of the file */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Blob ID where content is stored */
|
|
10
|
+
blobId: string;
|
|
11
|
+
/** File size in bytes */
|
|
12
|
+
size: number;
|
|
13
|
+
/** Last modified timestamp (milliseconds since epoch) */
|
|
14
|
+
mtime: number;
|
|
15
|
+
/** Original file path */
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Options for file-to-blob conversion
|
|
20
|
+
*/
|
|
21
|
+
export interface FileToBlobOptions {
|
|
22
|
+
/** Custom blob storage (defaults to BsMem) */
|
|
23
|
+
bs?: Bs;
|
|
24
|
+
/** Include full path in metadata (default: true) */
|
|
25
|
+
includePath?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Options for blob-to-file conversion
|
|
29
|
+
*/
|
|
30
|
+
export interface BlobToFileOptions {
|
|
31
|
+
/** Custom blob storage (defaults to BsMem) */
|
|
32
|
+
bs?: Bs;
|
|
33
|
+
/** Create parent directories if they don't exist (default: true) */
|
|
34
|
+
createDirs?: boolean;
|
|
35
|
+
/** Preserve modification time (default: true) */
|
|
36
|
+
preserveMtime?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Handles conversion between files and blobs in blob storage
|
|
40
|
+
*/
|
|
41
|
+
export declare class FsBlobAdapter {
|
|
42
|
+
private _bs;
|
|
43
|
+
constructor(bs?: Bs);
|
|
44
|
+
/**
|
|
45
|
+
* Gets the blob storage instance
|
|
46
|
+
*/
|
|
47
|
+
get bs(): Bs;
|
|
48
|
+
/**
|
|
49
|
+
* Converts a file to a blob and returns metadata
|
|
50
|
+
* @param filePath - Absolute path to the file
|
|
51
|
+
* @param options - Conversion options
|
|
52
|
+
* @returns File metadata including blob ID
|
|
53
|
+
*/
|
|
54
|
+
fileToBlob(filePath: string, options?: FileToBlobOptions): Promise<FileBlobMeta>;
|
|
55
|
+
/**
|
|
56
|
+
* Converts multiple files to blobs
|
|
57
|
+
* @param filePaths - Array of absolute file paths
|
|
58
|
+
* @param options - Conversion options
|
|
59
|
+
* @returns Array of file metadata
|
|
60
|
+
*/
|
|
61
|
+
filesToBlobs(filePaths: string[], options?: FileToBlobOptions): Promise<FileBlobMeta[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Writes a file from a blob using metadata
|
|
64
|
+
* @param metadata - File metadata including blob ID
|
|
65
|
+
* @param targetPath - Target path where file should be written
|
|
66
|
+
* @param options - Conversion options
|
|
67
|
+
*/
|
|
68
|
+
blobToFile(metadata: FileBlobMeta, targetPath: string, options?: BlobToFileOptions): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Writes multiple files from blobs
|
|
71
|
+
* @param metadataList - Array of file metadata
|
|
72
|
+
* @param targetDir - Target directory where files should be written
|
|
73
|
+
* @param options - Conversion options
|
|
74
|
+
*/
|
|
75
|
+
blobsToFiles(metadataList: FileBlobMeta[], targetDir: string, options?: BlobToFileOptions): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves file content from blob storage
|
|
78
|
+
* @param blobId - Blob ID
|
|
79
|
+
* @returns File content as Buffer
|
|
80
|
+
*/
|
|
81
|
+
getFileContent(blobId: string): Promise<Buffer>;
|
|
82
|
+
/**
|
|
83
|
+
* Checks if a blob exists in storage
|
|
84
|
+
* @param blobId - Blob ID to check
|
|
85
|
+
* @returns True if blob exists
|
|
86
|
+
*/
|
|
87
|
+
hasBlob(blobId: string): Promise<boolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Creates an example instance for testing
|
|
90
|
+
*/
|
|
91
|
+
static example(): FsBlobAdapter;
|
|
92
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Db } from '@rljson/db';
|
|
2
|
+
import { FsTree } from './fs-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for storing filesystem trees in database
|
|
5
|
+
*/
|
|
6
|
+
export interface StoreFsTreeOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Whether to trigger notifications after storing (defaults to false)
|
|
9
|
+
*/
|
|
10
|
+
notify?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Adapter for storing filesystem trees in a database
|
|
14
|
+
*/
|
|
15
|
+
export declare class FsDbAdapter {
|
|
16
|
+
private db;
|
|
17
|
+
private treeKey;
|
|
18
|
+
constructor(db: Db, treeKey: string);
|
|
19
|
+
/**
|
|
20
|
+
* Store a filesystem tree in the database
|
|
21
|
+
* @param fsTree - The filesystem tree to store
|
|
22
|
+
* @param options - Storage options
|
|
23
|
+
* @returns The root tree reference
|
|
24
|
+
*/
|
|
25
|
+
storeFsTree(fsTree: FsTree, options?: StoreFsTreeOptions): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Get the tree table key
|
|
28
|
+
*/
|
|
29
|
+
getTreeKey(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Get the database instance
|
|
32
|
+
*/
|
|
33
|
+
getDb(): Db;
|
|
34
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Bs } from '@rljson/bs';
|
|
2
|
+
import { Json } from '@rljson/json';
|
|
3
|
+
import { Tree, TreeRef } from '@rljson/rljson';
|
|
4
|
+
/**
|
|
5
|
+
* Metadata stored in Tree.meta for file system nodes
|
|
6
|
+
*/
|
|
7
|
+
export interface FsNodeMeta extends Json {
|
|
8
|
+
/** Node name (file or directory name) */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Type of node */
|
|
11
|
+
type: 'file' | 'directory';
|
|
12
|
+
/** Absolute path */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Relative path from scan root */
|
|
15
|
+
relativePath: string;
|
|
16
|
+
/** File size in bytes (for files) */
|
|
17
|
+
size?: number;
|
|
18
|
+
/** Last modified timestamp (milliseconds since epoch) */
|
|
19
|
+
mtime: number;
|
|
20
|
+
/** Blob ID for file content (files only) */
|
|
21
|
+
blobId?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Tree structure with hash mapping
|
|
25
|
+
*/
|
|
26
|
+
export interface FsTree {
|
|
27
|
+
/** Root tree hash */
|
|
28
|
+
rootHash: TreeRef;
|
|
29
|
+
/** Map of hash to tree node */
|
|
30
|
+
trees: Map<TreeRef, Tree>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Type of file system change
|
|
34
|
+
*/
|
|
35
|
+
export type FsChangeType = 'added' | 'modified' | 'deleted';
|
|
36
|
+
/**
|
|
37
|
+
* File system change event
|
|
38
|
+
*/
|
|
39
|
+
export interface FsChange {
|
|
40
|
+
/** Type of change */
|
|
41
|
+
type: FsChangeType;
|
|
42
|
+
/** Path that changed (relative to scan root) */
|
|
43
|
+
path: string;
|
|
44
|
+
/** Tree node (for added/modified) */
|
|
45
|
+
tree?: Tree;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Callback for file system changes
|
|
49
|
+
*/
|
|
50
|
+
export type FsChangeCallback = (change: FsChange) => void | Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Options for scanning
|
|
53
|
+
*/
|
|
54
|
+
export interface FsScanOptions {
|
|
55
|
+
/** Patterns to ignore (glob patterns) */
|
|
56
|
+
ignore?: string[];
|
|
57
|
+
/** Maximum depth to scan (undefined = unlimited) */
|
|
58
|
+
maxDepth?: number;
|
|
59
|
+
/** Follow symbolic links */
|
|
60
|
+
followSymlinks?: boolean;
|
|
61
|
+
/** Blob storage implementation (defaults to BsMem) */
|
|
62
|
+
bs?: Bs;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Scans and watches file system changes, extracting RLJSON tree structure
|
|
66
|
+
*/
|
|
67
|
+
export declare class FsScanner {
|
|
68
|
+
private _rootPath;
|
|
69
|
+
private _tree;
|
|
70
|
+
private _watcher;
|
|
71
|
+
private _changeCallbacks;
|
|
72
|
+
private _options;
|
|
73
|
+
private _bs;
|
|
74
|
+
private _paused;
|
|
75
|
+
constructor(rootPath: string, options?: FsScanOptions);
|
|
76
|
+
get tree(): FsTree | null;
|
|
77
|
+
get rootPath(): string;
|
|
78
|
+
get bs(): Bs;
|
|
79
|
+
scan(): Promise<FsTree>;
|
|
80
|
+
private _scanDirectory;
|
|
81
|
+
private _shouldIgnore;
|
|
82
|
+
watch(): Promise<void>;
|
|
83
|
+
private _handleFileChange;
|
|
84
|
+
private _findTreeByPath;
|
|
85
|
+
private _notifyChange;
|
|
86
|
+
onChange(callback: FsChangeCallback): void;
|
|
87
|
+
offChange(callback: FsChangeCallback): void;
|
|
88
|
+
stopWatch(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Temporarily pause file change notifications
|
|
91
|
+
* Used to prevent loops when updating filesystem from external source
|
|
92
|
+
*/
|
|
93
|
+
pauseWatch(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Resume file change notifications
|
|
96
|
+
*/
|
|
97
|
+
resumeWatch(): void;
|
|
98
|
+
getTreeByHash(treeHash: TreeRef): Tree | undefined;
|
|
99
|
+
getTreeByPath(relativePath: string): Tree | undefined;
|
|
100
|
+
getAllTrees(): Tree[];
|
|
101
|
+
getChildren(treeHash: TreeRef): Tree[];
|
|
102
|
+
getRootTree(): Tree | undefined;
|
|
103
|
+
static example(): FsScanner;
|
|
104
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { FsAgent, type FsAgentOptions } from './fs-agent.ts';
|
|
2
|
+
export { FsBlobAdapter, type BlobToFileOptions, type FileBlobMeta, type FileToBlobOptions, } from './fs-blob-adapter.ts';
|
|
3
|
+
export { FsDbAdapter, type StoreFsTreeOptions } from './fs-db-adapter.ts';
|
|
4
|
+
export { FsScanner, type FsChange, type FsChangeCallback, type FsChangeType, type FsNodeMeta, type FsScanOptions, type FsTree, } from './fs-scanner.ts';
|
|
5
|
+
export { runClientServerSetup, type ClientServerSetupOptions, type ClientServerSetupResult, } from './client-server/client-server-setup.ts';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @license
|
|
2
|
+
// Copyright (c) 2025 Rljson
|
|
3
|
+
//
|
|
4
|
+
// Use of this source code is governed by terms that can be
|
|
5
|
+
// found in the LICENSE file in the root of this package.
|
|
6
|
+
|
|
7
|
+
import { FsAgent } from './fs-agent.ts';
|
|
8
|
+
import { FsScanner } from './fs-scanner.ts';
|
|
9
|
+
|
|
10
|
+
export const example = async () => {
|
|
11
|
+
// Print methods
|
|
12
|
+
const l = console.log;
|
|
13
|
+
const h1 = (text: string) => l(`${text}`);
|
|
14
|
+
const h2 = (text: string) => l(` ${text}`);
|
|
15
|
+
const p = (text: string) => l(` ${text}`);
|
|
16
|
+
|
|
17
|
+
// Example 1: Basic FsAgent
|
|
18
|
+
h1('FsAgent.example');
|
|
19
|
+
h2('Returns an instance of the FsAgent.');
|
|
20
|
+
const fsAgent = FsAgent.example;
|
|
21
|
+
p(JSON.stringify(fsAgent, null, 2));
|
|
22
|
+
|
|
23
|
+
// Example 2: File System Scanner
|
|
24
|
+
h1('\nFsScanner - Scan file system');
|
|
25
|
+
h2('Scans a directory and extracts RLJSON tree structure');
|
|
26
|
+
|
|
27
|
+
const scanner = new FsScanner('./src', {
|
|
28
|
+
ignore: ['node_modules', '.git'],
|
|
29
|
+
maxDepth: 3,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const tree = await scanner.scan();
|
|
33
|
+
p(`Scanned ${tree.trees.size} tree nodes`);
|
|
34
|
+
p(`Root hash: ${tree.rootHash}`);
|
|
35
|
+
|
|
36
|
+
// Show root tree
|
|
37
|
+
h2('Root tree:');
|
|
38
|
+
const rootTree = scanner.getRootTree();
|
|
39
|
+
if (rootTree) {
|
|
40
|
+
p(`ID: ${rootTree.id}`);
|
|
41
|
+
p(`Is Parent: ${rootTree.isParent}`);
|
|
42
|
+
p(`Children: ${rootTree.children?.length || 0}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Show some trees
|
|
46
|
+
h2('Sample trees:');
|
|
47
|
+
let count = 0;
|
|
48
|
+
for (const [hash, treeNode] of tree.trees) {
|
|
49
|
+
if (count++ >= 5) break;
|
|
50
|
+
const meta = treeNode.meta as any;
|
|
51
|
+
p(
|
|
52
|
+
`${meta.type === 'directory' ? '📁' : '📄'} ${meta.name} [${hash.substring(0, 8)}...]`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Example 3: Watch for changes
|
|
57
|
+
h1('\nFsScanner - Watch for changes');
|
|
58
|
+
h2('Register a callback to be notified of file system changes');
|
|
59
|
+
|
|
60
|
+
scanner.onChange(async (change) => {
|
|
61
|
+
p(`${change.type.toUpperCase()}: ${change.path}`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
p('Scanner is ready. Changes will be logged.');
|
|
65
|
+
p('(Call scanner.watch() to start watching)');
|
|
66
|
+
|
|
67
|
+
// Example 4: Manual Database Sync
|
|
68
|
+
h1('\nFsAgent - Manual Database Sync');
|
|
69
|
+
h2('Manually sync filesystem changes to database using Connector');
|
|
70
|
+
p('Use syncToDb() with a Connector for socket-based synchronization:');
|
|
71
|
+
p('');
|
|
72
|
+
p('import { Connector } from "@rljson/db";');
|
|
73
|
+
p('import { Route } from "@rljson/rljson";');
|
|
74
|
+
p('import { SocketMock } from "@rljson/io";');
|
|
75
|
+
p('');
|
|
76
|
+
p('const agent = new FsAgent(');
|
|
77
|
+
p(' "./my-project",');
|
|
78
|
+
p(' myBlobStorage,');
|
|
79
|
+
p(' { ignore: ["node_modules", ".git"] }');
|
|
80
|
+
p(');');
|
|
81
|
+
p('');
|
|
82
|
+
p('const socket = new SocketMock();');
|
|
83
|
+
p('const route = Route.fromFlat("/projectTree+");');
|
|
84
|
+
p('const connector = new Connector(db, route, socket);');
|
|
85
|
+
p('');
|
|
86
|
+
p('const stopSync = await agent.syncToDb(db, connector, "projectTree");');
|
|
87
|
+
p('');
|
|
88
|
+
p('// Agent now:');
|
|
89
|
+
p('// 1. Watches for file changes');
|
|
90
|
+
p('// 2. Extracts trees and stores blobs');
|
|
91
|
+
p('// 3. Broadcasts changes via Connector');
|
|
92
|
+
p('');
|
|
93
|
+
p('// Stop syncing:');
|
|
94
|
+
p('stopSync();');
|
|
95
|
+
p('agent.dispose();');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
// Run via "npx vite-node src/example.ts"
|
|
100
|
+
example();
|
|
101
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rljson/fs-agent",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Rljson fs-agent description",
|
|
5
|
+
"homepage": "https://github.com/rljson/fs-agent",
|
|
6
|
+
"bugs": "https://github.com/rljson/fs-agent/issues",
|
|
7
|
+
"private": false,
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22.14.0"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/rljson/fs-agent.git"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/fs-agent.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@rljson/server": "^0.0.5",
|
|
24
|
+
"@types/node": "^25.1.0",
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
26
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
27
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
28
|
+
"cross-env": "^10.1.0",
|
|
29
|
+
"eslint": "^9.39.2",
|
|
30
|
+
"eslint-plugin-jsdoc": "^62.5.0",
|
|
31
|
+
"eslint-plugin-tsdoc": "^0.5.0",
|
|
32
|
+
"globals": "^17.3.0",
|
|
33
|
+
"jsdoc": "^4.0.5",
|
|
34
|
+
"read-pkg": "^10.0.0",
|
|
35
|
+
"typescript": "~5.9.3",
|
|
36
|
+
"typescript-eslint": "^8.54.0",
|
|
37
|
+
"vite": "^7.3.1",
|
|
38
|
+
"vite-node": "^5.3.0",
|
|
39
|
+
"vite-plugin-dts": "^4.5.4",
|
|
40
|
+
"vite-tsconfig-paths": "^6.0.5",
|
|
41
|
+
"vitest": "^4.0.18",
|
|
42
|
+
"vitest-dom": "^0.1.1"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@rljson/bs": "^0.0.20",
|
|
46
|
+
"@rljson/db": "^0.0.13",
|
|
47
|
+
"@rljson/hash": "^0.0.18",
|
|
48
|
+
"@rljson/io": "^0.0.65",
|
|
49
|
+
"@rljson/json": "^0.0.23",
|
|
50
|
+
"@rljson/rljson": "^0.0.75"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "pnpx vite build && tsc && node scripts/copy-readme-to-dist.js",
|
|
54
|
+
"test": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpx vitest run --coverage && pnpm run lint",
|
|
55
|
+
"prebuild": "npm run test",
|
|
56
|
+
"lint": "pnpx eslint",
|
|
57
|
+
"updateGoldens": "cross-env UPDATE_GOLDENS=true pnpm test"
|
|
58
|
+
}
|
|
59
|
+
}
|