// ── Everything this module reaches in core ───────────────────────────────── // // `ctx` arrives once, as an argument to `register()` (MODULE_API.md §2.3). The // code below it — models, utils, controllers — is ordinary Node that requires // its dependencies at file scope, the way it did when it lived in core. This // file is what lets both be true. // // **The shape is a lazy accessor, not a stored reference, and that is the whole // point.** A ported file 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` there would hand out // `undefined`, permanently, and the failure would surface much later as a // TypeError inside a model. So every export here is a stable function that // resolves `ctx` when it is CALLED. Require order stops mattering, and the port // stays a one-line import change per file rather than a signature change per // function. // // The other half of the same rule: nothing here may be destructured off `ctx` // at init time either, for the same reason in the other direction — 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 with the same message. That is // deliberate: the only way to reach one before `register()` is a require cycle // or a test that forgot to call `init`, and both want naming, not `undefined`. let ctx = null function need() { if (!ctx) { throw new Error('module-uo: 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()`. // // Ported files write `const log = require('../core').logger('shard-ingest')` at // file scope — the same shape as core's `require('./logger')('…')` — so the // object returned has to exist before `ctx` does. It is a façade whose four // methods each resolve the real logger on call. Core namespaces it with the // module id, so these come out as `[uo:shard-ingest]`. 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 lives outside core's `server/`, so it could not resolve // these for itself even if it were allowed to (§7.2). get express() { return need().express }, get validator() { return need().validator }, // Database. `query` is the one every `*.db.js` uses; `pool` is for the // streamed atlas import, which needs a connection it can hold. query: (...args) => need().db.query(...args), get pool() { return need().db.pool }, // Core state a module may read or append to, each narrowed to what is // actually used (§2.3). settings: { get: (...args) => need().settings.get(...args), set: (...args) => need().settings.set(...args), getInstanceName: (...args) => need().settings.getInstanceName(...args), }, activity: { log: (...args) => need().activity.log(...args) }, users: { getById: (...args) => need().users.getById(...args) }, posts: { listAll: (...args) => need().posts.listAll(...args), getById: (...args) => need().posts.getById(...args), linkAnnounceJob: (...args) => need().posts.linkAnnounceJob(...args), markAnnounced: (...args) => need().posts.markAnnounced(...args), }, auth: { getUserFromRequest: (...args) => need().auth.getUserFromRequest(...args) }, push: { publish: (...args) => need().push.publish(...args) }, secretBox: { encrypt: (...args) => need().secretBox.encrypt(...args), decrypt: (...args) => need().secretBox.decrypt(...args), }, get uploads() { return need().uploads }, // Middleware. Taken as values rather than wrapped, because express stores the // function reference at mount time — a wrapper would be what ends up in the // stack, and `requireRole('admin')` returns a middleware rather than being // one. Routers are built inside `register()`, so `ctx` is set by then. get middleware() { return need().middleware }, // Deployment facts. get baseUrl() { return need().site.baseUrl }, get moduleRoot() { return need().paths.moduleRoot }, get moduleId() { return need().moduleId }, }