Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
82 changes: 82 additions & 0 deletions docs/packages/models.md
Original file line number Diff line number Diff line change
@@ -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/<Name>.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/<Name>.ts`).
2. One collection = one `*Raw` class in `src/models/<Name>.ts` extending `BaseRaw<T>` 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<IFoo>` 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<IFoo> implements IFooModel {
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IFoo>>) {
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>('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<T>` / `FindOptions<T>`, never `any` for queries. Return `FindCursor<T>` 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<T>`:
```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.
11 changes: 11 additions & 0 deletions docs/packages/preflight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Pre-flight before reporting done

Run, in the workspace you changed:

```
yarn workspace <pkg-name> typecheck
yarn workspace <pkg-name> lint
yarn workspace <pkg-name> test
```

If a public surface (exported types / functions / components) changed, also run `yarn workspace @rocket.chat/meteor typecheck` to catch downstream breakage.
148 changes: 148 additions & 0 deletions docs/repo-overview.md
Original file line number Diff line number Diff line change
@@ -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/<pkg>/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 <task> --filter=<workspace>` 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 <path> # 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 "<name>"`

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/<random-name>.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/`.
1 change: 1 addition & 0 deletions packages/models/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../../docs/packages/models.md
1 change: 1 addition & 0 deletions packages/models/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
Loading