# How CereusDB (Apache Sedona WASM) Enables Client-Side Spatial SQL Execution in GeoLibre

> Discover how GeoLibre uses CereusDB (Apache Sedona WASM) for client-side spatial SQL execution directly in your browser. Learn about dynamic WASM loading and efficient query processing.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-04

---

**GeoLibre runs spatial SQL entirely in the browser using CereusDB, the WebAssembly build of Apache Sedona, through dynamic WASM loading, singleton instance management, and an exclusive execution queue.**

The **opengeos/GeoLibre** project delivers a complete client-side spatial SQL engine by integrating CereusDB—the WASM port of Apache Sedona. Unlike traditional setups requiring a remote database server, GeoLibre executes **spatial SQL queries** directly in the browser using a ~40 MB WebAssembly binary that loads on demand. This architecture eliminates network round-trips for query processing while maintaining compatibility with standard PostGIS-style spatial functions.

## Dynamic WASM Loading with Vite Integration

GeoLibre minimizes initial bundle size by deferring the CereusDB WASM binary until first use. The build process emits the binary as a hashed asset, imported via Vite's `?url` syntax.

In [`apps/geolibre-desktop/src/lib/cereus-loader.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/cereus-loader.ts) (lines 24-55), the loader handles this initialization:

```typescript
// Dynamic import pattern - WASM only fetched when needed
import cereusWasmUrl from '@cereusdb/standard/dist/cereus.wasm?url';

export async function loadCereusDb(): Promise<CereusInstance> {
  const { CereusDB } = await import('@cereusdb/standard');
  return CereusDB.create({ wasmUrl: cereusWasmUrl });
}

```

This approach keeps the initial application bundle lightweight. The `@cereusdb/standard` package loads dynamically, and the WASM binary fetches asynchronously only when the user first opens a SQL workspace.

## Singleton Instance Pattern for Memory Efficiency

The workspace implements a **memoized promise pattern** to prevent redundant WASM fetches. In [`apps/geolibre-desktop/src/lib/sedona-workspace.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/lib/sedona-workspace.ts) (lines 48-60):

- A `dbPromise` variable caches the initialization promise
- The ~40 MB WASM module downloads once per browser session
- Failed fetches automatically clear the promise, enabling graceful retries

```typescript
// Singleton pattern from sedona-workspace.ts
let dbPromise: Promise<CereusInstance> | undefined;

async function getDb(): Promise<CereusInstance> {
  if (!dbPromise) {
    dbPromise = loadCereusDb().catch(err => {
      dbPromise = undefined;  // Clear on failure for retry
      throw err;
    });
  }
  return dbPromise;
}

```

## Exclusive Execution Queue for Thread Safety

CereusDB's WASM engine processes **one operation at a time**. To prevent race conditions during concurrent queries, GeoLibre implements an exclusive queue at lines 65-74 of [`sedona-workspace.ts`](https://github.com/opengeos/GeoLibre/blob/main/sedona-workspace.ts):

```typescript
import { Mutex } from 'async-mutex';

const dbMutex = new Mutex();

async function runExclusive<T>(fn: () => Promise<T>): Promise<T> {
  return dbMutex.runExclusive(fn);  // Queues concurrent operations
}

```

This guarantees that table registrations and query executions never interleave—a critical safeguard when multiple SQL dialogs run simultaneously.

## Registering GeoJSON Layers as Spatial Tables

CereusDB exposes spatial data through standard SQL tables. GeoLibre converts in-memory GeoJSON layers into queryable tables via two-phase registration (lines 76-84, 99-107, 124-136):

```typescript
// Phase 1: Register raw GeoJSON
db.registerGeoJSON("layer_source", geojsonFeatureCollection);
// Creates table with: geometry (WKT text), properties (JSON string)

// Phase 2: Create parsing view
await db.sqlJSON(`
  CREATE OR REPLACE VIEW layer AS
  SELECT ST_GeomFromText(geometry) AS geometry, properties
  FROM layer_source
`);

```

The `registerGeoJSON()` method stores geometries as **Well-Known Text (WKT)** and properties as JSON strings. A subsequent view applies `ST_GeomFromText` to produce proper geometry columns usable with spatial predicates.

## Schema Discovery via Arrow IPC Streams

Before rendering results, GeoLibre discovers column names and geometry types. At lines 20-33 of [`sedona-workspace.ts`](https://github.com/opengeos/GeoLibre/blob/main/sedona-workspace.ts), the engine:

1. Executes the full statement via `db.sql()` returning an **Arrow IPC stream**
2. Extracts schema metadata from the Arrow table
3. Detects geometry columns through **GeoArrow extension metadata** or name-based heuristics

```typescript
const arrowTable = await db.sql(query);
const schema = arrowTable.schema;

// GeoArrow detection
const geoColumn = schema.fields.find(f => 
  f.metadata?.get('ARROW:extension:name') === 'geoarrow.geometry'
);

```

This schema discovery enables dynamic result grid generation without pre-defined table structures.

## Query Execution with Dual Geometry Formats

GeoLibre's query wrapper (lines 64-90) produces results in parallel formats for different consumption paths:

```typescript
const wrappedQuery = `
  SELECT 
    -- WKT for display grid
    ST_AsText(geometry) as geometry,
    -- Hidden GeoJSON for layer creation
    ST_AsGeoJSON(geometry) as __geojson_geometry,
    properties ->> 'name' as name
  FROM (${userQuery}) AS __geolibre_sql_subquery
`;

const result = await db.sqlJSON(wrappedQuery);

```

The execution flow:

- **Subquery wrapping** isolates user SQL while injecting geometry conversions
- `ST_AsText` produces human-readable WKT for the results grid
- `ST_AsGeoJSON` provides machine-readable geometry for the "Add as Layer" button
- `db.sqlJSON()` returns flattened rows with expanded properties columns

## Complete Client-Side Workflow Example

```typescript
import { loadCereusDb } from "./lib/cereus-loader";

// 1. Initialize (first call triggers WASM fetch)
const db = await loadCereusDb();

// 2. Load sample data
const cities = {
  type: "FeatureCollection",
  features: [{
    type: "Feature",
    geometry: { type: "Point", coordinates: [-122.4194, 37.7749] },
    properties: { name: "San Francisco", population: 873965 }
  }]
};

// 3. Register and prepare spatial table
db.registerGeoJSON("cities_raw", cities);
await db.sqlJSON(`
  CREATE OR REPLACE VIEW cities AS
  SELECT ST_GeomFromText(geometry) AS geom, properties
  FROM cities_raw
`);

// 4. Execute spatial SQL entirely in browser
const nearby = await db.sqlJSON(`
  SELECT 
    properties ->> 'name' AS city,
    ST_Distance(geom, ST_GeomFromText('POINT(-122.5 37.8)')) AS dist_km
  FROM cities
  WHERE ST_DWithin(geom, ST_GeomFromText('POINT(-122.5 37.8)'), 0.5)
`);
// Returns: [{ city: "San Francisco", dist_km: 8.92 }]

```

## Automatic Fallback Routing

When the optional Python sidecar (for heavier analytics) is unavailable, GeoLibre transparently routes all queries to CereusDB. The `runSedonaQuery` function at lines 84-98 of [`sedona-workspace.ts`](https://github.com/opengeos/GeoLibre/blob/main/sedona-workspace.ts) implements this engine selection logic—users experience seamless degradation without configuration changes.

## Build-Time Loader Variants

The project supports two WASM distribution strategies controlled by environment variables:

| Loader | File | Use Case |
|--------|------|----------|
| Bundled | [`cereus-loader.ts`](https://github.com/opengeos/GeoLibre/blob/main/cereus-loader.ts) | Self-contained builds, air-gapped environments |
| CDN | [`cereus-loader.cdn.ts`](https://github.com/opengeos/GeoLibre/blob/main/cereus-loader.cdn.ts) | Reduced bundle size, faster initial load |

Vite configuration at [`vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/vite.config.ts) swaps implementations via `GEOLIBRE_CEREUS_CDN` flag:

```typescript
// Conditional alias resolution
resolve: {
  alias: process.env.GEOLIBRE_CEREUS_CDN
    ? { './cereus-loader': './cereus-loader.cdn.ts' }
    : {}
}

```

## Summary

- **CereusDB** brings Apache Sedona's spatial SQL engine to the browser via WebAssembly
- **Dynamic loading** with Vite's `?url` syntax defers the ~40 MB WASM payload until first use
- **Singleton pattern** with `dbPromise` memoization prevents redundant module fetches
- **Mutex-based queueing** (`runExclusive`) ensures single-threaded WASM safety
- **Two-phase registration** converts GeoJSON to WKT-backed tables with parsed geometry views
- **Arrow IPC streams** enable automatic schema and geometry column discovery
- **Dual-format results** provide both WKT (display) and GeoJSON (layer creation) outputs
- **Transparent fallback** routes queries to CereusDB when Python sidecar is unavailable

## Frequently Asked Questions

### What is CereusDB and how does it relate to Apache Sedona?

**CereusDB is the official WebAssembly build of Apache Sedona**, compiling the JVM-based spatial analytics engine into browser-executable WASM. It exposes the same spatial SQL functions (ST_Intersects, ST_Buffer, ST_Distance, etc.) without requiring a Java runtime or server infrastructure.

### How large is the CereusDB WASM binary and when does it load?

**The WASM binary is approximately 40 MB compressed**. GeoLibre loads it only on first SQL workspace use through dynamic `import()`, keeping the initial application bundle small. The singleton pattern ensures one fetch per browser session regardless of how many queries execute.

### Can CereusDB handle concurrent spatial queries?

**No—the WASM engine is single-threaded.** GeoLibre wraps all database operations with `Mutex.runExclusive()` to serialize access. Concurrent queries queue automatically; users experience sequential execution without data corruption risks.

### What spatial data formats does CereusDB support?

**CereusDB ingests GeoJSON via `registerGeoJSON()`**, stores geometries internally as WKT, and outputs results through `ST_AsText()`, `ST_AsGeoJSON()`, or GeoArrow-encoded Arrow tables. The engine supports the full Sedona spatial predicate and function library.