Bundled origins
The package ships four abstract origins. Extend whichever one matches the protocol you are talking to, or extend Origin and write the client from scratch, since a fetch() that returns an array is the whole contract.
RemoteOrigin
A generic REST client. Declare the base url, the auth type and the endpoints:
use EduLazaro\Larasources\Origins\RemoteOrigin; class ListingOrigin extends RemoteOrigin { protected string $baseUrl = 'https://api.example.com'; protected string $authType = 'bearer'; // none, bearer, basic, key protected string $authHeader = 'Authorization'; // for authType key public static function getAlias(): string { return 'listing'; } protected function endpoint(array $arguments): string { return '/listings/' . ($arguments['id'] ?? ''); } protected function storeEndpoint(array $data): string { return '/listings'; } }
What it does for you:
- Creates with POST or updates with PUT depending on the stored external id, falling back to an
idin the payload, because the external id is optional. - Reports the response's
idback as the external id, which you redirect by overridingexternalIdFrom(). - Treats a 404 on delete as already deleted, because deleting twice is not a failure.
- Throws
OriginExceptionon anything else, so a failed write never reaches the row. - Checks the credentials its
authTypewill actually read before sending anything.
There are also two hooks for when their shape is close enough to yours that writing a whole Source mapping for it would be overkill:
protected function transformResponse(array $data): array { return $data['listing'] ?? $data; } protected function transformForSave(array $data): array { return ['listing' => $data]; }
The credentials come from config(), so api_token, api_key or username and password resolve wherever you decided they live.
ScraperOrigin
For when the external source is a page. Declare how to build the url and what to pull out of the HTML:
use EduLazaro\Larasources\Origins\ScraperOrigin; class RecipeOrigin extends ScraperOrigin { public static function getAlias(): string { return 'recipe'; } protected function url(array $arguments): string { return $arguments['url']; } protected function selectors(): array { return [ 'title' => 'h1', 'author' => '.author', 'servings' => 'div.card.servings', 'origin' => '#origin span', ]; } }
Tags, ids, several classes on one element and descendants are translated to XPath internally, and the page is read as UTF-8, which matters the moment a title has an accent in it. Larascraper is used instead when it is installed, which is what you want for proxies, headless browsers and anything that fights back.
The Source still describes the page as a typed resource with casts and accessors. The scraping stays inside the origin, and the day they publish an API you rewrite the origin and leave the Source alone.
AgentOrigin
For a source whose content a model writes. Declare the prompt and the provider:
use EduLazaro\Larasources\Origins\AgentOrigin; class DescriptionOrigin extends AgentOrigin { protected string $provider = 'anthropic'; // or openai protected string $model = 'claude-sonnet-4-5'; protected float $temperature = 0.4; protected int $maxTokens = 1200; public static function getAlias(): string { return 'description'; } protected function prompt(array $arguments): string { return "Write a listing description for: {$arguments['features']}"; } protected function systemPrompt(): ?string { return 'You write property listings. No superlatives, no exclamation marks.'; } protected function parseResponse(string $content): array { return ['description' => trim($content)]; } }
The key comes from the integration's api_key, falling back to services.{provider}.key. Paired with a pointer argument this is how a generated field is derived from a scraped one, with each step on its own row and its own signature, so the generation only runs again when the input actually changed.
Origin
The base. Override fetch(), and save() and delete() when the integration supports them. Everything else is already there: the alias, config() for the integration's settings, http() with its timeout and retries, and isConfigured().