Github|...

Feature Flags

Feature flags let you ship code dark, roll it out to a percentage of users, or flip it on for a single account, all without a redeploy. Flags are evaluated on the server and the resolved assignment streams to the client over the same SSP + sync pipeline that powers useQuery, so toggling a flag from your terminal updates the running UI without a refresh.

The important property: targeting rules never reach the client. Allowlists, rollout percentages, and flag definitions live in a root-only table. The browser only ever sees the single variant resolved for the signed-in user.

How it works

Two server tables back the whole system:

TableWho can read itWho can write it
_00_feature_flagnobody (root only)spky flag CLI + scheduler
_00_user_featureeach user, their own rows onlynobody but root
  1. You write a flag definition (variants, default, targeting rules) into _00_feature_flag with the spky flag CLI.
  2. On every write the CLI runs the evaluator inline and materializes one row per existing user into _00_user_feature with their resolved variant.
  3. The scheduler runs a periodic sweep that fills in assignments for users who signed up since the last write, so new accounts get their flags within seconds.
  4. The client subscribes to its own _00_user_feature row. When the materialized variant changes, the new value rides the existing websocket straight into your reactive UI.
Note

Because writes to _00_user_feature are root-only and a user can only ever SELECT their own row, a malicious client cannot self-enable a flag or discover who else is in a rollout. Permissions are enforced by SurrealDB, not by the client.

1. Define a flag

A flag has a set of variants, a default, and an optional description. With no variants specified you get a boolean flag (off, on) defaulting to off.

Bash
# Boolean flag (variants default to off, on)
spky flag create new-checkout --description "Redesigned checkout flow"

# Multi-variant flag with an explicit default
spky flag create homepage-theme \
  --variants control,blue,dark \
  --default control \
  --description "Homepage color experiment"

2. Target users

A fresh flag resolves to its default for everyone. Add targeting rules to change that. There are two kinds:

RuleSet withPriority
Allowlist--for-user <username>wins
Rollout--rollout <0-100>evaluated after allowlists

An allowlisted user always gets their variant, even if a rollout would have excluded them. Rollout bucketing is a stable hash of (flag key, user id), so the same user stays in (or out of) the rollout across deploys, and raising the percentage only ever adds users.

Bash
# Turn the flag on for a single user (allowlist)
spky flag set new-checkout --variant on --for-user alice

# Roll it out to 25% of users (stable per user)
spky flag set new-checkout --variant on --rollout 25

# Remove a user from the allowlist
spky flag unset new-checkout --for-user alice

# Global kill switch: force every user back to the default variant
spky flag disable new-checkout
spky flag enable new-checkout

# Dry-run an evaluation for one user without writing anything
spky flag eval new-checkout --as-user alice
Note

spky flag disable is a global kill switch: it forces every user back to the flag’s default variant regardless of any rules, without deleting them. spky flag enable restores rule evaluation.

3. Read it in your app

SolidJS

useFeatureFlag returns three reactive accessors that update live whenever the server assignment changes.

tsx
import { Show } from 'solid-js';
import { useFeatureFlag } from '@spooky-sync/client-solid';

export function Checkout() {
  const flag = useFeatureFlag('new-checkout');

  return (
    <Show when={flag.enabled()} fallback={<LegacyCheckout />}>
      <RedesignedCheckout />
    </Show>
  );
}
Prop Type Default Description
key string - The flag key, as created with `spky flag create`.
options.fallback string undefined Variant returned before the server assignment arrives, or when the user has none.
options.ttl QueryTimeToLive '10m' How long the underlying live query stays registered without activity.

The returned object exposes:

AccessorTypeDescription
variant()string | undefinedThe variant resolved for the current user (or the fallback).
enabled()booleantrue when the variant is set and not 'off'. Use for boolean flags.
payload()unknown | undefinedThe JSON payload attached to the resolved variant, if any.

Multi-variant flags

For experiments with more than two variants, read variant() directly instead of enabled(). Each variant can carry an arbitrary JSON payload (configured on the flag definition’s payloads map) that you read with payload().

tsx
const theme = useFeatureFlag('homepage-theme', { fallback: 'control' });

// theme.variant() -> 'control' | 'blue' | 'dark'
// theme.enabled() -> true once the variant is anything other than 'off'
// theme.payload() -> the JSON payload attached to the resolved variant

<div class={theme.variant()}>...</div>

Vanilla JS / TS

Outside of SolidJS, call feature() on your Sp00kyClient directly. It returns a FeatureFlagHandle with the same variant() / enabled() / payload() methods plus a subscribe() for change notifications.

TypeScript
import { client } from './db'; // your Sp00kyClient instance

const flag = client.feature('new-checkout', { fallback: 'off' });

flag.variant();   // string | undefined
flag.enabled();   // boolean
flag.payload<{ copy: string }>();

const unsubscribe = flag.subscribe((snapshot) => {
  console.log('variant is now', snapshot.variant);
});

// When you're done, stop listening and release the live query.
unsubscribe();
flag.close();

CLI reference

Bash
spky flag list                          # List every flag definition
spky flag create <key> [options]        # Create a flag
spky flag get <key>                     # Show config, rules, and assignment count
spky flag set <key> --variant <v> ...   # Add an allowlist or rollout rule
spky flag unset <key> --for-user <u>    # Remove a user from the allowlist
spky flag enable <key>                  # Re-enable rule evaluation
spky flag disable <key>                 # Global kill switch -> default variant
spky flag eval <key> --as-user <u>      # Dry-run an evaluation (no writes)
spky flag delete <key>                  # Delete a flag and all its assignments

Scheduler sweep

The scheduler sweep that backfills assignments for new signups runs every 30 seconds by default. Tune it with the SPKY_FEATURE_FLAG_SWEEP_SECS environment variable (or feature_flag_sweep_interval_secs in sp00ky.yml). Existing assignments are never touched by the sweep, the CLI keeps those current on every write, so a quiet table costs one cheap query per flag per tick.