The data half of the extraction: 8 model directories, 13 utils, the shard
stream catalog and the 27-table schema fragment with its purge.
server/core.js is what makes the port a one-line import change per file rather
than a signature change per function. Ported code requires its dependencies at
file scope -- `const { query } = require('../../core')` -- which runs before
register() has been called and before any ctx exists. So every member is a
stable function that resolves ctx when CALLED, and nothing may be destructured
off ctx at init either, because core is free to hand over a getter.
Two helpers are vendored rather than taken from ctx, and the line between them
is the point. utils/excerpt.js is core's deriveExcerpt -- nine lines of pure
text handling. Core's sanitiser next to it was NOT copied: a second copy of a
security control diverges silently the moment either is fixed. announceLinks.js
vendors legError and articleUrl the same way, but baseUrl could not be: core's
reads APP_BASE_URL, and §2.7 forbids a module reading core's environment, so it
comes off ctx.site.baseUrl.
The schema fragment is core's 27 shard_*/uo_link_* statements, verbs CREATE,
ALTER and UPDATE only, every CREATE TABLE guarded. Two of its tables carry a
foreign key INTO users, which is allowed and is why the replay order matters --
core's schema is in place before this runs. The reverse never occurs and must
not: it would make core unable to boot without a module installed.
One real port bug caught by the integration run, not by tests: the atlas art
map resolved `../../../db/data`, which pointed at core's tree when this file
lived there and points outside server/ now. A path that happens to resolve is
exactly what survives a green suite, because the absent-file branch returns {}
and looks like the normal case.
Co-Authored-By: Claude <noreply@anthropic.com>
50 lines
2.2 KiB
JavaScript
50 lines
2.2 KiB
JavaScript
// ── Shard event → push fan-out ─────────────────────────────────────────────
|
|
//
|
|
// MODULE-UO CONTENT, still living in core — the inverted half of
|
|
// MODULE_SYSTEM.md §1.8's second entangled file. `utils/pushDispatch.js` is core
|
|
// infrastructure, but its `fromShardEvent()` required the shardLinks model and
|
|
// the shard event mapper, which is a core file importing content. PR 4 inverted
|
|
// it: `publish()` stays core, and this — the thing that knows what a shard event
|
|
// is — moved out to call it. Phase 3 moves this file to module-uo whole, where it
|
|
// will reach `publish` through `ctx.push.publish` instead of a require.
|
|
//
|
|
// Owner resolution is the reason this cannot just be a mapper: a personal
|
|
// (owner-keyed) target names a GAME account, and turning that into a website user
|
|
// needs the shardLinks model. An unlinked account is simply nobody to notify.
|
|
|
|
const shardLinks = require('../model/shardLinks/shardLinks.model')
|
|
const { mapShardEvent } = require('../config/shardStreams')
|
|
const { push } = require('../core')
|
|
|
|
const { publish } = push
|
|
const log = require('../core').logger('shard-push')
|
|
|
|
// Fan a shard event out to push. Resolves personal (owner-keyed) targets to the
|
|
// owning website user via shardLinks (an unlinked account → nobody to notify).
|
|
// Never throws — a dead relay must never affect ingest.
|
|
async function fromShardEvent(event, deps = {}) {
|
|
const links = deps.shardLinks || shardLinks
|
|
const doPublish = deps.publish || publish
|
|
const targets = mapShardEvent(event, deps.tracker)
|
|
for (const t of targets) {
|
|
try {
|
|
if (t.ownerAccount) {
|
|
let owner = null
|
|
try {
|
|
owner = await links.getByAccount(t.ownerAccount)
|
|
} catch {
|
|
owner = null
|
|
}
|
|
if (!owner || owner.userId == null) continue
|
|
await doPublish(t.streamId, { ref: t.ref, ownerUserId: owner.userId }, deps)
|
|
} else {
|
|
await doPublish(t.streamId, { ref: t.ref }, deps)
|
|
}
|
|
} catch (err) {
|
|
log.warn('push dispatch target failed', { streamId: t.streamId, message: err.message })
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { fromShardEvent }
|