@kepler.gl/sqlrooms
v3.3.0-alpha.13
Published
Kepler.gl maps and configuration for SQLRooms applications
Downloads
184
Readme
@kepler.gl/sqlrooms
Kepler.gl integration for SQLRooms.
Use this package when you want a map-first analytics experience in a SQLRooms app, backed by DuckDB tables and SQL.
What this package provides
createKeplerSlice()to add Kepler state/actions to your Room storeKeplerMapContainerandKeplerPlotContainerfor rendering maps/overlaysKeplerSidePanelsfor layer/filter/interaction UI- utilities for map config persistence, dataset synchronization, and migration from legacy Kepler-owned tabs to artifact-backed tabs
Selection model
createKeplerSlice()manages Kepler map documents and runtime state keyed by map id, but it does not own host-level map selection.- Render maps with explicit ids, for example
<KeplerMapContainer mapId={id} />. - Use
@sqlrooms/artifactswhen an app needs multiple user-managed map tabs; artifact state should own the selected map artifact.
Installation
npm install @kepler.gl/sqlrooms @sqlrooms/room-shell @sqlrooms/duckdb @sqlrooms/uiQuick start
import {useEffect} from 'react';
import {createKeplerSlice, KeplerMapContainer, KeplerSliceState} from '@kepler.gl/sqlrooms';
import {
createRoomShellSlice,
createRoomStore,
RoomShell,
RoomShellSliceState
} from '@sqlrooms/room-shell';
type RoomState = RoomShellSliceState & KeplerSliceState;
export const {roomStore, useRoomStore} = createRoomStore<RoomState>((set, get, store) => ({
...createRoomShellSlice({
config: {
dataSources: [
{
type: 'url',
tableName: 'earthquakes',
url: 'https://huggingface.co/datasets/sqlrooms/earthquakes/resolve/main/earthquakes.parquet'
}
]
}
})(set, get, store),
...createKeplerSlice()(set, get, store)
}));
function MapPanel() {
const mapId = useRoomStore(state => state.kepler.config.maps[0]?.id);
const addTableToMap = useRoomStore(state => state.kepler.addTableToMap);
const isTableReady = useRoomStore(state => Boolean(state.db.findTable('earthquakes')));
useEffect(() => {
if (!isTableReady || !mapId) return;
void addTableToMap({
mapId,
tableName: 'earthquakes',
options: {
autoCreateLayers: true,
centerMap: true
}
});
}, [isTableReady, mapId, addTableToMap]);
if (!mapId) return null;
return <KeplerMapContainer mapId={mapId} />;
}
export function App() {
return (
<RoomShell roomStore={roomStore} className="h-screen">
<MapPanel />
</RoomShell>
);
}Adding tables to maps
Use state.kepler.addTableToMap() to load a DuckDB table into a Kepler map. The
preferred API is an object parameter:
await state.kepler.addTableToMap({
mapId,
tableName: 'earthquakes',
options: {
autoCreateLayers: true,
centerMap: true
}
});tableName is the SQL table reference to load. It can also be a saved Kepler
dataset id when the configured table-selection policy can resolve that id back
to a DuckDB table.
For normal add-table flows, omit datasetId. SQLRooms derives the persisted
Kepler dataset id from the table-selection policy:
await state.kepler.addTableToMap({
mapId,
tableName: 'main.places'
});Only pass datasetId when you need to load a table under an existing Kepler
dataId, such as when restoring a saved map config:
await state.kepler.addTableToMap({
mapId,
tableName: savedDataId,
options: {
autoCreateLayers: false,
centerMap: false
},
datasetId: savedDataId
});The older positional signature is still accepted for compatibility, but new code should use the object form so the table reference, Kepler options, config, and dataset-id override remain clear at the call site.
An optional signal: AbortSignal lets a caller discard a load's results if it
is cancelled before they are applied. The promise resolves without adding data;
the underlying database query is not cancelled. Overrides of addTableToMap
should forward or honor this signal. The positional form accepts it in its final
load-options argument.
Common customization
Restoring saved maps
Use kepler.setConfig(savedConfig) to replay configuration received after room
initialization, then await kepler.waitForConfigRestore() to wait for the
restore operation. Initial configuration and later restores both load datasets
referenced by pending layers, filters, and tooltips for every registered map;
mounting a map component is not required.
A newer restore cancels results from earlier dataset-sync requests. Explicit
addTableToMap calls remain independent of restores unless the caller supplies
a cancellation signal; restoring another map does not discard a requested table.
If a referenced table is unavailable or fails to load, the map's saved config is
preserved. Deferred Kepler actions cannot replace it with a partially restored
config. Refresh the available tables and call await kepler.syncKeplerDatasets()
to retry. Other, fully restored maps continue to autosave normally, including
intentional deletion of all their layers.
waitForConfigRestore() does not guarantee that unavailable datasets have loaded.
Persistence protection is based on each map's pending config, so it remains in
effect after that promise resolves. Edits to an incomplete map do not replace
its saved config until its pending config is resolved.
Use kepler.isMapConfigPending(mapId) to observe that per-map condition without
inspecting Kepler's internal merge fields. It reads current state and returns a
boolean, so it can be used directly in a Zustand selector inside a React component:
import {useStoreWithKepler} from '@kepler.gl/sqlrooms';
const configPending = useStoreWithKepler(state => state.kepler.isMapConfigPending(mapId));
// Show a warning that changes to this map are not being saved while pending.The result stays true if required data remains unavailable after
waitForConfigRestore() resolves, and becomes false once the pending config
and dataset merges are resolved. Other maps are checked independently. Unknown,
unregistered, and deleted maps return false; that does not mean they are ready
to render. This status does not include the slice's temporary persistence pause
or guarantee completion of all async work, and it does not itself disable editing.
duplicateMap copies a pending map's last preserved saved config, including
unresolved layers and settings. It does not wait for missing datasets. If no
saved config is available, it returns success: false with code
source-map-config-pending without creating a copy. Fully restored maps are
duplicated from their current runtime state, including unsaved edits.
Hosts that override dataset synchronization can reuse the same discovery and persistence checks from the public package:
import {
getReferencedKeplerDatasetIds,
hasPendingKeplerConfig
} from '@kepler.gl/sqlrooms';
const map = roomStore.getState().kepler.map[mapId];
if (map) {
const referencedIds = getReferencedKeplerDatasetIds(map.visState);
const missingIds = [...referencedIds].filter(id => !map.visState.datasets[id]);
const configPending = hasPendingKeplerConfig(map.visState);
// Use missingIds in custom loading logic and configPending in host UI.
}Dataset discovery includes live and pending layers/filters and pending tooltip
references. Split-map state references layer ids rather than dataset ids; its
pending state is included in hasPendingKeplerConfig. That boolean reports
pending config and dataset merges, not the slice's temporary persistence pause
or completion of all async work. The helpers do not expose the base sync's
restore cancellation signal; custom sync implementations still own cancellation.
getReferencedKeplerDatasetIds returns a new set. With Zustand, select the raw
visState and derive that set outside the selector. The boolean helper can be
used directly in a selector to observe whether a map has pending config.
Appearance
Pass options to createKeplerSlice():
import {createKeplerTheme, type KeplerThemeOverrides} from '@kepler.gl/sqlrooms';
createKeplerSlice({
basicKeplerProps: {
mapboxApiAccessToken: import.meta.env.VITE_MAPBOX_TOKEN
},
keplerTheme: createKeplerTheme({
modalOverLayZ: 40
} satisfies KeplerThemeOverrides),
modalPortalTarget: 'body',
actionLogging: false
});Notes:
basicKeplerPropsis for base Kepler registration/component props.keplerTheme(optional) sets the theme passed to KeplerThemeProvider.modalPortalTargetcontrols modal portal placement:'container'(default) or'body'.
Table selection and dataset ids
tableSelection controls which DuckDB tables Kepler exposes and how those
tables are represented in persisted Kepler layer and filter config.
createKeplerSlice({
tableSelection: {
defaultDbSchema: {
database: 'project',
schema: 'main'
},
includeTable: table => table.table.database === 'project'
}
});By default, tables in defaultDbSchema use bare dataset ids such as places.
Tables outside that database/schema use qualified SQL table references. This
keeps common main-schema project tables readable while preserving enough
identity for tables from other schemas.
Use includeTable to hide tables from Kepler's Add Layer UI and to skip matching
saved dataset ids during dataset synchronization. Host apps commonly use this to
hide attached databases that will not be available when a project is reopened.
If the default dataset-id policy is not right for your app, provide both
getDatasetIdForTable and findTableForDatasetId so new layers and restored
layers agree on the same identity scheme:
createKeplerSlice({
tableSelection: {
getDatasetIdForTable: table => `${table.table.schema}:${table.table.table}`,
findTableForDatasetId: (tables, datasetId) => {
const [schema, tableName] = datasetId.split(':');
return tables.find(table => table.table.schema === schema && table.table.table === tableName);
},
getTableLabel: table => [table.table.schema, table.table.table].filter(Boolean).join('.')
}
});getTableLabel only affects display labels in Kepler table selectors. It does
not change persisted dataset ids.
Related packages
@sqlrooms/artifactsfor artifact-backed map tabs@kepler.gl/sqlrooms/configis a lightweight entry point in this package for persisted config schemas and migrations@sqlrooms/room-shellfor Room store composition and UI shell@sqlrooms/duckdbfor DuckDB-backed table loading/querying
Examples
- Experimental SQLRooms demo: examples/demo-app-sqlrooms
- Original demo and deployed
/demoapplication: examples/demo-app
Migration and package boundaries
This package incorporates the implementation of @sqlrooms/kepler and
@sqlrooms/kepler-config from SQLRooms commit
26d8e78e086cebdfc4eb6b4047c9035c5704a068 under its MIT license. The original
license is included in LICENSE.
It also includes the saved-map hydration and per-map pending-status changes from
SQLRooms #920
(a986e5e0a2745973254a335b605907d179251cb5) and
#922
(104011c4b174b93fc7c0d2c731b28bc9786c428f).
Replace runtime imports from @sqlrooms/kepler with @kepler.gl/sqlrooms.
Replace schema-only imports from @sqlrooms/kepler-config with:
import {
KeplerMapSchema,
KeplerSliceConfig,
migrateKeplerTabsToArtifacts
} from '@kepler.gl/sqlrooms/config';The /config entry point does not import React, Kepler rendering, or the
SQLRooms runtime. The same schemas remain re-exported from the main entry point.
The serialized map envelope and legacy tab migration retain their original
format; moving the package does not require rewriting saved maps.
The adapter depends on Kepler and SQLRooms. Kepler's foundational packages must
not depend on this adapter. SQLRooms packages are pinned to the tested release
0.29.0; Kepler packages follow this repository's version. Keep React,
React Redux, React Intl, styled-components, and the deck.gl/luma.gl stack shared
within the host application. SQLRooms room-store, room-shell, and UI contexts must
also resolve to one copy (see the SQLRooms demo esbuild aliases).
Do not force all third-party versions of Immer to
one version; older Redux Toolkit dependencies require their own supported copy.
Use the SQLRooms connector as the application's database owner. Do not also initialize a separate Kepler DuckDB adapter for the same project. This adapter loads Arrow results into Kepler datasets; it does not promise SQL filter pushdown or automatic refresh of datasets already loaded in a map.
Factory recipes and Kepler application configuration currently have global scope. Configure them before rendering. Host storage, project save/reopen, and locale policy are separate from the config schemas. The SQLRooms demo retains its existing implementations of these features while migrating its shell.
The SQLRooms repository's old packages are intentionally not removed by this Kepler PR. After this package is published, a coordinated SQLRooms change can update consumers and deprecate or re-export the old package names.
Development
From the repository root:
yarn install --immutable
yarn workspaces foreach -At run stab
yarn workspace @kepler.gl/sqlrooms build
yarn workspace @kepler.gl/sqlrooms test
yarn start:sqlroomsbuild produces CommonJS and ESM output. Run build:types after building the
workspace dependencies and their declarations, as in the repository release workflow.
test includes config entry-point checks and adapter/task runtime regressions.
The upstream hydration regressions run with the repository Jest suite:
yarn jest --runInBand --runTestsByPath src/sqlrooms/test/KeplerSlice.hydration.spec.tsThe SQLRooms demo uses local Kepler sources and published SQLRooms packages, so no sibling SQLRooms checkout is required. The original demo and website remain unchanged for comparison.
Experimental application shell
@kepler.gl/sqlrooms/shell exports KeplerAppShell and SqlroomsSidebarFactory.
Pair the shell with the original injected Kepler application and sidePanelWidth={0}.
The shell reserves sidebar space; the recipe portals the original sidebar content
into it, preserving provider, localization, Redux, and drag-and-drop contexts.
The existing logo/version, data and layer panels, and export/storage menus remain
Kepler components. The SQLRooms demo uses SQLRooms LayoutRenderer and a room layout
slice for map, SQL, and assistant panels, with a ChordShell-style sidebar toggle.
This migrates application composition first. The SQLRooms demo keeps its existing
Redux map store, data loading, saved-map format, URLs, cloud providers, and query
integration. It does not convert all maps to KeplerSlice or to DuckDB tables.
Applications designed around SQLRooms-owned maps can still compose KeplerSlice
and the smaller map/panel components exported from the package root.
