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
73 changes: 73 additions & 0 deletions packages/api/src/ConfigurableData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { $ZodShape } from 'zod/v4/core'
import type * as DT from './types/Data.ts'
import type * as FTTT from './types/FallThroughTargeting.ts'
import type * as QT from './types/Query.ts'
import type * as TT from './types/Targeting.ts'
import type { Merge } from './util.ts'

/**
* Interface for configuring a {@link Data} instance with payload schemas,
* targeting descriptors, and fall-through targeting.
*
* @template $ - The metadata type extending {@link DT.Meta}
*
* @example
* ```ts
* import { Data, targetIncludes } from '@targetd/api'
* import { z } from 'zod'
*
* const data = await Data.create()
* .usePayload({ greeting: z.string() })
* .useTargeting({ country: targetIncludes(z.string()) })
* ```
*/
export interface ConfigurableData<$ extends DT.Meta> {
/**
* Register additional payload parsers for validating payloads.
*
* @param parsers - An object of Zod schemas keyed by payload name.
* @returns A new instance with the updated payload parsers.
*/
usePayload<Parsers extends $ZodShape>(
parsers: Parsers,
): Promise<
ConfigurableData<
Merge<$, { PayloadParsers: Merge<$['PayloadParsers'], Parsers> }>
>
>

/**
* Register targeting descriptors to enable query-based filtering.
*
* @param targeting - An object of targeting descriptors keyed by targeting name.
* @returns A new instance with the updated targeting and query parsers.
*/
useTargeting<TDs extends TT.DescriptorRecord>(targeting: TDs): Promise<
ConfigurableData<
Merge<$, {
TargetingParsers: Merge<$['TargetingParsers'], TT.ParserRecord<TDs>>
QueryParsers: Merge<$['QueryParsers'], QT.ParserRecord<TDs>>
}>
>
>

/**
* Register fall-through targeting descriptors.
* Fall-through targeting allows rules to continue matching after a successful match.
*
* @param targeting - An object of fall-through targeting descriptors.
* @returns A new instance with the updated fall-through targeting parsers.
*/
useFallThroughTargeting<TDs extends FTTT.DescriptorRecord>(
targeting: TDs,
): Promise<
ConfigurableData<
Merge<$, {
FallThroughTargetingParsers: Merge<
$['FallThroughTargetingParsers'],
FTTT.ParsersRecord<TDs>
>
}>
>
>
}
19 changes: 13 additions & 6 deletions packages/api/src/Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import {
import { partial, strictObject } from 'zod/mini'
import { PromisedData } from './PromisedData.ts'
import { resolveVariables } from './parsers/DataItemVariableResolver.ts'
import type { IData } from './IData.ts'
import type { ConfigurableData } from './ConfigurableData.ts'
import type { InsertableData } from './InsertableData.ts'
import type { QueryableData } from './QueryableData.ts'

/**
* In-memory data store.
Expand Down Expand Up @@ -59,7 +61,8 @@ import type { IData } from './IData.ts'
* )
* ```
*/
export default class Data<$ extends DT.Meta> implements IData<$> {
export default class Data<$ extends DT.Meta>
implements ConfigurableData<$>, InsertableData<$>, QueryableData<$> {
readonly #fallThroughTargetingParsers: $['FallThroughTargetingParsers']
readonly #data: DataItemsOut<$>
readonly #payloadParsers: $['PayloadParsers']
Expand Down Expand Up @@ -533,14 +536,18 @@ export default class Data<$ extends DT.Meta> implements IData<$> {
): Promise<
Data<
Merge<$, {
FallThroughTargetingParsers:
Merge<$['FallThroughTargetingParsers'], FTTT.ParsersRecord<TDs>>
FallThroughTargetingParsers: Merge<
$['FallThroughTargetingParsers'],
FTTT.ParsersRecord<TDs>
>
}>
>
> {
type $$ = Merge<$, {
FallThroughTargetingParsers:
Merge<$['FallThroughTargetingParsers'], FTTT.ParsersRecord<TDs>>
FallThroughTargetingParsers: Merge<
$['FallThroughTargetingParsers'],
FTTT.ParsersRecord<TDs>
>
}>

const fallThroughTargetingParsers = this.#mergeFallThroughTargeting(
Expand Down
54 changes: 0 additions & 54 deletions packages/api/src/IData.ts

This file was deleted.

23 changes: 23 additions & 0 deletions packages/api/src/InsertableData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type * as DT from './types/Data.ts'

/**
* Interface for inserting rule data into a {@link Data} instance.
*
* @template $ - The metadata type extending {@link DT.Meta}
*
* @example
* ```ts
* const updated = await data.insert({
* greeting: [{ targeting: { country: ['US'] }, payload: 'Hello!' }],
* })
* ```
*/
export interface InsertableData<$ extends DT.Meta> {
/**
* Insert rule data into the instance.
*
* @param data - The data to insert, keyed by payload name.
* @returns A new instance with the inserted data.
*/
insert(data: DT.InsertableData<$>): Promise<InsertableData<$>>
}
19 changes: 13 additions & 6 deletions packages/api/src/PromisedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ import type * as QT from './types/Query.ts'
import type { DataItemRulesIn } from './parsers/DataItemRules.ts'
import type { MaybePromise } from './types.ts'
import type { DataItemIn } from './parsers/DataItem.ts'
import type { IData } from './IData.ts'
import type { ConfigurableData } from './ConfigurableData.ts'
import type { InsertableData } from './InsertableData.ts'
import type { QueryableData } from './QueryableData.ts'
import type { Merge } from './util.ts'

/**
* A Promise-based wrapper for Data that implements the IData interface.
* A Promise-based wrapper for Data that implements the ConfigurableData,
* InsertableData, and QueryableData interfaces.
* Allows chaining operations on Data instances that may be resolved asynchronously.
* All methods return a new PromisedData instance, enabling fluent API usage.
*
* This is used under the hood by {@link Data} and you most probably won't use it directly.
*
* @template $ - The metadata type extending Data.Meta
* @extends {Promise<Data<$>>}
* @implements {IData<$>}
* @implements {ConfigurableData<$>}
* @implements {InsertableData<$>}
* @implements {QueryableData<$>}
*/
export class PromisedData<$ extends DT.Meta> extends Promise<Data<$>>
implements IData<$> {
implements ConfigurableData<$>, InsertableData<$>, QueryableData<$> {
/**
* Creates a new PromisedData instance.
*
Expand Down Expand Up @@ -138,8 +143,10 @@ export class PromisedData<$ extends DT.Meta> extends Promise<Data<$>>
targeting: TDs,
): PromisedData<
Merge<$, {
FallThroughTargetingParsers:
Merge<$['FallThroughTargetingParsers'], FTTT.ParsersRecord<TDs>>
FallThroughTargetingParsers: Merge<
$['FallThroughTargetingParsers'],
FTTT.ParsersRecord<TDs>
>
}>
> {
return this.#create((data) => data.useFallThroughTargeting(targeting))
Expand Down
55 changes: 55 additions & 0 deletions packages/api/src/QueryableData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type * as DT from './types/Data.ts'
import type * as PT from './types/Payload.ts'
import type * as QT from './types/Query.ts'

/**
* Interface for querying payloads from a {@link Data} instance.
*
* @template $ - The metadata type extending {@link DT.Meta}
*
* @example
* ```ts
* const greeting = await data.getPayload('greeting', { country: 'US' })
* const all = await data.getPayloadForEachName({ country: 'US' })
* ```
*/
export interface QueryableData<$ extends DT.Meta> {
/**
* Retrieve the first matching payload for every registered payload name.
*
* @param rawQuery - Optional query object for targeting.
* @returns An object mapping payload names to their matched values.
*/
getPayloadForEachName(
rawQuery?: QT.Raw<$['QueryParsers']>,
): Promise<PT.Payloads<$>>

/**
* Retrieve the first matching payload for a specific name.
*
* @param name - The payload name to look up.
* @param rawQuery - Optional query object for targeting.
* @returns The matched payload, or `undefined` if no rule matched.
*/
getPayload<Name extends keyof $['PayloadParsers']>(
name: Name,
rawQuery?: QT.Raw<$['QueryParsers']>,
): Promise<
| PT.Payload<$, $['PayloadParsers'][Name]>
| undefined
>

/**
* Retrieve all matching payloads for a specific name.
*
* @param name - The payload name to look up.
* @param rawQuery - Optional query object for targeting.
* @returns An array of all matching payloads.
*/
getPayloads<Name extends keyof $['PayloadParsers']>(
name: Name,
rawQuery?: QT.Raw<$['QueryParsers']>,
): Promise<
PT.Payload<$, $['PayloadParsers'][Name]>[]
>
}
3 changes: 3 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export type { default as TargetingPredicate } from './parsers/TargetingPredicate
export type { default as TargetingPredicates } from './parsers/TargetingPredicates.ts'
export { targetEquals } from './predicates/equals.ts'
export { targetIncludes } from './predicates/targetIncludes.ts'
export type { ConfigurableData } from './ConfigurableData.ts'
export type { InsertableData } from './InsertableData.ts'
export type { QueryableData } from './QueryableData.ts'
export type { StaticRecord } from './types.ts'
export type { Merge } from './util.ts'

Expand Down
Loading
Loading