Build on the supported package contract
Use @absolutejs/sync-capacitor through its supported public entry points.
@absolutejs/sync-capacitorv0.9.3betaData & SyncTransactional Capacitor SQLite local store and native lifecycle adapter for @absolutejs/sync
bun add @absolutejs/sync-capacitorNative persistence and lifecycle wiring for @absolutejs/sync applications running in Capacitor. It stores confirmed rows, cursors, installation identity, and the durable mutation outbox in one principal-partitioned SQLite database. It also exposes namespace-scoped collection discovery so finite native workers can resume safe id-keyed pulls without application-authored worker code.
AbsoluteJS provisions this automatically when Mobile, Sync, and Auth are enabled. Version 0.9.3 adds peer support tested against @absolutejs/devices@0.7.0 and @absolutejs/devices-capacitor@0.8.0, while retaining the earlier declared peer ranges. Intermediate Devices release lines are not newly certified by this change. Keep the application and this adapter on one compatible Devices pair; do not use package-manager overrides to hide an incompatible peer range.
Direct Capacitor applications can opt in explicitly:
Resume and restored-connectivity events refresh the Auth-backed socket and ask the client to flush within a finite ten-second budget. Explicitly retryable failures obey the client's delivery ceiling; conflicts and permanent failures remain in the principal's dead-letter partition for explicit remediation rather than replaying forever.
Generated mutation conflict policies are captured inside each encrypted outbox record. The TypeScript foreground/headless runner and the Android WorkManager or iOS BGProcessingTask runner therefore make the same bounded decision: manual conflicts become dead letters, server-wins discards the rejected local intent, and client-wins retries the unchanged operation ID up to its declared ceiling. An explicit argument-changing rebase is left to the foreground remediation API and creates a new traceable intent.
The namespace must come from a verified Auth principal, never from an untrusted route or form value. AbsoluteJS derives an opaque namespace from the verified issuer, public client ID, and subject. Signing out locks that partition by removing it from the active runtime; it does not silently destroy offline data. Signing back in as the same verified principal unlocks the same partition.
storageSchema accepts the same generated SyncLocalStoreSchema or component bundle used by createIndexedDbSyncLocalStore. Before any foreground transaction begins, the adapter migrates every principal's collections, durable mutations, and logical schema marker inside one SQLite transaction. A transform failure or process death rolls back the entire upgrade; a runtime older than the stored schema fails closed.
Migration callbacks are synchronous and deterministic. They may replace or delete persisted records, but cannot change a mutation's stable operation ID. AbsoluteJS will generate and provision the plan for ordinary applications; direct Capacitor integrations can pass it explicitly:
Absolute composes the app schema and every installed Sync pack into a deterministic component bundle. SQLite tracks each component independently, keeps removed-pack ledgers as orphan diagnostics, and migrates all records and ledger updates in one transaction.
AbsoluteJS automatically installs createCapacitorSyncProtection(). It creates one random AES-256-GCM data key, seals that key in the existing iOS Keychain or Android Keystore vault, and writes only authenticated ciphertext envelopes to SQLite. Namespace, record kind, and collection/mutation name are authenticated as associated data. The foreground adapter and both finite native workers use the same versioned format; a missing key, changed identity, or modified record fails closed. Direct Capacitor integrations opt in as shown above.
The generated localData policy also enforces memory-only records, whole-projection expiry, deterministic eviction priority, and a logical per-principal byte ceiling. Pending mutations are never evicted. When protected data declares a memory-only fallback, browsers without an audited key provider remain usable without writing that data to disk.
AbsoluteJS also configures AbsoluteBackgroundSync when the application uses both @absolutejs/auth and @absolutejs/sync. The native worker is deliberately finite: Android WorkManager or iOS BGProcessingTask wakes it, it refreshes a short-lived access token, pushes a bounded durable outbox batch, pulls the foreground client's persisted collection descriptors, commits the response to the same SQLite database, and exits. Foreground/resume Sync remains the correctness path because neither operating system guarantees when background work will run.
The worker does not run application JavaScript and does not expose Capacitor APIs to a background WebView. Its credential and network boundary is fixed:
the OAuth refresh token is read from the shared native Keychain/Keystore
vault and sent only to the issuer-advertised HTTPS token endpoint;
the resulting bearer token, mutation arguments, and collection parameters
are sent only to the configured same-origin AbsoluteJS endpoint;
redirects fail closed, and response bodies are bounded before parsing;
a rotated refresh token is written back to the vault, while returned Sync
data is written only to the principal's SQLite partition.
Direct Capacitor applications can configure the worker after resolving a verified principal:
Call AbsoluteBackgroundSync.clear() on sign-out. On iOS, register AbsoluteBackgroundSyncPlugin.registerBackgroundTask() during application launch and list .absolutejs.background-sync in BGTaskSchedulerPermittedIdentifiers; the AbsoluteJS mobile CLI owns those generated regions. Android scheduling is registered by the plugin.
AbsoluteJS protects record payloads with AES-256-GCM rather than claiming the
default SQLite connection is SQLCipher-encrypted. SQLite keys, namespaces, and ordering columns remain metadata; sensitive row and mutation payloads are ciphertext. Review platform encryption-export requirements before shipping.
Web/PWA builds should use createIndexedDbSyncLocalStore from
@absolutejs/sync/client; they do not need the plugin's WASM/web component.
The adapter serializes transactions so an app cannot overlap two explicit
transactions on the same Capacitor connection.
The native iOS worker currently targets the SQLite plugin's default Documents
location. Applications that override CapacitorSQLite.iosDatabaseLocation must keep foreground and background database locations aligned before enabling managed background Sync.
Outcomes
Use @absolutejs/sync-capacitor through its supported public entry points.
Hardening checklist
Follow in order
# @absolutejs/sync-capacitor
import { createSyncClient } from '@absolutejs/sync/client';
import { lifecycle, network } from '@absolutejs/devices';
import {
createCapacitorSyncLocalStore,
createCapacitorSyncProtection,
installCapacitorSyncLifecycle
} from '@absolutejs/sync-capacitor';
const client = createSyncClient({
url: 'wss://app.example.com/sync/ws',
durable: {
store: createCapacitorSyncLocalStore({
protection: createCapacitorSyncProtection()
}),
namespace: authenticatedPrincipalNamespace
}
});
const removeLifecycle = await installCapacitorSyncLifecycle({
client,
lifecycle,
network
});storageSchema accepts the same generated SyncLocalStoreSchema or component bundle used by createIndexedDbSyncLocalStore. Before any foreground transaction begins, the adapter migrates every principal's collections, durable mutations, and logical schema marker inside one SQLite transaction. A transform failure or process death rolls back the entire upgrade; a runtime older than the stored schema fails closed.
const store = createCapacitorSyncLocalStore({
storageSchema: {
version: 2,
migrations: [
{
toVersion: 2,
migrateCollection(record) {
return {
...record,
rows: record.rows.map((row) => ({
...(row as object),
archived: false
}))
};
}
}
]
}
});AbsoluteJS also configures AbsoluteBackgroundSync when the application uses both @absolutejs/auth and @absolutejs/sync. The native worker is deliberately finite: Android WorkManager or iOS BGProcessingTask wakes it, it refreshes a short-lived access token, pushes a bounded durable outbox batch, pulls the foreground client's persisted collection descriptors, commits the response to the same SQLite database, and exits. Foreground/resume Sync remains the correctness path because neither operating system guarantees when background work will run.
import { configureCapacitorBackgroundSync } from '@absolutejs/sync-capacitor';
await configureCapacitorBackgroundSync({
issuer: 'https://app.example.com',
clientId: 'mobile-public-client',
endpoint: 'https://app.example.com/__absolute/sync/background',
namespace: authenticatedPrincipalNamespace
});Supported entry points declared by this package manifest.
Scripts declared by this package manifest.