Download the PHP package jamescarr/ankusa without Composer
On this page you can find all versions of the php package jamescarr/ankusa. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download jamescarr/ankusa
More information about jamescarr/ankusa
Files in jamescarr/ankusa
Package ankusa
Short Description Client SDK for Ankusa deployments: the claim-check gateway client, the route-management, operator (admin) and source-management clients, and a webhook-receiving header helper.
License Apache-2.0
Homepage https://github.com/jamescarr/ankusa/tree/main/packages/sdk-php
Informations about the package ankusa
ankusa
The client SDK for Ankusa deployments: one Packagist package meant to bundle everything a non-Elixir consumer needs to talk to an Ankusa deployment. Today that's the claim-check gateway client, the route-management and operator (admin) clients, the source-management client, and a webhook-receiving header helper; more clients (ingest) land here as they're built.
This repository is the read-only split mirror of
packages/sdk-php
in jamescarr/ankusa, published so
Packagist — which reads composer.json at a repository root — can serve it.
Every push here is generated by the release workflow; development, issues and
pull requests happen in the main repository.
Install
Until the first release is on Packagist, depend on it as a local path, the same
way the Elixir packages in this monorepo depend on ankusa core before their
first Hex release:
PHP 8.3 or newer. guzzlehttp/guzzle and its PSR-7/PSR-18 interfaces are
installed as dependencies; any other PSR-18 client can be injected instead.
Claim-check client
Redeem a claim-check ref, verify the bytes it returns against the message's
sha256, and classify failures into dead-letter vs. retry, without holding any
object-store credentials. Conforms to the framework's own contract,
priv/openapi/claim_check.v1.yaml:
the spec is the source of truth, this package conforms to it, not the reverse.
redeem() does three things GET /v1/claims/... alone doesn't:
- Parses the ref into its tenant id, claim id, and gateway path
(
GET /v1/claims/{tenant_id}/{claim_id}) —ParsedClaimRef::parse(), which is also public on its own. - Fetches the bytes.
- Verifies them against the message's
sha256(the gateway itself does not check this, see "Redeem a claim" in docs/claim-check.md) before ever returning them to you.
Every failure is a ClaimCheckError with an isRetryable(), so a consumer
needs exactly one bit to decide dead-letter vs. retry:
| Class | isRetryable() |
Cause |
|---|---|---|
InvalidClaimRefError |
false |
$ref isn't a well-formed claim-check URN, or $sha256 isn't 64-char lowercase hex |
ClaimNotFoundError |
false |
gateway 404: expired by retention, or never written |
ClaimRejectedError |
false |
gateway 4xx other than 404 (public $status, $body) |
ClaimIntegrityError |
false |
sha256 of the returned bytes doesn't match |
ClaimCheckUnavailableError |
true |
gateway 5xx/503, or unreachable |
health() hits GET /health for a liveness probe.
Webhook receiver helper
Every receiver of Ankusa's HTTP sink needs the same handful of headers off each
request; HookHeaders::fromHeaders() replaces the hand-rolled $headers[...]
lookups with one call and a typed result. It takes a PSR-7 message or a plain
array (getallheaders(), Symfony's HeaderBag::all()), and looks the names up
case-insensitively either way:
HookHeaders->id is what a receiver dedupes on: delivery is at-least-once (see
"HTTP handoff" in
docs/integrations.md),
so the same hook can arrive twice after a retry.
Routes client
Manage route definitions and the global IP rules on the route-management
listener (routes.admin.port, default 4003) — the routes tag of
priv/openapi/admin.v1.yaml.
Methods: health(), listRoutes(), createRoute(), getRoute(),
replaceRoute(), updateRoute(), deleteRoute(), getIpRules(),
putIpRules(), testRoute(). Route ids are percent-encoded as one path
segment, so /, ?, #, % and a space in an id can't reshape the URL.
Failures are RoutesError subclasses: InvalidRouteIdError (an empty id, or
exactly ./.. — raised before any request, because a URL parser would
otherwise normalize it into the collection endpoint and hand back the list page
as if it were a route), RouteNotFoundError (404), RoutesRejectedError (any
other 4xx, carrying $errorCode, $field, $detail — the body's message,
since PHP's message is taken by Throwable — $conflictingId and
$maxRoutes), and RoutesUnavailableError (5xx, an unfollowed redirect, a
non-JSON success body, or unreachable; retryable).
Admin client
The operator API on admin.port (default 4002): health, Prometheus metrics,
the redacted config, the DLQ, and the quarantine list — the operations, dlq
and quarantine tags of admin.v1.yaml.
Methods: health(), metrics() (Prometheus text), config(),
listDeadLetters(), replayDeadLetters(), listQuarantined(). Failures are
AdminError subclasses: RoleNotEnabledError (409 role_not_enabled,
carrying $role), AdminRejectedError (any other 4xx, carrying $errorCode),
and AdminUnavailableError (5xx, an unfollowed redirect, or unreachable;
retryable).
Sources client
Ankusa's ingest sources are tenant-scoped: a source is addressed as
<tenant>.<name>, and Ankusa.Admin.Router serves their CRUD API on the same
admin.port as the operator API. SourcesClient speaks in those terms and
builds the paths for you.
Methods: serverVersion(), listSources(), getSource(), createSource(),
updateSource(), deleteSource(). A write takes the whole spec (SourceSpec,
whose toJson() omits unset fields); a read returns a Source, which is always
redacted — resending a read-back verify map is not the same as resending the
stored secret, so supply secrets through SourceSpec.
new SourcesClient($url, '0.3.0') is an optional latch: the first API call
fetches GET /health, compares its version field, and raises
VersionMismatchError on a mismatch (the version is cached afterwards, so no
further request checks it). Failures are SourcesError subclasses, each
carrying $status and $body: SourceNotFoundError (404),
SourceConflictError (409), SourceStoreReadOnlyError (409 — the deployment's
source store is a static seed), SourceInvalidError (400, or an invalid
tenant/name caught before any request; getMessage() carries the server's own
message or its error code), VersionMismatchError, and
SourcesUnavailableError (unreachable, timed out, or 5xx).
Tenants and source names must match ^[A-Za-z0-9_-]{1,64}$; anything else
raises SourceInvalidError before a path is built.
Errors and retries
- Every exception this SDK throws implements
Ankusa\AnkusaException, so onecatchcovers everything the package can raise. Each client's errors share a base class as well (ClaimCheckError,RoutesError,AdminError,SourcesError). - The claim-check, routes and admin hierarchies answer
isRetryable():falsefor a rejected request or an invalid input caught locally,truefor an unreachable listener, a timeout, a5xx, or an unfollowed redirect. Sorting a failure into dead-letter vs. retry needs no status-code knowledge. - Every client takes an optional PSR-18 client as its last constructor
argument. The default is Guzzle, which never follows redirects and never
throws on a status code; an injected client owns its own timeout and redirect
policy, though the SDK still treats any
3xxas a failure. Client headers ride on every request. - Request bodies are JSON objects:
replayDeadLetters()with no filter sends{}, never[]. Nested empty objects are the caller's job — pass(object) []for a nested{}, or[]for a nested[].
Layout
A future client (say, an ingest helper) gets its own src/<Name>/ directory with
the same shape.
Develop
composer test runs the same vectors every other SDK in the monorepo runs
(conformance/check.mjs, mise run check:conformance); the runner starts PHP's
built-in server on a free port to stand in for a deployment's gateway.
All versions of ankusa with dependencies
guzzlehttp/guzzle Version ^7.9
guzzlehttp/psr7 Version ^2.13
psr/http-client Version ^1.0
psr/http-message Version ^2.0