// ── Everything this module reaches in core ───────────────────────────────── // // `ctx` arrives once, as an argument to `register()` (MODULE_API.md §2.3). The // code beneath it — models, controllers, utilities — is ordinary Node that // requires its dependencies at file scope, the way any Node file does. This file // is what lets both of those be true at the same time. // // **Every export is a lazy accessor, not a stored reference, and that is the // whole point.** A model writes // // const { query } = require('../../core') // // at require time, which is before `register()` has been called and therefore // before any `ctx` exists. Handing out `ctx.db.query` at that moment would hand // out `undefined`, permanently, and the failure would surface much later as a // TypeError inside a model with no clue pointing here. So each member resolves // `ctx` when it is CALLED. Require order stops mattering for everything except // `core.init()` itself, which `index.js` runs first. // // The same rule in the other direction: **never destructure off `ctx` at init // time.** Core is free to hand over a getter — `ctx.site.baseUrl` is one — and a // value captured once is a value that cannot change. // // If `ctx` is missing every accessor throws the same message. The only ways to // reach one before `register()` are a require cycle or a test that forgot to call // `init`, and both want naming rather than `undefined`. // // ── This file is a NARROWING, on purpose ─────────────────────────────────── // // §2.3 lists everything core hands over. What is re-exported below is only what // this module actually uses, which is the discipline worth copying: the file is // then an honest statement of what your module depends on, and a test double for // it (see `test/_fakes.js`) is a complete one. Add a member here when you reach // for it — not in advance. let ctx = null function need() { if (!ctx) { throw new Error('examplegame: core accessed before register() — see server/core.js') } return ctx } /** Called once, first thing in `register()`. */ function init(value) { ctx = value } /** Test seam. Nothing in the module calls this; there is no de-registration. */ function _reset() { ctx = null } // A logger that can be taken at require time and used after `register()`. // // A file writes `const log = require('../core').logger('world')` at file scope, // so the object returned has to exist before `ctx` does. It is a façade whose // four methods each resolve the real logger when called. Core namespaces the // output with your module id, so these come out as `[examplegame:world]`. function logger(namespace) { const call = (level) => (message, meta) => need().log(namespace)[level](message, meta) return { error: call('error'), warn: call('warn'), info: call('info'), debug: call('debug') } } module.exports = { init, _reset, logger, // Shared server dependencies. Core owns exactly one express, as it owns // exactly one React on the client, and for the same reason: a second copy in // the process is a second Router prototype and a second set of `instanceof` // checks. A module could not resolve these for itself even if it were allowed // to — it lives outside core's `server/` (§7.2). get express() { return need().express }, get validator() { return need().validator }, // The database. `query(sql, params)` is what every `*.db.js` file uses; raw // parameterised SQL, no ORM, the same as core. `pool` is there for the rare // case that needs a connection it can hold (a streamed import, say). query: (...args) => need().db.query(...args), get pool() { return need().db.pool }, // Read-only access to who is asking. Minting a session is core's job; a module // that needs an identity needs to *read* one. auth: { getUserFromRequest: (...args) => need().auth.getUserFromRequest(...args) }, // Core's middleware, taken as values rather than wrapped: express stores the // function reference at mount time, so a wrapper is what would end up in the // stack. Routers are built inside `register()`, so `ctx` is set by then. get middleware() { return need().middleware }, // Deployment facts. `moduleRoot` is the absolute path to `modules//` — the // only correct way to find a file you shipped, because the working directory is // core's and the module's location is the loader's business. get moduleRoot() { return need().paths.moduleRoot }, get moduleId() { return need().moduleId }, }