SemiLayerDocs

Config Schema

The sl.config.ts file declares your entire SemiLayer setup. The defineConfig helper provides type-safety and IDE autocomplete.

Top-Level Shape

import { defineConfig } from '@semilayer/core'

export default defineConfig({
  stack: 'my-app',    // unique stack identifier — used in generated code
  sources: { ... },
  lenses: { ... },
  auth?: { ... },
})

Sources

sources: {
  [name: string]: {
    bridge: string       // required — npm package name of the bridge
    // Any additional fields are passed through to the bridge constructor.
    // For bridge-postgres, only the connection URL is needed — the service
    // resolves the stored credentials at ingest time.
  }
}

Example:

sources: {
  'main-db': {
    bridge: '@semilayer/bridge-postgres',
  },
}

Lenses

lenses: {
  [name: string]: {
    source: string          // source name from sources{}
    table: string           // target name (table, collection, endpoint, ...)
    primaryKey?: string     // primary key field name (auto-detected if omitted)

    fields: {
      [fieldName: string]: FieldConfig
    }

    feeds?: Record<string, FeedConfig>   // named feed declarations — see below

    syncInterval?: SyncInterval        // periodic incremental sync cadence
    smartSyncInterval?: SyncInterval   // periodic smart sync (full scan + tombstones)
    changeTrackingColumn?: string      // column for incremental detection (default: updated_at)

    grants?: GrantsConfig
  }
}

FieldConfig

interface FieldConfig {
  type: 'text' | 'number' | 'boolean' | 'date' | 'json' | 'enum' | 'relation'

  searchable?: boolean | { weight: number }  // include in vector embeddings
  primaryKey?: boolean                        // mark this field as the PK

  from?: string          // source column name if different from field name
  transform?: TransformSpec | TransformSpec[] // value transform chain
}

TransformSpec

TransformOptionsDescription
lowercaseConvert to lowercase
uppercaseConvert to uppercase
trimStrip leading/trailing whitespace
truncate{ length: number }Truncate string to N characters
slugifyConvert to URL-safe slug
round{ decimals?: number }Round number
floorFloor number
ceilCeil number
multiply{ by: number }Multiply number
divide{ by: number }Divide number
default{ value: unknown }Use value if source is null/undefined
prefix{ value: string }Prepend string
suffix{ value: string }Append string
replace{ from: string; to: string }String replace

Chain multiple transforms as an array:

price: {
  type: 'number',
  from: 'price_cents',
  transform: [
    { type: 'divide', by: 100 },
    { type: 'round', decimals: 2 },
  ],
},

FeedConfig

feeds is Record<feedName, FeedConfig>. Each named entry compiles to beam.<lens>.feed.<name>(...) plus a subscribe() and explain(). Every feed gets its own access grant under grants.feed[name].

interface FeedConfig {
  candidates: {
    from: 'embeddings' | 'recent'
    limit?: number          // pre-rank pool size (server cap: 5000)
  }

  rank: {
    similarity?: FeedRankSimilarity
    recency?:    FeedRankRecency
    engagement?: FeedRankEngagement
    diversify?:  FeedRankDiversify
  }

  pagination?: {
    pageSize?:   number     // default 20, max 100
    seenWindow?: number     // cursor dedup window (default 200)
  }

  evolve?: {
    onSubscribe?:       'newCount' | 'firstPage' | 'none'
    pollOnIngest?:      boolean    // emit a tick when ingest fires NOTIFY
    minNotifyInterval?: '5s' | '15s' | '30s' | '1m' | '5m' | '15m'
  }
}

interface FeedRankSimilarity {
  weight: number                                // contribution weight
  against: string | { from: string; mode: 'recordVector' }
}

interface FeedRankRecency {
  weight: number
  halfLife: '1h' | '6h' | '1d' | '7d' | '30d' | '90d'
  field?: string                                // default: changeTrackingColumn
}

interface FeedRankEngagement {
  weight: number
  lens: string                                  // sibling lens name (same env)
  relation?: string                             // declared relations entry on the owning lens
  join?: { local: string; foreign: string }     // required when `relation` is absent
  aggregate: 'count' | 'sum' | 'recent_count'
  column?: string                               // field on the engagement lens (for sum / recent_count)
  decay?: 'none' | 'linear' | 'log'
  window?: '1h' | '24h' | '7d' | '30d'         // for `recent_count`
}

interface FeedRankDiversify {
  by: string                                    // field name
  maxPerGroup: number
  strategy?: 'cap' | 'mmr'
}

engagement.lens is always a sibling lens declared in the same environment. The read flows through that lens's bridge and respects its grants.query (so pk_ callers without access see engagement=0; sk_ bypasses). There is no raw-table path: a likes / views / bookmarks table must be declared as a lens before feeds can reference it.

Pick relation when your lens already declares a relations entry pointing at the engagement lens — the join columns are derived from that relation's on map, so the config stays DRY. Otherwise provide an explicit join: { local, foreign } with both sides referring to declared lens fields.

SyncInterval

'1m' | '5m' | '15m' | '30m' | '1h' | '6h' | '24h'

Used by two independent fields:

  • syncInterval — incremental poll (WHERE changeTrackingColumn > cursor). Cheap, no deletes. Floor: tier.features.minSyncInterval.
  • smartSyncInterval — scheduled full scan with content-hash dedup + tombstone detection. Catches deletes. Floor: tier.features.smartSyncIntervalMin (null on Free = scheduled not allowed). Monthly cap: tier.smartSyncJobsPerMonth.

You can set either or both. Recommended pairing for production:

syncInterval: '5m',
smartSyncInterval: '24h',

Per-tier floors (defaults — adjustable via admin tier editor):

TierminSyncIntervalsmartSyncIntervalMinsmartSyncJobsPerMonth
Free15mscheduled: no5 (manual only)
Pro5m24h60
Team1m6h400
Scale1m1hunlimited

Enterprise deployments (DEPLOYMENT_MODE=enterprise) bypass both floor and cap.

GrantsConfig

interface GrantsConfig {
  search?:    AccessRule
  similar?:   AccessRule
  query?:     AccessRule | false                   // false = query disabled
  subscribe?: AccessRule
  stream?:    StreamConfig                         // streaming gates
  feed?:      Record<string, AccessRule>           // per-named-feed access

  allowOrigins?: string[]                          // CORS origins for pk_ keys
}

interface AccessRule {
  allowPublicKey?: boolean         // allow pk_ keys (default: true for search/similar)
  userClaim?: string               // require this JWT claim to be present
  userClaimValue?: unknown         // require claim to equal this value
}

Auth

auth?: {
  jwksUrl?: string      // JWKS endpoint for user JWT verification
  issuer?: string       // Expected iss claim
  audience?: string     // Expected aud claim
}

Used when passing user JWTs (X-User-Token header) for per-user access rules.


Full Example

import { defineConfig } from '@semilayer/core'

export default defineConfig({
  stack: 'my-store',

  sources: {
    'main-db': {
      bridge: '@semilayer/bridge-postgres',
    },
  },

  lenses: {
    products: {
      source: 'main-db',
      table: 'public.products',
      primaryKey: 'id',

      fields: {
        id:          { type: 'number', primaryKey: true },
        name:        { type: 'text', searchable: { weight: 3 } },
        description: { type: 'text', searchable: true },
        category:    { type: 'text' },
        price:       { type: 'number' },
        inStock:     { type: 'boolean', from: 'in_stock' },
        slug:        { type: 'text', from: 'name', transform: { type: 'slugify' } },
      },

      syncInterval: '5m',           // fast incremental refresh
      smartSyncInterval: '24h',     // nightly tombstone sweep — catches deletes
      changeTrackingColumn: 'updated_at',

      grants: {
        search:  { allowPublicKey: true },
        similar: { allowPublicKey: true },
        query:   { allowPublicKey: false },   // backend only
      },
    },
  },

  auth: {
    jwksUrl: 'https://your-auth-provider.com/.well-known/jwks.json',
    issuer: 'https://your-auth-provider.com/',
    audience: 'your-api-audience',
  },
})