Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/. |
||
|---|---|---|
| .. | ||
| src | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Scheduling
Shared schedule time semantics for WP3 (publishing) and WP13 (multi-output
schedules). Pure functions and types only — no app, database or Temporal
imports, and no runtime dependency beyond Node's Intl tzdata.
A schedule is a five-field cron expression plus an explicit IANA timezone
(never server-local; UTC allowed). Occurrences are evaluated in that zone as
nominal local date-times, then materialized to UTC fire instants. The
occurrence key is the materialized UTC instant, so UNIQUE (workspace_id, schedule_id, fire_at_utc) holds by construction.
Policies
- DST gap (nonexistent local time): fires at the first valid instant after the gap (shift forward). Works for 1-hour gaps and Pacific/Apia's whole-day skip alike.
- DST fold (ambiguous local time): fires ONCE, at its first (earlier-offset) instant.
- Collapse: several nominal local times materializing onto one post-gap
instant produce exactly one occurrence, labelled with the latest collapsed
slot — the conservative value for the
last_fired_nominal_localgate. - Re-materialization on tzdata or timezone change: deletes all unclaimed
future occurrences, then regenerates only occurrences strictly after
max(change instant, last claimed fire instant) whose nominal slot is strictly
later than
last_fired_nominal_local. A slot already fired under the old zone (daily 09:00 fired in New York, zone switched to Los Angeles) never fires again. The tzdata version is reported for recording.
API
import {
parseCron, // five-field cron -> sets (rejects 6-field/`?`/`L`/`W`/`#`/macros)
nextOccurrences, // (spec, from, count, {exclusive}) -> Occurrence[]
materializeOccurrence, // ("2024-03-10T02:30", zone) -> epoch ms
rematerialize, // (oldSpec, newSpec, storedRows, {changeInstant, ...})
canonicalTimezone, // validates + canonicalizes; rejects "local"/offsets
currentTzdataVersion, // process.versions.tz
} from "@tactical-media-broadcast-hub/scheduling";
Errors: SchedulingError base, with InvalidCronError, ImpossibleScheduleError,
InvalidTimezoneError, InvalidNominalLocalError, InvalidInstantError. Note
toInstantMs rejects offset-less ISO datetimes that would otherwise parse in
the server's local zone.
Verification
pnpm exec tsc -p tsconfig.json --noEmit
pnpm exec vitest run
pnpm install is required once so the workspace links this package's
vitest/typescript devDependencies into packages/scheduling/node_modules.