AbsoluteJS

@absolutejs/sync-capacitor

@absolutejs/sync-capacitorv0.9.3betaData & Sync

Transactional Capacitor SQLite local store and native lifecycle adapter for @absolutejs/sync

#Installation

BASH
bun add @absolutejs/sync-capacitor

#Capabilities

Overview

Native 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:

Show 3 more

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.

Installed-data upgrades

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.

Protected local data and quotas

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.

Managed native background Sync

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

Show 8 more

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.

Platform notes

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

Show 5 more

@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

What you can build

Build on the supported package contract

Use @absolutejs/sync-capacitor through its supported public entry points.

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/sync-capacitor version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Trace from the first failed boundary
Reproduce the smallest canonical @absolutejs/sync-capacitor example, confirm the supported entry point and version in the API explorer, then inspect the first boundary that did not produce its documented result.

#@absolutejs/sync-capacitor quick start

Partial snippet

# @absolutejs/sync-capacitor

TS
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
});

#Installed-data upgrades

Partial snippet

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.

TS
const store = createCapacitorSyncLocalStore({
	storageSchema: {
		version: 2,
		migrations: [
			{
				toVersion: 2,
				migrateCollection(record) {
					return {
						...record,
						rows: record.rows.map((row) => ({
							...(row as object),
							archived: false
						}))
					};
				}
			}
		]
	}
});

#Managed native background Sync

Partial snippet

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.

TS
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
});

#Public entry points

Supported entry points declared by this package manifest.

@absolutejs/sync-capacitorPackage entry point declared in package.json.

#Package commands

Scripts declared by this package manifest.

bun run buildrm -rf dist && bun build src/index.ts --outdir dist --sourcemap --target=browser --external @absolutejs/sync --external @absolutejs/devices --external @absolutejs/devices-capacitor --external @capacitor/core --external @capacitor-community/sqlite && tsc --project tsconfig.build.json
bun run formatprettier --write "./**/*.{ts,json,md}"
bun run testbun test
bun run typechecktsc --noEmit