diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 1a761d4c8..6b95bcabe 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -28,6 +28,7 @@ import { import { partial, strictObject } from 'zod/mini' import { PromisedData } from './PromisedData.ts' import { resolveVariables } from './parsers/DataItemVariableResolver.ts' +import type { IData } from './IData.ts' /** * In-memory data store. @@ -57,42 +58,29 @@ import { resolveVariables } from './parsers/DataItemVariableResolver.ts' * ) * ``` */ -export default class Data< - PayloadParsers extends $ZodShape, - TargetingParsers extends $ZodShape, - QueryParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, -> { - readonly #fallThroughTargetingParsers: FallThroughTargetingParsers - readonly #data: DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - > - readonly #payloadParsers: PayloadParsers - readonly #targetingPredicates: TargetingPredicates< - TargetingParsers, - QueryParsers - > - readonly #targetingParsers: TargetingParsers - readonly #queryParsers: QueryParsers - readonly #QueryParser: ZodPartialObject - - static create(): PromisedData<{}, {}, {}, {}> { - return PromisedData.create<{}, {}, {}, {}>(new Data({}, {}, {}, {}, {}, {})) +export default class Data<$ extends DT.Meta> implements IData<$> { + readonly #fallThroughTargetingParsers: $['FallThroughTargetingParsers'] + readonly #data: DataItemsOut<$> + readonly #payloadParsers: $['PayloadParsers'] + readonly #targetingPredicates: TargetingPredicates<$> + readonly #targetingParsers: $['TargetingParsers'] + readonly #queryParsers: $['QueryParsers'] + readonly #QueryParser: ZodPartialObject<$['QueryParsers']> + + static create(): PromisedData { + return PromisedData.create(new Data({}, {}, {}, {}, {}, {})) } + /** + * @see {@link Data.create} + */ private constructor( - data: DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - >, - payloadParsers: PayloadParsers, - targetingPredicates: TargetingPredicates, - targetingParsers: TargetingParsers, - queryParsers: QueryParsers, - fallThroughTargetingParsers: FallThroughTargetingParsers, + data: DataItemsOut<$>, + payloadParsers: $['PayloadParsers'], + targetingPredicates: TargetingPredicates<$>, + targetingParsers: $['TargetingParsers'], + queryParsers: $['QueryParsers'], + fallThroughTargetingParsers: $['FallThroughTargetingParsers'], ) { this.#fallThroughTargetingParsers = Object.freeze( fallThroughTargetingParsers, @@ -105,52 +93,38 @@ export default class Data< this.#QueryParser = partial(strictObject(this.#queryParsers)) } - get data(): DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - > { + get data(): DataItemsOut<$> { return this.#data } - get payloadParsers(): PayloadParsers { + get payloadParsers(): $['PayloadParsers'] { return this.#payloadParsers } - get targetingPredicates(): TargetingPredicates< - TargetingParsers, - QueryParsers - > { + get targetingPredicates(): TargetingPredicates<$> { return this.#targetingPredicates } - get targetingParsers(): TargetingParsers { + get targetingParsers(): $['TargetingParsers'] { return this.#targetingParsers } - get queryParsers(): QueryParsers { + get queryParsers(): $['QueryParsers'] { return this.#queryParsers } - get QueryParser(): ZodPartialObject { + get QueryParser(): ZodPartialObject<$['QueryParsers'], $strict> { return this.#QueryParser } - get fallThroughTargetingParsers(): FallThroughTargetingParsers { + get fallThroughTargetingParsers(): $['FallThroughTargetingParsers'] { return this.#fallThroughTargetingParsers } - async usePayload( - parsers: Parsers, - ): Promise< - Data< - PayloadParsers & Parsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > + async usePayload(parsers: Parsers): Promise< + Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }> > { - type NewPayloadParsers = PayloadParsers & Parsers + type $$ = $ & { PayloadParsers: $['PayloadParsers'] & Parsers } const payloadParsers = this.#mergePayloadParsers(parsers) @@ -158,18 +132,9 @@ export default class Data< payloadParsers, this.#targetingParsers, this.#fallThroughTargetingParsers, - ).parseAsync(this.#data)) as DataItemsOut< - NewPayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - > + ).parseAsync(this.#data)) as DataItemsOut<$$> - return new Data< - NewPayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - >( + return new Data<$$>( data, payloadParsers, this.#targetingPredicates, @@ -181,27 +146,14 @@ export default class Data< #mergePayloadParsers( parsers: Parsers, - ): PayloadParsers & Parsers { + ): $['PayloadParsers'] & Parsers { return { ...this.#payloadParsers, ...parsers, } } - async insert( - data: DT.InsertableData< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - >, - ): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - > { + async insert(data: DT.InsertableData<$>): Promise> { const newData = { ...this.#data, ...(await DataItemsParser( @@ -248,39 +200,21 @@ export default class Data< ) } - readonly #isFallThroughRulesPayload = ( - payload: PT.Payload< - PayloadParsers[Name], - TargetingParsers | FallThroughTargetingParsers - >, - ): payload is FTTT.Rules< - PayloadParsers[Name], - TargetingParsers | FallThroughTargetingParsers - > => typeof payload === 'object' && payload !== null && '__rules__' in payload + readonly #isFallThroughRulesPayload = < + Name extends keyof $['PayloadParsers'], + >( + payload: PT.Payload<$, $['PayloadParsers'][Name]>, + ): payload is FTTT.Rules<$, $['PayloadParsers'][Name]> => + typeof payload === 'object' && payload !== null && '__rules__' in payload async addRules< - Name extends keyof PayloadParsers, + Name extends keyof $['PayloadParsers'], >( name: Name, opts: - | DataItemIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - > - | DataItemRulesIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - >, - ): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - > { + | DataItemIn<$, $['PayloadParsers'][Name]> + | DataItemRulesIn<$, $['PayloadParsers'][Name]>, + ): Promise> { const dataItem = this.#data[name] || { rules: [], @@ -318,12 +252,7 @@ export default class Data< ) } - removeAllRules(): Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + removeAllRules(): Data<$> { return new Data( {} as any, this.#payloadParsers, @@ -334,46 +263,39 @@ export default class Data< ) } - async useTargeting( - targeting: TDs, - ): Promise< + async useTargeting(targeting: TDs): Promise< Data< - PayloadParsers, - TargetingParsers & TT.ParserRecord, - QueryParsers & QT.ParserRecord, - FallThroughTargetingParsers + $ & { + TargetingParsers: $['TargetingParsers'] & TT.ParserRecord + QueryParsers: $['QueryParsers'] & QT.ParserRecord + } > > { - type NewTargetingParsers = TargetingParsers & TT.ParserRecord - - type NewQueryParsers = QueryParsers & QT.ParserRecord + type $$ = $ & { + TargetingParsers: $['TargetingParsers'] & TT.ParserRecord + QueryParsers: $['QueryParsers'] & QT.ParserRecord + } - const targetingParsers: NewTargetingParsers = this.#mergeTargetingParsers( - targeting, - ) + const targetingParsers: $$['TargetingParsers'] = this + .#mergeTargetingParsers( + targeting, + ) const targetingPredicates = this.#mergeTargetingPredicates( targeting, - ) as TargetingPredicates + ) as TargetingPredicates<$$> - const queryParsers: NewQueryParsers = this.#mergeQueryPredicates(targeting) + const queryParsers: $$['QueryParsers'] = this.#mergeQueryPredicates( + targeting, + ) const data = await DataItemsParser( this.#payloadParsers, targetingParsers, this.#fallThroughTargetingParsers, - ).parseAsync(this.#data) as DataItemsOut< - PayloadParsers, - NewTargetingParsers, - FallThroughTargetingParsers - > + ).parseAsync(this.#data) as DataItemsOut<$$> - return new Data< - PayloadParsers, - NewTargetingParsers, - NewQueryParsers, - FallThroughTargetingParsers - >( + return new Data<$$>( data, this.#payloadParsers, targetingPredicates, @@ -385,7 +307,7 @@ export default class Data< #mergeTargetingParsers( targeting: TDs, - ): TargetingParsers & TT.ParserRecord { + ): $['TargetingParsers'] & TT.ParserRecord { return { ...this.targetingParsers, ...objectMap(targeting, ({ targetingParser }) => targetingParser), @@ -406,7 +328,7 @@ export default class Data< #mergeQueryPredicates( targeting: TDs, - ): QueryParsers & QT.ParserRecord { + ): $['QueryParsers'] & QT.ParserRecord { return { ...this.#queryParsers, ...objectMap(targeting, ({ queryParser }) => queryParser), @@ -417,15 +339,18 @@ export default class Data< targeting: TDs, ): Promise< Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers & FTTT.ParsersRecord + $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } > > { - type NewFallThroughTargetingParsers = - & FallThroughTargetingParsers - & FTTT.ParsersRecord + type $$ = $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } const fallThroughTargetingParsers = this.#mergeFallThroughTargeting( targeting, @@ -435,18 +360,9 @@ export default class Data< this.#payloadParsers, this.#targetingParsers, fallThroughTargetingParsers, - ).parseAsync(this.#data)) as DataItemsOut< - PayloadParsers, - TargetingParsers, - NewFallThroughTargetingParsers - > + ).parseAsync(this.#data)) as DataItemsOut<$$> - return new Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - NewFallThroughTargetingParsers - >( + return new Data<$$>( data, this.#payloadParsers, this.#targetingPredicates, @@ -458,7 +374,7 @@ export default class Data< #mergeFallThroughTargeting( targeting: TDs, - ): FallThroughTargetingParsers & FTTT.ParsersRecord { + ): $['FallThroughTargetingParsers'] & FTTT.ParsersRecord { return { ...this.#fallThroughTargetingParsers, ...objectMap( @@ -472,14 +388,9 @@ export default class Data< } async getPayloadForEachName( - rawQuery: QT.Raw = {}, - ): Promise< - PT.Payloads - > { - const payloads = {} as PT.Payloads< - PayloadParsers, - FallThroughTargetingParsers - > + rawQuery: QT.Raw<$['QueryParsers']> = {}, + ): Promise> { + const payloads = {} as PT.Payloads<$> await Promise.all( objectKeys(this.#data).map(async (name) => { @@ -490,16 +401,17 @@ export default class Data< return payloads } - async getPayload( + async getPayload( name: Name, - rawQuery: QT.Raw = {}, + rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - PT.Payload | undefined + | PT.Payload<$, $['PayloadParsers'][Name]> + | undefined > { const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) let payload: - | PT.Payload + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined for (const rule of targetableItem.rules) { @@ -538,18 +450,10 @@ export default class Data< : resolvedPayload } - async #getVariables( - targetableItem: DataItemOut< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - >, + async #getVariables( + targetableItem: DataItemOut<$, $['PayloadParsers'][Name]>, predicate: ( - rule: DataItemRule< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - >, + rule: DataItemRule<$, $['PayloadParsers'][Name]>, ) => Promise, ) { const variables: Record = {} @@ -568,14 +472,13 @@ export default class Data< return variables } - async getPayloads( + async getPayloads( name: Name, - rawQuery: QT.Raw = {}, - ): Promise[]> { - const payloads: PT.Payload< - PayloadParsers[Name], - FallThroughTargetingParsers - >[] = [] + rawQuery: QT.Raw<$['QueryParsers']> = {}, + ): Promise< + PT.Payload<$, $['PayloadParsers'][Name]>[] + > { + const payloads: PT.Payload<$, $['PayloadParsers'][Name]>[] = [] const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) for (const rule of targetableItem.rules) { @@ -591,33 +494,22 @@ export default class Data< } #mapRule( - rule: DataItemRule< - PayloadParser, - TargetingParsers, - FallThroughTargetingParsers - >, - ): PT.Payload | undefined { + rule: DataItemRule<$, PayloadParser>, + ): PT.Payload<$, PayloadParser> | undefined { return hasPayload(rule) ? rule.payload as output : 'fallThrough' in rule - ? { __rules__: rule.fallThrough } as FTTT.Rules< - PayloadParser, - FallThroughTargetingParsers - > + ? { __rules__: rule.fallThrough } as FTTT.Rules<$, PayloadParser> : undefined } - async #createRulePredicate( - rawQuery: QT.Raw, + async #createRulePredicate( + rawQuery: QT.Raw<$['QueryParsers']>, ) { const query = await this.#QueryParser.parseAsync(rawQuery) return ( - rule: DataItemRule< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - >, + rule: DataItemRule<$, $['PayloadParsers'][Name]>, ) => ( !('targeting' in rule) || @@ -641,14 +533,13 @@ export default class Data< ) as Promise } - #getTargetableItem(name: Name) { + #getTargetableItem(name: Name) { return ( ( this.#data as unknown as { - [Name in keyof PayloadParsers]: DataItemOut< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers + [Name in keyof $['PayloadParsers']]: DataItemOut< + $, + $['PayloadParsers'][Name] > } )[name] ?? { rules: [], variables: {} } @@ -657,12 +548,16 @@ export default class Data< async #targetingPredicate( query: $InferObjectOutput< - { [K in keyof QueryParsers]: $ZodOptional }, + { [K in keyof $['QueryParsers']]: $ZodOptional<$['QueryParsers'][K]> }, {} >, targeting: MaybeArray< $InferObjectOutput< - { [K in keyof TargetingParsers]: $ZodOptional }, + { + [K in keyof $['TargetingParsers']]: $ZodOptional< + $['TargetingParsers'][K] + > + }, {} > >, diff --git a/packages/api/src/IData.ts b/packages/api/src/IData.ts new file mode 100644 index 000000000..8aa52379b --- /dev/null +++ b/packages/api/src/IData.ts @@ -0,0 +1,54 @@ +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 PT from './types/Payload.ts' +import type * as QT from './types/Query.ts' +import type * as TT from './types/Targeting.ts' + +export interface IData<$ extends DT.Meta> { + usePayload( + parsers: Parsers, + ): Promise> + + useTargeting(targeting: TDs): Promise< + IData< + $ & { + TargetingParsers: $['TargetingParsers'] & TT.ParserRecord + QueryParsers: $['QueryParsers'] & QT.ParserRecord + } + > + > + + useFallThroughTargeting( + targeting: TDs, + ): Promise< + IData< + $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } + > + > + + insert(data: DT.InsertableData<$>): Promise> + + getPayloadForEachName( + rawQuery?: QT.Raw<$['QueryParsers']>, + ): Promise> + + getPayload( + name: Name, + rawQuery?: QT.Raw<$['QueryParsers']>, + ): Promise< + | PT.Payload<$, $['PayloadParsers'][Name]> + | undefined + > + + getPayloads( + name: Name, + rawQuery?: QT.Raw<$['QueryParsers']>, + ): Promise< + PT.Payload<$, $['PayloadParsers'][Name]>[] + > +} diff --git a/packages/api/src/PromisedData.ts b/packages/api/src/PromisedData.ts index 626d747d7..070f633dc 100644 --- a/packages/api/src/PromisedData.ts +++ b/packages/api/src/PromisedData.ts @@ -8,148 +8,61 @@ 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' -export class PromisedData< - PayloadParsers extends $ZodShape, - TargetingParsers extends $ZodShape, - QueryParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, -> extends Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > -> { +export class PromisedData<$ extends DT.Meta> extends Promise> + implements IData<$> { constructor( executor: ( - resolve: ( - value: MaybePromise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - >, - ) => void, + resolve: (value: MaybePromise>) => void, reject: (reason?: any) => void, ) => void, ) { super(executor) } - static create< - PayloadParsers extends $ZodShape, - TargetingParsers extends $ZodShape, - QueryParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, - >( - promisedData: MaybePromise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - >, - ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + static create<$ extends DT.Meta>( + promisedData: MaybePromise>, + ): PromisedData<$> { return new PromisedData((resolve) => resolve(promisedData)) } - #create< - NewPayloadParsers extends $ZodShape, - NewTargetingParsers extends $ZodShape, - NewQueryParsers extends $ZodShape, - NewFallThroughTargetingParsers extends $ZodShape, - >( - cb: ( - data: Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - >, - ) => MaybePromise< - Data< - NewPayloadParsers, - NewTargetingParsers, - NewQueryParsers, - NewFallThroughTargetingParsers - > - >, - ): PromisedData< - NewPayloadParsers, - NewTargetingParsers, - NewQueryParsers, - NewFallThroughTargetingParsers - > { + #create<$$ extends DT.Meta>( + cb: (data: Data<$>) => MaybePromise>, + ): PromisedData<$$> { return new PromisedData((resolve) => resolve(this.then(cb))) } usePayload( parsers: Parsers, ): PromisedData< - PayloadParsers & Parsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers + $ & { PayloadParsers: $['PayloadParsers'] & Parsers } > { return this.#create((data) => data.usePayload(parsers)) } - insert( - insertableData: DT.InsertableData< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers - >, - ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + insert(insertableData: DT.InsertableData<$>): PromisedData<$> { return this.#create((data) => data.insert(insertableData)) } addRules< - Name extends keyof PayloadParsers, + Name extends keyof $['PayloadParsers'], >( name: Name, opts: - | DataItemIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - > - | DataItemRulesIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers - >, - ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + | DataItemIn<$, $['PayloadParsers'][Name]> + | DataItemRulesIn<$, $['PayloadParsers'][Name]>, + ): PromisedData<$> { return this.#create((data) => data.addRules(name, opts)) } useTargeting( targeting: TDs, ): PromisedData< - PayloadParsers, - TargetingParsers & TT.ParserRecord, - QueryParsers & QT.ParserRecord, - FallThroughTargetingParsers + $ & { + TargetingParsers: $['TargetingParsers'] & TT.ParserRecord + QueryParsers: $['QueryParsers'] & QT.ParserRecord + } > { return this.#create((data) => data.useTargeting(targeting)) } @@ -157,41 +70,46 @@ export class PromisedData< useFallThroughTargeting( targeting: TDs, ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers & FTTT.ParsersRecord + $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } > { return this.#create((data) => data.useFallThroughTargeting(targeting)) } async getPayloadForEachName( - rawQuery: QT.Raw = {}, + rawQuery?: QT.Raw<$['QueryParsers']>, ): Promise< - PT.Payloads + PT.Payloads<$> > { const data = await this return data.getPayloadForEachName(rawQuery) } - async getPayload( + async getPayload( name: Name, - rawQuery: QT.Raw = {}, + rawQuery?: QT.Raw<$['QueryParsers']>, ): Promise< - PT.Payload | undefined + | PT.Payload<$, $['PayloadParsers'][Name]> + | undefined > { const data = await this return data.getPayload(name, rawQuery) } - removeAllRules(): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > + async getPayloads( + name: Name, + rawQuery?: QT.Raw<$['QueryParsers']>, + ): Promise< + PT.Payload<$, $['PayloadParsers'][Name]>[] > { + const data = await this + return data.getPayloads(name, rawQuery) + } + + removeAllRules(): Promise> { return this.#create((data) => data.removeAllRules()) } } diff --git a/packages/api/src/parsers/DataItem.ts b/packages/api/src/parsers/DataItem.ts index d1f1dcb9e..3f9bcbeb8 100644 --- a/packages/api/src/parsers/DataItem.ts +++ b/packages/api/src/parsers/DataItem.ts @@ -5,7 +5,7 @@ import { type ZodMiniDefault, type ZodMiniObject, } from 'zod/mini' -import type { $strict, $ZodShape, $ZodType } from 'zod/v4/core' +import type { $strict, $ZodType } from 'zod/v4/core' import { type DataItemRulesIn, type DataItemRulesOut, @@ -13,6 +13,7 @@ import { } from './DataItemRules.ts' import { DataItemVariablesParser } from './DataItemVariablesParser.ts' import { variablesFor } from './variablesRegistry.ts' +import type * as DT from '../types/Data.ts' /** * Parses an "item". @@ -24,15 +25,14 @@ import { variablesFor } from './variablesRegistry.ts' * Currently this is just restricted to `rules`. */ export function DataItemParser< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, >( - Payload: P, - targeting: T, - fallThroughTargeting: FTT, + Payload: PayloadParser, + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strictTargeting: boolean, -): DataItemParser { +): DataItemParser<$, PayloadParser> { const variablesRegistry = variablesFor(Payload) return strictObject({ rules: DataItemRulesParser( @@ -55,19 +55,12 @@ export function DataItemParser< } export type DataItemParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, > = ZodMiniObject< { - rules: DataItemRulesParser< - Payload, - Targeting, - FallThroughTargeting - > - variables: ZodMiniDefault< - DataItemVariablesParser - > + rules: DataItemRulesParser<$, PayloadParser> + variables: ZodMiniDefault> }, $strict > @@ -76,14 +69,13 @@ export type DataItemParser< * The data shape expected for {@link DataItemParser} inputs. */ export interface DataItemIn< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, > { - rules: DataItemRulesIn + rules: DataItemRulesIn<$, PayloadParser> variables?: Record< string, - DataItemRulesIn + DataItemRulesIn<$, ZodMiniAny> > } @@ -91,13 +83,12 @@ export interface DataItemIn< * The data shape expected for {@link DataItemParser} outputs. */ export interface DataItemOut< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, > { - rules: DataItemRulesOut + rules: DataItemRulesOut<$, PayloadParser> variables: Record< string, - DataItemRulesOut + DataItemRulesOut<$, ZodMiniAny> > } diff --git a/packages/api/src/parsers/DataItemRule.ts b/packages/api/src/parsers/DataItemRule.ts index f241ff301..d30749817 100644 --- a/packages/api/src/parsers/DataItemRule.ts +++ b/packages/api/src/parsers/DataItemRule.ts @@ -17,6 +17,7 @@ import { type RecursiveVariableResolver, } from './attachVariableResolver.ts' import type { VariablesRegistry } from './variablesRegistry.ts' +import type * as DT from '../types/Data.ts' /** * Parses a single item rule. @@ -88,21 +89,19 @@ import type { VariablesRegistry } from './variablesRegistry.ts' * ``` */ export function DataItemRuleParser< - P extends $ZodType, - T extends $ZodShape, - CT extends $ZodShape, - V extends Record, + $ extends DT.Meta, + PayloadParser extends $ZodType, + VariableRegistry extends Record, AllowMultipleTargeting extends boolean = true, >( variablesRegistry: VariablesRegistry, - Payload: P, - targeting: T, - fallThroughTargeting: CT, + Payload: PayloadParser, + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], allowMultipleTargeting = true, ): DataItemRuleParser< - P, - T, - CT, + $, + PayloadParser, AllowMultipleTargeting > { const Rule = RuleWithPayloadParser( @@ -121,31 +120,20 @@ export function DataItemRuleParser< ) return union([Rule, FallThroughRule]) as DataItemRuleParser< - P, - T, - CT, + $, + PayloadParser, AllowMultipleTargeting > } export type DataItemRuleParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = ZodMiniUnion< [ - RuleWithPayloadParser< - Payload, - Targeting, - AllowMultipleTargeting - >, - RuleWithFallThroughParser< - Payload, - Targeting, - FallThroughTargeting, - AllowMultipleTargeting - >, + RuleWithPayloadParser<$, PayloadParser, AllowMultipleTargeting>, + RuleWithFallThroughParser<$, PayloadParser, AllowMultipleTargeting>, ] > @@ -153,18 +141,16 @@ export type DataItemRuleParser< * @see https://github.com/colinhacks/zod/issues/4698 */ export type DataItemRule< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = - | RuleWithPayload - | RuleWithFallThrough< - Payload, - Targeting, - FallThroughTargeting, + | RuleWithPayload< + PayloadParser, + $['TargetingParsers'], AllowMultipleTargeting > + | RuleWithFallThrough<$, PayloadParser, AllowMultipleTargeting> type SingularRuleTargeting = ZodPartialObject< Targeting, @@ -194,31 +180,32 @@ function MultipleRuleTargeting( } export type RuleWithPayloadParser< + $ extends Pick, Payload extends $ZodType, - Targeting extends $ZodShape, AllowMultipleTargeting extends boolean = true, > = ZodMiniObject< { payload: RecursiveVariableResolver targeting: ZodMiniOptional< - AllowMultipleTargeting extends true ? MultipleRuleTargeting - : SingularRuleTargeting + AllowMultipleTargeting extends true + ? MultipleRuleTargeting<$['TargetingParsers']> + : SingularRuleTargeting<$['TargetingParsers']> > }, $strict > export function RuleWithPayloadParser< - P extends $ZodType, - T extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, >( variablesRegistry: VariablesRegistry, - Payload: P, - targeting: T, + Payload: PayloadParser, + targeting: $['TargetingParsers'] & $['FallThroughTargetingParsers'], strictTargeting: boolean, allowMultipleTargeting = true as AllowMultipleTargeting, -): RuleWithPayloadParser { +): RuleWithPayloadParser<$, PayloadParser, AllowMultipleTargeting> { return strictObject({ payload: attachVariableResolver(variablesRegistry, Payload), targeting: optional( @@ -226,21 +213,25 @@ export function RuleWithPayloadParser< ? MultipleRuleTargeting(targeting, strictTargeting) : SingularRuleTargeting(targeting, strictTargeting), ), - }) as RuleWithPayloadParser + }) as RuleWithPayloadParser<$, PayloadParser, AllowMultipleTargeting> } /** * @see https://github.com/colinhacks/zod/issues/4698 */ export interface RuleWithPayloadIn< - Payload extends $ZodType, - Targeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > { - payload: input> + payload: input> targeting?: input< - AllowMultipleTargeting extends true ? MultipleRuleTargeting - : SingularRuleTargeting + AllowMultipleTargeting extends true ? MultipleRuleTargeting< + $['TargetingParsers'] & $['FallThroughTargetingParsers'] + > + : SingularRuleTargeting< + $['TargetingParsers'] & $['FallThroughTargetingParsers'] + > > } @@ -260,20 +251,20 @@ export interface RuleWithPayload< } export type RuleWithFallThroughParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = ZodMiniObject< { targeting: ZodMiniOptional< - AllowMultipleTargeting extends true ? MultipleRuleTargeting - : SingularRuleTargeting + AllowMultipleTargeting extends true + ? MultipleRuleTargeting<$['TargetingParsers']> + : SingularRuleTargeting<$['TargetingParsers']> > fallThrough: ZodMiniArray< RuleWithPayloadParser< - Payload, - FallThroughTargeting, + $, + PayloadParser, AllowMultipleTargeting > > @@ -285,41 +276,35 @@ export type RuleWithFallThroughParser< * @see https://github.com/colinhacks/zod/issues/4698 */ export interface RuleWithFallThrough< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > { fallThrough: RuleWithPayload< - Payload, - FallThroughTargeting, + PayloadParser, + $['FallThroughTargetingParsers'], AllowMultipleTargeting >[] targeting?: output< - AllowMultipleTargeting extends true ? MultipleRuleTargeting - : SingularRuleTargeting + AllowMultipleTargeting extends true + ? MultipleRuleTargeting<$['TargetingParsers']> + : SingularRuleTargeting<$['TargetingParsers']> > } export function RuleWithFallThroughParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, Variables extends Record, AllowMultipleTargeting extends boolean = true, >( variablesRegistry: VariablesRegistry, - payload: Payload, - targeting: Targeting, - fallThroughTargeting: FallThroughTargeting, + payload: PayloadParser, + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strictTargeting: boolean, allowMultipleTargeting = true, -): RuleWithFallThroughParser< - Payload, - Targeting, - FallThroughTargeting, - AllowMultipleTargeting -> { +): RuleWithFallThroughParser<$, PayloadParser, AllowMultipleTargeting> { return strictObject({ targeting: optional( allowMultipleTargeting @@ -334,10 +319,5 @@ export function RuleWithFallThroughParser< strictTargeting, ), ), - }) as RuleWithFallThroughParser< - Payload, - Targeting, - FallThroughTargeting, - AllowMultipleTargeting - > + }) as RuleWithFallThroughParser<$, PayloadParser, AllowMultipleTargeting> } diff --git a/packages/api/src/parsers/DataItemRules.ts b/packages/api/src/parsers/DataItemRules.ts index 54ba62183..ec4374cc6 100644 --- a/packages/api/src/parsers/DataItemRules.ts +++ b/packages/api/src/parsers/DataItemRules.ts @@ -21,6 +21,7 @@ import { } from 'zod/mini' import type { $ZodShape, $ZodType } from 'zod/v4/core' import type { ZodPartialInferObject } from '../types.ts' +import type * as DT from '../types/Data.ts' import type { VariablesRegistry } from './variablesRegistry.ts' /** @@ -58,28 +59,38 @@ import type { VariablesRegistry } from './variablesRegistry.ts' * ``` */ export function DataItemRulesParser< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, >( variablesRegistry: VariablesRegistry, - payloadParser: P, - targetingParsers: T, - fallThroughTargetingParsers: FTT, + payloadParser: PayloadParser, + targetingParsers: $['TargetingParsers'], + fallThroughTargetingParsers: $['FallThroughTargetingParsers'], strictTargeting: boolean, -): DataItemRulesParser { +): DataItemRulesParser<$, PayloadParser> { return pipe( - array(RuleWithPayloadParser(variablesRegistry, payloadParser, { - ...targetingParsers, - ...fallThroughTargetingParsers, - }, strictTargeting)), + array( + RuleWithPayloadParser<$, PayloadParser>( + variablesRegistry, + payloadParser, + { + ...targetingParsers, + ...fallThroughTargetingParsers, + }, + strictTargeting, + ), + ), transform( ( - rules: RuleWithPayloadIn[], - ): DataItemRule[] => { + rules: RuleWithPayloadIn<$, PayloadParser>[], + ): DataItemRule< + $, + PayloadParser, + false + >[] => { const singularTargetedRules = spreadMultiTargetsToSeparateRules(rules) - let transformedRules: DataItemRule[] = [] + let transformedRules: DataItemRule<$, PayloadParser, false>[] = [] for (const rule of singularTargetedRules) { const prevRule = arrayLast(transformedRules) @@ -107,7 +118,7 @@ export function DataItemRulesParser< const adaptedRule = adaptRuleIntoFallThroughRule( targetingParsers, fallThroughTargetingParsers, - rule as DataItemRule, + rule as DataItemRule<$, PayloadParser, false>, ) adaptedPrevRule.fallThrough.push(...adaptedRule.fallThrough) @@ -130,36 +141,31 @@ export function DataItemRulesParser< } export type DataItemRulesParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, > = ZodMiniPipe< - ZodMiniArray< - RuleWithPayloadParser - >, + ZodMiniArray>, ZodMiniTransform< - DataItemRulesOut, - DataItemRulesIn + DataItemRulesOut<$, PayloadParser>, + DataItemRulesIn<$, PayloadParser> > > export type DataItemRulesIn< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = RuleWithPayloadIn[] + $ extends DT.Meta, + PayloadParser extends $ZodType, +> = RuleWithPayloadIn<$, PayloadParser>[] export type DataItemRulesOut< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = DataItemRule[] + $ extends DT.Meta, + PayloadParser extends $ZodType, +> = DataItemRule<$, PayloadParser, false>[] function spreadMultiTargetsToSeparateRules< - P extends $ZodType, - T extends $ZodShape, ->(rules: RuleWithPayloadIn[]) { - return rules.reduce[]>( + $ extends DT.Meta, + PayloadParser extends $ZodType, +>(rules: RuleWithPayloadIn<$, PayloadParser>[]) { + return rules.reduce[]>( (rules, rule) => { if (Array.isArray(rule.targeting)) { for (const targeting of rule.targeting) { @@ -169,7 +175,7 @@ function spreadMultiTargetsToSeparateRules< }) } } else { - rules.push(rule as RuleWithPayloadIn) + rules.push(rule as RuleWithPayloadIn<$, PayloadParser, false>) } return rules }, @@ -179,8 +185,8 @@ function spreadMultiTargetsToSeparateRules< function canRulesCombine( targetingParsers: $ZodShape, - a: DataItemRule<$ZodType, $ZodShape, $ZodShape, false>, - b: DataItemRule<$ZodType, $ZodShape, $ZodShape, false>, + a: DataItemRule, + b: DataItemRule, ) { const aTargeting = a.targeting ? intersection(a.targeting, targetingParsers) @@ -203,40 +209,38 @@ function canRulesCombine( } function adaptRule< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, >( - targetingParsers: T, - fallThroughTargetingParsers: FTT, - rule: RuleWithPayloadIn, + targetingParsers: $['TargetingParsers'], + fallThroughTargetingParsers: $['FallThroughTargetingParsers'], + rule: RuleWithPayloadIn<$, PayloadParser, false>, ) { return ( someKeysIntersect(fallThroughTargetingParsers, rule.targeting || {}) ? adaptRuleIntoFallThroughRule( targetingParsers, fallThroughTargetingParsers, - rule as DataItemRule, + rule as DataItemRule<$, PayloadParser, false>, ) : rule - ) as DataItemRule + ) as DataItemRule<$, PayloadParser, false> } function adaptRuleIntoFallThroughRule< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends DT.Meta, + PayloadParser extends $ZodType, >( - targetingParsers: T, - fallThroughTargetingParsers: FTT, - rule: DataItemRule, -): RuleWithFallThrough { + targetingParsers: $['TargetingParsers'], + fallThroughTargetingParsers: $['FallThroughTargetingParsers'], + rule: DataItemRule<$, PayloadParser, false>, +): RuleWithFallThrough<$, PayloadParser, false> { if ('fallThrough' in rule) return rule return { targeting: intersection( rule.targeting || {}, targetingParsers, - ) as ZodPartialInferObject, + ) as ZodPartialInferObject<$['TargetingParsers']>, fallThrough: [ { payload: rule.payload, @@ -246,5 +250,5 @@ function adaptRuleIntoFallThroughRule< ), }, ], - } as RuleWithFallThrough + } as RuleWithFallThrough<$, PayloadParser, false> } diff --git a/packages/api/src/parsers/DataItemVariablesParser.ts b/packages/api/src/parsers/DataItemVariablesParser.ts index 8028691a8..cf3cca7cb 100644 --- a/packages/api/src/parsers/DataItemVariablesParser.ts +++ b/packages/api/src/parsers/DataItemVariablesParser.ts @@ -1,4 +1,4 @@ -import type { $ZodShape, $ZodType } from 'zod/v4/core' +import type { $ZodType } from 'zod/v4/core' import { any, record, @@ -10,16 +10,14 @@ import { } from 'zod/mini' import { DataItemRulesParser } from './DataItemRules.ts' import type { VariablesRegistry } from './variablesRegistry.ts' +import type * as DT from '../types/Data.ts' -export function DataItemVariablesParser< - T extends $ZodShape, - FTT extends $ZodShape, ->( +export function DataItemVariablesParser<$ extends DT.Meta>( variablesRegistry: VariablesRegistry, - targeting: T, - fallThroughTargeting: FTT, + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strictTargeting: boolean, -): DataItemVariablesParser { +): DataItemVariablesParser<$> { return record( string(), DataItemRulesParser( @@ -89,10 +87,7 @@ export function DataItemVariablesParser< }) } -export type DataItemVariablesParser< - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = ZodMiniRecord< +export type DataItemVariablesParser<$ extends DT.Meta> = ZodMiniRecord< ZodMiniString, - DataItemRulesParser + DataItemRulesParser<$, ZodMiniAny> > diff --git a/packages/api/src/parsers/DataItems.ts b/packages/api/src/parsers/DataItems.ts index aa72b8493..09bb683c6 100644 --- a/packages/api/src/parsers/DataItems.ts +++ b/packages/api/src/parsers/DataItems.ts @@ -1,11 +1,12 @@ -import type { ZodPartialObject } from '../types.ts' +import type * as DT from '../types/Data.ts' +import type { ObjValues, ZodPartialObject } from '../types.ts' import { type DataItemIn, type DataItemOut, DataItemParser, } from './DataItem.ts' import { object, partial, strictObject } from 'zod/mini' -import type { $strict, $ZodShape } from 'zod/v4/core' +import type { $strict } from 'zod/v4/core' /** * Parses all items in a Data class. @@ -30,19 +31,17 @@ import type { $strict, $ZodShape } from 'zod/v4/core' * ``` */ export function DataItemsParser< - D extends $ZodShape, - T extends $ZodShape, - CT extends $ZodShape, + $ extends DT.Meta, >( - payloadParsers: D, - targeting: T, - fallThroughTargeting: CT, + payloadParsers: $['PayloadParsers'], + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strict = true, -): DataItemsParser { +): DataItemsParser<$> { const dataItems: Record = {} for (const [key, Payload] of Object.entries(payloadParsers)) { dataItems[key] = DataItemParser( - Payload, + Payload as ObjValues<$['PayloadParsers']>, targeting, fallThroughTargeting, strict, @@ -50,44 +49,28 @@ export function DataItemsParser< } return partial( (strict ? strictObject : object)(dataItems), - ) as DataItemsParser + ) as DataItemsParser<$> } export type DataItemsParser< - DataTypes extends $ZodShape, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends DT.Meta, > = ZodPartialObject< { - [Name in keyof DataTypes]: DataItemParser< - DataTypes[Name], - Targeting, - FallThroughTargeting + [Name in keyof $['PayloadParsers']]: DataItemParser< + $, + $['PayloadParsers'][Name] > }, $strict > -export type DataItemsIn< - DataTypes extends $ZodShape, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = { - [Name in keyof DataTypes]?: DataItemIn< - DataTypes[Name], - Targeting, - FallThroughTargeting - > +export type DataItemsIn<$ extends DT.Meta> = { + [Name in keyof $['PayloadParsers']]?: DataItemIn<$, $['PayloadParsers'][Name]> } -export type DataItemsOut< - DataTypes extends $ZodShape, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = { - [Name in keyof DataTypes]?: DataItemOut< - DataTypes[Name], - Targeting, - FallThroughTargeting +export type DataItemsOut<$ extends DT.Meta> = { + [Name in keyof $['PayloadParsers']]?: DataItemOut< + $, + $['PayloadParsers'][Name] > } diff --git a/packages/api/src/parsers/TargetingPredicates.ts b/packages/api/src/parsers/TargetingPredicates.ts index 16273eb17..97ced07d0 100644 --- a/packages/api/src/parsers/TargetingPredicates.ts +++ b/packages/api/src/parsers/TargetingPredicates.ts @@ -1,22 +1,16 @@ import type { Keys } from 'ts-toolbelt/out/Any/Keys' import type TargetingPredicate from './TargetingPredicate.ts' -import type { - $InferObjectOutput, - $strict, - $ZodShape, - $ZodType, - output, -} from 'zod/v4/core' +import type * as DT from '../types/Data.ts' +import type { $InferObjectOutput, $strict, output } from 'zod/v4/core' type TargetingPredicates< - Targeting extends $ZodShape, - Query extends Record, $ZodType>, + $ extends Pick, > = { - [Name in Keys]: { + [Name in Keys<$['TargetingParsers']>]: { predicate: TargetingPredicate< - Query[Name], - Targeting[Name], - Partial>> + $['QueryParsers'][Name], + $['TargetingParsers'][Name], + Partial>> > requiresQuery: boolean } diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index e65bcc346..efcfb27ab 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -30,3 +30,5 @@ export type ZodPartialInferObject = $InferObjectOutput< > export type MaybeArray = T | T[] + +export type ObjValues = T[keyof T] diff --git a/packages/api/src/types/Data.ts b/packages/api/src/types/Data.ts index 9573dce67..974884e71 100644 --- a/packages/api/src/types/Data.ts +++ b/packages/api/src/types/Data.ts @@ -1,63 +1,77 @@ import type Data from '../Data.ts' import type * as FTTT from './FallThroughTargeting.ts' -import type { $ZodShape, $ZodType, output } from 'zod/v4/core' +import type { $ZodShape, output } from 'zod/v4/core' + +export interface Meta { + PayloadParsers: $ZodShape + TargetingParsers: $ZodShape + QueryParsers: $ZodShape + FallThroughTargetingParsers: $ZodShape +} + +export interface EmptyMeta { + PayloadParsers: {} + TargetingParsers: {} + QueryParsers: {} + FallThroughTargetingParsers: {} +} /** * Match any Data */ -export type Any = Data +export type Any = Data< + { + PayloadParsers: any + TargetingParsers: any + QueryParsers: any + FallThroughTargetingParsers: any + } +> + +export type $ = D extends Data ? $ : never /** * Get the PayloadParsers from a Data type */ -export type PayloadParsers = D extends - Data ? V : never +export type PayloadParsers = D extends Data + ? $['PayloadParsers'] + : never /** * Get the TargetingParsers from a Data type */ -export type TargetingParsers = D extends - Data ? V : never +export type TargetingParsers = D extends Data + ? $['TargetingParsers'] + : never /** * Get the QueryParsers from a Data type */ -export type QueryParsers = D extends Data - ? V +export type QueryParsers = D extends Data + ? $['QueryParsers'] : never /** * Get the FallThroughTargetingParsers from a Data type */ -export type FallThroughTargetingParsers = D extends - Data ? V : never - -/** - * Create the fall through Data type from a Data type - */ -export type FallThrough = Data< - PayloadParsers, - { [K in keyof FallThroughTargetingParsers]: $ZodType }, - Omit, keyof TargetingParsers>, - {} -> +export type FallThroughTargetingParsers = D extends Data + ? $['FallThroughTargetingParsers'] + : never -/** - * A union of possible payloads for a Data's PayloadParser - */ -export type Payload> = - | output[Name]> - | FTTT.Rules[Name], TargetingParsers> - -export type InsertableData< - D extends $ZodShape, - TP extends $ZodShape, - FTP extends $ZodShape, -> = Partial< +export type InsertableData<$ extends Meta> = Partial< { - [Name in keyof D]: - | output - | FTTT.Rules - | FTTT.Rules + [Name in keyof $['PayloadParsers']]: + | output<$['PayloadParsers'][Name]> + | FTTT.Rules< + $, + $['PayloadParsers'][Name] + > + | FTTT.Rules< + $ & { + TargetingParsers: {} + FallThroughTargetingParsers: $['TargetingParsers'] + }, + $['PayloadParsers'][Name] + > } > diff --git a/packages/api/src/types/FallThroughTargeting.ts b/packages/api/src/types/FallThroughTargeting.ts index a3fb9f3ac..91b0ed96c 100644 --- a/packages/api/src/types/FallThroughTargeting.ts +++ b/packages/api/src/types/FallThroughTargeting.ts @@ -1,8 +1,9 @@ import type TargetingDescriptor from '../parsers/TargetingDescriptor.ts' import type * as TT from './Targeting.ts' import type { RuleWithPayload } from '../parsers/DataItemRule.ts' -import type { $ZodShape, $ZodType } from 'zod/v4/core' +import type { $ZodType } from 'zod/v4/core' import type { DataItemRulesOut } from '../parsers/DataItemRules.ts' +import type * as DT from './Data.ts' import type { ZodMiniAny } from 'zod/mini' /** @@ -31,10 +32,16 @@ export type ParsersRecord = { /** * The data shape of a set of fallthrough rules */ -export type Rules

= { - __rules__: RuleWithPayload[] +export type Rules< + $ extends DT.Meta, + PayloadParser extends $ZodType, +> = { + __rules__: RuleWithPayload[] __variables__?: Record< string, - DataItemRulesOut + DataItemRulesOut< + $ & { TargetingParsers: $['FallThroughTargetingParsers'] }, + ZodMiniAny + > > } diff --git a/packages/api/src/types/Payload.ts b/packages/api/src/types/Payload.ts index de18d89b7..1c791f87d 100644 --- a/packages/api/src/types/Payload.ts +++ b/packages/api/src/types/Payload.ts @@ -1,16 +1,14 @@ -import type { $ZodShape, $ZodType, output } from 'zod/v4/core' +import type { $ZodType, output } from 'zod/v4/core' import type * as FTTT from './FallThroughTargeting.ts' +import type * as DT from './Data.ts' -export type Payload

= - | output

- | FTTT.Rules +export type Payload< + $ extends DT.Meta, + PayloadParser extends $ZodType, +> = + | output + | FTTT.Rules<$, PayloadParser> -export type Payloads< - PayloadParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, -> = { - [Name in keyof PayloadParsers]?: Payload< - PayloadParsers[Name], - FallThroughTargetingParsers - > +export type Payloads<$ extends DT.Meta> = { + [Name in keyof $['PayloadParsers']]?: Payload<$, $['PayloadParsers'][Name]> } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 09559e6fa..81a4f8a88 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,34 +1,18 @@ import type { Data, DT, PT, StaticRecord } from '@targetd/api' -import type { $ZodShape } from 'zod/v4/core' import { queryToURLSearchParams } from './queryToURLSearchParams.ts' import { ZodError } from 'zod' import { ResponseError } from './ResponseError.ts' -export class Client< - PayloadParsers extends $ZodShape, - TargetingParsers extends $ZodShape, - QueryParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, -> { +export class Client<$ extends DT.Meta> { #baseURL: string - #data: Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > + #data: Data<$> #init?: RequestInit constructor( baseURL: string, - data: Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - >, + data: Data<$>, init?: RequestInit, ) { this.#baseURL = baseURL @@ -36,11 +20,12 @@ export class Client< this.#init = init } - async getPayload( + async getPayload( name: Name, - rawQuery: Partial> = {}, + rawQuery: Partial> = {}, ): Promise< - PT.Payload | void + | PT.Payload<$, $['PayloadParsers'][Name]> + | void > { const query = this.#data.QueryParser.parse(rawQuery) const urlSearchParams = queryToURLSearchParams(query) @@ -78,12 +63,12 @@ export class Client< } async getPayloadForEachName( - rawQuery: Partial> = {}, + rawQuery: Partial> = {}, ): Promise< Partial< { - [Name in keyof PayloadParsers]: - | PT.Payload + [Name in keyof $['PayloadParsers']]: + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined } > @@ -99,11 +84,4 @@ export class Client< } } -export type ClientWithData< - D extends DT.Any, -> = Client< - DT.PayloadParsers, - DT.TargetingParsers, - DT.QueryParsers, - DT.FallThroughTargetingParsers -> +export type ClientWithData = Client> diff --git a/packages/server/src/middleware/castQueryArrayProps.ts b/packages/server/src/middleware/castQueryArrayProps.ts index 53939d920..ce6a6938a 100644 --- a/packages/server/src/middleware/castQueryArrayProps.ts +++ b/packages/server/src/middleware/castQueryArrayProps.ts @@ -1,34 +1,27 @@ -import type { Data } from '@targetd/api' +import type { DT } from '@targetd/api' import type { RequestHandler } from 'express' -import type { $ZodShape } from 'zod/v4/core' import type { MaybePromise } from '../types.ts' +import type { ParsedQs } from './castQueryProp.ts' export function castQueryArrayProps< - PayloadParsers extends $ZodShape, - TargetingParsers extends $ZodShape, - QueryParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, + P extends Record, + ResBody, + ReqBody, + ReqQuery, + Locals extends { query: ParsedQs }, >( - getData: () => MaybePromise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - >, -): RequestHandler { - return async (req, res, next) => { + getData: () => MaybePromise, +): RequestHandler { + return async (_req, res, next) => { const { queryParsers } = await getData() - for (const [key, value] of Object.entries(req.query)) { + for (const [key, value] of Object.entries(res.locals.query)) { if ( key in queryParsers && queryParsers[key]._zod.def.type === 'array' && value !== undefined && !Array.isArray(value) ) { - res.locals.query ?? {} res.locals.query[key] = [value] } } diff --git a/packages/server/src/middleware/castQueryProp.ts b/packages/server/src/middleware/castQueryProp.ts index f51e1c40a..093b6ca56 100644 --- a/packages/server/src/middleware/castQueryProp.ts +++ b/packages/server/src/middleware/castQueryProp.ts @@ -1,7 +1,19 @@ import type { RequestHandler } from 'express' import type { ParsedQs as $ParsedQs } from 'qs' -export function castQueryProp(): RequestHandler { +export function castQueryProp< + P extends Record, + ResBody, + ReqBody, + ReqQuery extends $ParsedQs, + Locals extends Record, +>(): RequestHandler< + P, + ResBody, + ReqBody, + ReqQuery, + Locals & { query: ParsedQs } +> { return function (req, res, next) { res.locals.query = parseObject(req.query) as any next() @@ -15,7 +27,7 @@ type ParsedQsParam = | ParsedQsParam[] | ParsedQs -type ParsedQs = { [key: string]: ParsedQsParam } +export type ParsedQs = { [key: string]: ParsedQsParam } function isObject(val: unknown): val is $ParsedQs { return typeof val === 'object' && !!val