{"id":50372124,"url":"https://github.com/F1nnM/just-bash-postgres","last_synced_at":"2026-06-15T22:00:49.041Z","repository":{"id":346602890,"uuid":"1186984884","full_name":"F1nnM/just-bash-postgres","owner":"F1nnM","description":"PostgreSQL-backed filesystem provider for just-bash — ltree hierarchy, full-text search, pgvector semantic search, and row-level security","archived":false,"fork":false,"pushed_at":"2026-03-25T10:56:22.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-25T19:55:26.774Z","etag":null,"topics":["bun","filesystem","full-text-search","ltree","multi-tenant","pgvector","postgresql","row-level-security","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/just-bash-postgres","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/F1nnM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-20T07:55:30.000Z","updated_at":"2026-03-25T10:56:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/F1nnM/just-bash-postgres","commit_stats":null,"previous_names":["f1nnm/just-bash-postgres"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/F1nnM/just-bash-postgres","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/F1nnM%2Fjust-bash-postgres","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/F1nnM%2Fjust-bash-postgres/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/F1nnM%2Fjust-bash-postgres/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/F1nnM%2Fjust-bash-postgres/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/F1nnM","download_url":"https://codeload.github.com/F1nnM/just-bash-postgres/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/F1nnM%2Fjust-bash-postgres/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34381762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["bun","filesystem","full-text-search","ltree","multi-tenant","pgvector","postgresql","row-level-security","typescript"],"created_at":"2026-05-30T08:00:19.050Z","updated_at":"2026-06-15T22:00:49.036Z","avatar_url":"https://github.com/F1nnM.png","language":"TypeScript","funding_links":[],"categories":["Filesystem Adapters"],"sub_categories":[],"readme":"# just-bash-postgres\n\n[![CI](https://github.com/F1nnM/just-bash-postgres/actions/workflows/ci.yml/badge.svg)](https://github.com/F1nnM/just-bash-postgres/actions/workflows/ci.yml)\n[![coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Ff1nnm.github.io%2Fjust-bash-postgres%2Fcoverage.json)](https://github.com/F1nnM/just-bash-postgres/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/just-bash-postgres)](https://www.npmjs.com/package/just-bash-postgres)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA PostgreSQL-backed filesystem provider for [just-bash](https://github.com/nicholasgasior/just-bash). Implements the `IFileSystem` interface using a single `fs_nodes` table with ltree for hierarchy, built-in full-text search, optional pgvector semantic search, and row-level security for per-session isolation.\n\n## Features\n\n- **Full `IFileSystem` implementation** -- files, directories, symlinks, hard links, chmod, stat, recursive cp/rm/mv\n- **Full-text search** -- PostgreSQL `tsvector` with weighted filename + content ranking\n- **Semantic search** -- pgvector cosine similarity with any embedding provider\n- **Hybrid search** -- combined text + vector ranking with configurable weights\n- **Multi-tenant isolation** -- per-session scoping via `sessionId`, enforced by both application logic and RLS\n- **Idempotent schema setup** -- safe to call `setup()` on every startup\n\n## Installation\n\n```bash\nbun add just-bash-postgres\n```\n\nOr with npm:\n\n```bash\nnpm install just-bash-postgres\n```\n\n### Prerequisites\n\n- **Bun** \u003e= 1.0\n- **PostgreSQL** 14+ with the **ltree** extension (included in most distributions)\n- **pgvector** extension (optional, only needed for semantic/hybrid search)\n\n## Quick Start\n\n```typescript\nimport postgres from \"postgres\";\nimport { PgFileSystem } from \"just-bash-postgres\";\nimport { Bash } from \"just-bash\";\n\nconst sql = postgres(\"postgres://user:pass@localhost:5432/mydb\");\n\nconst fs = new PgFileSystem({ sql, sessionId: 1 });\nawait fs.setup(); // creates tables, indexes, and RLS policies\n\nconst bash = new Bash({ fs, cwd: \"/\", defenseInDepth: false });\n\nawait bash.exec('echo \"hello\" \u003e /greeting.txt');\nconst result = await bash.exec(\"cat /greeting.txt\");\nconsole.log(result.stdout); // \"hello\\n\"\n```\n\n\u003e **Note:** `defenseInDepth: false` is required when using just-bash with postgres.js because the defense-in-depth sandbox restricts raw network access that postgres.js needs for its connection.\n\n## Schema Setup\n\n`fs.setup()` runs an idempotent migration that creates the `fs_nodes` table, indexes, and RLS policies, along with the root directory for the session. Safe to call on every startup -- all statements use `IF NOT EXISTS` guards.\n\nIf you pass `embeddingDimensions` in the options, `setup()` also creates the pgvector extension and adds an `embedding` column with an HNSW index.\n\n## Search\n\nThree search methods are available beyond the standard `IFileSystem` interface.\n\n### Full-Text Search\n\nAlways available. Uses PostgreSQL's `tsvector` with filename weighted higher than content.\n\n```typescript\nconst results = await fs.search(\"database migration\");\n// [{ path: \"/docs/migration-guide.txt\", name: \"migration-guide.txt\", rank: 0.8, snippet: \"...\" }]\n```\n\n### Semantic Search\n\nRequires an embedding provider. Uses pgvector cosine similarity over HNSW indexes.\n\n```typescript\nconst fs = new PgFileSystem({\n  sql,\n  sessionId: 1,\n  embed: async (text) =\u003e\n    openai.embeddings\n      .create({ input: text, model: \"text-embedding-3-small\" })\n      .then((r) =\u003e r.data[0].embedding),\n  embeddingDimensions: 1536,\n});\nawait fs.setup();\n\nconst results = await fs.semanticSearch(\"how to deploy the app\");\n```\n\n### Hybrid Search\n\nCombines full-text and vector search with configurable weights (default: 0.4 text, 0.6 vector).\n\n```typescript\nconst results = await fs.hybridSearch(\"deployment guide\", {\n  textWeight: 0.3,\n  vectorWeight: 0.7,\n  limit: 10,\n});\n```\n\nAll search methods accept an optional `path` parameter to scope results to a subtree:\n\n```typescript\nconst results = await fs.search(\"config\", { path: \"/app/settings\" });\n```\n\n### SearchResult\n\n```typescript\ninterface SearchResult {\n  path: string;\n  name: string;\n  rank: number;\n  snippet?: string; // only present for full-text search\n}\n```\n\n## Session Isolation\n\nEach `PgFileSystem` instance is bound to a `sessionId`. All queries include `WHERE session_id = $sessionId`, and the database schema enforces the same constraint via RLS policies. Sessions cannot see or modify each other's files.\n\n```typescript\nconst sessionAFs = new PgFileSystem({ sql, sessionId: 1 });\nconst sessionBFs = new PgFileSystem({ sql, sessionId: 2 });\n\nawait sessionAFs.setup();\nawait sessionBFs.setup();\n\nawait sessionAFs.writeFile(\"/secret.txt\", \"session A data\");\nawait sessionBFs.exists(\"/secret.txt\"); // false -- completely isolated\n```\n\nNo sessions table is required. `sessionId` is just a positive integer; session management is the consuming application's responsibility.\n\n## Configuration\n\n```typescript\ninterface PgFileSystemOptions {\n  /** postgres.js connection instance */\n  sql: postgres.Sql;\n\n  /** Positive integer session ID for isolation. All operations are scoped to this session. */\n  sessionId: number;\n\n  /** Maximum file size in bytes (default: 100MB) */\n  maxFileSize?: number;\n\n  /** Statement timeout in milliseconds (default: 30000) */\n  statementTimeout?: number;\n\n  /** Async function that returns an embedding vector for text content.\n      When provided, writeFile generates embeddings automatically. */\n  embed?: (text: string) =\u003e Promise\u003cnumber[]\u003e;\n\n  /** Dimension of embedding vectors. Required if embed is provided.\n      Must match your embed function output (e.g. 1536 for text-embedding-3-small). */\n  embeddingDimensions?: number;\n}\n```\n\n## API\n\n### Filesystem Operations\n\n| Method | Description |\n|--------|-------------|\n| `setup()` | Create schema, indexes, RLS policies, and root directory |\n| `writeFile(path, content)` | Create or overwrite a file |\n| `readFile(path)` | Read file as UTF-8 string |\n| `readFileBuffer(path)` | Read file as `Uint8Array` |\n| `appendFile(path, content)` | Append to a file |\n| `exists(path)` | Check if path exists |\n| `stat(path)` | Get file stats (follows symlinks) |\n| `lstat(path)` | Get file stats (does not follow symlinks) |\n| `mkdir(path, options?)` | Create directory (`{ recursive: true }` supported) |\n| `readdir(path)` | List directory entries as strings |\n| `readdirWithFileTypes(path)` | List directory entries with type info |\n| `rm(path, options?)` | Delete file or directory (`{ recursive: true }` supported) |\n| `mv(src, dest)` | Move/rename file or directory |\n| `cp(src, dest, options?)` | Copy file or directory (`{ recursive: true }` supported) |\n| `chmod(path, mode)` | Change file mode |\n| `utimes(path, atime, mtime)` | Update modification time |\n| `symlink(target, path)` | Create a symbolic link |\n| `readlink(path)` | Read symlink target |\n| `link(src, dest)` | Create a hard link (copies content) |\n| `realpath(path)` | Resolve symlinks (max 16 levels) |\n\n### Search Operations\n\n| Method | Description |\n|--------|-------------|\n| `search(query, options?)` | Full-text search with websearch syntax |\n| `semanticSearch(query, options?)` | Vector cosine similarity search |\n| `hybridSearch(query, options?)` | Combined text + vector search |\n\n### Schema Utilities\n\n| Export | Description |\n|--------|-------------|\n| `setupSchema(sql)` | Run schema migration standalone |\n| `setupVectorColumn(sql, dimensions)` | Add vector column standalone |\n| `FsError` | Error class with POSIX codes (ENOENT, EISDIR, etc.) |\n\n## Security\n\n### Trust Model\n\nThe `sessionId` is trusted without verification. The library assumes the consuming application has validated the session before constructing a `PgFileSystem` instance.\n\n### Connection Security\n\nUse TLS for database connections in production:\n\n```typescript\nconst sql = postgres(\"postgres://user:pass@host:5432/db?sslmode=require\");\n```\n\n### Row-Level Security\n\nIsolation is enforced at two levels: application-level `WHERE session_id = ...` on every query, and database-level RLS policies. **For RLS to be effective, connect as a non-superuser role** -- PostgreSQL superusers bypass RLS.\n\n`setup()` automatically grants permissions to a role named `fs_app` if it exists:\n\n```sql\nCREATE ROLE fs_app LOGIN PASSWORD 'your-password';\nGRANT CONNECT ON DATABASE your_db TO fs_app;\n```\n\nRun `setup()` once with a superuser connection to create the schema, then use `fs_app` for normal operations:\n\n```typescript\nconst sql = postgres(\"postgres://fs_app:your-password@localhost:5432/mydb\");\n```\n\n### Defense in Depth\n\nSetting `defenseInDepth: false` on the just-bash `Bash` instance disables just-bash's built-in sandbox, which is necessary because postgres.js requires raw network access. Compensate with network-level controls (firewall rules, VPC configuration) to restrict what the host can reach.\n\n## Development\n\n### Setup\n\n```bash\ngit clone https://github.com/F1nnM/just-bash-postgres.git\ncd just-bash-postgres\nbun install\ndocker compose up -d\n```\n\n### Running Tests\n\nTests run against a real PostgreSQL instance (110+ tests across 7 files):\n\n```bash\ndocker compose up -d\nbun test\n```\n\nBy default, tests connect to `postgres://postgres@localhost:5433/just_bash_postgres_test`. Override with `TEST_DATABASE_URL`.\n\n### Type Checking\n\n```bash\nbun run typecheck\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FF1nnM%2Fjust-bash-postgres","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FF1nnM%2Fjust-bash-postgres","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FF1nnM%2Fjust-bash-postgres/lists"}