SemiLayerDocs

Schema (Config)

The sl.config.ts file is the single source of truth for your SemiLayer setup. It declares your sources (database connections), lenses (intelligent collections), and auth configuration.

File Location

SemiLayer searches for sl.config.ts (or sl.config.js) starting from the current working directory and walking up to the filesystem root. Place it at the root of your project.

Top-Level Shape

import { defineConfig } from '@semilayer/core'

export default defineConfig({
  stack: 'my-app',          // unique name for this stack — used in generated code

  sources: { ... },         // database connections
  lenses: { ... },          // intelligent collections
  auth?: { ... },           // JWKS / access rule config
})

Sources

A Source declares a database connection. The key becomes the source name referenced by Lenses.

sources: {
  'main-db': {
    bridge: '@semilayer/bridge-postgres',  // required — bridge package name
    // All other fields are bridge-specific and passed through:
    connectionString: process.env.DATABASE_URL,  // bridge-postgres specific
    timeout: 30000,  // optional — query timeout in ms (default: 30000)
  },
  'analytics-db': {
    bridge: '@semilayer/bridge-postgres',
    connectionString: process.env.ANALYTICS_DATABASE_URL,
  },
}
💡

In production (cloud deployments), the connection string is stored encrypted on the SemiLayer platform via semilayer source add --connection-string "...". The config file then only needs the bridge name — the worker resolves credentials from the platform at runtime.


Lenses

A Lens is a declaration of intelligence over a database table. The key becomes the lens name (and the property name on the generated Beam client).

lenses: {
  products: {
    source: 'main-db',               // which source to read from
    table: 'public.products',        // fully-qualified table name
    primaryKey: 'id',                // source column name for the primary key
    fields: { ... },                 // field declarations
    facets: { ... },                 // semantic operations to enable
    rules?: { ... },                 // access control
    syncInterval?: '15m',            // periodic incremental sync
    changeTrackingColumn?: 'updated_at',  // column for incremental sync
  },
}

Fields

Fields declare what to read from the source and how to expose it. The key becomes the output field name.

fields: {
  id:          { type: 'number' },
  name:        { type: 'text', searchable: true },
  description: { type: 'text', searchable: true },
  tags:        { type: 'json' },
  category:    { type: 'text' },
  price:       { type: 'number' },
  createdAt:   { type: 'date' },
  isActive:    { type: 'boolean' },
}

Field Types

TypeTypeScript outputUse for
textstringFree-form strings, descriptions, titles
numbernumberIntegers, floats, prices
booleanbooleanFlags, toggles
datestringISO date / datetime strings
jsonunknownArbitrary JSON objects / arrays
enumstringOne of a declared set of values
relationstring | numberForeign key references

Marking Fields for Embedding

Add searchable: true to include a field in the semantic search embedding:

name:        { type: 'text', searchable: true },            // weight: 1 (default)
description: { type: 'text', searchable: { weight: 2 } },  // 2× weight in embedding

Fields with higher weight are repeated in the embedding input, boosting their relevance signal in search results.

Column Mapping (from)

When the source column name differs from your desired output field name:

displayName:  { type: 'text', from: 'product_name', searchable: true },

When a field is built from multiple source columns:

fullAddress: {
  type: 'text',
  from: ['address_line1', 'address_line2', 'city'],
  merge: 'concat',
  separator: ', ',
  searchable: true,
}

Transforms

Apply a transform chain to the resolved value before indexing:

priceDollars: {
  type: 'number',
  from: 'price_cents',
  transform: { type: 'round', decimals: 2 },
}

slug: {
  type: 'text',
  from: 'title',
  transform: [
    { type: 'lowercase' },
    { type: 'replace', pattern: '\\s+', replacement: '-' },
    { type: 'truncate', length: 100 },
  ],
  searchable: true,
}

Available Transforms

TransformParametersWhat it does
toStringConvert to string
toNumberParse as number
toBooleanParse as boolean
toDateformat?Parse as date
rounddecimals?, mode?Round number
trimTrim whitespace
lowercaseConvert to lowercase
uppercaseConvert to uppercase
defaultvalueReplace null/undefined
splitseparatorString → array
joinseparatorArray → string
truncatelengthLimit string length
replacepattern, replacementRegex replace
custombodyInline function body (advanced)

Facets

Facets declare which semantic operations are enabled on the Lens.

facets: {
  search: {
    fields: ['name', 'description'],  // output field names to embed for search
    mode?: 'semantic' | 'keyword' | 'hybrid',  // default mode for queries
  },
  similar: {
    fields: ['name', 'description'],  // fields to use for similarity matching
    limit?: 5,  // default limit for similar queries
  },
}

Sync Configuration

Keep the index fresh as your source data changes.

syncInterval — Periodic incremental sync. Valid values: '1m' | '5m' | '15m' | '30m' | '1h' | '6h' | '24h'

syncInterval: '15m',  // re-sync every 15 minutes

changeTrackingColumn — The column used to detect new/updated records during incremental sync. Defaults to updated_at.

changeTrackingColumn: 'modified_at',

Access Rules

See Auth & RBAC for the full access rule reference.

rules: {
  search: 'public',       // allow anyone to search
  similar: 'public',
  query: 'authenticated', // require a user JWT for direct queries
  subscribe: {            // require a specific claim for live tail
    authenticated: true,
    claims: { plan: ['pro', 'enterprise'] }
  },
}

Auth

Configure JWKS validation for user JWTs. This enables 'authenticated' rules and function rules that receive user claims.

auth: {
  client: {
    jwt: {
      jwksUrl: 'https://your-issuer/.well-known/jwks.json',
      issuer: 'https://your-issuer/',
      audience: 'https://your-api/',  // optional
    }
  }
}

Complete Example

import { defineConfig } from '@semilayer/core'

export default defineConfig({
  stack: 'storefront',

  sources: {
    'main-db': {
      bridge: '@semilayer/bridge-postgres',
      connectionString: process.env.DATABASE_URL,
    },
  },

  lenses: {
    products: {
      source: 'main-db',
      table: 'public.products',
      primaryKey: 'id',
      syncInterval: '15m',
      changeTrackingColumn: 'updated_at',

      fields: {
        id:           { type: 'number' },
        name:         { type: 'text', searchable: { weight: 2 } },
        description:  { type: 'text', searchable: true },
        brand:        { type: 'text', searchable: true },
        category:     { type: 'text' },
        priceDollars: {
          type: 'number',
          from: 'price_cents',
          transform: { type: 'round', decimals: 2 },
        },
        tags:         { type: 'json' },
        isActive:     { type: 'boolean' },
        createdAt:    { type: 'date', from: 'created_at' },
      },

      facets: {
        search:  { fields: ['name', 'description', 'brand'], mode: 'hybrid' },
        similar: { fields: ['name', 'description', 'brand'] },
      },

      rules: {
        search:  'public',
        similar: 'public',
        query:   'authenticated',
      },
    },
  },

  auth: {
    client: {
      jwt: {
        jwksUrl: 'https://your-issuer/.well-known/jwks.json',
        issuer: 'https://your-issuer/',
      },
    },
  },
})

Drift Detection

When you run semilayer push, SemiLayer computes a hash of your Lens config and compares it against the last pushed hash. If the config changed, it reports a drift:

semilayer push
# ⚠ Lens "products" config has drifted from last push
# → run with --rebuild to re-index all records with the new config

Changes to fields (adding, removing, or changing a searchable field) require a rebuild to generate fresh embeddings. Changes to rules, facets.mode, or syncInterval take effect immediately without a rebuild.