// ── The module's single point of contact with core ───────────────────────── // // Every other file in this module imports THIS file instead of reaching into // the website's tree. That is the whole mechanical trick behind the // zero-internal-imports rule (docs/website/MODULE_API.md §5.1): the moved files // changed by one `require` line each, and a CI grep for a relative path // escaping the module root can then be an exact test rather than a heuristic. // // It exists because `ctx` arrives as an ARGUMENT to register(), while the files // that need it are plain CommonJS modules that were written against top-level // requires. Rather than thread ctx through nine constructors, register() parks // it here once and everything else reads it lazily. // // Lazily is load-bearing: this file is required at module-require time, which is // during app.js's own require, and reading `ctx.db` eagerly would rebuild the // startup-time database dependency the loader is careful not to have. let ctx = null /** Called exactly once, by server/index.js, at the top of register(). */ function init(next) { if (ctx) throw new Error('module-uo: core.init() called twice') ctx = next } function require_() { if (!ctx) throw new Error('module-uo: core used before register() ran') return ctx } // Forwarders rather than re-exports: `const { query } = require('./core')` // destructures at require time, which is before init(), so a plain re-export // would capture undefined. Each of these resolves ctx at CALL time. const query = (sql, params) => require_().db.query(sql, params) const logger = (namespace) => require_().log(namespace) const settings = { get: (key) => require_().settings.get(key), // `updatedBy` is the third parameter core's settings.model.set carries — the // atlas path setter passes it (shardAtlas.model.js:60), so dropping it here // would silently lose the audit attribution rather than fail. set: (key, value, updatedBy) => require_().settings.set(key, value, updatedBy), getInstanceName: () => require_().settings.getInstanceName(), } const auth = { getUserFromRequest: (req) => require_().auth.getUserFromRequest(req), } const middleware = { siteMode: (req, res, next) => require_().middleware.siteMode(req, res, next), validate: (req, res, next) => require_().middleware.validate(req, res, next), requireAuth: (req, res, next) => require_().middleware.requireAuth(req, res, next), noindex: (req, res, next) => require_().middleware.noindex(req, res, next), requireRole: (...roles) => { // requireRole is a FACTORY, so it must be resolved at call time and the // resulting middleware kept — resolving it per request would build a new // closure on every hit. let built = null return (req, res, next) => { built = built || require_().middleware.requireRole(...roles) return built(req, res, next) } }, } module.exports = { init, // Shared server dependencies, taken from core rather than required directly. // A module lives outside server/, so `require('express')` from here does not // resolve at all — and even where it did, a second express in the process // would be a second Router prototype. Same rule as React on the client. get express() { return require_().express }, get validator() { return require_().validator }, query, logger, settings, auth, middleware, get pool() { return require_().db.pool }, get secretBox() { return require_().secretBox }, get push() { return require_().push }, get uploads() { return require_().uploads }, get posts() { return require_().posts }, get paths() { return require_().paths }, get moduleId() { return require_().moduleId }, }