{"id":51219487,"url":"https://github.com/ctrotech-tutor/ctrodb","last_synced_at":"2026-06-28T06:04:06.193Z","repository":{"id":367857956,"uuid":"1038837411","full_name":"ctrotech-tutor/ctrodb","owner":"ctrotech-tutor","description":"A zero-dependency, reactive, client-side database for the browser and Node.js. MongoDB-like API, IndexedDB persistence, Signal-based reactivity.","archived":false,"fork":false,"pushed_at":"2026-06-28T02:22:46.000Z","size":447,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-28T03:14:05.014Z","etag":null,"topics":["client-side","database","indexeddb","reactive","typescript"],"latest_commit_sha":null,"homepage":"https://ctrodb.vercel.app","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/ctrotech-tutor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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},"funding":{"github":["ctrotech-tutor"]}},"created_at":"2025-08-15T23:06:44.000Z","updated_at":"2026-06-28T02:22:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ctrotech-tutor/ctrodb","commit_stats":null,"previous_names":["ctrotech-tutor/ctrodb"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/ctrotech-tutor/ctrodb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrotech-tutor%2Fctrodb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrotech-tutor%2Fctrodb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrotech-tutor%2Fctrodb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrotech-tutor%2Fctrodb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ctrotech-tutor","download_url":"https://codeload.github.com/ctrotech-tutor/ctrodb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ctrotech-tutor%2Fctrodb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34878965,"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-28T02:00:05.809Z","response_time":54,"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":["client-side","database","indexeddb","reactive","typescript"],"created_at":"2026-06-28T06:04:05.652Z","updated_at":"2026-06-28T06:04:06.186Z","avatar_url":"https://github.com/ctrotech-tutor.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ctrotech-tutor"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"ctrodb\" src=\"/public/logo.png\" width=\"400\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003ectrodb\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eZero-dependency, reactive, client-side database for browser and Node.js\u003c/strong\u003e\n  \u003cbr /\u003e\n  Schema-driven · Full-text search · Relations · React hooks · IndexedDB persistence\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#-features\"\u003eFeatures\u003c/a\u003e •\n  \u003ca href=\"#-installation\"\u003eInstallation\u003c/a\u003e •\n  \u003ca href=\"#-quick-start\"\u003eQuick Start\u003c/a\u003e •\n  \u003ca href=\"#-api\"\u003eAPI\u003c/a\u003e •\n  \u003ca href=\"#-plugins\"\u003ePlugins\u003c/a\u003e •\n  \u003ca href=\"#-react\"\u003eReact\u003c/a\u003e •\n  \u003ca href=\"#-migrating-from-v10x\"\u003eMigration\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ctrotech-tutor/ctrodb/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/ctrotech-tutor/ctrodb?style=flat\u0026label=version\u0026color=6366f1\" alt=\"version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/ctrodb\"\u003e\u003cimg src=\"https://img.shields.io/badge/dependencies-zero-brightgreen?style=flat\" alt=\"zero dependencies\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/ctrodb\"\u003e\u003cimg src=\"https://img.shields.io/bundlephobia/minzip/ctrodb?style=flat\u0026label=size\u0026color=22c55e\" alt=\"size\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.typescriptlang.org\"\u003e\u003cimg src=\"https://img.shields.io/badge/types-TypeScript%20strict-3178C6?style=flat\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ctrotech-tutor/ctrodb/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/ctrotech-tutor/ctrodb/publish.yml?style=flat\u0026branch=main\u0026label=build\" alt=\"build\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/tests-189%20passing-success?style=flat\" alt=\"tests\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-22c55e?style=flat\" alt=\"license\" /\u003e\n\u003c/p\u003e\n\n---\n\n\u003e **⚠️ Documentation site** ([ctrodb.vercel.app](https://ctrodb.vercel.app)) is currently being updated.\n\u003e For now, this README is the single source of truth.\n\n---\n\n## ✨ Features\n\n| Category | Feature | Description |\n|---|---|---|\n| **Core** | Zero Dependencies | No runtime libraries. Core ~25 KB minified. |\n| | Schema Validation | Declarative schemas with type checking, defaults, required fields, format validation (email, URL, regex), min/max constraints. |\n| | Proxy Models | Transparent property access on query results. Call `.update()` and `.delete()` directly on models. |\n| | TypeScript Strict | Full generics, exhaustive type exports, strict mode throughout. |\n| **Storage** | IndexedDB | Persistent browser storage — survives page reloads and tab closures. |\n| | Memory Adapter | In-memory store for Node.js, testing, and prototyping. Auto-detected. |\n| | UUID IDs | Collision-free IDs via `crypto.randomUUID()`. No auto-increment conflicts across tabs. |\n| **Query** | Fluent Builder | Chain `.where()`, `.sort()`, `.limit()`, `.offset()` — MongoDB-style. |\n| | Index-Aware Planner | Automatically selects the best index for each query. Falls back to `id_lookup` for direct ID access. |\n| | OR Groupings | `.orWhere()` for complex filter logic. |\n| | Pagination | `.limit()` + `.offset()` for cursor-based or page-based pagination. |\n| **Reactivity** | Signal-Based | Subscribe to collection or database-level change events. Automatic re-rendering in React. |\n| | Change Events | `create`, `update`, `delete` events with old/new record snapshots. |\n| **Plugins** | Full-Text Search | Inverted-index FTS with tokenization, stop-word filtering, and multi-field search. |\n| | Relations | `has_many`, `belongs_to`, `has_one` — lazy getters and eager loading via `.with()`. |\n| | Validation | Extensible rule engine with built-in email, URL, and no-empty-string validators. |\n| **React** | useQuery | Reactive queries that auto-refetch on data changes. Returns `{ data, loading, error }`. |\n| | useDoc | Single-document reactive fetch by ID. |\n| | useMutation | CRUD operations with loading and error state tracking. |\n| **Distribution** | Universal Build | ESM, CJS, and IIFE (CDN-ready) outputs. Works everywhere JavaScript runs. |\n| | CDN Ready | Use via `\u003cscript\u003e` tag — no bundler required. |\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install ctrodb\n```\n\n### CDN (script tag)\n\n```html\n\u003cscript src=\"https://unpkg.com/ctrodb@1.1.0/dist/index.global.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const { Database } = CtroDB\n  const db = new Database({ name: \"my-app\" })\n  await db.connect()\n\u003c/script\u003e\n```\n\n---\n\n## 🚀 Quick Start\n\n### 1. Define a schema\n\n```typescript\nimport { Database } from \"ctrodb\"\n\nconst db = new Database({\n  name: \"my-app\",\n  adapter: \"indexeddb\", // \"memory\" for Node/testing\n  schema: {\n    version: 1,\n    collections: {\n      users: {\n        fields: {\n          name: { type: \"string\", required: true },\n          email: { type: \"string\", validate: \"email\" },\n          age: { type: \"number\", min: 0, max: 150 },\n          role: { type: \"string\", default: \"user\" },\n        },\n        indexes: [{ field: \"email\", unique: true }],\n      },\n    },\n  },\n})\n```\n\n### 2. Connect and create records\n\n```typescript\nawait db.connect()\n\nconst users = db.collection(\"users\")\nconst alice = await users.create({ name: \"Alice\", email: \"alice@test.com\", age: 30 })\n// alice.name === \"Alice\", alice.role === \"user\" (default applied)\n```\n\n### 3. Query with the fluent builder\n\n```typescript\nconst results = await users\n  .query()\n  .where(\"age\", \"\u003e=\", 18)\n  .where(\"age\", \"\u003c=\", 65)\n  .sort({ name: \"asc\" })\n  .limit(10)\n  .fetch()\n\n// Direct property access via Proxy\nconsole.log(results[0].name)\n```\n\n### 4. Update and delete through models\n\n```typescript\nawait alice.update({ age: 31 })\nawait alice.delete()\n```\n\n### 5. React to changes\n\n```typescript\nconst unsub = users.onChange((event) =\u003e {\n  console.log(event.type, event.recordId, event.record)\n})\n```\n\n---\n\n## 📖 API\n\n### Database\n\n```typescript\nconst db = new Database(config)\n\nawait db.connect()                          // Open connection\nawait db.disconnect()                       // Close connection\nconst col = db.collection\u003cT\u003e(\"name\")        // Get or create collection\nawait db.transaction(fn)                    // Run in transaction\ndb.on(callback)                             // Subscribe to all changes\n```\n\n#### DatabaseConfig\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `name` | `string` | `\"ctrodb\"` | Database name (used for IndexedDB store name) |\n| `adapter` | `\"indexeddb\" \\| \"memory\" \\| StorageAdapter` | auto-detect | Storage backend |\n| `schema` | `SchemaConfig` | — | Schema definition |\n| `plugins` | `CtroDBPlugin[]` | — | Plugins to load |\n\n### Collection\\\u003cT\\\u003e\n\n```typescript\nawait collection.create(data)               // Insert a record (id auto-generated)\nawait collection.get(id)                    // Find by ID\nawait collection.getAll()                   // Fetch all records\nawait collection.update(id, changes)        // Partial update\nawait collection.delete(id)                 // Delete by ID\nawait collection.deleteMany(ids)            // Bulk delete\nawait collection.put(data)                  // Upsert (create or update)\nawait collection.count()                    // Record count\ncollection.query()                          // Get a QueryBuilder\ncollection.with(...relations)               // Get a QueryBuilder with eager loading\ncollection.onChange(callback)               // Subscribe to collection changes\n```\n\n### QueryBuilder\\\u003cT\\\u003e\n\n```typescript\nquery.where(field, op, value)               // Add filter (op: ==, !=, \u003e, \u003c, \u003e=, \u003c=)\nquery.orWhere((q) =\u003e q.where(...))           // OR group\nquery.sort({ field: \"asc\" | \"desc\" })       // Sort\nquery.limit(n)                              // Limit results\nquery.offset(n)                             // Skip results\nquery.search(field, term)                   // Full-text search\nawait query.fetch()                         // Execute, return models\nawait query.first()                         // First result or undefined\nawait query.count()                         // Count results\nawait query.toArray()                       // Return plain objects (no Proxy)\n```\n\n### Model\\\u003cT\\\u003e\n\n```typescript\nmodel.id                                    // Record ID (UUID string)\nmodel.update(changes)                       // Update this record\nmodel.delete()                              // Delete this record\nmodel.toJSON()                              // Return plain object\n// + direct property access to all fields via Proxy\n// + lazy relation getters (e.g., await post.author.first())\n```\n\n### Schema\n\n```typescript\nconst schema = new Schema({\n  version: 1,\n  collections: {\n    users: {\n      fields: {\n        name: { type: \"string\", required: true },\n        email: { type: \"string\", validate: \"email\" },\n        age: { type: \"number\", min: 0, max: 150, default: 18 },\n      },\n      indexes: [{ field: \"email\", unique: true }],\n      searchable: [\"name\", \"bio\"],\n      relations: {\n        posts: { type: \"has_many\", collection: \"posts\", foreignKey: \"userId\" },\n        profile: { type: \"has_one\", collection: \"profiles\", foreignKey: \"userId\" },\n      },\n    },\n  },\n})\n```\n\n#### Field Options\n\n| Option | Type | Description |\n|---|---|---|\n| `type` | `\"string\" \\| \"number\" \\| \"boolean\" \\| \"object\" \\| \"array\"` | Field data type |\n| `required` | `boolean` | Field must be provided at creation |\n| `default` | `unknown` | Default value if not provided |\n| `validate` | `\"email\" \\| \"url\" \\| RegExp \\| ((v) =\u003e boolean)` | Format validation |\n| `min` | `number` | Minimum value (number fields) |\n| `max` | `number` | Maximum value (number fields) |\n| `maxLength` | `number` | Maximum string length |\n| `items` | `FieldDefinition` | Item type for array fields |\n| `unique` | `boolean` | Enforce uniqueness (via index) |\n\n---\n\n## 🔌 Plugins\n\nPlugins extend ctrodb through lifecycle hooks. Load them in the `Database` constructor:\n\n```typescript\nimport { Database } from \"ctrodb\"\nimport { ftsPlugin, relationsPlugin, validationPlugin } from \"ctrodb\"\n\nconst db = new Database({\n  name: \"my-app\",\n  schema: { ... },\n  plugins: [\n    validationPlugin(),     // 1. Validate first\n    ftsPlugin(),            // 2. Index for search\n    relationsPlugin(),      // 3. Eager loading\n  ],\n})\n```\n\n### Full-Text Search Plugin\n\nInverted-index FTS with automatic indexing on create/update/delete.\n\n**Basic search (substring matching) — no plugin required:**\n```typescript\n// Case-insensitive substring search\nconst results = await articles.query().search(\"title\", \"typescript\").fetch()\n```\n\n**Indexed search (tokenized AND) — requires `ftsPlugin()`:**\n```typescript\nimport { FTSIndexer } from \"ctrodb\"\n\nconst indexer = new FTSIndexer(adapter)\nconst ids = await indexer.search(\"articles\", \"typescript database\")\n// Only documents matching ALL tokens\n```\n\nFeatures: tokenization, stop-word removal, case-insensitive, multi-field, automatic index updates on create/update/delete.\n\n### Relations Plugin\n\nDefine relationships in your schema and load them eagerly or lazily.\n\n**Schema definition:**\n```typescript\nrelations: {\n  author: { type: \"belongs_to\", collection: \"users\", foreignKey: \"userId\" },\n  comments: { type: \"has_many\", collection: \"comments\", foreignKey: \"postId\" },\n}\n```\n\n**Eager loading — `.with()` (built-in, no plugin needed):**\n```typescript\nconst posts = await postsCol.with(\"author\", \"comments\").fetch()\n// posts[0].author  — resolved user object\n// posts[0].comments — resolved comment array\n```\n\n**Lazy loading — getters (built into Model):**\n```typescript\nconst author = await post.author.first()      // belongs_to\nconst comments = await post.comments.fetch()  // has_many\n```\n\n\u003e Note: `relationsPlugin()` is still required in the plugins array for the plugin system to recognize and initialize relation metadata.\n\n### Validation Plugin\n\nExtensible validation with built-in and custom rules:\n\n```typescript\nconst db = new Database({\n  plugins: [\n    validationPlugin([\n      {\n        name: \"noReservedNames\",\n        validate(collection, field, value, data) {\n          if (value === \"admin\") return \"Username 'admin' is reserved\"\n          return null\n        },\n      },\n    ]),\n  ],\n})\n```\n\n**Built-in rules:** `email` format, `url` format, `noEmptyStrings` (applied to required fields only).\n\n---\n\n## ⚛️ React\n\n```bash\nnpm install ctrodb react\n```\n\n```tsx\nimport { Database } from \"ctrodb\"\nimport { DatabaseProvider, useQuery, useMutation } from \"ctrodb/react\"\n\nconst db = new Database({ name: \"my-app\", adapter: \"indexeddb\", schema: { ... } })\nawait db.connect()\n\nfunction App() {\n  return (\n    \u003cDatabaseProvider db={db}\u003e\n      \u003cTodoList /\u003e\n    \u003c/DatabaseProvider\u003e\n  )\n}\n\nfunction TodoList() {\n  const { data: todos, loading } = useQuery(\"todos\", (q) =\u003e q.sort({ createdAt: \"desc\" }))\n  const { create, delete: remove } = useMutation(\"todos\")\n\n  if (loading) return \u003cdiv\u003eLoading...\u003c/div\u003e\n\n  return (\n    \u003cul\u003e\n      {todos.map((todo) =\u003e (\n        \u003cli key={todo.id}\u003e\n          {todo.text}\n          \u003cbutton onClick={() =\u003e remove(todo.id)}\u003eDelete\u003c/button\u003e\n        \u003c/li\u003e\n      ))}\n      \u003cbutton onClick={() =\u003e create({ text: \"New task\" })}\u003eAdd\u003c/button\u003e\n    \u003c/ul\u003e\n  )\n}\n```\n\n### Hooks API\n\n| Hook | Returns | Description |\n|---|---|---|\n| `useQuery\u003cT\u003e(name, queryFn?, deps?)` | `{ data: (Model\u003cT\u003e \u0026 T)[]; loading: boolean; error: Error \\| null }` | Reactive query, re-fetches on changes |\n| `useDoc\u003cT\u003e(name, id)` | `{ data: (Model\u003cT\u003e \u0026 T) \\| undefined; loading: boolean; error: Error \\| null }` | Single document by ID |\n| `useMutation\u003cT\u003e(name)` | `{ create, update, delete, loading, error, reset }` | CRUD with loading/error state |\n\n---\n\n## 📁 Examples\n\n| Example | Location | Description |\n|---|---|---|\n| CDN Todo App | [`examples/cdn/index.html`](examples/cdn/index.html) | Browser todo app via `\u003cscript\u003e` tag |\n| Node.js CLI | [`examples/node/index.mjs`](examples/node/index.mjs) | CRUD, queries, FTS in Node.js |\n| React Integration | [`tests/unit/react.test.tsx`](tests/unit/react.test.tsx) | Hook API usage patterns |\n\n---\n\n## 🏗 Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│            User Code / Framework             │\n├─────────────────────────────────────────────┤\n│    Database       Collection     Plugin API  │\n├─────────────────────────────────────────────┤\n│    QueryBuilder   QueryPlanner              │\n│    QueryExecutor  Schema/Model              │\n├─────────────────────────────────────────────┤\n│    MemoryAdapter  IndexedDBAdapter          │\n│    (Node/testing) (production browser)      │\n├─────────────────────────────────────────────┤\n│    Reactivity System (Signal-based)         │\n└─────────────────────────────────────────────┘\n```\n\n### Project Structure\n\n```\nsrc/\n├── adapter/          # Storage adapters (Memory, IndexedDB)\n├── model/            # Proxy-based record wrapper\n├── plugins/          # FTS, Relations, Validation plugins\n├── query/            # Query engine (builder, planner, executor)\n├── reactive/         # Signal reactivity system\n├── utils/            # Plugin hook runner\n├── collection.ts     # Collection CRUD + change events\n├── database.ts       # Database entry point\n├── errors.ts         # Error classes\n├── index.ts          # Public API barrel exports\n├── react.ts          # React hooks (useQuery, useDoc, useMutation)\n├── schema.ts         # Schema definition and validation\n└── types.ts          # Core TypeScript interfaces\ntests/\n├── unit/             # Unit tests (14 files, 189 tests)\n├── benchmarks/       # Performance benchmarks (WIP)\n└── integration/      # Cross-component tests (WIP)\nexamples/\n├── cdn/              # Browser CDN example\n└── node/             # Node.js CLI example\n```\n\n---\n\n## 🔄 Migrating from v1.0.x\n\n### v1.1.0 Breaking Changes\n\n**1. `useQuery` / `useDoc` return `{ data, loading, error }`**\n\n```diff\n- const todos = useQuery(\"todos\")\n+ const { data: todos, loading } = useQuery(\"todos\")\n\n- const post = useDoc(\"posts\", id)\n+ const { data: post } = useDoc(\"posts\", id)\n```\n\n**2. IDs are now UUID strings instead of auto-incremented numbers**\n\nAll records created via `Collection.create()` get a UUID string ID. Foreign key fields in relation schemas must use `type: \"string\"`.\n\n```diff\n- authorId: { type: \"number\", required: true }\n+ authorId: { type: \"string\", required: true }\n```\n\n**3. Plugin hooks can now be async**\n\nCustom plugin hooks may return a Promise — `runHook` now `await`s results before passing data to the next hook.\n\n**4. `Collection.create()` now requires all schema-defined fields**\n\nThe TypeScript signature uses `Omit\u003cT, \"id\"\u003e \u0026 { id?: ID }` instead of `Partial\u003cT\u003e`. Missing required fields are caught at compile time.\n\n```diff\n- await users.create({ name: \"Alice\" })             // age missing — runtime OK, compile error now\n+ await users.create({ name: \"Alice\", age: 30 })    // all fields provided\n```\n\n---\n\n## 💻 Development\n\n```bash\ngit clone https://github.com/ctrotech-tutor/ctrodb.git\ncd ctrodb\nnpm install\n\nnpm run dev           # Watch mode\nnpm test              # Run tests (189 tests)\nnpm run typecheck     # TypeScript check\nnpm run lint          # Biome lint\nnpm run build         # Build ESM + CJS + IIFE\nnpm run bench         # Run benchmarks\n```\n\n### Commands\n\n| Command | Description |\n|---|---|\n| `npm test` | Run all tests |\n| `npm run test:watch` | Watch mode |\n| `npm run test:coverage` | Coverage report |\n| `npm run build` | Build all output formats (ESM + CJS + IIFE + DTS) |\n| `npm run dev` | Watch build |\n| `npm run typecheck` | TypeScript type checking |\n| `npm run lint` | Biome lint |\n| `npm run lint:fix` | Auto-fix lint issues |\n| `npm run format` | Format source code |\n\n---\n\n## 🤝 Contributing\n\nWe welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n- **Bug reports** — Open an issue with reproduction steps.\n- **Feature requests** — Open an issue with use case and proposed API.\n- **Pull requests** — Fork from `main`, follow code style, add tests.\n\nAll contributors must follow our [Code of Conduct](CONTRIBUTING.md#code-of-conduct).\n\n---\n\n## 🔒 Security\n\nReport security vulnerabilities to **[security@ctrodb.dev](mailto:security@ctrodb.dev)**.\nSee [SECURITY.md](SECURITY.md) for supported versions and disclosure process.\n\n---\n\n## 📄 License\n\n[MIT](LICENSE) © 2026 Ctrotech\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ctrotech-tutor/ctrodb\"\u003eGitHub\u003c/a\u003e •\n  \u003ca href=\"https://ctrodb.vercel.app\"\u003eDocumentation\u003c/a\u003e •\n  \u003ca href=\"https://www.npmjs.com/package/ctrodb\"\u003enpm\u003c/a\u003e •\n  \u003ca href=\"https://github.com/ctrotech-tutor/ctrodb/issues\"\u003eIssues\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctrotech-tutor%2Fctrodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fctrotech-tutor%2Fctrodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fctrotech-tutor%2Fctrodb/lists"}