diff --git a/AGENTS.md b/AGENTS.md index f981c0889..2f44ae354 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,19 +59,20 @@ The plugin/application loader. Applications export a `handleApplication(scope)` Files within a component are discovered via micromatch glob patterns and automatically mapped to URL paths. **Server** (`server/`) -Two HTTP stacks coexist: +Multiple HTTP entry points coexist: -- **Native layer** (`server/http.ts`) — direct socket handling for HTTP/1.1, HTTPS, HTTP/2, and WebSockets in one path; highest performance -- **Fastify layer** (`server/fastifyRoutes.ts`) — used for legacy custom functions; wraps Fastify with autoload +- **Native layer** (`server/http.ts`) — direct socket handling for application-level HTTP/1.1, HTTPS, HTTP/2, and WebSockets in one path; highest performance. Most user traffic goes through here. +- **Operations API** (`server/operationsServer.ts`) — Fastify-based JSON operations API (`{operation: 'create_table', ...}`); internal/admin surface. +- **Custom Functions (legacy)** (`server/fastifyRoutes.ts`) — legacy Fastify autoload for user-defined routes. Don't add new code here. -All inbound protocols (REST, GraphQL, MQTT, NATS, WebSockets) eventually resolve to the same **Resource interface**. +All inbound protocols (REST, GraphQL, MQTT, NATS, WebSockets) eventually resolve to the same **Resource interface**. See `server/DESIGN.md` for the file-by-file map and the `http.ts` section index. **Resources** (`resources/`) The universal abstraction. Everything that can be queried or mutated — database tables, caches, message topics, custom endpoints — extends `Resource` (`resources/Resource.ts`). Static methods (`Resource.get`, `Resource.put`, `Resource.post`, `Resource.delete`, `Resource.patch`, `Resource.subscribe`) are the entry points and are automatically wrapped with `transactional()` for transaction management. Override instance methods (`get`, `put`, etc.) for custom behavior. -`Table.ts` is the database table implementation (~177KB) — the most complex file in the codebase. +`Table.ts` is the database table implementation (4744 lines, one giant `makeTable()` factory) — the most complex file in the codebase. **Use `resources/DESIGN.md` as a section index instead of reading top-to-bottom.** **Data Layer** (`dataLayer/`) Legacy translation modules plus SQL translation (`sqlTranslator/`) via AlaSQL; these should be avoided. The storage engine is selectable via `HARPER_STORAGE_ENGINE=lmdb`. @@ -80,7 +81,62 @@ Legacy translation modules plus SQL translation (`sqlTranslator/`) via AlaSQL; t YAML-based. `configUtils.js` parses config; `RootConfigWatcher.ts` enables hot reload. Environment variables override YAML values. **Utility** (`utility/`) -Logging, error types, helpers, async utilities. +Logging, error types, helpers, async utilities. Most-used: `utility/hdbTerms.ts` (global constants), `utility/logging/harper_logger.js`, `utility/errors/hdbError.js`. + +--- + +## Repository map + +Use this to land in the right folder before grepping. Every top-level folder is listed; deeper docs are noted where they exist. + +### Source — covered above + +- **`components/`** — plugin/app loader. Entry: `Scope.ts`, `OptionsWatcher.ts`. Tests: `unitTests/components/`. +- **`server/`** — HTTP/WS/MQTT/etc. Entry: `operationsServer.ts` (boot), `http.ts` (native HTTP). **See [server/DESIGN.md](server/DESIGN.md).** Tests: `unitTests/server/`. +- **`resources/`** — universal Resource abstraction; tables. Entry: `Resource.ts`, `Table.ts`. **See [resources/DESIGN.md](resources/DESIGN.md).** Tests: `unitTests/resources/`. +- **`dataLayer/`** — legacy translation modules (`insert.js`, `search.js`, `update.js`). **Avoid for new code.** Tests: `unitTests/dataLayer/`. +- **`config/`** — YAML config + hot reload. Entry: `configUtils.js`, `RootConfigWatcher.ts`. Tests: `unitTests/config/`. +- **`utility/`** — logging, errors, helpers. Tests: `unitTests/utility/`. + +### Other source folders + +- **`bin/`** — CLI entry points. `harper.js` is the executable; `run.js` initializes and runs the server; `cliOperations.js` translates CLI args → API operations. Tests: `unitTests/bin/`. **Don't look here for** business logic. +- **`security/`** — auth, authz, certificate handling, context. Entry: `jsLoader.ts` exposes `getContext()`, `getResponse()`, `getUser()`; `user.ts` for User/Role; `certificateVerification/` for TLS validation; `data_objects/` for permission/role models. Tests: `unitTests/security/`. +- **`sqlTranslator/`** — SQL → internal operations via AlaSQL AST. Entry: `sqlTranslator/index.js` exports `evaluateSQL`, `processAST`, `convertSQLToAST`, `checkASTPermissions`. **Legacy — avoid for new code.** Tests: `unitTests/sqlTranslator/`. +- **`validation/`** — input shape validation (Joi + `validate.js`). Entry: `validationWrapper.js`. **Not authorization** — that's in `security/`. Tests: `unitTests/validation/`. +- **`upgrade/`** — version-upgrade orchestration. Entry: `directivesManager.js` exports `processDirectives()`. Per-version logic in `directives/`. Tests: `integrationTests/upgrade/`. +- **`launchServiceScripts/`** — thin launchers that delegate to `server/operationsServer.ts`. `checkNodeVersion.js` is the pre-flight Node version check. +- **`json/`** — system schema definitions. `systemSchema.json` defines built-in tables (`hdb_user`, `hdb_role`, `hdb_permission`). Loaded at startup; no code. + +### Non-source + +- **`bin/`** — covered above (it's source). +- **`benchmarks/`** — HNSW vector-search benchmark only (`hnsw-search.js`). Stand-alone; not part of CI. +- **`build-tools/`** — shell scripts for the build pipeline (`build.sh`, `build-studio.sh`, `download-prebuilds.js`). No tests. +- **`dev/`** — single dev utility (`sync-commits.js`) for cross-repo commit syncing. Not runtime. +- **`integrationTests/`** — end-to-end tests against a built distribution. Run with `npm run test:integration` / `npm run test:integration:all`. Subdirs mirror source. See `integrationTests/README.md`. +- **`unitTests/`** — Mocha unit tests; subdir per source layer. Run with `npm run test:unit:`. +- **`static/`** — assets only: `defaultConfig.yaml`, `ascii_logo.txt`. + +### Top-level docs to consult + +- **[DESIGN.md](DESIGN.md)** — running list of non-obvious internals (RecordObject prototype, getFromSource timing, blob orphan cleanup). Read this before debugging anything record-store-related. +- **[dependencies.md](dependencies.md)** — rationale for every npm dependency. Required reading before adding a new package. +- **[storage-format.md](storage-format.md)** — on-disk layout (RocksDB/LMDB). +- **[CONTRIBUTING.md](CONTRIBUTING.md)** — contribution workflow. + +--- + +## Detailed navigation + +For megafiles and complex subsystems, jump to the section index instead of reading top-to-bottom: + +| If you are touching… | Read first | +| ------------------------------------------------------ | ------------------------------------------ | +| Anything in `resources/` (especially `Table.ts`) | [resources/DESIGN.md](resources/DESIGN.md) | +| HTTP/WS/MQTT, middleware ordering, content types | [server/DESIGN.md](server/DESIGN.md) | +| Record-store internals (commit timing, blobs, encoder) | [DESIGN.md](DESIGN.md) | +| Adding a dependency | [dependencies.md](dependencies.md) | --- diff --git a/resources/DESIGN.md b/resources/DESIGN.md new file mode 100644 index 000000000..ec1d6c498 --- /dev/null +++ b/resources/DESIGN.md @@ -0,0 +1,194 @@ +# resources/ — Navigation Guide + +The Resource layer is Harper's universal abstraction: all queryable/mutable things (tables, caches, message topics, custom endpoints) extend `Resource`. Inbound protocols (REST, GraphQL, MQTT, NATS, WebSockets) all converge on this interface. + +**Read this when:** you're touching the read/write path, authorization, subscriptions, or table CRUD semantics. + +See also: `../DESIGN.md` for cross-cutting non-obvious internals (RecordObject prototype, `getFromSource` timing, blob orphan cleanup). + +--- + +## File overview + +| File | Purpose | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `Resource.ts` (800 lines) | Base class; `transactional()` wrapper; method routing | +| `Table.ts` (4744 lines) | Table-as-Resource implementation. Factory `makeTable()` returns a `TableResource` subclass per table. **See section index below.** | +| `Resources.ts` | Registry mapping URL paths → Resource classes | +| `RequestTarget.ts` | Parses path/query into a structured target | +| `ResourceInterface.ts` | Type definitions (`Context`, `Record`, etc.) | +| `RecordEncoder.ts` | msgpack encoding + `entryMap` (record → storage entry) | +| `IterableEventQueue.ts` | Async iterable used for subscriptions and streaming responses | +| `transaction.ts` | Per-request transaction object stored in `contextStorage` | +| `auditStore.ts` | Append-only audit log records | +| `nodeIdMapping.ts` | Maps node IDs ↔ timestamps for replication ordering | +| `openApi.ts` | Generates OpenAPI/JSON Schema from `@export` schemas | +| `analytics/` | Telemetry recording (separate from monitoring) | + +--- + +## `Resource.ts` — base class + +The class itself spans **lines 41–461**. + +| Member | Line | Notes | +| ----------------------------------------------------------- | --------------------- | -------------------------------------------------------------------- | +| `Resource` class declaration | 41 | Generic over `Record extends object` | +| `constructor(identifier, source)` | 48 | | +| `static get` (transactional) | 57 | Entry point — wraps instance `get()` | +| `static put` | 91 | | +| `static patch` | 117 | | +| `static delete` | 129 | | +| `static getNewId` | 139 | | +| `static create` (overloads) | 150–192 | | +| `static invalidate` | 192 | | +| `static post` | 199 | | +| `static update` | 207 | | +| `static connect` | 214 | WebSocket-like persistent connection | +| `static subscribe` | 225 | | +| `static publish` | 232 | | +| `static search` | 244 | | +| `static query` | 257 | | +| `static copy` / `static move` | 268 / 279 | | +| `static parsePath` | 320 | URL → `RequestTarget` | +| `static getResource` | 351 | Path → Resource class lookup | +| `allowRead` / `allowUpdate` / `allowCreate` / `allowDelete` | 393 / 397 / 401 / 405 | Default permission hooks — override per resource | +| `getId` / `getContext` / `getCurrentUser` | 412 / 419 / 427 | Instance helpers | +| `transactional()` wrapper | 475 | **Do not remove from static methods** — breaks transaction isolation | +| `missingMethod` / `allowedMethods` | 688 / 698 | 405 response helpers | +| `transformForSelect` | 735 | Select-clause expansion | + +--- + +## `Table.ts` — section index + +4744 lines, one giant `makeTable()` factory (line 136 → 4607) that returns a `TableResource extends Resource` class. Jump by responsibility: + +### Setup & factory (lines 136–218) + +- `makeTable(options)` opens at **136** +- Attribute parsing & primary key detection: **155–175** +- Replication wiring: **185–195** +- `class Updatable` (RecordObject prototype): **200–218** — provides `getUpdatedTime`/`getExpiresAt`, `addTo`/`subtractFrom` + +### `TableResource` class begins: line **220** + +### Static configuration (228–274) + +Properties: `name`, `primaryStore`, `auditStore`, `primaryKey`, `indices`, `audit`, `databasePath`, `attributes`, `replicate`, `sealed`, `splitSegments`, `getResidencyById`, `dbisDB`, `schemaDefined`. + +### Resource registry / sub-class resolution (263–639) + +- `static sourcedFrom(source, options)` — **263–527** (the largest static; sets up cache/source hierarchy) +- `static get isCaching` / `shouldRevalidateEvents` — **528 / 534** +- `static getResource(...)` — **546–614** (path-to-class lookup) +- `static _updateResource` — **615** +- `ensureLoaded()` — **625** + +### Lifecycle / admin (640–941) + +- `static getNewId()` — **640–813** (id generation strategies: UUID, autoincrement, prefix, time-based) +- `static setTTLExpiration` — **814** +- Residency: `static getResidencyRecord` (**832**), `setResidency` (**836**), `setResidencyById` (**848**), `getResidency` (**860**) +- `static enableAuditing` — **897** +- `static coerceId` — **908** +- `static async dropTable` — **913** + +### Read path (942–1055) + +- `get()` overloads & impl — **942–1055** + +### Authorization hooks (1056–1165) + +- `allowRead` — **1056** +- `allowUpdate` — **1104** +- `allowCreate` — **1131** +- `allowDelete` — **1158** + +### Write path — public API (1167–1588) + +- `update()` overloads & impl — **1167–1239** +- `save()` — **1240** +- `addTo()` / `subtractFrom()` — **1253 / 1264** +- `getMetadata` / `getRecord` / `getChanges` / `_setChanges` / `setRecord` — **1271–1286** +- `invalidate()` — **1287** +- `static operation()` — **1484** +- `put()` — **1494** +- `create()` — **1533** +- `patch()` — **1571** + +### Write path — internals (1589–2021) + +- **`_writeUpdate()` — 1589–1942** (the central write routine, ~353 lines). Handles versioning, conflict resolution, audit, residency, replication metadata, and blob orphan tracking. The `write.skipped` flag mentioned in `../DESIGN.md` is set in this method's early-return paths. +- `_writeInvalidate()` — **1301** +- `_writeRelocate()` — **1348** +- `static _recordRelocate()` — **1408** +- `static evict()` — **1439** +- `lock()` — **1481** +- `async delete()` — **1943** +- `_writeDelete()` — **1970** + +### Search & query (2022–2639) + +- `search()` — **2022–2271** (the query engine; index selection, filter evaluation) +- `static transformToOrderedSelect` — **2272–2439** (select-clause ordering) +- `static transformEntryForSelect` — **2440–2639** (record → response shape) + +### Pub/Sub (2640–3028) + +- `async subscribe()` — **2640–2937** (~297 lines — subscription request handling, replay, cursor management) +- `static subscribeOnThisThread` — **2938** +- `doesExist()` — **2941** +- `publish()` — **2952** +- `_writePublish()` — **2972** + +### Validation (3029–3197) + +- `validate(record, patch?)` — **3029–3197** (~168 lines — schema enforcement, computed attributes, attribute coercion) + +### Stats & admin (3198–3501) + +- `getUpdatedTime` — **3198** +- `static async addAttributes` — **3201** +- `static async removeAttributes` — **3218** +- `static getSize` / `getAuditSize` / `getStorageStats` — **3232 / 3239 / 3247** +- `static async getRecordCount` — **3255** +- `static updatedAttributes` — **3318–3501** (~183 lines — schema diff machinery) + +### Computed / history / cleanup (3502–3598) + +- `static setComputedAttribute` — **3502** +- `static async deleteHistory` — **3514** +- `static async *getHistory` — **3537** (generator) +- `static async getHistoryOfRecord` — **3555** +- `static clear` / `cleanup` / `_readTxnForContext` — **3589 / 3592 / 3595** + +### After the class + +- `async function getFromSource()` — **4062–~4400** (cache miss → source load; see `../DESIGN.md` for the resolve-before-commit timing trap) +- Helpers (`coerceType`, `isDescendantId`, etc.) — **4607+** + +--- + +## "Where is X" cheat sheet + +| Question | File:line | +| --------------------------------------------------- | --------------------------------------------------------------------------------------- | +| How is a CRUD request authorized? | `Table.ts:1056–1165` (allowRead/Update/Create/Delete), `Resource.ts:393–410` (defaults) | +| Where does versioning / conflict resolution happen? | `Table.ts:1589` (`_writeUpdate`) | +| How does `search()` choose an index? | `Table.ts:2022` | +| How are subscriptions replayed? | `Table.ts:2640` | +| How is the response body shaped (select clause)? | `Table.ts:2440` (`transformEntryForSelect`) | +| Where is record-level TTL evaluated? | `Table.ts:814` (`setTTLExpiration`), `Updatable.getExpiresAt` (`Table.ts:206`) | +| How are residencies enforced (replication)? | `Table.ts:832–895` | +| How is the RecordObject prototype applied? | `RecordEncoder.ts` (see `../DESIGN.md`) | +| Where is the per-request transaction stored? | `transaction.ts` + `contextStorage` (AsyncLocalStorage) | + +--- + +## Conventions + +- **Never** remove `transactional()` from a static method on `Resource` — it owns transaction context lifetime. +- New `Resource` subclasses should override **instance** methods (`get`, `put`, ...) for behavior; static methods are the protocol entry points and stay generic. +- When adding a new early-return path inside a commit handler in `_writeUpdate`, follow the blob-cleanup protocol documented in `../DESIGN.md` ("Blob orphan cleanup"). +- Tests for this layer live in `../unitTests/resources/`. diff --git a/server/DESIGN.md b/server/DESIGN.md new file mode 100644 index 000000000..3d92289f0 --- /dev/null +++ b/server/DESIGN.md @@ -0,0 +1,135 @@ +# server/ — Navigation Guide + +This layer accepts inbound traffic on every supported protocol (HTTP/1.1, HTTP/2, HTTPS, WebSockets, MQTT, NATS) and routes it through to the Resource layer. + +**Read this when:** you're touching request/response, protocol handling, middleware ordering, or WebSocket upgrade behavior. + +--- + +## Three HTTP stacks coexist — know which one + +| Stack | File | Used for | +| ----------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Native** | `http.ts` | Direct socket handling for application-level HTTP/1.1, HTTPS, HTTP/2, and WebSockets. Highest performance. This is the path most user requests take (REST, GraphQL, custom resource endpoints). | +| **Operations API** | `operationsServer.ts` | Fastify-based JSON operations API (`{operation: 'create_table', ...}`). Internal/admin surface — not on the hot path for application data. | +| **Custom Functions (legacy)** | `fastifyRoutes.ts` | Legacy custom functions only. Wraps Fastify with autoload. Don't add new code here. | + +A request entering `http.ts` does **not** go through Fastify. The two `handleApplication(scope)` functions (one in each Fastify file) load independently from component config. + +--- + +## File overview + +### Core dispatch + +| File | Lines | Purpose | +| -------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Server.ts` | 100 | Defines the `Server` interface — the contract that protocol plugins use to register listeners. Has `socket()`, `http()`, `ws()`, `upgrade()`, `contentTypes`, `getUser()`, `operation()`, `replication`, etc. | +| `http.ts` | 838 | Native HTTP/WS server. Registration entry points (`onRequest`, `onUpgrade`, `onWebSocket`), per-port middleware chains, UDS support, PROXY protocol. | +| `middlewareChain.ts` | 270 | Topological sort respecting `before`/`after` constraints on listener registrations. Falls back to registration order on cycle. | +| `REST.ts` | 434 | Resource-routed REST handler: URL → `Resource.getResource()` → method dispatch + content negotiation. | +| `graphqlQuerying.ts` | 701 | GraphQL query/mutation/subscription execution against Resources. | +| `mqtt.ts` | 506 | MQTT broker (connect/sub/pub mapped onto Resource interface). | +| `DurableSubscriptionsSession.ts` | 507 | Persistent subscription state (resume across reconnects). | + +### Operations & Fastify + +| File | Lines | Purpose | +| --------------------- | ----- | -------------------------------------------------------------------------------------------------- | +| `operationsServer.ts` | 313 | Boots Fastify for operations API. `handler()` (line 246) parses `{operation: ...}` and dispatches. | +| `fastifyRoutes.ts` | 206 | Legacy custom functions. Discovers routes from each component's `routes/` folder. | + +### Helpers + +| File | Lines | Purpose | +| ------------------------------------------ | ----- | ------------------------------------------------------------------------------- | +| `serverHelpers/Request.ts` | — | Wraps `IncomingMessage` with Harper-specific fields (user, response, headers). | +| `serverHelpers/Headers.ts` | — | Header mutation/merge utilities. | +| `serverHelpers/contentTypes.ts` | — | (de)serialization registry; `serialize`, `serializeMessage`, `getDeserializer`. | +| `serverHelpers/serverUtilities.ts` | — | `OperationDefinition` and shared helpers. | +| `serverHelpers/OperationFunctionObject.ts` | — | Wraps an operation handler with metadata. | +| `serverHelpers/JSONStream.ts` | — | Streaming JSON output for large responses. | +| `nodeName.ts` | 85 | Resolves this node's name (config → hostname). | +| `static.ts` | 187 | Static file serving for component-bundled assets. | +| `throttle.ts` | 91 | Per-IP / per-user request throttling. | +| `storageReclamation.ts` | 81 | Disk-pressure signals to downstream consumers. | +| `serverRegistry.ts` | 8 | Trivial registry export. | +| `status/` | — | Server status reporting (cluster status, per-port info). | + +### Threads + +| File | Purpose | +| -------------------------- | -------------------------------------------------------- | +| `threads/socketRouter.ts` | Routes accepted sockets to worker threads based on port. | +| `threads/manageThreads.js` | Thread pool lifecycle. | +| `threads/threadServer.js` | Worker entry point — receives sockets via IPC. | +| `threads/itc.js` | Inter-thread comms primitives. | + +> Workers receive `workerData.noServerStart = true` — never start the server inside a worker. + +--- + +## `http.ts` section index (838 lines) + +| Section | Line | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| UDS metadata (`registerUdsCleanupPaths`, `writeUdsMetadata`, `cleanupSocketsDirectory`) | 53–122 | +| `handleApplication(scope)` (component entry) | 123 | +| `getHttpOptions()` | 130 | +| `deliverSocket()` — IPC-delivered socket handoff | 134 | +| `proxyRequest()` — cross-port request routing | 171 | +| `registerServer()` | 221 | +| `getPorts()` | 248 | +| `httpServer()` — main listener registration | 274 | +| `getHTTPServer(port, secure, options)` — creates/retrieves the underlying Node HTTP/HTTPS server. **The largest function (~283 lines)** — wires `request`, `upgrade`, error handlers, TLS context, and the per-port middleware chain. | 299 | +| `makeCallbackChain()` — builds the per-port handler chain via `middlewareChain.topoSort` | 582 | +| `unhandled()` — terminal 404 handler | 595 | +| `onRequest()` — thin alias of `httpServer({requestOnly: true})` | 606 | +| `onUpgrade()` — register HTTP upgrade listener | 625 | +| `onWebSocket()` — register WebSocket listener; auto-adds default upgrade handler | 662 | +| `enableProxyProtocol()` — PROXY v1 parsing (Node 24+-compatible workaround) | 743 | +| `defaultNotFound()` | 800 | +| `logRequest()` | 808 | +| `getRequestId()` | 830 | + +### Middleware ordering (`before`/`after`) + +Components register listeners with optional `before: 'name'` / `after: 'name'` options. `middlewareChain.topoSort` resolves order; cycles fall back to registration order with a warning. Listener registration happens in three lists: + +- `httpResponders` — request handlers +- `upgradeListeners` (line 622) +- `websocketListeners` (line 654) + +The default WebSocket upgrade handler is registered automatically the first time `onWebSocket()` runs for a port (line 693). + +--- + +## Resource ↔ HTTP boundary + +`REST.ts:22` (`http(request, nextHandler)`) is the chief integration point: it takes a `Request`, asks the `Resources` registry for a match, builds a `RequestTarget`, and dispatches into the Resource class's static method. Cache headers are translated to `request.expiresAt` / `onlyIfCached` / `noCache` flags (`REST.ts:46–73`). + +--- + +## "Where is X" cheat sheet + +| Question | File:line | +| --------------------------------------------------- | --------------------------------------------------------------- | +| Where do I register a new HTTP handler? | `http.ts:274` (`httpServer()`) or `http.ts:606` (`onRequest()`) | +| Where do I register a WebSocket handler? | `http.ts:662` (`onWebSocket()`) | +| How does `before`/`after` middleware ordering work? | `middlewareChain.ts:21` (`topoSort`) | +| Where does PROXY protocol get parsed? | `http.ts:743` | +| Where is the REST request → Resource dispatch? | `REST.ts:22` | +| Where is the operations API request handled? | `operationsServer.ts:246` (`handler()`) | +| How are content types (de)serialized? | `serverHelpers/contentTypes.ts` | +| Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` | +| How are sockets dispatched to worker threads? | `threads/socketRouter.ts` | +| Where is the Operations API wired into Fastify? | `operationsServer.ts:138` (`buildServer`) | + +--- + +## Conventions + +- Don't add new code to `fastifyRoutes.ts` — it's the legacy custom-functions path. +- New protocol plugins implement `Server` (`Server.ts:21`) and register via `onRequest`/`onUpgrade`/`onWebSocket`. +- Always pass `name` when registering a listener with `before`/`after` — anonymous entries can't be ordered against. +- Tests live in `../unitTests/server/`.