Skip to content

🚀 GetPackageChanges

Fetches incremental change records for Packagist package metadata, used for incremental sync of self-hosted mirrors.

When to use

🛠️ When building a self-hosted Packagist mirror to avoid pulling the full dataset every time; 🔍 when monitoring which packages are added / updated / deleted; ⏱️ for sync jobs that need to be resumable and advance by a time cursor.

Signature

go
func (c *ComposerClient) GetPackageChanges(ctx context.Context, since int64) (*domain.ChangeTrackingResponse, error)

Parameters

ParameterTypeDescription
ctxcontext.ContextContext, used for timeout and cancellation
sinceint64Incremental cursor (Unix timestamp); pass 0 to get the current timestamp back with Actions empty, useful for obtaining the initial cursor

Return value

ValueTypeDescription
Result*domain.ChangeTrackingResponseContains Timestamp (the next cursor) and an Actions change list whose Type is update/delete
ErrorerrorReturned on HTTP failure, non-200 status, or JSON parse failure

Corresponding endpoint: GET https://packagist.org/metadata/changes.json?since={timestamp}.

Example

go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

func main() {
	c := client.NewComposerClient(30 * time.Second)
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// First sync: obtain a starting cursor
	resp, err := c.GetPackageChanges(ctx, 0)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Starting cursor: %d\n", resp.Timestamp)

	// Next time, use this cursor for an incremental pull
	resp, err = c.GetPackageChanges(ctx, resp.Timestamp)
	if err != nil {
		log.Fatal(err)
	}
	for _, a := range resp.Actions {
		fmt.Printf("[%s] %s @ %d\n", a.Type, a.Package, a.Time)
	}
	fmt.Printf("Next cursor: %d\n", resp.Timestamp)
}

Advanced

Incremental sync pattern

When since=0, only the current Timestamp is returned without historical changes. The correct approach is: ① first call with since=0 to obtain the starting Timestamp and persist it; ② for every subsequent call, pass the previous Timestamp as since, process the returned Actions, then save the new Timestamp. If since is invalid, the response Error field will contain an error message.

  • 📦 For packages with Type=update, call GetPackage to refresh the local cache; for packages with Type=delete, remove them locally.
  • 🧩 For V2 mirrors, use GetPackageWithV2Metadata.

Released under the MIT License