{"id":17777171,"url":"https://github.com/jrfso/jrfs","last_synced_at":"2026-02-12T13:04:54.827Z","repository":{"id":257808786,"uuid":"865685545","full_name":"jrfso/jrfs","owner":"jrfso","description":"JrFS is a transactional, collaborative File System access library.","archived":false,"fork":false,"pushed_at":"2025-02-19T17:18:34.000Z","size":440,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-27T04:04:44.267Z","etag":null,"topics":["collaborative","collaborative-editing","database","design","designer","developer","developer-tools","editing","editor","filesystem-library","json","json-schema","typescript","websockets"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jrfso.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-01T00:24:21.000Z","updated_at":"2025-02-19T17:09:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"f4a4be93-1846-41a6-8e05-eeaf6f18fba7","html_url":"https://github.com/jrfso/jrfs","commit_stats":null,"previous_names":["jrfso/jrfs"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrfso%2Fjrfs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrfso%2Fjrfs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrfso%2Fjrfs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrfso%2Fjrfs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jrfso","download_url":"https://codeload.github.com/jrfso/jrfs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243775751,"owners_count":20346251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["collaborative","collaborative-editing","database","design","designer","developer","developer-tools","editing","editor","filesystem-library","json","json-schema","typescript","websockets"],"created_at":"2024-10-26T23:05:23.187Z","updated_at":"2026-02-12T13:04:49.790Z","avatar_url":"https://github.com/jrfso.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JrFS\n\n**JSON + resources File System**\n\n**JrFS** is a `[transactional|queryable|collaborative|caching]` **File System**\naccess library with _customizable_\n`[drivers|plugins|commands|file types|schemas]`.\n\nIt lets your designer/developer frontend easily access and command local files\nvia your custom devserver.\n\n## How it works\n\nThe same-ish code works in the browser or Node...\n\n```ts\nimport { Repository } from \"@jrfs/node\"; // or \"@jrfs/web\";\n\n/** File types by name. (Declare here or augment elsewhere...) */\ninterface ProjectFileTypes {\n  db: DbDesignFile; // See ./labs/demo-shared/src/features/db/\n}\n/** Create a typed repository for our project. */\nconst repo = new Repository\u003cProjectFileTypes\u003e({\n  driver: \"fs\",                     // \"fs\" | \"web\" (server | client)\n  fs: \"/my/project/projectDb.json\", // fs server config, points to data dir.\n  // web: { client, fileCache }     // web client options\n});\n\nawait repo.open();\n\n// Write to a specific known file type.\nawait repo.fs.write\u003c\"db\"\u003e(\"backend/db/main/_.db.json\", (data) =\u003e {\n  data.db.name = \"main\"; // \u003c-- Autocompletes from DbDesignFile type\n  data.db.dialect = \"mysql\";\n});\n\n// Do an fs operation (add|copy|get|move|remove|rename)\nawait repo.fs.rename(\"backend/db/main/_.db.json\", \"my.db.json\");\n\n// Call a custom plugin command... (see plugin commands demo)\nawait repo.git.commit({ message: \"Testing...\" });\n\n// Find all files that match a registered file type along with the\n// data that's been retrieved and cached in memory for that file.\nconst files = await repo.findTypes(\"db\");\nfor (const { node, data } of nodes) {\n  console.log(\"FOUND\", node.name, \"{ id:\", [node.id], \"} =\", data);\n}\n\n// More planned: Other types of data queries, aggregate views/commands.\n```\n\n## For local development tools\n\nVersion 1.0 of this library is being designed specifically for local-first\ndeveloper tools that need more than the standard browser File System API but\nless than a full-blown Electron/Chromium installation.\n\n## Current Status\n\n**ALPHA** :: Experimental :: _\"Works for local dev, no security!\"_\n\n### Work in Progress\n\n- Dogfooding the current version `0.3.0` in a planned product.\n- Developing `simple-git` based git integration.\n- Planning a system of `Views` or \"live queries\" to allow developers to easily\n  observe file listings and/or data in aggregate.\n\n## Things You Can Do With JrFS\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nDescribe your file types onto an interface\n[\u003ccode\u003eProjectFileTypes\u003c/code\u003e].\n\u003c/summary\u003e\n\n\u003cp\u003e\u003cem\u003e\u0026nbsp;\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport type { FileTypeInfo } from \"@jrfs/core\";\nimport type { DbDesign } from \"@/my/model/interfaces/or/someth\";\n\n/** File-types (initialize here or extend elsewhere via `declare module`) + */\ninterface ProjectFileTypes {\n  db: DbDesignFile;\n  // foo: YourFooFile;\n}\n/** Collection of registered project file-type specification objects. */\nconst ProjectFileTypes: {\n  [P in keyof ProjectFileTypes]: FileTypeInfo\u003cProjectFileTypes[P][\"meta\"]\u003e;\n} = {} as any;\n\n/** Your custom metadata for the DbDesign file type. */\ninterface DbDesignFileMeta {\n  /** Directory layout rules. */\n  dir: DirDesignMeta;\n}\n/** Your DbDesign file type-spec + */\nconst DbDesignFile: FileTypeInfo\u003cDbDesignFileMeta\u003e = {\n  schema: DbDesign, // \u003c-- Schema object compatible with your FileTypeProvider\n  desc: \"Database design\",\n  end: \".db.json\", // \u003c-- Match file names with this ending.\n  meta: {\n    dir: {\n      of: {\n        \"tables/*\": \"db-table\",\n      },\n    },\n  },\n};\n/** DbDesign file-type data and file-type wide metadata type declaration. */\ntype DbDesignFile = FileType\u003cDbDesign, DbDesignFileMeta\u003e;\n\n// Add our design file-type specifications to the global collection.\nProjectFileTypes.db = DbDesignFile;\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nCreate a custom \u003ccode\u003eRepository\u003c/code\u003e class called [\u003ccode\u003eProjectRepo\u003c/code\u003e].\n\u003c/summary\u003e\n\n\u003cp\u003e\u003cem\u003e\u0026nbsp;\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport { Repository } from \"@jrfs/node\";\nimport { TypeboxFileTypes } from \"@jrfs/typebox\";\nimport { ProjectFileTypes } from \"demo-shared/platform/project\";\n\nexport class ProjectRepo extends Repository\u003cProjectFileTypes\u003e {\n  constructor(configFilePath: string) {\n    super({\n      driver: \"fs\",\n      fileTypes: new TypeboxFileTypes\u003cProjectFileTypes\u003e().set(ProjectFileTypes),\n      fs: configFilePath,\n    });\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nUse your repo to access host files in your Node program.\n\u003c/summary\u003e\n\n\u003cbr /\u003e\n\u003cp\u003e\u003cem\u003e\nNOTE: Please open a discussion if you're interested in helping with a\ncompatible Go or Rust library!\n\u003c/em\u003e\u003c/p\u003e\n\n```ts\nconst repo = new ProjectRepo(absoluteConfigFilePath);\nawait repo.open();\n\nawait repo.fs.write\u003c\"db\"\u003e(\"backend/db/main/_.db.json\", (data) =\u003e {\n  data.db.name = \"main\"; // \u003c-- Autocompletes from DbDesignFile type\n  data.db.dialect = \"mysql\";\n});\n\nawait repo.fs.rename(\"backend/db/main/_.db.json\", \"my.db.json\");\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nServe the host file system to browsers over web sockets.\n\u003c/summary\u003e\n\n\u003cbr /\u003e\n\u003cp\u003e\u003cem\u003e\nUsing our lightweight ws integration... Other libraries and \nchannel-types are also possible (e.g. REST/gRPC).\n\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport { createWsServer } from \"@jrfs/ws\";\n\n/** Function to call after opening repo. */\nfunction registerSockets(repo: ProjectRepo) {\n  server = createWsServer({ repo });\n  server.start();\n  // See labs/demo-server projectServer.ts src...\n  sockets.register({\n    name: \"projectRepo\",\n    heartbeat: 12000,\n    dispose,\n    path: new RegExp(\"^\" + \"/\" + BASE_PATH),\n    wss: server.wss,\n  });\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nCreate a matching client \u003ccode\u003eProjectRepo\u003c/code\u003e to connect from the browser.\n\u003c/summary\u003e\n\n\u003cbr /\u003e\n\u003cp\u003e\u003cem\u003eAnd sprinkle in an optional IndexedDB based file cache...\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport { Repository, createWebClient } from \"@jrfs/web\";\nimport { TypeboxFileTypes } from \"@jrfs/typebox\";\nimport { createFileCache } from \"@jrfs/idb\";\n\nconst client = createWebClient({\n  ws: \"ws://localhost:40141/sockets/v1/project/repo/fs\",\n});\n\nclass ProjectRepo extends Repository\u003cProjectFileTypes\u003e {\n  constructor() {\n    super({\n      driver: \"web\",\n      fileTypes: new TypeboxFileTypes\u003cProjectFileTypes\u003e().set(ProjectFileTypes),\n      web: {\n        client,\n        fileCache: createFileCache(),\n      },\n    });\n    (this as any)[Symbol.toStringTag] = `ProjectRepo(\"/project/repo/\")`;\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cbr /\u003e\n\nThe same code works on the server and client:\n\n```ts\nconst repo = new ProjectRepo();\n\nawait repo.open();\n\nawait repo.fs.write\u003c\"db\"\u003e(\"backend/db/main/_.db.json\", (data) =\u003e {\n  data.db.name = \"main\"; // \u003c-- Autocompletes from DbDesignFile type\n  data.db.dialect = \"mysql\";\n});\n\nawait repo.fs.rename(\"backend/db/main/_.db.json\", \"my.db.json\");\n\n// Call a custom plugin command... (see plugin commands demo)\nawait repo.git.commit({ message: \"Testing...\" });\n\n// Find all files that match a registered file type along with the\n// data that's been retrieved and cached in memory for that file.\nconst files = await repo.findTypes(\"db\");\nfor (const { node, data } of nodes) {\n  console.log(\"FOUND\", node.name, \"{ id:\", [node.id], \"} =\", data);\n}\n```\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nMake a plugin to \u003cem\u003eDECLARE\u003c/em\u003e and \u003cstrong\u003eexpose\u003c/strong\u003e some custom\ncommands...\n\u003c/summary\u003e\n\n\u003cbr /\u003e\n\u003cp\u003e\u003cem\u003e\n...but implement them somewhere else, not here, in this example.\n\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport { CommandType, PluginType, registerPlugin } from \"@jrfs/core\";\n\nexport interface GitPlugin {\n  add(files?: string[]): Promise\u003cany\u003e;\n  commit(message: string): Promise\u003cany\u003e;\n  push(force?: boolean): Promise\u003cany\u003e;\n}\n\nexport interface GitCommands {\n  \"git.add\": CommandType\u003c{ files?: string[] }, { files: string[] }\u003e;\n  \"git.commit\": CommandType\u003c{ message: string }, { commit: string }\u003e;\n  \"git.push\": CommandType\u003c{ force?: boolean }, { commit: string }\u003e;\n}\n\ndeclare module \"@jrfs/core\" {\n  /* eslint-disable @typescript-eslint/no-unused-vars */\n\n  interface Commands extends GitCommands {}\n\n  interface Plugins {\n    git: PluginType\u003cundefined\u003e;\n  }\n\n  interface Repository\u003cFT\u003e {\n    get git(): GitPlugin;\n  }\n\n  interface RepositoryHostConfig {\n    gitPath: string;\n  }\n  /* eslint-enable @typescript-eslint/no-unused-vars */\n}\n\nexport default registerPlugin(\"git\", function registerGitPlugin({ repo }) {\n  console.log(\"[GIT] Registering plugin interface...\");\n\n  const plugin = Object.freeze({\n    add: async (files?) =\u003e {\n      console.log(\"[GIT] Add...\");\n      return repo.exec(\"git.add\", { files });\n    },\n    commit: async (message) =\u003e {\n      console.log(\"[GIT] Commit...\");\n      return repo.exec(\"git.commit\", { message });\n    },\n    push: async (force?) =\u003e {\n      console.log(\"[GIT] Push...\");\n      return repo.exec(\"git.push\", { force });\n    },\n  } satisfies GitPlugin);\n\n  Object.defineProperty(repo, \"git\", {\n    enumerable: true,\n    value: plugin,\n    writable: false,\n  });\n});\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary style=\"user-select:none\"\u003e\nThe \u003cem\u003eserver\u003c/em\u003e module of your plugin can register command implementations.\n\u003c/summary\u003e\n\n\u003cbr /\u003e\n\u003cp\u003e\u003cem\u003e\nNOTE: Commands can be implemented anywhere (client, server, library).\n\u003c/em\u003e\u003c/p\u003e\n\n```ts\nimport { simpleGit } from \"simple-git\";\nimport { command, registerPlugin } from \"@jrfs/core\";\nimport registerGitPluginShared from \"demo-shared/jrfs/git\";\n\n/**\n * Command implementations may be registered on any layer (client/server).\n * Drivers are responsible for executing commands or forwarding them.\n */\nconst gitCommands = [\n  command(\"git.add\", async function gitAdd({ files, fileTypes }, params) {\n    // TODO: Run git.add via simple-git...\n    return { files: [\"OK!\"] };\n  }),\n  command(\"git.commit\", async function gitCommit({ config }, params) {\n    // TODO: Run git.commit via simple-git...\n    return { commit: \"OK!\" };\n  }),\n  command(\"git.push\", async function gitPush(props, params) {\n    // TODO: Run git.push via simple-git...\n    return { commit: \"OK!\" };\n  }),\n];\n\nregisterPlugin(\"git\", function registerGitPlugin(props, params) {\n  // Call our shared plugin setup to declare and expose custom commands.\n  registerGitPluginShared(props, params);\n  // Register the actual command implementations..\n  const { config, commands /*,repo*/ } = props;\n  console.log(\"[GIT] Registering plugin host commands...\");\n  commands.register(gitCommands);\n  config.host.gitPath = findUpGitPath(config.host.dataPath);\n});\n```\n\n\u003c/details\u003e\n\n## Overview\n\nHere's an overview of how the innards of this beast work.\n\n```mermaid\nflowchart TD;\n  subgraph Opt [\"Options\"]\n    RO1(\"driver\n        [fs, sqlite*, web]\");\n    RO2(\"FileTypeProvider\n        [@jrfs/typebox, zod, ...]\");\n    RO3(\"plugins\n        [diff, git, zip, ...]\");\n  end\n  subgraph Repo [\"Repository\"]\n    CmdReg(\"CommandsRegistry\");\n    FT(\"FileTree\");\n    PI(\"Plugins\");\n    RCfg(\"RepositoryConfig\");\n  end\n  subgraph DR [\"Driver\"]\n    FSD(\"FsDriver\");\n    SQL(\"SQLite*\");\n    WBD(\"WebDriver\");\n  end\n  subgraph DRC [\" \"]\n    direction TB;\n    DRC1@{ shape: text, label: \"*wraps FileTree*\" }\n    WFT(\"WritableFileTree\");\n    DRC2@{ shape: text, label: \"*writes FileTree*\" }\n  end\n  Opt --\u003e |\"\u0026nbsp; configure \u0026nbsp;\"| Repo;\n  Repo --\u003e |\"\u0026nbsp; creates \u0026nbsp;\"| DR;\n  DR --\u003e |\"\u0026nbsp; creates \u0026nbsp;\"| DRC;\n```\n\n_[*] The SQLite driver does not yet exist, but the others do!_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjrfso%2Fjrfs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjrfso%2Fjrfs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjrfso%2Fjrfs/lists"}