Beam Client
The @semilayer/client package provides BeamClient — the canonical SemiLayer runtime client.
You can use it directly or via the generated Beam wrapper created by semilayer generate.
Copy
npm install @semilayer/client
Copy
import { BeamClient , type BeamConfig } from '@semilayer/client'
const client = new BeamClient ({
baseUrl : 'https://api.semilayer.com' ,
apiKey : 'sk_live_...' ,
userToken ?: string ,
wsUrl ?: string ,
fetch ?: typeof fetch,
WebSocket ?: WebSocketConstructor ,
})
Returns a new BeamClient bound to a user JWT. Cheap — shares the same connection
and config, only differs in the X-User-Token header / ?userToken= parameter it
attaches.
Copy
const sharedClient = new BeamClient ({ baseUrl, apiKey : 'pk_live_...' })
const userClient = sharedClient.withUser (req.headers ['x-user-token' ])
const { results } = await userClient.search ('products' , { query : 'shoes' })
These methods always use HTTP (no WebSocket).
Copy
async search<M = Record <string , unknown >>(
lens : string ,
params : SearchParams ,
): Promise <SearchResponse <M>>
Copy
interface SearchParams {
query : string
limit ?: number
minScore ?: number
mode ?: 'semantic' | 'keyword' | 'hybrid'
}
interface SearchResponse <M> {
results : SearchResult <M>[]
meta : { lens : string ; query : string ; mode : string ; count : number ; durationMs : number }
}
interface SearchResult <M> {
id : string
sourceRowId : string
content : string | null
metadata : M
score : number
}
Example:
Copy
interface Product { id : number ; name : string ; category : string ; price : number }
const { results } = await client.search <Product >('products' , {
query : 'eco-friendly water bottle' ,
limit : 10 ,
mode : 'hybrid' ,
})
Copy
async similar<M = Record <string , unknown >>(
lens : string ,
params : SimilarParams ,
): Promise <SimilarResponse <M>>
Copy
interface SimilarParams {
id : string
limit ?: number
minScore ?: number
}
interface SimilarResponse <M> {
results : SearchResult <M>[]
meta : { lens : string ; sourceId : string ; count : number ; durationMs : number }
}
Example:
Copy
const { results } = await client.similar <Product >('products' , { id : '42' , limit : 5 })
Copy
async query<M = Record <string , unknown >>(
lens : string ,
params ?: QueryParams ,
): Promise <QueryResponse <M>>
Copy
interface QueryParams {
where ?: Record <string , unknown >
orderBy ?: { field : string ; dir ?: 'asc' | 'desc' }
| Array <{ field : string ; dir ?: 'asc' | 'desc' }>
limit ?: number
offset ?: number
cursor ?: string
select ?: string []
}
interface QueryResponse <M> {
rows : M[]
meta : { lens : string ; total ?: number ; nextCursor ?: string ; count : number ; durationMs : number }
}
Example:
Copy
const { rows, meta } = await client.query <Product >('products' , {
where : { category : 'footwear' },
orderBy : { field : 'price' , dir : 'asc' },
limit : 20 ,
})
if (meta.nextCursor ) {
const page2 = await client.query <Product >('products' , {
where : { category : 'footwear' },
cursor : meta.nextCursor ,
limit : 20 ,
})
}
These methods use a WebSocket connection. The socket is opened lazily on the first
streaming call and reused across all subsequent calls on the same client instance.
Copy
stream.search <M = Record <string , unknown >>(
lens : string ,
params : StreamSearchParams ,
): AsyncIterable <SearchResult <M>>
Example:
Copy
for await (const result of client.stream .search <Product >('products' , {
query : 'eco-friendly water bottle' ,
limit : 50 ,
})) {
render (result)
}
Copy
stream.query <M = Record <string , unknown >>(
lens : string ,
params : StreamQueryParams ,
): AsyncIterable <M>
Example:
Copy
for await (const row of client.stream .query <Product >('products' , {
where : { category : 'footwear' },
})) {
process (row)
}
Copy
stream.subscribe <M = Record <string , unknown >>(
lens : string ,
params ?: SubscribeParams ,
): AsyncIterable <StreamEvent <M>>
Copy
interface SubscribeParams {
filter ?: Record <string , unknown >
}
interface StreamEvent <M> {
kind : 'insert' | 'update' | 'delete'
record : M
}
Example:
Copy
for await (const event of client.stream .subscribe <Product >('products' )) {
if (event.kind === 'insert' ) addToList (event.record )
if (event.kind === 'update' ) updateInList (event.record )
if (event.kind === 'delete' ) removeFromList (event.record )
}
Observe a single record. First yield is the current record state; subsequent yields
fire only when the record changes.
Copy
observe<M = Record <string , unknown >>(
lens : string ,
recordId : string ,
): AsyncIterable <M>
Example:
Copy
for await (const snapshot of client.observe <Product >('products' , '42' )) {
setProduct (snapshot)
}
Copy
import {
BeamError ,
BeamAuthError ,
BeamNotFoundError ,
BeamStreamError ,
BeamStreamRateLimitError ,
BeamStreamClosedError ,
} from '@semilayer/client'
Class When thrown BeamErrorHTTP 4xx/5xx (base class) BeamAuthErrorHTTP 401 — invalid or missing API key BeamNotFoundErrorHTTP 404 — lens not found BeamStreamErrorWebSocket error frame received BeamStreamRateLimitErrorRate limit on streaming (subclass of BeamStreamError) BeamStreamClosedErrorWebSocket closed unexpectedly
Handling errors:
Copy
import { BeamAuthError , BeamStreamClosedError } from '@semilayer/client'
try {
const { results } = await client.search ('products' , { query : 'shoes' })
} catch (err) {
if (err instanceof BeamAuthError ) {
}
throw err
}
async function watchFeed ( ) {
while (true ) {
try {
for await (const event of client.stream .subscribe ('products' )) {
handleEvent (event)
}
} catch (err) {
if (err instanceof BeamStreamClosedError ) {
await sleep (1000 )
continue
}
throw err
}
}
}
semilayer generate creates a typed wrapper around BeamClient based on your config.
Copy
import { createBeam, type Beam } from './semilayer'
const beam = createBeam ({
baseUrl : 'https://api.semilayer.com' ,
apiKey : process.env .SEMILAYER_API_KEY !,
})
The generated Beam class has one property per Lens, each typed to your field declarations:
Copy
beam.products .search (params : SearchParams ): Promise <SearchResponse <ProductsMetadata >>
beam.products .similar (params : SimilarParams ): Promise <SimilarResponse <ProductsMetadata >>
beam.products .query (params ?: QueryParams ): Promise <QueryResponse <ProductsMetadata >>
beam.products .stream .search (params): AsyncIterable <SearchResult <ProductsMetadata >>
beam.products .stream .query (params): AsyncIterable <ProductsMetadata >
beam.products .stream .subscribe (params?): AsyncIterable <StreamEvent <ProductsMetadata >>
beam.products .observe (recordId : string ): AsyncIterable <ProductsMetadata >
Where ProductsMetadata is generated from your field declarations:
Copy
export interface ProductsMetadata {
id : number
name : string
category : string
price : number
}
In Node.js 18+, fetch and WebSocket are available globally. Earlier versions need
polyfills:
Copy
import { BeamClient } from '@semilayer/client'
import WebSocket from 'ws'
const client = new BeamClient ({
baseUrl : 'https://api.semilayer.com' ,
apiKey : 'sk_live_...' ,
WebSocket ,
})