Files
Module-uo/server/model/teamProvider/teamProvider.db.js
wtclaude c6929c6bae
Some checks failed
PR Checks / client-build (pull_request) Successful in 23s
PR Checks / server-tests (pull_request) Successful in 29s
PR Checks / frozen-manifest (pull_request) Failing after 40s
fix(teams): take query from the core facade, not a core.db that does not exist
The Team provider's db layer built its query helper as `core.db.query(...)`. The
facade has no `db` member -- every other *.db.js in this module destructures
`query` from it directly -- so every call threw `Cannot read properties of
undefined (reading 'query')`.

The failure mode is the bad part. That throw is caught by the provider's own
error handling and turned into `{ ok: false, reason: 'roster unreadable: …' }`,
which is a perfectly valid refusal -- so core would have accepted it, held the
projection it had, and reported staleness. A provider that answers correctly and
never returns data, forever, with nothing in any log louder than a warning.

Invisible to the unit tests because they stub every db function, so the helper
was never called. Found by running a real roster frame through the ingest and
then asking the provider what it saw, against the real database.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 17:41:22 -05:00

68 lines
2.9 KiB
JavaScript

// SQL behind the Team provider — three questions core asks, answered from the
// guild board and the roster Protocol 4 put there.
//
// Every statement reads only THIS module's tables. Core's Team tables are
// core-internal (docs/website/TEAMS.md §10.3) and this module must never name
// one, even though it is what fills them.
// `query` is destructured from the core facade at require time, like every other
// *.db.js here. The facade resolves `ctx` per call, so taking it now is safe even
// though `ctx` does not exist yet when this file is first required.
const { query } = require('../../core')
/**
* The guild board — one row per guild the shard has told us about.
*
* `members`/`online` here are the COUNTS `guild.update` carries; the roster is a
* separate table (Protocol 4). Both are read, because a count is what the shard
* asserts and a roster is what it enumerated, and they can legitimately disagree
* for the moment between a membership change and the sweep that reports it.
*/
const listGuilds = () =>
query(
`SELECT id, name, abbr, alliance, members, online, leader_serial, leader_name, leader_acct
FROM shard_guilds ORDER BY name ASC`,
)
const findGuild = (id) =>
query(
`SELECT id, name, abbr, alliance, members, online, leader_serial, leader_name, leader_acct
FROM shard_guilds WHERE id = ? LIMIT 1`,
[id],
)
/**
* One guild's roster, with the site link and live presence folded in.
*
* Two LEFT JOINs, both deliberate:
*
* - `shard_account_links` resolves `user_id` HERE rather than in core, because
* this module owns that table and a core that read it would be core naming a
* module's table by name (§2.3). It is also why a freshly linked account
* appears as linked on the next reconcile rather than needing core to know
* anything about linking.
* - `shard_online` is how a member's `online` is answered at all. The roster
* frame does not carry it — the wire's member is the standard actor object
* (`serial`, `name`, `player`, `acct?`, `webId?`), and the board's `online` is
* a count, not a set. Presence therefore comes from the online table, which
* is the same source the public "who's online" surface already uses.
*
* `web_id` on the roster row is preferred over the link table when present: it is
* what the shard itself asserted at roster time, and the join is the fallback for
* a member whose row predates their link.
*/
const listGuildMembers = (guildId) =>
query(
`SELECT m.serial, m.name, m.acct, m.web_id, m.is_player,
l.user_id AS linked_user_id,
(o.serial IS NOT NULL) AS is_online
FROM shard_guild_members m
LEFT JOIN shard_account_links l ON l.account = m.acct
LEFT JOIN shard_online o ON o.serial = m.serial
WHERE m.guild_id = ?
ORDER BY m.name ASC`,
[guildId],
)
module.exports = { listGuilds, findGuild, listGuildMembers }