Skip to content

jg-wright/targetd

Repository files navigation

@targetd

A powerful, type-safe targeting and feature flag system for dynamically serving different content based on query conditions.

Overview

@targetd is a TypeScript-first monorepo providing a complete solution for building context-aware applications with dynamic content delivery. Perfect for feature flags, A/B testing, content personalization, and configuration management.

Key Features:

  • 🎯 Type-safe targeting - Full TypeScript inference and runtime validation with Zod
  • ⚑ High performance - In-memory data store optimized for speed
  • πŸ”Œ Modular architecture - Use only what you need with focused packages
  • 🌐 HTTP-ready - Built-in server and client for distributed systems
  • πŸ“ File-based rules - Load rules from JSON/YAML files with hot-reloading
  • 🎨 Extensible - Custom targeting descriptors and predicates

Packages

Core targeting and data querying API. Define payloads, targeting rules, and query logic.

import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { z } from 'zod'

const schema = DataSchema.create()
  .usePayload({ greeting: z.string() })
  .useTargeting({ country: targetIncludes(z.string()) })

const data = await Data.create(schema).addRules('greeting', [
  { targeting: { country: ['US'] }, payload: 'Hello!' },
  { targeting: { country: ['ES'] }, payload: 'Β‘Hola!' },
  { payload: 'Hi!' },
])

await data.getPayload('greeting', { country: 'US' }) // 'Hello!'

View Documentation β†’

HTTP server for exposing @targetd/api data over REST endpoints.

import { createServer } from '@targetd/server'

createServer(data).listen(3000)
// GET /greeting?country=US β†’ "Hello!"

View Documentation β†’

Type-safe HTTP client for querying @targetd/server instances.

import { Client } from '@targetd/client'

const client = await Client.create('http://localhost:3000', schema)
const greeting = await client.getPayload('greeting', { country: 'US' })

View Documentation β†’

Load targeting rules from JSON/YAML files with hot-reloading support.

import { load, watch } from '@targetd/fs'

const data = await load(baseData, './rules')
watch(baseData, './rules', (error, updatedData) => {
  // Rules automatically reload on file changes
})

View Documentation β†’

Built-in targeting descriptor for date range queries.

import dateRangeTargeting from '@targetd/date-range'

const schema = DataSchema.create()
  .usePayload({ campaign: z.string() })
  .useTargeting({ date: dateRangeTargeting })

const data = await Data.create(schema).addRules('campaign', [
  {
    targeting: { date: { start: '2024-12-01', end: '2024-12-31' } },
    payload: 'Holiday Campaign',
  },
])

View Documentation β†’

Transform flat key notation to nested objects.

import { explode } from '@targetd/explode'

explode({ 'user.name': 'John' })
// { user: { name: 'John' } }

View Documentation β†’

Generate JSON Schema from Zod schemas for documentation and validation.

View Documentation β†’

Quick Start

Installation

# Core API
npm install zod && npx jsr add @targetd/api

# With server and client
npx jsr add @targetd/api @targetd/server @targetd/client

# With file loading
npx jsr add @targetd/api @targetd/fs

Basic Example

1. Define your data:

import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { z } from 'zod'

const schema = DataSchema.create()
  .usePayload({
    banner: z.string(),
    feature: z.object({
      enabled: z.boolean(),
      maxUsers: z.number(),
    }),
  })
  .useTargeting({
    platform: targetIncludes(z.string()),
    isPremium: targetIncludes(z.boolean()),
  })

export const data = await Data.create(schema)
  .addRules('banner', [
    { targeting: { platform: ['mobile'] }, payload: 'πŸ“± Mobile Banner' },
    { targeting: { platform: ['desktop'] }, payload: 'πŸ–₯ Desktop Banner' },
    { payload: 'Default Banner' },
  ])
  .addRules('feature', [
    {
      targeting: { isPremium: [true] },
      payload: { enabled: true, maxUsers: 1000 },
    },
    { payload: { enabled: true, maxUsers: 10 } },
  ])

2. Start a server:

import { createServer } from '@targetd/server'
import { data } from './data.ts'

createServer(data).listen(3000)

3. Query from a client:

import { Client } from '@targetd/client'
import { data } from './data.ts'

const client = new Client('http://localhost:3000', data)

// Type-safe queries
const banner = await client.getPayload('banner', { platform: 'mobile' })
const allPayloads = await client.getPayloadForEachName({ isPremium: true })

Use Cases

Feature Flags

const data = await Data.create(
  DataSchema.create()
    .usePayload({ newFeature: z.boolean() })
    .useTargeting({ userTier: targetEquals(z.string()) }),
).addRules('newFeature', [
  { targeting: { userTier: 'beta' }, payload: true },
  { payload: false },
])

A/B Testing

const data = await Data.create(
  DataSchema.create()
    .usePayload({ variant: z.string() })
    .useTargeting({ userId: targetIncludes(z.string()) }),
).addRules('variant', [
  { targeting: { userId: experimentGroup }, payload: 'variant-a' },
  { payload: 'variant-b' },
])

Content Personalization

const data = await Data.create(
  DataSchema.create()
    .usePayload({ content: z.string() })
    .useTargeting({
      region: targetIncludes(z.string()),
      language: targetIncludes(z.string()),
    }),
).addRules('content', [
  {
    targeting: { region: ['US'], language: ['en'] },
    payload: 'US English content',
  },
  {
    targeting: { region: ['US'], language: ['es'] },
    payload: 'US Spanish content',
  },
  { payload: 'Default content' },
])

Configuration Management

const data = await Data.create(
  DataSchema.create()
    .usePayload({
      config: z.object({
        apiUrl: z.string(),
        timeout: z.number(),
      }),
    })
    .useTargeting({ environment: targetEquals(z.string()) }),
).addRules('config', [
  {
    targeting: { environment: 'production' },
    payload: { apiUrl: 'https://api.prod.com', timeout: 5000 },
  },
  {
    targeting: { environment: 'staging' },
    payload: { apiUrl: 'https://api.staging.com', timeout: 10000 },
  },
  { payload: { apiUrl: 'http://localhost:3000', timeout: 30000 } },
])

Core Concepts

Schema Configuration

Use DataSchema to declare payload schemas and targeting descriptors, then pass the resulting schema directly to Data.create(). Splitting the schema and data phases keeps TypeScript compilation cheap even with hundreds of payloads and targeting descriptors.

Payloads

Define what data you want to serve using Zod schemas.

Targeting

Specify conditions that determine which payload to serve using predicates like targetIncludes and targetEquals, or create custom ones.

Rules

Map targeting conditions to payloads. Rules are evaluated in orderβ€”first match wins.

Variables

Reusable values with their own targeting rules that can be referenced in payloads using {{variableName}} syntax.

Fall-through Targeting

Pass unresolved targeting conditions between services for evaluation in distributed systems.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   @targetd/api  β”‚  Core targeting engine
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
    β”‚         β”‚
β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ server β”‚ β”‚ file loadersβ”‚
β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”
β”‚ client β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Documentation

Contributing

This is a monorepo managed with Deno workspaces.

Development

# Install dependencies
deno install

# Run tests
deno task test

# Run tests for specific package
cd packages/api && deno task test

License

See individual package LICENSE files for details.

About

A speedy, extensible, typed, in-memory data store built in Typescript.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages