{"id":25354162,"url":"https://github.com/owservable/owservable","last_synced_at":"2025-08-20T11:17:07.152Z","repository":{"id":40561299,"uuid":"388191053","full_name":"owservable/owservable","owner":"owservable","description":"owservable fastify backend","archived":false,"fork":false,"pushed_at":"2025-03-26T08:54:12.000Z","size":3474,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-04T10:44:12.672Z","etag":null,"topics":["owservable"],"latest_commit_sha":null,"homepage":"https://owservable.github.io/owservable/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/owservable.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2021-07-21T17:09:47.000Z","updated_at":"2025-03-26T08:54:17.000Z","dependencies_parsed_at":"2023-11-21T12:28:00.593Z","dependency_job_id":"83febf8b-b9c7-455c-b991-021067713d13","html_url":"https://github.com/owservable/owservable","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owservable%2Fowservable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owservable%2Fowservable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owservable%2Fowservable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owservable%2Fowservable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/owservable","download_url":"https://codeload.github.com/owservable/owservable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247958704,"owners_count":21024823,"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":["owservable"],"created_at":"2025-02-14T19:59:16.344Z","updated_at":"2025-08-20T11:17:07.144Z","avatar_url":"https://github.com/owservable.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"![owservable](https://avatars0.githubusercontent.com/u/87773159?s=75)\n\n# Owservable\n\nA reactive backend library for Node.js applications that provides real-time MongoDB change streams, reactive data stores, and automated task scheduling. Built with RxJS and TypeScript.\n\n**Owservable is a replacement for [Reactive Stack JS](https://github.com/reactive-stack-js).**\n\n## 🚀 Features\n\n- **Real-time MongoDB Integration**: MongoDB change streams with reactive observables\n- **Reactive Data Stores**: Count, Document, and Collection stores with automatic updates\n- **WebSocket Client Management**: Built-in client connection and subscription handling\n- **Task Scheduling**: Automated cronjobs, watchers, and workers\n- **Action Pattern**: Structured business logic with multiple execution contexts\n- **Data Middleware**: Transform and filter data based on user permissions\n- **Type Safety**: Full TypeScript support with comprehensive type definitions\n\n## 📦 Installation\n\n```bash\nnpm install owservable\n```\n\nor\n\n```bash\nyarn add owservable\n```\n\nor\n\n```bash\npnpm add owservable\n```\n\n## 🏗️ Requirements\n\n- **Node.js**: \u003e= 20\n- **MongoDB**: \u003e= 3.6 (Replica Set required for change streams)\n- **TypeScript**: Recommended for full type safety\n\n## 🔧 MongoDB Setup\n\nOwservable requires MongoDB to be running as a Replica Set to enable change streams:\n\n```bash\n# Start MongoDB as a single-node replica set\nmongod --replSet rs0\n\n# Initialize the replica set\nmongo --eval \"rs.initiate()\"\n```\n\nFor more details, see: [MongoDB Change Streams on localhost with Node.js](http://stojadinovic.net/2020/07/05/mongodb-change-streams-on-localhost-with-nodejs/)\n\n## 🚀 Quick Start\n\n### 1. Basic Server Setup\n\n```typescript\nimport { \n  OwservableClient, \n  MongoDBConnector, \n  processModels,\n  IConnectionManager \n} from 'owservable';\n\n// Initialize MongoDB connection\nconst mongoConnector = new MongoDBConnector();\nawait mongoConnector.connect('mongodb://localhost:27017/myapp');\n\n// Process your Mongoose models\nawait processModels('./models');\n\n// Implement connection manager\nclass MyConnectionManager implements IConnectionManager {\n  async connected(jwt: string): Promise\u003cvoid\u003e {\n    // Handle client connection\n  }\n  \n  async disconnected(): Promise\u003cvoid\u003e {\n    // Handle client disconnection\n  }\n  \n  async checkSession(): Promise\u003cany\u003e {\n    // Handle session validation\n    return { refresh_in: 300000 };\n  }\n  \n  ping(ms: number): void {\n    // Handle ping updates\n  }\n  \n  location(path: string): void {\n    // Handle location updates\n  }\n  \n  get user(): any {\n    // Return current user\n    return this.currentUser;\n  }\n}\n\n// Create owservable client\nconst connectionManager = new MyConnectionManager();\nconst client = new OwservableClient(connectionManager);\n\n// Handle client messages\nclient.subscribe({\n  next: (message) =\u003e {\n    // Forward to WebSocket clients\n    websocket.send(JSON.stringify(message));\n  },\n  error: (error) =\u003e console.error('Client error:', error)\n});\n```\n\n### 2. WebSocket Integration\n\n```typescript\nimport { WebSocketServer } from 'ws';\n\nconst wss = new WebSocketServer({ port: 8080 });\n\nwss.on('connection', (ws) =\u003e {\n  const client = new OwservableClient(connectionManager);\n  \n  // Subscribe to client updates\n  client.subscribe({\n    next: (message) =\u003e ws.send(JSON.stringify(message)),\n    error: (error) =\u003e console.error('Error:', error)\n  });\n  \n  // Handle incoming messages\n  ws.on('message', async (data) =\u003e {\n    const message = JSON.parse(data.toString());\n    await client.consume(message);\n  });\n  \n  // Handle disconnect\n  ws.on('close', () =\u003e {\n    client.disconnected();\n  });\n  \n  // Start ping\n  client.ping();\n});\n```\n\n### 3. Reactive Data Stores\n\n```typescript\nimport { storeFactory, EStoreType } from 'owservable';\n\n// Count store - returns only document count\nconst countStore = storeFactory(EStoreType.COUNT, 'users', 'user-count');\ncountStore.config = {\n  query: { active: true },\n  fields: {}\n};\n\n// Collection store - returns array of documents\nconst collectionStore = storeFactory(EStoreType.COLLECTION, 'users', 'user-list');\ncollectionStore.config = {\n  query: { active: true },\n  fields: { name: 1, email: 1 },\n  sort: { name: 1 },\n  page: 1,\n  pageSize: 10\n};\n\n// Document store - returns single document\nconst documentStore = storeFactory(EStoreType.DOCUMENT, 'users', 'user-profile');\ndocumentStore.config = {\n  query: { _id: 'user123' },\n  fields: { name: 1, email: 1, profile: 1 }\n};\n```\n\n### 4. Task Scheduling\n\n```typescript\nimport { \n  initiateCronjobs, \n  initiateWatchers, \n  initiateWorkers \n} from 'owservable';\n\n// Initialize cronjobs\nawait initiateCronjobs('./cronjobs');\n\n// Initialize file watchers\nawait initiateWatchers('./watchers');\n\n// Initialize background workers\nawait initiateWorkers('./workers');\n```\n\n### 5. Data Middleware\n\n```typescript\nimport { DataMiddlewareMap } from 'owservable';\n\n// Register middleware for collection\nDataMiddlewareMap.set('users', async (payload, user) =\u003e {\n  // Filter sensitive data based on user permissions\n  if (!user.isAdmin) {\n    payload.data = payload.data.map(doc =\u003e ({\n      ...doc,\n      email: '***@***.***' // Hide emails for non-admins\n    }));\n  }\n  return payload;\n});\n```\n\n## 📚 Core Components\n\n### OwservableClient\n\nThe main client class that manages WebSocket connections and subscriptions:\n\n```typescript\nconst client = new OwservableClient(connectionManager);\n\n// Handle different message types\nawait client.consume({\n  type: 'subscribe',\n  target: 'user-list',\n  scope: 'collection',\n  observe: 'users',\n  config: {\n    query: { active: true },\n    fields: { name: 1, email: 1 }\n  }\n});\n```\n\n### Store Types\n\n#### CollectionStore\nReturns arrays of documents with real-time updates:\n\n```typescript\nconst store = storeFactory(EStoreType.COLLECTION, 'posts', 'post-list');\nstore.config = {\n  query: { published: true },\n  fields: { title: 1, content: 1, author: 1 },\n  sort: { createdAt: -1 },\n  populate: ['author'],\n  page: 1,\n  pageSize: 20,\n  incremental: true // Enable incremental updates\n};\n```\n\n#### CountStore\nReturns document counts with real-time updates:\n\n```typescript\nconst store = storeFactory(EStoreType.COUNT, 'users', 'user-count');\nstore.config = {\n  query: { active: true }\n};\n```\n\n#### DocumentStore\nReturns single documents with real-time updates:\n\n```typescript\nconst store = storeFactory(EStoreType.DOCUMENT, 'users', 'current-user');\nstore.config = {\n  query: { _id: userId },\n  fields: { name: 1, email: 1, preferences: 1 }\n};\n```\n\n### MongoDB Integration\n\n#### Observable Models\nMonitor MongoDB collections for changes:\n\n```typescript\nimport { observableModel } from 'owservable';\nimport UserModel from './models/User';\n\nconst userObservable = observableModel(UserModel);\nuserObservable.subscribe({\n  next: (change) =\u003e {\n    console.log('User collection changed:', change);\n  }\n});\n```\n\n#### Observable Database\nMonitor entire database for changes:\n\n```typescript\nimport { observableDatabase } from 'owservable';\n\nconst dbObservable = observableDatabase();\ndbObservable.subscribe({\n  next: (change) =\u003e {\n    console.log('Database changed:', change);\n  }\n});\n```\n\n### Action Pattern\n\nCreate structured business logic with the action pattern:\n\n```typescript\nimport { Action, ActionInterface } from 'owservable';\n\nclass SendEmailAction extends Action implements ActionInterface {\n  protected _description = 'Send email notification';\n  \n  async handle(to: string, subject: string, body: string): Promise\u003cvoid\u003e {\n    // Email sending logic\n    console.log(`Sending email to ${to}: ${subject}`);\n  }\n  \n  description(): string {\n    return this._description;\n  }\n}\n\n// Use in cronjobs, workers, or watchers\nconst emailAction = new SendEmailAction();\nawait emailAction.handle('user@example.com', 'Welcome!', 'Hello World');\n```\n\n## 🔧 Configuration\n\n### Environment Variables\n\n```bash\n# MongoDB connection\nMONGODB_URI=mongodb://localhost:27017/myapp\n\n# WebSocket server\nWS_PORT=8080\n\n# JWT settings\nJWT_SECRET=your-secret-key\nJWT_EXPIRATION=24h\n```\n\n### TypeScript Configuration\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"ES2020\"],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true\n  }\n}\n```\n\n## 📖 Advanced Usage\n\n### Custom Store Implementation\n\n```typescript\nimport { AStore, EStoreType } from 'owservable';\n\nclass CustomStore extends AStore {\n  constructor(model: Model\u003cany\u003e, target: string) {\n    super(model, target);\n    this._type = EStoreType.COLLECTION;\n  }\n  \n  protected async load(change: any): Promise\u003cvoid\u003e {\n    // Custom loading logic\n    const data = await this.customQuery();\n    this.emitMany(Date.now(), this._subscriptionId, { data });\n  }\n}\n```\n\n### Performance Optimization\n\n```typescript\n// Use incremental updates for large collections\nstore.config = {\n  incremental: true,\n  page: 1,\n  pageSize: 50\n};\n\n// Optimize queries with indexes\nawait addIndexToAttributes(model, ['field1', 'field2']);\n\n// Use field projection\nstore.config = {\n  fields: { \n    name: 1, \n    email: 1,\n    _id: 0 // Exclude _id\n  }\n};\n```\n\n## 🧪 Testing\n\n```bash\nnpm test\n```\n\n## 📖 Documentation\n\n- **Main Website**: [owservable.github.io](https://owservable.github.io/)\n- **TypeDoc Documentation**: [owservable.github.io/owservable/docs/](https://owservable.github.io/owservable/docs/)\n- **Test Coverage**: [owservable.github.io/owservable/coverage/](https://owservable.github.io/owservable/coverage/)\n- **Udemy Course**: [Reactive Stack Course](https://www.udemy.com/course/reactive-stack/)\n\n## 🔗 Related Projects\n\n- [@owservable/actions](https://github.com/owservable/actions) - Action pattern implementation\n- [@owservable/folders](https://github.com/owservable/folders) - File system utilities\n- [@owservable/fastify-auto-routes](https://github.com/owservable/fastify-auto-routes) - Fastify auto routing\n\n## 🛣️ Roadmap\n\n- [ ] Web Server abstraction\n- [ ] Database abstraction\n- [ ] Client-side datastore abstraction\n- [ ] Large Data handling\n- [ ] Server-Side Events\n- [ ] Redis integration\n- [ ] GraphQL subscriptions\n- [ ] Microservices support\n\n## 📄 License\n\nLicensed under [The Unlicense](./LICENSE).\n\n## 🤝 Contributors\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n        \u003ca href=\"http://stojadinovic.net\"\u003e\n            \u003cimg src=\"https://avatars.githubusercontent.com/u/112515?v=4\" width=\"100px;\" alt=\"\"/\u003e\n            \u003cbr /\u003e\n            \u003csub\u003e\u003cb\u003ePredrag Stojadinović\u003c/b\u003e\u003c/sub\u003e\n            \u003cbr /\u003e\n        \u003c/a\u003e\n        \u003cbr /\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Code\"\u003e💻\u003c/a\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Documentation\"\u003e📖\u003c/a\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Ideas \u0026 Planning\"\u003e🤔\u003c/a\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Project Management\"\u003e📆\u003c/a\u003e\n        \u003ca href=\"https://github.com/owservable/owservable/commits?author=cope\" title=\"Tests\"\u003e⚠️\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.\n\nContributions of any kind welcome!\n\n## 📊 UML Diagram\n\nCheckout the [UML diagram](https://raw.githubusercontent.com/owservable/owservable/main/uml.png) for a visual overview of the architecture.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowservable%2Fowservable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fowservable%2Fowservable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowservable%2Fowservable/lists"}