Skip to content

📊 Statistics Models

Structures in pkg/domain describing Packagist statistics and change tracking data. They correspond to the following APIs:

  • https://packagist.org/statistics.jsonStatisticsResponse
  • https://packagist.org/packages/<vendor>/<name>/stats.jsonPackageStatsResponse
  • https://packagist.org/metadata/changes.json?since=<timestamp>ChangeTrackingResponse

Type Overview

TypeRoleSource API
StatisticsResponseRepository overall statistics top-level/statistics.json
TotalsRepository totals (downloads/packages/versions)Nested in StatisticsResponse
PackageStatsResponseSingle package download stats/<package>/stats.json
ChangeTrackingResponseMetadata change tracking response/metadata/changes.json
ChangeActionSingle change actionNested in ChangeTrackingResponse

📈 StatisticsResponse

Top-level structure for Packagist repository overall statistics.

go
type StatisticsResponse struct {
    Totals Totals `json:"totals"`
}
FieldTypeDescription
TotalsTotalsRepository total statistics, see next section

🔢 Totals

Repository overall statistics.

go
type Totals struct {
    Downloads int64 `json:"downloads"`
    Packages  int   `json:"packages"`
    Versions  int   `json:"versions"`
}
FieldTypeDescriptionExample
Downloadsint64Total download count for all packages10000000000
PackagesintTotal package count in repository300000
VersionsintTotal version count for all packages2500000

Downloads uses int64

Whole-site downloads can reach tens of billions, exceeding 32-bit int range, so Downloads explicitly uses int64. Do not assign to int variables when accumulating or comparing (will overflow on 32-bit platforms).


📉 PackageStatsResponse

Single package download statistics. Downloads field reuses PackageDownloads type from package.md.

go
type PackageStatsResponse struct {
    Downloads PackageDownloads `json:"downloads"`
    Versions  []string         `json:"versions"`
    Date      string           `json:"date"`
}
FieldTypeDescription
DownloadsPackageDownloadsDownload stats (Total/Monthly/Daily)
Versions[]stringAvailable version list
DatestringStatistics start date

🔄 ChangeTrackingResponse

Metadata change tracking response. Used for incremental sync: pass a since timestamp, Packagist returns all package change actions after that time point.

go
type ChangeTrackingResponse struct {
    Error     string          `json:"error,omitempty"`
    Timestamp int64           `json:"timestamp"`
    Actions   []ChangeAction  `json:"actions,omitempty"`
}
FieldTypeDescription
ErrorstringError message returned when since parameter missing or invalid (empty when normal)
Timestampint64Current response timestamp (Unix seconds)
Actions[]ChangeActionChange action list, see next section

Incremental sync mode

Typical usage: locally save last Timestamp, pass it as since next request, gradually catch up with update/delete in Actions. If Error is non-empty, means since expired or invalid, need full resync.


⚡ ChangeAction

Single change action.

go
type ChangeAction struct {
    Type    string `json:"type"`
    Package string `json:"package"`
    Time    int64  `json:"time"`
}
FieldTypeDescriptionValue
TypestringAction type"update" or "delete"
PackagestringPackage name being operated onsymfony/console
Timeint64Unix timestamp when action occurred1700000000

🚀 Examples

Repository Total Statistics

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/scagogogo/composer-skills/pkg/client"
)

func main() {
    c := client.NewComposerClient(30 * time.Second)
    stats, err := c.GetStatistics()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Total packages: %d\n", stats.Totals.Packages)
    fmt.Printf("Total versions: %d\n", stats.Totals.Versions)
    fmt.Printf("Total downloads: %d\n", stats.Totals.Downloads)
}

Single Package Download Stats

go
stats, _ := c.GetPackageStats("monolog/monolog")
fmt.Printf("Today downloads: %d, this month: %d, total: %d\n",
    stats.Downloads.Daily, stats.Downloads.Monthly, stats.Downloads.Total)

Incremental Change Tracking

go
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/scagogogo/composer-skills/pkg/client"
)

func main() {
    c := client.NewComposerClient(60)
    var since int64 = 1700000000 // Last sync timestamp

    changes, err := c.GetPackageChanges(context.Background(), since)
    if err != nil {
        log.Fatal(err)
    }
    if changes.Error != "" {
        log.Fatalf("API returned error: %s (since may be expired, need full resync)", changes.Error)
    }
    fmt.Printf("Current timestamp: %d, change count: %d\n", changes.Timestamp, len(changes.Actions))
    for _, a := range changes.Actions {
        fmt.Printf("  [%s] %s @ %d\n", a.Type, a.Package, a.Time)
    }
    // Next time use changes.Timestamp as new since
}

Released under the MIT License