Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ See
and built-in predicates in
[packages/api/src/predicates/](packages/api/src/predicates/).

**PromisedData Pattern**: The `Data.create()` builder uses
[PromisedData](packages/api/src/PromisedData.ts) to enable fluent async
configuration before resolving to a `Data` instance.
**Two-phase API**: Schema setup uses
[DataSchema](packages/api/src/DataSchema.ts) (synchronous, accumulates parser
shapes via intersection for cheap type inference). `.build()` produces a
`BuiltDataSchema<Meta>`, which `Data.create(schema)` turns into a
[PromisedData](packages/api/src/PromisedData.ts) — an awaitable chain for
`addRules`/`insert`/query operations.

**Type Inference**: Heavy use of TypeScript's type system with Zod schemas. The
`Data` class uses complex type inference (`DT.Meta`) to ensure payloads,
Expand Down Expand Up @@ -137,10 +140,10 @@ cd packages/api && deno task test
**Import Paths**: Use `.ts` extensions in imports, even though it's Deno. Use
workspace references like `@targetd/api` in package dependencies.

**Zod Schemas**: For new code and public examples, prefer `import { z } from 'zod'`
for consistency with the root README and package READMEs. Some internal,
performance-sensitive modules may instead use `zod/mini` for schemas and
`zod/v4/core` for types; when editing those files (e.g.
**Zod Schemas**: For new code and public examples, prefer
`import { z } from 'zod'` for consistency with the root README and package
READMEs. Some internal, performance-sensitive modules may instead use `zod/mini`
for schemas and `zod/v4/core` for types; when editing those files (e.g.
[packages/api/src/Data.ts](packages/api/src/Data.ts#L22-L25)), follow the
existing local style.

Expand Down Expand Up @@ -190,16 +193,19 @@ challenging:

```typescript
// Instead of chaining everything
const data = await Data.create()
.usePayload({ foo: z.string() })
.useTargeting({ bar: targetIncludes(z.string()) })
.addRules('foo', rules)
const data = await Data.create(
DataSchema.create()
.usePayload({ foo: z.string() })
.useTargeting({ bar: targetIncludes(z.string()) })
.build(),
).addRules('foo', rules)

// Break it down to inspect types
const step1 = Data.create()
const step1 = DataSchema.create()
const step2 = step1.usePayload({ foo: z.string() })
const step3 = step2.useTargeting({ bar: targetIncludes(z.string()) })
type Step3Type = Awaited<typeof step3> // Inspect in IDE
const built = step3.build()
type BuiltType = typeof built // Inspect in IDE
```

**Strategy 2: Use helper types from `types/` directories**
Expand Down
155 changes: 89 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ Core targeting and data querying API. Define payloads, targeting rules, and
query logic.

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

const data = await Data.create()
const schema = DataSchema.create()
.usePayload({ greeting: z.string() })
.useTargeting({ country: targetIncludes(z.string()) })
.addRules('greeting', [
{ targeting: { country: ['US'] }, payload: 'Hello!' },
{ targeting: { country: ['ES'] }, payload: '¡Hola!' },
{ payload: 'Hi!' },
])
.build()

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!'
```
Expand Down Expand Up @@ -93,14 +95,17 @@ Built-in targeting descriptor for date range queries.
```typescript
import dateRangeTargeting from '@targetd/date-range'

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

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

**[View Documentation →](./packages/date-range)**
Expand Down Expand Up @@ -144,10 +149,10 @@ npx jsr add @targetd/api @targetd/fs
**1. Define your data:**

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

export const data = await Data.create()
const schema = DataSchema.create()
.usePayload({
banner: z.string(),
feature: z.object({
Expand All @@ -159,6 +164,9 @@ export const data = await Data.create()
platform: targetIncludes(z.string()),
isPremium: targetIncludes(z.boolean()),
})
.build()

export const data = await Data.create(schema)
.addRules('banner', [
{ targeting: { platform: ['mobile'] }, payload: '📱 Mobile Banner' },
{ targeting: { platform: ['desktop'] }, payload: '🖥 Desktop Banner' },
Expand Down Expand Up @@ -200,75 +208,90 @@ const allPayloads = await client.getPayloadForEachName({ isPremium: true })
### Feature Flags

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

### A/B Testing

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

### Content Personalization

```typescript
const data = await Data.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' },
])
const data = await Data.create(
DataSchema.create()
.usePayload({ content: z.string() })
.useTargeting({
region: targetIncludes(z.string()),
language: targetIncludes(z.string()),
})
.build(),
).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

```typescript
const data = await Data.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 } },
])
const data = await Data.create(
DataSchema.create()
.usePayload({
config: z.object({
apiUrl: z.string(),
timeout: z.number(),
}),
})
.useTargeting({ environment: targetEquals(z.string()) })
.build(),
).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
`.build()` and pass the result 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.
Expand Down
3 changes: 2 additions & 1 deletion deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading