npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

peertube-plugin-video-lifecycle

v0.4.0

Published

PeerTube video lifecycle: minors presence declaration, validity period, low-visibility handling and automated deletion.

Readme

peertube-plugin-video-lifecycle

A PeerTube plugin that gives videos a lifecycle, built for apps.education.fr, fit for any instance.

Its purpose is twofold, and both halves are about what earns its place on disk:

  • A legal basis. A video showing identifiable minors is published under an authorisation their guardians gave, and that authorisation expires. Past it, keeping the video is the breach and deleting it is the duty.
  • A cost. Storage is not free and it only grows. A video nobody has opened in a year is paid for every month, by whoever runs the instance.

The plugin was built for apps.education.fr, where the eleven PeerTube platforms of the French Éducation nationale hold 290 000 videos and some 67 TiB — one service among several whose shared storage runs to petabytes, for the agents of primary and secondary education. Nothing of that case is wired in: the school-year rule is a setting, the thresholds are settings, and the figures appear only where they make a trap concrete. If you run an instance where storage costs money and some videos are kept under a permission that ends, the shape fits.

Before turning a cycle on anywhere with tens of thousands of videos, read at scale — a first pass has a measured cost, and the shipped defaults were not chosen against numbers like these.

The two cycles

A lifecycle consists of a declaration, a materialised deadline, a public display, a warning, a grace period, and deletion at expiry.

  • Minors present — asked from the start, deleting only once you switch that on. The publication form asks whether a video shows identifiable minors. An answer of yes materialises a deletion date on the spot; once deletion is armed, that date is announced on the watch page and the video is deleted when the day comes. The answer is one-way and the date cannot be moved. Its owner is mailed when the grace period opens, unless notifications hands that to an orchestrator — in which case the date on that page, and a row on their countdown list, are all they get until the orchestrator writes to them. See the minors cycle.
  • Low visibility — off until you enable it. A view threshold and an observation window decide which videos count as unwatched. Their owner is warned, in the application and by mail if you turn it on, then a grace period runs and the video is deleted. A view or a press of Renew lifts the deadline. See the low-visibility cycle.

Whichever cycle started the countdown, an owner sees it in one place: My account → Videos scheduled for deletion lists the videos of theirs in a grace period, soonest first, with a Renew button on each one that can still be saved. A video declared as showing minors is listed there without that button — its date cannot be moved, so downloading a copy before the day is its owner's only recourse.

Deletion applies to the instance only — backups and archives are out of scope. Deciding whether content is lawful remains the uploader's responsibility.

Settings

| Setting | Default | Description | | --- | --- | --- | | minors-schedule | rolling-months | Which rule computes a minors deadline. rolling-months counts minors-validity-months from each video; school-year gives every video the end of the school year it was published in. See Two rules for one deadline. | | minors-grace-months | 3 | Added after the validity period; the watch page shows the sum. Applies to videos declared from then on. | | minors-validity-months | 12 | How long a video declared as showing minors stays online, under rolling-months. Applies to videos declared from then on. | | school-year-ends-on | 08-31 | The day every video declared as showing minors expires under school-year, as MM-DD. An impossible day (02-30, 13-01) falls back to the default. | | minors-declaration-enabled | true | Asking, and only that: the publication form puts the question, and a yes is recorded with a deletion date computed and frozen on the spot. Deletes nothing and shows no date on its own. While unchecked, the form does not ask; what is already recorded is kept. | | minors-deletion-enabled | false | Acting: a declared video is unpublished when its grace opens, its date is shown on the watch page, its owner is mailed, and it is deleted on the day. Off on a fresh install, like the other cycle. Checking it later applies the dates already recorded, reopening from that pass any that went by meanwhile. | | low-visibility-enabled | false | The low-visibility cycle, off on a fresh install too. While unchecked, it is paused: nothing is taken in, nothing is deleted, and watch pages announce nothing. | | low-visibility-views-threshold | 1 | View count below which a video counts as unwatched. The test is strict, so a video sitting on the threshold is out, and 0 qualifies nothing. | | low-visibility-window-months | 12 | Observation window for the view count. | | max-per-pass | 100 | What one pass may do to owners, for both cycles: how many videos it takes into the low-visibility cycle, and how many owners it warns. So switching a cycle on does not flag the whole back catalogue, and a first pass over videos already declared as showing minors does not write to all of them the same day. The scan reports moreToFlag and moreToWarn when it stopped short. 0 lifts the cap. | | low-visibility-grace-months | 3 | Grace period before deletion. Applies to videos flagged from then on. | | notifications | this-instance | Who warns an owner before a deletion, for both cycles: this-instance mails them, external-orchestrator leaves that to something upstream — see Warning the owner. | | low-visibility-mail-subject | (empty) | Subject of the low-visibility warning mail; {date} is substituted. Empty falls back to a built-in text, resolved in the owner's own language — so filling this field replaces that with one wording for everyone. | | low-visibility-mail-text | (empty) | Body of that mail; {date} is substituted. A button to the owner's countdown page is added under it. Translated by default, as the subject is. | | low-visibility-mail-link-label | (empty) | Label of that button. Translated by default; set it with the two above. | | minors-mail-subject | (empty) | Subject of the minors warning mail. Translated by default, as the two above. It must not say what the video contains. | | minors-mail-text | (empty) | Body of that mail; {date} is substituted. Do not offer a way to postpone the date — there is none. | | minors-mail-link-label | (empty) | Label of the button under it. It leads to the video's watch page, where the date has been shown since the declaration, not to the countdown page. | | scan-interval-hours | 24 | How often the periodic scan runs. 0 disables the internal timer and leaves an external cron to drive the passes. | | automation-secret | (empty) | Shared secret for the machine endpoints. While it is empty those endpoints stay closed — see plugin API. | | minors-notice-text | (empty) | Overrides the minors notice; {date} is substituted. Empty falls back to the built-in translated text. | | low-visibility-notice-text | (empty) | Overrides the low-visibility notice; {date} is substituted here too. | | renew-button-label | (empty) | Overrides the Renew button label. That button is offered during a low-visibility grace period, on the watch page and in the owner's countdown list. |

A deadline is materialised once — at declaration, or when a video is flagged — and then frozen. Changing a setting above reaches videos taken in from then on; no announced date moves. For the minors cycle nothing moves it ever: that deadline is immutable (DNE, 2026-09-23), and a declaration made in error is cleared rather than deferred. The one exception is a deadline materialised before minors-grace-months existed, which gains the grace on the next pass — the only time an announced date moves, and it moves later.

A value the form cannot use — a count that is not digits, or a count of months past the ceiling of a century — falls back to the default above rather than failing the call. A typo is therefore ignored, not applied.

The two cycles can hold the same video, and then the earlier deadline commands — the public date only ever moves earlier, never later (DNE, 2026-09-03). See when a video is under both cycles.

Warning the owner

A deletion date is on the video's own watch page from the moment it is set, and it holds: the video goes on that day even if no warning ever reached anybody.

Four surfaces carry a warning, and only some can prove it arrived. That proof is what stops the plugin warning again on every pass:

| Surface | Cycle | Proves delivery | | --- | --- | --- | | The notice on the video's own watch page | both | No — only whoever opens the video sees it | | My account → Videos scheduled for deletion, plus a one-line notice above whatever page the owner is on | both are listed | Low visibility only | | Mail sent by the plugin | both, under notifications | Yes | | Mail sent by your own orchestrator, which then calls POST /videos/:id/lifecycle/notified | either | Yes |

One setting picks the mailer, for both cycles: an owner should hear one voice about their own videos, and two warnings for one video is the confusing direction.

Six things worth knowing before you change it:

  • This setting picks the channel, it does not switch the engine off. A deadline holds whichever value it has: to stop a cycle, uncheck the cycle.
  • A built-in wording reaches each owner in their own language, falling back to the instance's default. Filling one of the mail fields replaces that with the one wording you typed, for everyone. GET /mail-texts serves whichever applies, so an orchestrator doing the mailing can send the instance's own words.
  • Mail needs PeerTube 8.3 and SMTP configured on the instance. Failing either, nothing is sent and nothing is recorded, and the warning is tried again next pass.
  • Under external-orchestrator the minors cycle warns nobody by itself. Its videos are on the countdown page, but that listing is not accepted as a warning: warn them yourself and call POST /videos/:id/lifecycle/notified.
  • A minors deadline cannot be postponed. The mail is a courtesy before a date the owner has been shown for a year; downloading a copy is the recourse, not Renew.
  • No mail says what a video contains. Subject lines get forwarded.

How each surface is built, what counts as seen, and which worklist an orchestrator should poll: warning channels.

Monitoring missed deadlines

A low-visibility video the engine forgot is merely kept longer than intended. A video declared as showing minors that outlives the date on its own watch page is not acceptable, so that case is made visible from outside:

curl -sH "X-Automation-Secret: $SECRET" \
  "https://<instance>/plugins/video-lifecycle/router/overdue"
# { "count": 0, "unreadable": 0, "videos": [] }

| Alert on | Read from | Watch for | | --- | --- | --- | | count > 0 or unreadable > 0 | GET /overdue | Both answer null while minorsDeletionEnabled is false: nothing deletes, so nothing is overdue. | | A status other than 200 | GET /overdue | A fresh install has no automation-secret, so the route answers 403 with no count; a probe reading undefined stays quiet for ever. | | The probe itself going missing | your own monitor | The ALERT line is written by a pass, so no pass means no line. The likely failure with scan-interval-hours = 0. | | overdueMinors !== 0 | the POST /scan summary | null means the check itself failed, not "nothing overdue". | | skipped === "stopped" | the POST /scan reply | Nothing scans again on this registration, and it carries no since, so a duration rule never fires. | | now - since past what an honest pass takes | the POST /scan reply, when skipped is already-running | That body carries none of the summary, so read skipped first. |

What each figure can and cannot say, and what happens when the index itself cannot be read: monitoring. Where a metrics scraper and a log shipper plug in, and who owns the retention: collecting.

Plugin API

Every route, the automation secret, and running your own orchestrator.

Before you switch a cycle on

Each section of the plugin's own settings page states, under its heading, what that cycle currently reaches: how many videos are declared or already counting down, how many await a warning, how many are past their date, and what has been deleted and reclaimed so far. The low-visibility section adds how many videos the platform holds and how many the cycle would take in right now.

That intake figure is the one no API can answer from outside — a view count is not filterable and the declaration is not indexed — so it is computed inside, on GET /forecast, which is the route those lines render.

At scale

What a pass costs as the tracked index grows, how to count your videos without walking them, what a first run does to a back catalogue, and how to size the two settings that govern both: at scale.

Development

The layers, the build, a local instance, mail, and the smoke test.

Licence

EUPL-1.2 — the European Union Public Licence, whose text ships with the published package.