diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..b249760dde224 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md + +This file provides guidance to coding agents (Claude Code, Codex, Copilot, etc.) when working with code in this repository. + +@docs/repo-overview.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000..43c994c2d3617 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/docs/packages/models.md b/docs/packages/models.md new file mode 100644 index 0000000000000..211913ea19808 --- /dev/null +++ b/docs/packages/models.md @@ -0,0 +1,82 @@ +# `@rocket.chat/models` + +MongoDB data layer. Typed model classes exposed as lazy proxies; CE/EE/dummy bindings swap at runtime. + +## Layout + +``` +src/ + index.ts barrel + proxify accessors + registerServiceModels() + modelClasses.ts re-exports *Raw classes + proxify.ts proxify() / registerModel() — lazy DI + updater.ts UpdaterImpl — typed $set/$unset/$inc/$addToSet + readSecondaryPreferred.ts + models/BaseRaw.ts abstract base + models/.ts one file per collection + dummy/BaseDummy.ts no-op fallback + helpers/ shared pure helpers +``` + +Record types → `@rocket.chat/core-typings`. Model interfaces → `@rocket.chat/model-typings`. This package implements only. + +## Rules + +1. Consumers import the proxy (`Users`, `Rooms`, …) from `@rocket.chat/models`. `new FooRaw(db)` is allowed only inside `registerServiceModels` (this package) and EE startup files (`apps/meteor/ee/server/models/.ts`). +2. One collection = one `*Raw` class in `src/models/.ts` extending `BaseRaw` and implementing `IFooModel`. +3. `super(db, 'foo')` — pass the bare collection name. `getCollectionName` adds the `rocketchat_` prefix. +4. Writes through `BaseRaw` auto-stamp `_updatedAt`. Pass `{ preventSetUpdatedAt: true }` only for append-only event/log collections (e.g. `server_events`, `analytics`). +5. When `trash` is passed to the constructor, `deleteOne` / `deleteMany` / `findOneAndDelete` mirror docs to trash with `__collection__: this.name`. Do not call `this.col.deleteX` directly. +6. Indexes go in `protected override modelIndexes(): IndexDescription[]`. Don't call `this.col.createIndex` from anywhere else. +7. Proxy is read-only. `Users.foo = …` throws `Models accessed via proxify are read-only` outside production. +8. No `db.collection(...)` access from outside this package. New collection → new model. +9. Use `readSecondaryPreferred(db)` for analytics and reports and heavy queries unless the user explicitly requests otherwise. Default reads stay on primary. +10. **No param validation inside model methods.** Don't throw on bad input, don't silently no-op, don't early-return to skip the DB call. Hand the args to MongoDB and let the driver / schema reject. The data the model receives should be considered trusted. Caller should be responsible of validating the data before saving it into the database. +11. **No cross-model calls.** A model file must not import another model. If method `Foo.x()` needs data from `Bar`, the caller fetches from `Bar` first and passes the data in as a parameter. The only allowed imports inside `src/models/*.ts` are `BaseRaw`, types, mongodb primitives, and this package's helpers/updater. +12. **No dependency on settings.** Models must not import `settings`, env-derived config, or feature flags. Pass any required configuration through method parameters or constructor arguments. +13. The name of the function that performs the actual database update must resemble the actual action being performed and be descriptive. +14. Suggest proper indexing for new queries if there's no suitable index for it already. + +## Add a new model + +1. Add the record interface to `core-typings`. +2. Add `interface IFooModel extends IBaseModel` in `packages/model-typings/src/models/IFooModel.ts` and export from that package's `index.ts`. +3. Create `packages/models/src/models/Foo.ts`: + ```ts + export class FooRaw extends BaseRaw implements IFooModel { + constructor(db: Db, trash?: Collection>) { + super(db, 'foo', trash); + } + protected override modelIndexes(): IndexDescription[] { + return [{ key: { someField: 1 }, unique: true }]; + } + } + ``` +4. Re-export from `src/modelClasses.ts`. +5. Add `export const Foo = proxify('IFooModel');` to `src/index.ts`. +6. Register the binding: + - Monolith: alongside existing calls in `apps/meteor/server/models/raw/*`. + - Service broker: add to `registerServiceModels` in `src/index.ts` **only if** the model is consumed directly by microservices. Not every model belongs there — service-broker registration loads the model in every service process. When unsure, ask the user before adding it; default to local-only. + +## Add methods to an existing model + +- Update the `model-typings` interface first; the implementation must satisfy it. +- Use `Filter` / `FindOptions`, never `any` for queries. Return `FindCursor` for list queries; only `await cursor.toArray()` if the caller truly needs an array. +- Use `findOneById(_id, options)` instead of building `{ _id }` filters. +- Multi-field updates use `Updater`: + ```ts + const u = this.getUpdater().set('a', 1).inc('b', 1); + await this.updateFromUpdater({ _id }, u); + ``` + One updater per write — `getUpdateFilter()` throws `Updater is dirty` on reuse and `No changes to update` when empty. +- Type aggregation results: `this.col.aggregate<{ … }>([…])`. +- Don't call `this.col.updateOne/updateMany/insertOne/insertMany/deleteOne/deleteMany` directly — `BaseRaw`'s wrappers handle `_updatedAt` and trash. `this.col` is fine for `aggregate`, `findOneAndReplace`, `bulkWrite`, `watch`. + +@./preflight.md + +## Pitfalls + +- Missing `registerModel` → `Error: Model IFooModel not found` at first proxy access. +- New proxy in `index.ts` without the matching `model-typings` interface → TS errors across consumers. +- Mixed inclusion + exclusion projection keys: `BaseRaw.find` strips exclusion keys when both are present. Pick one mode per call. +- Deprecated APIs in new code: `update(filter, …, { multi: true })` (use `updateMany`); `fields` option (use `projection`). +- `findOneAndUpdate` upsert without `_id` in filter or update auto-injects `_id: new ObjectId().toHexString()` — don't add a manual one on top. diff --git a/docs/packages/preflight.md b/docs/packages/preflight.md new file mode 100644 index 0000000000000..81146d1f8ba6c --- /dev/null +++ b/docs/packages/preflight.md @@ -0,0 +1,11 @@ +## Pre-flight before reporting done + +Run, in the workspace you changed: + +``` +yarn workspace typecheck +yarn workspace lint +yarn workspace test +``` + +If a public surface (exported types / functions / components) changed, also run `yarn workspace @rocket.chat/meteor typecheck` to catch downstream breakage. diff --git a/docs/repo-overview.md b/docs/repo-overview.md new file mode 100644 index 0000000000000..62372738caf49 --- /dev/null +++ b/docs/repo-overview.md @@ -0,0 +1,148 @@ +## Repo shape + +Yarn 4 + Turborepo monorepo. Workspaces: + +- `apps/meteor` — main app (Meteor 3 + React 18 + TypeScript). 90 % of work happens here. +- `apps/uikit-playground` — Apps-Engine UIKit dev playground. +- `packages/*` — community-edition shared libs (`core-services`, `core-typings`, `model-typings`, `models`, `rest-typings`, `i18n`, `ui-client`, `fuselage-ui-kit`, `patch-injection`, `tools`, `agenda`, `cron`, `logger`, etc.). +- `ee/apps/*` — enterprise microservices (`account-service`, `authorization-service`, `ddp-streamer`, `presence-service`, `stream-hub-service`, `queue-worker`, `omnichannel-transcript`, `federation-service`). +- `ee/packages/*` — enterprise libs (`license`, `abac`, `presence`, `omnichannel-services`, `federation-matrix`, `pdf-worker`, `ui-theming`, `network-broker`, `omni-core-ee`, `media-calls`). +- `apps/meteor/ee/server/services` — declared as workspace; EE-only server code lives under `apps/meteor/ee/`. + +For the current Node / Yarn / TypeScript versions check `package.json` (`engines`, `volta`, `devDependencies`) or `.tool-versions`. If available, use `volta`/`nvm` as the node version manager. + +### EE vs CE licensing + +EE code is under a **different license** from the rest of the repo. Rules: + +- CE code MUST NOT carelessly import from EE without explicit human approval. +- EE code MUST NOT be moved into non-EE folders. +- This applies to **any** EE code, anywhere in the tree — not just `ee/` paths. +- Identify EE code by a `LICENSE` file sitting next to it. **License files are recursive**: if a parent folder has an enterprise `LICENSE`, every file in that folder and all its descendants is EE too (e.g. `apps/meteor/ee/LICENSE`, `ee/LICENSE`, `ee/packages//LICENSE`). +- When in doubt, walk up the tree until you find a `LICENSE`. + +## Top-level commands (run from repo root) + +``` +yarn build # turbo build all workspaces +yarn build:services # only ee/apps services + deps +yarn dev # dev server for @rocket.chat/meteor (parallel turbo) +yarn dsv # meteor dev (`meteor npm run dev`) inside apps/meteor +yarn ms # microservices dev (TRANSPORTER=TCP by default) +yarn lint # turbo lint all workspaces +yarn testunit # turbo testunit all workspaces +``` + +`yarn` (no args) bootstraps deps. `turbo run --filter=` to scope. + +## Inside `apps/meteor` + +Most relevant scripts: + +``` +yarn dev # meteor run, excludes legacy/cordova archs +yarn dsv # alias for meteor npm run dev (same) +yarn ms # microservices mode (TRANSPORTER=TCP) +yarn obj:dev # TEST_MODE=true yarn dev (required for playwright) +yarn ha:start / ha:add # multi-instance dev (HA / horizontal scale) + +yarn lint # stylelint + meteor lint + eslint . +yarn eslint # eslint with cache; `:fix` variant available +yarn stylelint # CSS only +yarn typecheck # meteor lint + tsc --noEmit (8 GB heap) + +yarn testunit # runs all 3: definition + jest + server-mocha+nyc +yarn .testunit:jest # jest only (TZ=UTC, allowJs:false) +yarn .testunit:server # mocha for server (.mocharc.js) +yarn .testunit:definition # mocha for definition (.mocharc.definition.js) +yarn testapi # mocha REST integration (.mocharc.api.js) — needs running server (needs server with TEST_MODE=true) +yarn testapi:livechat # livechat REST integration (needs server with TEST_MODE=true) +yarn test:e2e # playwright (needs server with TEST_MODE=true) +yarn test:e2e ./tests/e2e/foo.spec.ts # single suite (needs server with TEST_MODE=true) +``` + +Single test file: + +- Jest: `yarn .testunit:jest path/to/file.spec.ts` +- Mocha server: `yarn .testunit:server -- path/to/file.spec.ts` +- Mocha API: `yarn testapi -- --grep ""` + +Mocha specs are enumerated in `apps/meteor/.mocharc.js` — new server unit tests must match an entry there or be added to the glob list. + +E2E env vars: `BASE_URL=...`, `PWDEBUG=1`. Server must be started with `TEST_MODE=true`. + +## Architecture (apps/meteor) + +Legacy Meteor layout coexists with newer structure. Three roots load code in parallel: + +- `app/` — legacy per-feature modules (`app/api`, `app/livechat`, `app/authorization`, ...). Contains `client/`, `server/`, sometimes `lib/` per feature. Loaded via `server/importPackages.ts` and `client/importPackages.ts`. +- `server/` — newer server entry points: `services/`, `methods/`, `publications/`, `routes/`, `lib/`, `startup/`, `cron/`, `settings/`, `models.ts`. `main.ts` is the server bootstrap. +- `client/` — React app: `views/`, `components/`, `hooks/`, `providers/`, `contexts/`, `stores/`, `cachedStores/`, `router/`, `sidebar/`, `navbar/`, `apps/`, `startup/`. Heavy use of TanStack Query, Fuselage UI, i18next. +- `ee/` — enterprise overlay: `ee/server/`, `ee/app/`, `ee/client/`. Loaded only when license permits. +- `definition/` — shared types local to meteor (most types live in `packages/core-typings` / `packages/model-typings`). +- `tests/` — `unit/` (mocha+jest), `end-to-end/` (mocha REST), `e2e/` (playwright), `mocks/`, `data/`. + +Models: declared in `packages/model-typings`, implemented in `packages/models/src/models/*` (BaseRaw is the MongoDB raw collection wrapper). Meteor binds them via `apps/meteor/server/models.ts`. + +Services: `apps/meteor/server/services/*` are local-broker services consumed via `@rocket.chat/core-services`. EE microservices in `ee/apps/*` register against the network broker (Moleculer). + +REST API: routes registered in `app/api/server/v1/*.ts` against the typed router from `packages/rest-typings`. Client calls go through `@rocket.chat/api-client`. + +Settings: declared in `apps/meteor/server/settings/`. Auto-generates per-setting permission `change-setting-{id}`; admins bypass via `view-privileged-setting`. + +### CE / EE hook pattern + +Do **not** roll ad-hoc hook registries. Use `@rocket.chat/patch-injection`: + +```ts +// CE side +import { makeFunction } from '@rocket.chat/patch-injection'; +export const doX = makeFunction((arg: A): B => { /* default impl */ }); + +// EE side (only loaded when license active) +doX.patch((next, arg) => { /* override or wrap next(arg) */ }); +``` + +Patches stack and run in order; pass `condition` for license/feature gates. + +## Test conventions + +- Mocha + chai for server/unit + REST API integration. Jest only where wired (search `jest.config.ts`). +- Playwright lives in `apps/meteor/tests/e2e/` (`.spec.ts`). Page Objects under `tests/e2e/page-objects/`. Locators must use `getByRole` / `getByLabel` / `getByText` — `data-qa-id` and `getByTestId` are last resort. Locator names start with `btn`/`link`/`input`/`select`/`checkbox`/`text`. See `apps/meteor/tests/e2e/README.md` and `.cursor/rules/playwright.mdc`. +- REST integration tests assume a running server (default `http://localhost:3000`). They share state — order can matter. +- Don't mock the database in integration tests — hit the real Mongo. + +## Changesets + +**Always confirm with the human before adding a changeset.** + +Every user-visible change needs a changeset: + +``` +yarn changeset +``` + +Creates `.changeset/.md` with affected workspaces and bump type (`patch`/`minor`/`major`) + a short release-note line. **Only changesets that bump `'@rocket.chat/meteor'` show up in the public release notes** — so it must be listed for any user-visible change, even when the actual code edit is in a sub-package (e.g. `@rocket.chat/ui-client`, `@rocket.chat/i18n`). Still bump every other workspace whose published surface changed; `@rocket.chat/meteor` is added on top of those. + +## Migrations (Meteor) + +`yarn migration:add` (in `apps/meteor`) scaffolds a server migration. The migration runner is in `apps/meteor/server/startup/migrations.ts` (search for it). + +## PR / commit conventions + +- Title prefix: `feat:`, `fix:`, `refactor:`, `chore:`, `docs:`, `ci:`, `test:`, `i18n:`, `regression:`. See `.github/PULL_REQUEST_TEMPLATE.md`. +- Branch off `develop`; `master` lags. Releases tagged from `develop`. +- CI: `.github/workflows/ci.yml` is the monolith pipeline; unit/storybook/E2E/code-check split into peer files. + +## Permissions / authorization + +Two layers: RBAC (`Authorization` service, role-based) and ABAC (EE, `@rocket.chat/abac` + `ee/packages/abac`). Settings auto-generate permissions; check for `view-privileged-setting` before assuming an admin can read a setting. + +## Useful entrypoints when lost + +- Server bootstrap: `apps/meteor/server/main.ts` → `server/startup/`. +- Client bootstrap: `apps/meteor/client/startup/`. +- Method/publication registry: `apps/meteor/server/methods/`, `server/publications/`. +- REST routes: `apps/meteor/app/api/server/v1/*`. +- License gating: `ee/packages/license/src/` (`License.has(...)`). +- Model definitions: `packages/models/src/models/`, types in `packages/model-typings/src/models/`. diff --git a/packages/models/AGENTS.md b/packages/models/AGENTS.md new file mode 100644 index 0000000000000..7e3d85d79793c --- /dev/null +++ b/packages/models/AGENTS.md @@ -0,0 +1 @@ +@../../docs/packages/models.md diff --git a/packages/models/CLAUDE.md b/packages/models/CLAUDE.md new file mode 100644 index 0000000000000..43c994c2d3617 --- /dev/null +++ b/packages/models/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md