SDKsPHP / Laravel

PHP / Laravel SDK

causet/laravel-sdk is a Laravel package for submitting intents, running named queries, and streaming live events (intent SSE and causet-realtime ledger/projection SSE) from controllers, jobs, and services.

Webhook handlers and most controllers: use submitIntent(), not submitIntentStream(). Intent-progress SSE is a long-lived POST response — unsuitable for webhooks or short request paths. Use connectStream() to consume live Realtime events from a queue worker or artisan command, not a web request handler.

The PHP SDK is Early access. Source: github.com/Causet-Inc/causet-sdks (packages/laravel); Packagist publish in progress. See What runs today? for maturity and production-support details.


Requirements

  • PHP 8.2+
  • Laravel 11+ or 12+
  • Guzzle 7.8+ (included with Laravel)

Installation

Packagist is not published yet. Use a path repository from a local clone:

git clone https://github.com/Causet-Inc/causet-sdks.git
{
  "repositories": [{ "type": "path", "url": "../causet-sdks/packages/laravel", "options": { "symlink": true } }],
  "require": { "causet/laravel-sdk": "*" }
}

Then:

composer update causet/laravel-sdk
php artisan vendor:publish --tag=causet-config

When Packagist publishing completes, composer require causet/laravel-sdk will work directly.

Path repository for team contributors with source access:

{
  "repositories": [{ "type": "path", "url": "../causet-sdks/packages/laravel", "options": { "symlink": true } }],
  "require": { "causet/laravel-sdk": "*" }
}

Configuration

CAUSET_API_URL=https://api.causet.cloud
CAUSET_PLATFORM=my-platform
CAUSET_APPLICATION=my-app
CAUSET_FORK=main
CAUSET_API_KEY=ck_live_xxx.secret
CAUSET_BEARER_TOKEN=          # alternative to API key

Quick start — Facade

use Causet\Laravel\Facades\Causet;
 
// In a controller, job, or command
Causet::init();
 
$result = Causet::submitIntent('ticket_stream', 'tkt_1', 'CREATE_TICKET', [
    'customer_id' => 'cust_1',
    'subject' => 'Billing question',
    'body' => 'I was charged twice.',
]);
 
if ($result['accepted']) {
    $rows = Causet::runQuery('open_tickets', ['status' => 'open'], limit: 20);
}

Quick start — dependency injection

use Causet\Laravel\CausetClient;
 
class TicketController extends Controller
{
    public function __construct(private CausetClient $causet) {}
 
    public function index()
    {
        $this->causet->init();
 
        return response()->json(
            $this->causet->runQuery('open_tickets', [], limit: 50)
        );
    }
 
    public function close(string $ticketId)
    {
        $this->causet->init();
 
        return response()->json(
            $this->causet->submitIntent('ticket_stream', $ticketId, 'CLOSE_TICKET', [])
        );
    }
}

API reference

Entity state

$client->subscribe('stream_id', 'entity_id');
$state = $client->getState('stream_id', 'entity_id');  // array|null
$client->fetchState('stream_id', 'entity_id');
$client->listEntities(streamName: 'orders', limit: 50);

Intents

Use submitIntent() for webhook handlers, controllers, and jobs — synchronous HTTP submit to the runtime.

$result = $client->submitIntent(
    'stream_id', 'entity_id', 'INTENT_TYPE',
    ['key' => 'value'],
    intentId: 'optional-idempotency-key',
);
// ['accepted' => bool, 'execution_id' => ?, 'error' => ?, 'state_patch' => ?]

submitIntentStream() is intent-progress SSE (not the Realtime service). It blocks until execution completes — use only in long-running CLI/queue workers when you need START / COMPLETE events, not in webhooks or typical HTTP responses.

$client->submitIntentStream(
    'stream_id', 'entity_id', 'INTENT_TYPE', $payload,
    function (array $ev): void {
        logger()->info($ev['event'] ?? 'message', $ev['data'] ?? []);
    },
);

Queries

$client->runQuery('query_slug', ['param' => 'value'], limit: 30, includeTotal: true);
$client->runQuery('query_slug', null, cursor: 'abc123');
$client->listQueries();
$client->listProjections();

Real-time (SSE)

Live ledger and projection events come from causet-realtime, not the SaaS API host. connectStream() blocks the calling process for the connection’s lifetime — run it from a queue job or artisan command:

Causet::on('stream_event', function (array $ev): void {
    logger()->info($ev['event_type'] ?? 'event', $ev);
});
 
// Stream + fork + entity, resume from cursor 0
Causet::connectStream('sku_stream:sku-1', function (array $ev): void {
    logger()->info($ev['event_type'] ?? 'event', $ev);
}, forkId: 'sandbox', fromCursor: 0);

Stop the loop from a signal handler in long-running commands:

pcntl_async_signals(true);
pcntl_signal(SIGTERM, fn () => Causet::disconnectStream());
 
Causet::connectStream('sku_stream', $onEvent);

Example event (delivered to your handler):

{
  "cursor": 42,
  "stream_id": "sku_stream",
  "entity_id": "sku-1",
  "fork_id": "sandbox",
  "event_type": "STOCK_ADJUSTED",
  "patch": [{"op": "replace", "path": "/quantity", "value": 95}]
}

There is no wrapped WebSocket client (Laravel/Guzzle is synchronous) — see SSE / WebSocket for the WebSocket protocol reference if you need duplex subscriptions from a long-running worker with a separate WebSocket library.

Selectors and events

$unsub = $client->select(
    'stream_id', 'entity_id',
    fn (array $state) => $state['total'] ?? 0,
    fn ($total) => logger()->info("Total: $total"),
);
 
$off = $client->on('state', function ($ev) { /* ... */ });
$off();

All of the above methods are also available statically via the Causet facade (Causet::submitIntent(...), Causet::runQuery(...), etc.).

Error handling

use Causet\Laravel\Exceptions\CausetApiException;
use Causet\Laravel\Exceptions\CausetAuthException;
 
try {
    Causet::submitIntent('ticket_stream', 'tkt_1', 'CREATE_TICKET', []);
} catch (CausetApiException $e) {
    report($e);
    return response()->json(['error' => $e->getMessage()], $e->statusCode);
} catch (CausetAuthException $e) {
    return response()->json(['error' => 'Causet auth failed'], 401);
}

Testing your app

Mock the client in Laravel tests:

use Causet\Laravel\CausetClient;
 
$this->mock(CausetClient::class, function ($mock) {
    $mock->shouldReceive('init');
    $mock->shouldReceive('runQuery')->andReturn(['items' => []]);
});

HTTP endpoints

OperationPath
TokenPOST /v1/token
IntentPOST /v1/runtime/platforms/{p}/applications/{a}/intents/submit
Intent SSEPOST /v1/runtime/stream/platforms/{p}/applications/{a}/intents/submit
Entity stateGET /v1/platforms/{p}/applications/{a}/entities/{stream}/{id}/state
QueryPOST /v1/platforms/{p}/applications/{a}/forks/{fork}/queries/{slug}/run
Stream SSEGET {realtimeUrl}/v1/platforms/{p}/applications/{a}/streams/{streamId}/events

Next steps