endoflife.date source preset
What this ships
A ready-to-run, credential-free source preset for the public endoflife.date lifecycle API:
- One source object (
endoflife-date,https://endoflife.date/api,auth: none) — ships enabled, not dormant. There is nothing to configure before it starts working: the API is public and free. - Two new schemas in the existing
integriqregister:eol_product— a tracked product/technology (slug,name,category,homepage,endoflifeUrl).eol_cycle— one release-cycle's lifecycle data for a product (product,cycle,releaseDate,eol,support,latest,latestReleaseDate,lts,discontinued).
- Eight curated
eol_productobjects, seeded declaratively:php,nodejs,python,postgresql,mysql,nextcloud,wordpress,laravel. - One
mapping+synchronization+jobtriple per curated product, each pulling that product's cycle data fromhttps://endoflife.date/api/{slug}.jsonon a daily schedule (86400s interval) via the existing, generic Synchronization/Mapping/Job engine — no new PHP code.
Where to find it
- Sources page: a source named "endoflife.date", already enabled.
- Synchronizations page: 8 synchronizations named
endoflife.date — <Product> cycles, one per curated product. - Jobs page: 8 jobs named
endoflife.date — <Product> cycles sync, each running daily viaOCA\Integriq\Action\SynchronizationAction. - Catalog page: an "endoflife.date" card appears automatically among
the source-template entries (no bespoke UI code — this is a side effect
of the seed fragment declaring a
sourceobject, picked up by the existingCatalogRegistryService.collectFromSeedFragments()mechanism).
Field shapes
eol_product
| Field | Type | Notes |
|---|---|---|
slug | string, req'd | The endoflife.date product identifier (path segment used at /api/{slug}.json) |
name | string, req'd | Human-readable name |
category | string | e.g. "Language runtime", "Database", "Platform", "CMS", "Framework" |
homepage | string (uri) | Product's official homepage |
endoflifeUrl | string (uri) | https://endoflife.date/{slug} |
eol_cycle
| Field | Type | Notes |
|---|---|---|
product | string, req'd | Owning eol_product.slug, set as a literal by the product's mapping |
cycle | string, req'd | Release-cycle label (e.g. "3.14") — the sync origin id |
releaseDate | string (date) | First release date of this cycle |
eol | string | ISO end-of-life date, or "" when none is scheduled upstream |
support | string | ISO end-of-active-support date, or "" |
latest | string | Latest patch release within this cycle |
latestReleaseDate | string (date) | Release date of latest |
lts | boolean or string | Long Term Support flag — upstream sometimes reports an ISO date instead of true/false; left uncast |
discontinued | string | ISO discontinuation date, or "" — not reported for every product/cycle |
eol/support/discontinued are cast to string by each product's
mapping because endoflife.date reports these fields as either an ISO date
string or the JSON literal false (no scheduled date) — casting collapses
both shapes into one consistently-typed column (false → "").
eol_cycle is never hand-seeded — it is populated live by each curated
product's daily Synchronization. A static seed here would be immediately
stale.
Extending the tracked set (no code change required)
To track a ninth product (e.g. django) from the 460+ listed at
https://endoflife.date/api/all.json:
- Duplicate one curated product's
eol_productseed object inlib/Settings/register.d/endoflife-date-source.json, substituting the new product'sslug/name/category/homepage/endoflifeUrl. - Duplicate that same product's
mapping+synchronization+jobtriple inlib/Settings/register.d/endoflife-date-source-cycles.json, substituting the slug everywhere it appears (object slugs, thesourceConfig.endpoint, the mapping's literalproductvalue, and the job'sarguments.synchronizationId). - Keep
sourceConfig.resultsPosition: "_root"— required, because/api/{slug}.jsonreturns a bare top-level array with none of theitems/result/resultskeys the engine otherwise falls back to. Omitting it fails every run with "Cannot determine the position of objects in the return body." - Re-run
InitializeRegister(occ app:enable integriqor an upgrade). The new product'seol_cycledata begins syncing on the same daily cadence — no PHP or engine change required.
Give every product its own Synchronization (its own
synchronizationId). SynchronizationContract identity is scoped to
(synchronizationId, originId) only — if two products shared one
Synchronization, two ecosystems reporting the same cycle label (e.g. both
having a "3.14" release) would silently overwrite each other's target
object.
Not shipped out of the box
Auto-discovering and syncing all 460+ endoflife.date products is a
known, intentionally out-of-scope follow-up, not a bug — /api/all.json
returns a bare array of product-slug strings, which is structurally
incompatible with the generic per-item Synchronization pipeline (it
requires each fetched item to already be array-shaped). Full-catalog
auto-discovery would need a small, separately-scoped repair-step/command
(per ADR-031's external-integration exemption) and is a natural follow-up
once this preset is live — not part of this preset.
A note on the schema slugs
These schemas were called eolProduct and eolCycle until 2026-09-07. They were
the only two camelCase slugs among the fifty-five this app declares, so their
object URLs were the only ones you could not guess from the pattern the other
fifty-three follow.
An existing install is renamed in place on upgrade by the
MigrateEolSchemaSlugs repair step. Nothing moves: an object binds to its schema
by numeric id and the storage tables are named from ids, so no slug appears
anywhere in the physical layout. The step also rewrites the
integriq/eolCycle string the eight seeded synchronizations hold in
targetId, which is the one place a schema slug is written into data rather
than referenced by id.
If you scripted anything against /apps/openregister/api/objects/integriq/eolProduct
or .../eolCycle, point it at eol_product and eol_cycle.