Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jrfso/jrfs
JrFS is a transactional, collaborative File System access library.
https://github.com/jrfso/jrfs
collaborative collaborative-editing database design designer developer developer-tools editing editor filesystem-library json json-schema typescript websockets
Last synced: 3 months ago
JSON representation
JrFS is a transactional, collaborative File System access library.
- Host: GitHub
- URL: https://github.com/jrfso/jrfs
- Owner: jrfso
- Created: 2024-10-01T00:24:21.000Z (4 months ago)
- Default Branch: master
- Last Pushed: 2024-10-25T16:40:05.000Z (3 months ago)
- Last Synced: 2024-10-26T13:35:15.491Z (3 months ago)
- Topics: collaborative, collaborative-editing, database, design, designer, developer, developer-tools, editing, editor, filesystem-library, json, json-schema, typescript, websockets
- Language: TypeScript
- Homepage:
- Size: 409 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# JrFS
**JSON + resources File System**
**JrFS** is a `[transactional|queryable|collaborative|caching]` **File System**
access library with _customizable_
`[drivers|plugins|commands|file types|schemas]`.It lets your designer/developer frontend easily access and command local files
via your custom devserver.## For local development tools
Version 1.0 of this library is being designed specifically for local-first
developer tools that need more than the standard browser File System API but
less than a full-blown Electron/Chromium installation.## Current Status
**ALPHA** :: Experimental :: _"Works for local dev, no security!"_
### Work in Progress
- Dogfooding the current version `0.3.0` in a planned product.
- Developing `simple-git` based git integration.
- Planning a system of `Views` or "live queries" to allow developers to easily
observe file listings and/or data in aggregate.## Things You Can Do With JrFS
Describe your file types onto an interface
[ProjectFileTypes
].
```ts
import type { FileTypeInfo } from "@jrfs/core";
import type { DbDesign } from "@/my/model/interfaces/or/someth";/** File-types (initialize here or extend elsewhere via `declare module`) + */
interface ProjectFileTypes {
db: DbDesignFile;
// foo: YourFooFile;
}
/** Collection of registered project file-type specification objects. */
const ProjectFileTypes: {
[P in keyof ProjectFileTypes]: FileTypeInfo;
} = {} as any;/** Your custom metadata for the DbDesign file type. */
interface DbDesignFileMeta {
/** Directory layout rules. */
dir: DirDesignMeta;
}
/** Your DbDesign file type-spec + */
const DbDesignFile: FileTypeInfo = {
schema: DbDesign, // <-- Schema object compatible with your FileTypeProvider
desc: "Database design",
end: ".db.json", // <-- Match file names with this ending.
meta: {
dir: {
of: {
"tables/*": "db-table",
},
},
},
};
/** DbDesign file-type data and file-type wide metadata type declaration. */
type DbDesignFile = FileType;// Add our design file-type specifications to the global collection.
ProjectFileTypes.db = DbDesignFile;
```Create a custom
Repository
class called [ProjectRepo
].
```ts
import { Repository } from "@jrfs/node";
import { TypeboxFileTypes } from "@jrfs/typebox";
import { ProjectFileTypes } from "demo-shared/platform/project";export class ProjectRepo extends Repository {
constructor(configFilePath: string) {
super({
driver: "fs",
fileTypes: new TypeboxFileTypes().set(ProjectFileTypes),
fs: configFilePath,
});
}
}
```Use your repo to access host files in your Node program.
NOTE: Please open a discussion if you're interested in helping with a
compatible Go or Rust library!```ts
const repo = new ProjectRepo(absoluteConfigFilePath);
await repo.open();await repo.fs.write<"db">("backend/db/main/_.db.json", (data) => {
data.db.name = "main"; // <-- Autocompletes from DbDesignFile type
data.db.dialect = "mysql";
});await repo.fs.rename("backend/db/main/_.db.json", "my.db.json");
```Serve the host file system to browsers over web sockets.
Using our lightweight ws integration... Other libraries and
channel-types are also possible (e.g. REST/gRPC).```ts
import { createWsServer } from "@jrfs/ws";/** Function to call after opening repo. */
function registerSockets(repo: ProjectRepo) {
server = createWsServer({ repo });
server.start();
// See labs/demo-server projectServer.ts src...
sockets.register({
name: "projectRepo",
heartbeat: 12000,
dispose,
path: new RegExp("^" + "/" + BASE_PATH),
wss: server.wss,
});
}
```Create a matching client
ProjectRepo
to connect from the browser.
And sprinkle in an optional IndexedDB based file cache...
```ts
import { Repository, createWebClient } from "@jrfs/web";
import { TypeboxFileTypes } from "@jrfs/typebox";
import { createFileCache } from "@jrfs/idb";const client = createWebClient({
ws: "ws://localhost:40141/sockets/v1/project/repo/fs",
});class ProjectRepo extends Repository {
constructor() {
super({
driver: "web",
fileTypes: new TypeboxFileTypes().set(ProjectFileTypes),
web: {
client,
fileCache: createFileCache(),
},
});
(this as any)[Symbol.toStringTag] = `ProjectRepo("/project/repo/")`;
}
}
```
The same code works on the server and client:
```ts
const repo = new ProjectRepo();await repo.open();
await repo.fs.write<"db">("backend/db/main/_.db.json", (data) => {
data.db.name = "main"; // <-- Autocompletes from DbDesignFile type
data.db.dialect = "mysql";
});await repo.fs.rename("backend/db/main/_.db.json", "my.db.json");
// Call a custom plugin command... (see plugin commands demo)
await repo.git.commit({ message: "Testing..." });// Find all files that match a registered file type along with the
// data that's been retrieved and cached in memory for that file.
const files = await repo.findTypes("db");
for (const { node, data } of nodes) {
console.log("FOUND", node.name, "{ id:", [node.id], "} =", data);
}
```Make a plugin to DECLARE and expose some custom
commands...
...but implement them somewhere else, not here, in this example.```ts
import { CommandType, PluginType, registerPlugin } from "@jrfs/core";export interface GitPlugin {
add(files?: string[]): Promise;
commit(message: string): Promise;
push(force?: boolean): Promise;
}export interface GitCommands {
"git.add": CommandType<{ files?: string[] }, { files: string[] }>;
"git.commit": CommandType<{ message: string }, { commit: string }>;
"git.push": CommandType<{ force?: boolean }, { commit: string }>;
}declare module "@jrfs/core" {
/* eslint-disable @typescript-eslint/no-unused-vars */interface Commands extends GitCommands {}
interface Plugins {
git: PluginType;
}interface Repository {
get git(): GitPlugin;
}interface RepositoryHostConfig {
gitPath: string;
}
/* eslint-enable @typescript-eslint/no-unused-vars */
}export default registerPlugin("git", function registerGitPlugin({ repo }) {
console.log("[GIT] Registering plugin interface...");const plugin = Object.freeze({
add: async (files?) => {
console.log("[GIT] Add...");
return repo.exec("git.add", { files });
},
commit: async (message) => {
console.log("[GIT] Commit...");
return repo.exec("git.commit", { message });
},
push: async (force?) => {
console.log("[GIT] Push...");
return repo.exec("git.push", { force });
},
} satisfies GitPlugin);Object.defineProperty(repo, "git", {
enumerable: true,
value: plugin,
writable: false,
});
});
```The server module of your plugin can register command implementations.
NOTE: Commands can be implemented anywhere (client, server, library).```ts
import { simpleGit } from "simple-git";
import { command, registerPlugin } from "@jrfs/core";
import registerGitPluginShared from "demo-shared/jrfs/git";/**
* Command implementations may be registered on any layer (client/server).
* Drivers are responsible for executing commands or forwarding them.
*/
const gitCommands = [
command("git.add", async function gitAdd({ files, fileTypes }, params) {
// TODO: Run git.add via simple-git...
return { files: ["OK!"] };
}),
command("git.commit", async function gitCommit({ config }, params) {
// TODO: Run git.commit via simple-git...
return { commit: "OK!" };
}),
command("git.push", async function gitPush(props, params) {
// TODO: Run git.push via simple-git...
return { commit: "OK!" };
}),
];registerPlugin("git", function registerGitPlugin(props, params) {
// Call our shared plugin setup to declare and expose custom commands.
registerGitPluginShared(props, params);
// Register the actual command implementations..
const { config, commands /*,repo*/ } = props;
console.log("[GIT] Registering plugin host commands...");
commands.register(gitCommands);
config.host.gitPath = findUpGitPath(config.host.dataPath);
});
```## Overview
Here's an overview of how the innards of this beast work.
```mermaid
flowchart TD;
subgraph DI ["Driver"]
direction LR;
FSD("FsDriver");
SQL("SQLite*");
WBD("WebDriver");
end
subgraph FTS ["FileTree"]
direction LR;
FT("FileTree");
WFT("WritableFileTree");
WFT -->|writes| FT;
end
RO1("driver
[fs, sqlite*, web]");
RO2("FileTypeProvider
[@jrfs/typebox, zod, ...]");
RO3("plugins
[diff, git, zip, ...]");
RO{"_options_"} --> R;
RO1 -->RO;
RO2 -->RO;
RO3 -->RO;
R((("Repository"))) --> RC{"_creates_"};
RC --> RCfg("RepositoryConfig");
RC --> CmdReg("CommandsRegistry");
RC --> DI;
RC --> FT;
RC --> PI("Plugins");
DI --> |creates| WFT;```
_[*] The SQLite driver does not yet exist, but the others do!_