moderator-toolbox-nxg-for-reddit / modules/shared/proposals/moduleapi

modules/shared/proposals/moduleapi

Functions

appendProposal()

appendProposal(subreddit, proposal): Promise<ProposalMutationResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:158

Appends a new proposal. Idempotent on id: if a proposal with the same id is already present, no write happens and the existing one is returned.

Parameters

subreddit

string

The subreddit to write to.

proposal

Proposal

The fully-built proposal (id already assigned).

Returns

Promise<ProposalMutationResult>


claimProposalForReplay()

claimProposalForReplay(subreddit, id, reviewer): Promise<ProposalClaimResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:292

Atomically claims a proposal for replay: the conditional wiki write is the compare-and-set that lets only one of several concurrent reviewers begin the accept, so an irreversible action can never be replayed twice. Re-checks the accept preconditions against the fresh page (not the caller’s possibly-stale snapshot): a now-terminal proposal is already-resolved, a needs_attention proposal that already landed an irreversible side effect is irreversible-retry, and a proposal any accept is actively replaying (a live, non-expired claim, even on the same account) is in-progress. Only an expired claim - a crashed holder - is reclaimable. On success the returned proposal is the one to replay.

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

reviewer

string

Username of the accepting moderator.

Returns

Promise<ProposalClaimResult>


createProposalId()

createProposalId(): string

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:49

Generates a collision-free proposal id.

Returns

string


dismissProposal()

dismissProposal(subreddit, id, pruneRetentionDays?): Promise<ProposalMutationResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:409

Marks a resolved proposal as acknowledged by its proposer (clears it from the proposer’s “My proposals” badge and makes it eligible for pruning). Allowed on any existing proposal regardless of status; only the proposer should call this (enforced at the gateway/UI).

When pruneRetentionDays is given, the same write also prunes resolved/acked proposals past the retention window - so dismissing a resolved proposal removes it immediately (an acked terminal proposal is always prune-eligible) rather than lingering until a later maintenance pass.

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

pruneRetentionDays?

number

Retention window (days) to prune by in the same write, or omit to skip pruning.

Returns

Promise<ProposalMutationResult>


loadProposals()

loadProposals(subreddit, opts?): Promise<ProposalsData>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:68

Reads a subreddit’s proposals for display. Returns the session cache when warm unless force is set, otherwise fetches canonically and caches the result. Pure read - never writes the wiki (reconciliation/pruning are explicit mutations, never side effects of a display read).

Concurrent cold-cache reads of the same subreddit are coalesced into a single wiki fetch (e.g. when many inline badges mount in the same tick), so a queue render does not fire N duplicate reads of the same page before the first one populates the cache.

Parameters

subreddit

string

The subreddit to load.

opts?

force: true bypasses both the cache and the in-flight coalescing.

force?

boolean

Returns

Promise<ProposalsData>


loadProposalsForSubs()

loadProposalsForSubs(subreddits, opts?): Promise<FanoutResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:126

Reads several subreddits’ proposals concurrently for the cross-subreddit views. Each subreddit degrades independently: a sub with no proposals page, no wiki access, or a transient read failure contributes empty data rather than failing the whole batch (readWikiPageVersioned already maps “no page / no perm” to empty; this adds a catch for unexpected throws), and its name is collected in failedSubs. Reads honor the session cache, optionally refreshing entries older than maxAgeMs so reopening a view re-fetches stale subs without re-scanning fresh ones.

Parameters

subreddits

readonly string[]

The subreddits to read.

opts?

maxAgeMs forces a refetch of caches older than this; concurrency caps simultaneous reads (default DEFAULT_FANOUT_CONCURRENCY).

concurrency?

number

maxAgeMs?

number

Returns

Promise<FanoutResult>


markProposalObsolete()

markProposalObsolete(subreddit, id, obsoleteReason): Promise<ProposalMutationResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:382

Auto-resolves a proposal to obsolete (target deleted or actioned elsewhere).

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

obsoleteReason

ObsoleteReason

Why it became obsolete.

Returns

Promise<ProposalMutationResult>


pruneResolvedProposals()

pruneResolvedProposals(subreddit, retentionDays, now?): Promise<number>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:442

Prunes resolved proposals that the proposer has acknowledged, or whose resolution is older than the subreddit’s retention window. Pending and needs_attention proposals are always kept. Explicit maintenance call - never run as a side effect of a display read.

Parameters

subreddit

string

The subreddit to prune.

retentionDays

number

Days to keep a resolved-but-unacknowledged proposal.

now?

number = ...

Current epoch seconds (injectable for tests; defaults to now).

Returns

Promise<number>

The number of proposals pruned.


rejectProposal()

rejectProposal(subreddit, id, reviewer, feedback?, pruneRetentionDays?): Promise<ProposalMutationResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:355

Rejects a proposal with optional reviewer feedback.

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

reviewer

string

Username of the rejecting moderator.

feedback?

string

Optional explanation shown to the proposer.

pruneRetentionDays?

number

Retention window (days) to prune by in the same write, or omit to skip pruning.

Returns

Promise<ProposalMutationResult>


releaseProposalClaim()

releaseProposalClaim(subreddit, id, reviewer): Promise<void>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:334

Releases this reviewer’s replay claim on a proposal (a no-op if they don’t hold it or the proposal is gone), so a perform that failed before resolving the proposal doesn’t block a retry until the claim expires. Resolving the proposal clears the claim on its own - this is only for the abandon/failure paths that leave it pending.

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

reviewer

string

Username whose claim to release.

Returns

Promise<void>


transitionProposal()

transitionProposal(subreddit, id, to, patch, reason, pruneRetentionDays?): Promise<ProposalMutationResult>

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:215

Applies a status transition to one proposal, enforcing canTransition. Internal helper behind rejectProposal/markProposalObsolete and accept/needs-attention (via the gateway).

When pruneRetentionDays is given, the same write that resolves this proposal also prunes resolved/acked proposals past the retention window - opportunistic maintenance with no extra I/O, and never as a side effect of a display read. The just-resolved proposal keeps a fresh resolvedAt, so it is never pruned here.

Parameters

subreddit

string

The subreddit to mutate.

id

string

The proposal id.

to

ProposalStatus

The target status.

patch

Partial<Proposal>

Extra fields to set alongside the status (e.g. resolvedBy, feedback).

reason

string

The wiki revision note.

pruneRetentionDays?

number

Retention window (days) to prune by in the same write, or omit to skip pruning.

Returns

Promise<ProposalMutationResult>

Interfaces

FanoutResult

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:107

The result of a cross-subreddit fan-out: one entry per subreddit (failing subs contribute empty data so the aggregate is always complete) plus the names of the subreddits whose read threw, so the UI can distinguish “no proposals” from “couldn’t load” and offer a targeted retry.

Properties

entries

entries: SubredditProposals[]

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:109

One entry per requested subreddit, in request order.

failedSubs

failedSubs: string[]

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:111

Subreddits whose read failed (empty when everything loaded).

Type Aliases

ProposalClaimResult

ProposalClaimResult = { ok: true; proposal: Proposal; } | { current?: undefined; ok: false; reason: "not-found"; } | { current: Proposal; ok: false; reason: "already-resolved" | "irreversible-retry" | "in-progress"; }

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:273

The typed result of a claimProposalForReplay attempt.

  • ok - this reviewer now holds the claim (proposal is the freshly-claimed state, authoritative for the replay).

  • otherwise reason explains why the claim was refused; current is the live proposal when one exists (for messaging and to surface who else is mid-accept).


ProposalMutationResult

ProposalMutationResult = { ok: true; proposal: Proposal; } | { current?: undefined; ok: false; reason: "not-found"; } | { current: Proposal; ok: false; reason: "already-resolved" | "invalid-transition" | "in-progress"; }

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:43

The typed result of a proposal mutation.

  • ok - the change was applied (proposal is the new state).

  • otherwise reason explains why nothing changed; current is the live proposal when one exists (e.g. for “already resolved by u/X” messaging).

Variables

SYSTEM_RESOLVER

const SYSTEM_RESOLVER: "[system]" = '[system]'

Defined in: extension/data/modules/shared/proposals/moduleapi.ts:35

Username sentinel recorded as the resolver when a proposal auto-resolves.