@supacloud/js
v0.19.4
Published
Platform SDK for SupaCloud, built on top of supabase-js
Downloads
805
Maintainers
Readme
@supacloud/js
@supacloud/js is the platform SDK for SupaCloud.
It does not replace @supabase/supabase-js. Instead, it wraps a normal Supabase client and adds SupaCloud-specific capabilities such as:
- background task submission
- task detail and list APIs
- cancel / retry helpers
- Realtime subscription with polling fallback
- Supabase Queues helpers backed by the official
pgmq_publicRPC API, plus SupaCloud management extensions for queue administration and diagnostics - project OAuth/OIDC migration and OAuth client management
- SupAuth provisioning and runtime verification helpers
Install
npm install @supacloud/js @supabase/supabase-jsQuick Start
import { createClient } from "@supabase/supabase-js";
import { createSupaCloudClient } from "@supacloud/js";
const supabase = createClient("https://api.example.com", "anon-key");
const supacloud = createSupaCloudClient({
supabase,
managementApiUrl: "https://admin.example.com",
projectRef: "abcd1234",
});
const task = await supacloud.tasks.submit("aorist-ai/generate/crop", {
body: { image_id: "img_123" },
idempotencyKey: "crop-img_123-v1",
correlationId: "workflow-run-123",
businessTaskId: "aorist-task-123",
metadata: {
workflow_id: "workflow-123",
billing_subject: "user-123",
},
});
const finalState = await task.wait();
console.log(finalState.status);SupAuth OAuth refresh with supabase-js
SupAuth OAuth public clients can require client_id on refresh-token requests.
Keep using @supabase/supabase-js for session storage, locking, and automatic
refresh, and provide the SupaCloud transport adapter when creating the client:
import { createClient } from "@supabase/supabase-js";
import { createSupaCloudOAuthFetch } from "@supacloud/js";
const supabase = createClient("https://auth.example.com", "anon-key", {
global: {
fetch: createSupaCloudOAuthFetch({
clientId: "public-oauth-client-id",
tokenEndpoint: "https://auth.example.com/auth/v1/oauth/token",
}),
},
});For a standard single-project Supabase app without SupAuth, omit clientId (or
omit the adapter entirely). With no clientId, the returned transport is a
transparent pass-through, so the regular /auth/v1/token refresh flow remains
unchanged. This allows shared application setup to enable SupAuth by
environment configuration without maintaining a second session implementation.
The adapter only transforms POST /auth/v1/token?grant_type=refresh_token:
it sends the same refresh token as an OAuth form request, moves
grant_type=refresh_token into the form body, and adds client_id when the
request does not already contain one. Rewritten refresh requests reject HTTP
redirects instead of forwarding the refresh token to a second endpoint. All
other Supabase Auth, Database, Storage, Realtime, and Functions requests pass
through unchanged. Never pass a client secret to browser code.
SupAuth Management
supacloud.supauth is a management-plane helper for provisioning and verifying a SupAuth/SupaOAuth runtime on SupaCloud. It is intended for trusted server-side tools, CI jobs, or admin backends that can call the SupaCloud Management API.
const supacloud = createSupaCloudClient({
supabase,
managementApiUrl: "https://admin.example.com",
projectRef: "abcd1234",
getAccessToken: () => process.env.SUPACLOUD_MANAGEMENT_TOKEN ?? null,
});
await supacloud.supauth.provision({
authDomain: "auth.example.com",
apiDomain: "api.example.com",
adminMode: "sso",
storageBuckets: [{ id: "avatars", public: true }],
});
await supacloud.supauth.reconcile({ dryRun: false });
const health = await supacloud.supauth.verify();
if (!health.healthy) {
throw new Error("SupAuth runtime is not healthy");
}
const config = await supacloud.supauth.getClientConfig();
console.log(config.authUrl);The helper maps to these SupaCloud Management API routes:
POST /v1/projects/:projectRef/supauth/provisionPOST /v1/projects/:projectRef/supauth/reconcilePOST /v1/projects/:projectRef/supauth/rollbackGET /v1/projects/:projectRef/supauth/client-configGET /v1/projects/:projectRef/supauth/verify
Boundary:
- Use
@supacloud/jsfor SupaCloud-owned infrastructure orchestration: GoTrue env injection, restart/reconcile, Caddy route setup, runtime health checks, and public client config discovery. - Use
@supabase/supabase-jsfor normal application runtime calls: auth session, database, storage, realtime, and edge functions. - Use the SupaOAuth product SDK or Management API for SupaOAuth-owned resources: applications, connectors, organizations, roles, permissions, audit logs, and webhooks.
- Do not expose SupaCloud Management API credentials in browser code.
Design
This SDK is intentionally thin:
supabase-jsstill owns auth, storage, database, Realtime transport, and plain function invokes@supacloud/jsowns SupaCloud platform semantics layered on top of that transport
tasks.submit() expects the target function path to be configured in background_routes.
That keeps frontend calls compatible with strict CORS deployments while preserving the same task receipt API.
Use correlationId, businessTaskId, and metadata when the application already has its own task, workflow, or billing records. SupaCloud stores these fields but does not interpret them; lifecycle webhooks echo them back so the application can update its own tables.
The current package focuses on:
tasks.submittasks.gettasks.listtasks.listDlqtasks.canceltasks.retrytasks.waittasks.subscribequeues.listqueues.createqueues.dropqueue(name).sendqueue(name).sendBatchqueue(name).readqueue(name).receivequeue(name).popqueue(name).archivequeue(name).ackqueue(name).deletequeue(name).releasequeue(name).listqueue(name).listArchivedqueue(name).statsqueue(name).purgequeue(name).getSettingsqueue(name).updateSettingsauth.oauthServer.getStatusauth.oauthServer.migrateToOidcauth.oauthServer.getDiscoveryauth.oauthServer.getJwksauth.oauthServer.buildAuthorizeUrlauth.oauthClients.list/create/get/update/delete/regenerateSecretsupauth.provisionsupauth.reconcilesupauth.rollbacksupauth.getClientConfigsupauth.verify
Status Subscription
tasks.subscribe() uses this strategy:
- try
postgres_changesonpublic.tasks - if Realtime is unavailable, fall back to polling the management API
This lets apps degrade gracefully when websocket or channel health is transient.
Task Lifecycle Webhook
Applications that already own a business task table should keep it. SupaCloud emits lifecycle events so the app can sync public.tasks, billing, Realtime, and workflow rows without adopting platform-internal mirror tables.
Register a webhook from a trusted backend:
POST /v1/projects/:ref/task-events/webhook
Authorization: Bearer <management-token>
Content-Type: application/json
{
"url": "https://app.example.com/supacloud/task-events",
"secret": "shared-hmac-secret"
}Events are delivered as { events: [...] }. Each event includes event_type, task_id, status, attempt, correlation_id, business_task_id, and metadata.
Supported lifecycle events:
task.createdtask.runningtask.succeededtask.failedtask.retry_scheduledtask.dead_letteredtask.cancelled
If secret is set, verify X-SupaCloud-Signature: sha256=<hmac> against the raw JSON body.
Queue Helpers
The core message operations use the official Supabase Queues API exposed through pgmq_public:
pgmq_public.send(queue_name, message, sleep_seconds)pgmq_public.send_batch(queue_name, messages, sleep_seconds)pgmq_public.read(queue_name, sleep_seconds, n)pgmq_public.pop(queue_name)pgmq_public.archive(queue_name, message_id)pgmq_public.delete(queue_name, message_id)
These calls go through your wrapped supabase client as supabase.schema('pgmq_public').rpc(...). Queue creation/drop, queue listing, metrics, purge, settings, diagnostics, and visibility-timeout adjustment are SupaCloud management extensions because Supabase's public Queue API intentionally does not expose those as client-side RPCs.
const queue = supacloud.queue("emails");
const message = await queue.send(
{ to: "[email protected]", template: "welcome" },
{
sleepSeconds: 10,
},
);
const leased = await queue.receive({ visibilityTimeoutSec: 60 });
if (leased) {
try {
await sendEmail(leased.payload);
await queue.ack(leased.msg_id);
} catch (error) {
await queue.release(leased.msg_id, { delayMs: 30_000, error: String(error) });
}
}
const stats = await queue.stats();
console.log(stats.queue_length, stats.oldest_msg_age_sec);Queue API surface:
queue.send(payload, { sleepSeconds }): enqueue one message throughpgmq_public.sendqueue.sendBatch(messages, { sleepSeconds }): enqueue messages throughpgmq_public.send_batchqueue.read({ sleepSeconds, n }): read up tonmessages throughpgmq_public.readqueue.receive({ visibilityTimeoutSec }): compatibility shortcut forread({ n: 1 })queue.pop(): read and delete the next message throughpgmq_public.popqueue.archive(messageId)/queue.ack(messageId): archive a message throughpgmq_public.archivequeue.delete(messageId): delete a message throughpgmq_public.deletequeue.release(messageId, { sleepSeconds | delayMs }): SupaCloud extension forpgmq.set_vtqueue.list(filters): SupaCloud diagnostic extension for queue/archive table inspectionqueue.listArchived(limit): SupaCloud diagnostic shortcut for archived messagesqueue.stats(): SupaCloud extension forpgmq.metricsqueue.purge(): SupaCloud extension forpgmq.purge_queuequeue.getSettings(): read concurrency, lease, retry, and rate-limit settingsqueue.updateSettings(settings): patch queue settingssupacloud.queues.list(): list queues withpgmq.list_queuessupacloud.queues.create(name, { unlogged }): create a basic or unlogged queuesupacloud.queues.drop(name): drop a queue
Queue settings:
max_in_flight: max concurrently leased/running messages for this queuedefault_visibility_timeout_sec: lease timeout used byreceive()max_attempts: application-level retry budget for SupaCloud consumers; PGMQ itself stores plain JSON messagesrate_limit_per_minute: producer enqueue limit
Management extension conflicts are surfaced as SupaCloudApiError with status, code, and responseBody, so callers do not need to parse raw fetch responses.
OAuth/OIDC Helpers
client.auth.oauthServer is the SupaCloud SDK surface for project-scoped OAuth 2.1 / OIDC migration and discovery.
It does not take a global account scope. The SDK always sends the normal Management API Bearer token and lets the server enforce project ownership.
