From 0323e34bbb7c228ba9e1024cc66b31897fd3e642 Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 10:07:27 +0100 Subject: [PATCH 01/11] refactor(server): typing middleware --- .../server/src/middleware/castQueryArrayProps.ts | 13 +++++++++---- packages/server/src/middleware/castQueryProp.ts | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/server/src/middleware/castQueryArrayProps.ts b/packages/server/src/middleware/castQueryArrayProps.ts index 53939d920..354608a8e 100644 --- a/packages/server/src/middleware/castQueryArrayProps.ts +++ b/packages/server/src/middleware/castQueryArrayProps.ts @@ -2,12 +2,18 @@ import type { Data } 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< @@ -17,18 +23,17 @@ export function castQueryArrayProps< FallThroughTargetingParsers > >, -): RequestHandler { - return async (req, res, next) => { +): 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 From abe3f255b11075bd2b2b575de71df3e3deaa598c Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 11:02:23 +0100 Subject: [PATCH 02/11] refactor: meta data typing Prior to the Meta ($) type, the Data class was difficult to maintain. Everytime a new feature was added every reference needed to change. We now contain all the Data generics in an interface. BREAKING CHANGE: Data generics have changed. --- packages/api/src/Data.ts | 282 ++++++++---------- packages/api/src/PromisedData.ts | 154 +++------- packages/api/src/types/Data.ts | 56 +++- packages/client/src/index.ts | 47 +-- .../src/middleware/castQueryArrayProps.ts | 17 +- 5 files changed, 226 insertions(+), 330 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 1a761d4c8..56bdf3ae0 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -57,42 +57,40 @@ 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 +export default class Data<$ extends DT.Meta> { + readonly #fallThroughTargetingParsers: $['FallThroughTargetingParsers'] readonly #data: DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > - readonly #payloadParsers: PayloadParsers + readonly #payloadParsers: $['PayloadParsers'] readonly #targetingPredicates: TargetingPredicates< - TargetingParsers, - QueryParsers + $['TargetingParsers'], + $['QueryParsers'] > - readonly #targetingParsers: TargetingParsers - readonly #queryParsers: QueryParsers - readonly #QueryParser: ZodPartialObject + readonly #targetingParsers: $['TargetingParsers'] + readonly #queryParsers: $['QueryParsers'] + readonly #QueryParser: ZodPartialObject<$['QueryParsers']> - static create(): PromisedData<{}, {}, {}, {}> { - return PromisedData.create<{}, {}, {}, {}>(new Data({}, {}, {}, {}, {}, {})) + static create(): PromisedData { + return PromisedData.create(new Data({}, {}, {}, {}, {}, {})) } private constructor( data: DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - payloadParsers: PayloadParsers, - targetingPredicates: TargetingPredicates, - targetingParsers: TargetingParsers, - queryParsers: QueryParsers, - fallThroughTargetingParsers: FallThroughTargetingParsers, + payloadParsers: $['PayloadParsers'], + targetingPredicates: TargetingPredicates< + $['TargetingParsers'], + $['QueryParsers'] + >, + targetingParsers: $['TargetingParsers'], + queryParsers: $['QueryParsers'], + fallThroughTargetingParsers: $['FallThroughTargetingParsers'], ) { this.#fallThroughTargetingParsers = Object.freeze( fallThroughTargetingParsers, @@ -106,51 +104,46 @@ export default class Data< } get data(): DataItemsOut< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > { return this.#data } - get payloadParsers(): PayloadParsers { + get payloadParsers(): $['PayloadParsers'] { return this.#payloadParsers } get targetingPredicates(): TargetingPredicates< - TargetingParsers, - QueryParsers + $['TargetingParsers'], + $['QueryParsers'] > { 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 - > + Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }> > { - type NewPayloadParsers = PayloadParsers & Parsers + type NewPayloadParsers = $['PayloadParsers'] & Parsers const payloadParsers = this.#mergePayloadParsers(parsers) @@ -160,16 +153,11 @@ export default class Data< this.#fallThroughTargetingParsers, ).parseAsync(this.#data)) as DataItemsOut< NewPayloadParsers, - TargetingParsers, - FallThroughTargetingParsers + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > - return new Data< - NewPayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - >( + return new Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }>( data, payloadParsers, this.#targetingPredicates, @@ -181,7 +169,7 @@ export default class Data< #mergePayloadParsers( parsers: Parsers, - ): PayloadParsers & Parsers { + ): $['PayloadParsers'] & Parsers { return { ...this.#payloadParsers, ...parsers, @@ -190,18 +178,11 @@ export default class Data< async insert( data: DT.InsertableData< - PayloadParsers, - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - ): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - > { + ): Promise> { const newData = { ...this.#data, ...(await DataItemsParser( @@ -248,39 +229,34 @@ export default class Data< ) } - readonly #isFallThroughRulesPayload = ( + readonly #isFallThroughRulesPayload = < + Name extends keyof $['PayloadParsers'], + >( payload: PT.Payload< - PayloadParsers[Name], - TargetingParsers | FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'] | $['FallThroughTargetingParsers'] >, ): payload is FTTT.Rules< - PayloadParsers[Name], - TargetingParsers | FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'] | $['FallThroughTargetingParsers'] > => 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 + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > | DataItemRulesIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - ): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - > { + ): Promise> { const dataItem = this.#data[name] || { rules: [], @@ -318,12 +294,7 @@ export default class Data< ) } - removeAllRules(): Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + removeAllRules(): Data<$> { return new Data( {} as any, this.#payloadParsers, @@ -338,15 +309,15 @@ export default class Data< 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 NewTargetingParsers = $['TargetingParsers'] & TT.ParserRecord - type NewQueryParsers = QueryParsers & QT.ParserRecord + type NewQueryParsers = $['QueryParsers'] & QT.ParserRecord const targetingParsers: NewTargetingParsers = this.#mergeTargetingParsers( targeting, @@ -363,16 +334,16 @@ export default class Data< targetingParsers, this.#fallThroughTargetingParsers, ).parseAsync(this.#data) as DataItemsOut< - PayloadParsers, + $['PayloadParsers'], NewTargetingParsers, - FallThroughTargetingParsers + $['FallThroughTargetingParsers'] > return new Data< - PayloadParsers, - NewTargetingParsers, - NewQueryParsers, - FallThroughTargetingParsers + $ & { + TargetingParsers: $['TargetingParsers'] & TT.ParserRecord + QueryParsers: $['QueryParsers'] & QT.ParserRecord + } >( data, this.#payloadParsers, @@ -385,7 +356,7 @@ export default class Data< #mergeTargetingParsers( targeting: TDs, - ): TargetingParsers & TT.ParserRecord { + ): $['TargetingParsers'] & TT.ParserRecord { return { ...this.targetingParsers, ...objectMap(targeting, ({ targetingParser }) => targetingParser), @@ -406,7 +377,7 @@ export default class Data< #mergeQueryPredicates( targeting: TDs, - ): QueryParsers & QT.ParserRecord { + ): $['QueryParsers'] & QT.ParserRecord { return { ...this.#queryParsers, ...objectMap(targeting, ({ queryParser }) => queryParser), @@ -417,14 +388,15 @@ export default class Data< targeting: TDs, ): Promise< Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers & FTTT.ParsersRecord + $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } > > { type NewFallThroughTargetingParsers = - & FallThroughTargetingParsers + & $['FallThroughTargetingParsers'] & FTTT.ParsersRecord const fallThroughTargetingParsers = this.#mergeFallThroughTargeting( @@ -436,16 +408,17 @@ export default class Data< this.#targetingParsers, fallThroughTargetingParsers, ).parseAsync(this.#data)) as DataItemsOut< - PayloadParsers, - TargetingParsers, + $['PayloadParsers'], + $['TargetingParsers'], NewFallThroughTargetingParsers > return new Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - NewFallThroughTargetingParsers + $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } >( data, this.#payloadParsers, @@ -458,7 +431,7 @@ export default class Data< #mergeFallThroughTargeting( targeting: TDs, - ): FallThroughTargetingParsers & FTTT.ParsersRecord { + ): $['FallThroughTargetingParsers'] & FTTT.ParsersRecord { return { ...this.#fallThroughTargetingParsers, ...objectMap( @@ -472,13 +445,13 @@ export default class Data< } async getPayloadForEachName( - rawQuery: QT.Raw = {}, + rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - PT.Payloads + PT.Payloads<$['PayloadParsers'], $['FallThroughTargetingParsers']> > { const payloads = {} as PT.Payloads< - PayloadParsers, - FallThroughTargetingParsers + $['PayloadParsers'], + $['FallThroughTargetingParsers'] > await Promise.all( @@ -490,16 +463,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], $['FallThroughTargetingParsers']> + | undefined > { const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) let payload: - | PT.Payload + | PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']> | undefined for (const rule of targetableItem.rules) { @@ -538,17 +512,17 @@ export default class Data< : resolvedPayload } - async #getVariables( + async #getVariables( targetableItem: DataItemOut< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, predicate: ( rule: DataItemRule< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, ) => Promise, ) { @@ -568,13 +542,15 @@ export default class Data< return variables } - async getPayloads( + async getPayloads( name: Name, - rawQuery: QT.Raw = {}, - ): Promise[]> { + rawQuery: QT.Raw<$['QueryParsers']> = {}, + ): Promise< + PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']>[] + > { const payloads: PT.Payload< - PayloadParsers[Name], - FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['FallThroughTargetingParsers'] >[] = [] const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) @@ -593,30 +569,30 @@ export default class Data< #mapRule( rule: DataItemRule< PayloadParser, - TargetingParsers, - FallThroughTargetingParsers + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - ): PT.Payload | undefined { + ): PT.Payload | undefined { return hasPayload(rule) ? rule.payload as output : 'fallThrough' in rule ? { __rules__: rule.fallThrough } as FTTT.Rules< PayloadParser, - FallThroughTargetingParsers + $['FallThroughTargetingParsers'] > : 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 + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, ) => ( @@ -641,14 +617,14 @@ 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], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > } )[name] ?? { rules: [], variables: {} } @@ -657,12 +633,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/PromisedData.ts b/packages/api/src/PromisedData.ts index 626d747d7..526bfba38 100644 --- a/packages/api/src/PromisedData.ts +++ b/packages/api/src/PromisedData.ts @@ -9,147 +9,72 @@ import type { DataItemRulesIn } from './parsers/DataItemRules.ts' import type { MaybePromise } from './types.ts' import type { DataItemIn } from './parsers/DataItem.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> { 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( + 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 + $['PayloadParsers'], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + ): 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 + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > | DataItemRulesIn< - PayloadParsers[Name], - TargetingParsers, - FallThroughTargetingParsers + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] >, - ): PromisedData< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > { + ): 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 +82,36 @@ 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<$['PayloadParsers'], $['FallThroughTargetingParsers']> > { 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], $['FallThroughTargetingParsers']> + | undefined > { const data = await this return data.getPayload(name, rawQuery) } - removeAllRules(): Promise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - > { + removeAllRules(): Promise> { return this.#create((data) => data.removeAllRules()) } } diff --git a/packages/api/src/types/Data.ts b/packages/api/src/types/Data.ts index 9573dce67..d2207ea4e 100644 --- a/packages/api/src/types/Data.ts +++ b/packages/api/src/types/Data.ts @@ -2,45 +2,71 @@ import type Data from '../Data.ts' import type * as FTTT from './FallThroughTargeting.ts' import type { $ZodShape, $ZodType, 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 +export type FallThroughTargetingParsers = D extends Data + ? $['FallThroughTargetingParsers'] + : 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 FallThrough = Data<{ + PayloadParsers: PayloadParsers + TargetingParsers: { [K in keyof FallThroughTargetingParsers]: $ZodType } + QueryParsers: Omit, keyof TargetingParsers> + FallThroughTargetingParsers: {} +}> /** * A union of possible payloads for a Data's PayloadParser diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 09559e6fa..5d5b744f0 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], $['FallThroughTargetingParsers']> + | void > { const query = this.#data.QueryParser.parse(rawQuery) const urlSearchParams = queryToURLSearchParams(query) @@ -78,12 +63,15 @@ 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], + $['FallThroughTargetingParsers'] + > | undefined } > @@ -99,11 +87,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 354608a8e..db1593a83 100644 --- a/packages/server/src/middleware/castQueryArrayProps.ts +++ b/packages/server/src/middleware/castQueryArrayProps.ts @@ -1,28 +1,17 @@ -import type { Data } from '@targetd/api' +import type { Data, 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, + $ extends DT.Meta, P extends Record, ResBody, ReqBody, ReqQuery, Locals extends { query: ParsedQs }, >( - getData: () => MaybePromise< - Data< - PayloadParsers, - TargetingParsers, - QueryParsers, - FallThroughTargetingParsers - > - >, + getData: () => MaybePromise>, ): RequestHandler { return async (_req, res, next) => { const { queryParsers } = await getData() From 28997bb4969ee7d27ee2ac12b6d7a9226d426f5a Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 11:16:25 +0100 Subject: [PATCH 03/11] refactor: meta data typing Prior to the Meta ($) type, the Data class was difficult to maintain. Everytime a new feature was added every reference needed to change. We now contain all the Data generics in an interface. BREAKING CHANGE: Data generics have changed. --- packages/api/src/Data.ts | 98 ++++++------------- packages/api/src/parsers/DataItems.ts | 61 +++++------- .../api/src/parsers/TargetingPredicates.ts | 20 ++-- 3 files changed, 60 insertions(+), 119 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 56bdf3ae0..9887a3a23 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -59,16 +59,9 @@ import { resolveVariables } from './parsers/DataItemVariableResolver.ts' */ export default class Data<$ extends DT.Meta> { readonly #fallThroughTargetingParsers: $['FallThroughTargetingParsers'] - readonly #data: DataItemsOut< - $['PayloadParsers'], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > + readonly #data: DataItemsOut<$> readonly #payloadParsers: $['PayloadParsers'] - readonly #targetingPredicates: TargetingPredicates< - $['TargetingParsers'], - $['QueryParsers'] - > + readonly #targetingPredicates: TargetingPredicates<$> readonly #targetingParsers: $['TargetingParsers'] readonly #queryParsers: $['QueryParsers'] readonly #QueryParser: ZodPartialObject<$['QueryParsers']> @@ -78,16 +71,9 @@ export default class Data<$ extends DT.Meta> { } private constructor( - data: DataItemsOut< - $['PayloadParsers'], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + data: DataItemsOut<$>, payloadParsers: $['PayloadParsers'], - targetingPredicates: TargetingPredicates< - $['TargetingParsers'], - $['QueryParsers'] - >, + targetingPredicates: TargetingPredicates<$>, targetingParsers: $['TargetingParsers'], queryParsers: $['QueryParsers'], fallThroughTargetingParsers: $['FallThroughTargetingParsers'], @@ -103,11 +89,7 @@ export default class Data<$ extends DT.Meta> { this.#QueryParser = partial(strictObject(this.#queryParsers)) } - get data(): DataItemsOut< - $['PayloadParsers'], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > { + get data(): DataItemsOut<$> { return this.#data } @@ -115,10 +97,7 @@ export default class Data<$ extends DT.Meta> { return this.#payloadParsers } - get targetingPredicates(): TargetingPredicates< - $['TargetingParsers'], - $['QueryParsers'] - > { + get targetingPredicates(): TargetingPredicates<$> { return this.#targetingPredicates } @@ -143,7 +122,7 @@ export default class Data<$ extends DT.Meta> { ): Promise< Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }> > { - type NewPayloadParsers = $['PayloadParsers'] & Parsers + type $$ = $ & { PayloadParsers: $['PayloadParsers'] & Parsers } const payloadParsers = this.#mergePayloadParsers(parsers) @@ -151,13 +130,9 @@ export default class Data<$ extends DT.Meta> { payloadParsers, this.#targetingParsers, this.#fallThroughTargetingParsers, - ).parseAsync(this.#data)) as DataItemsOut< - NewPayloadParsers, - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > + ).parseAsync(this.#data)) as DataItemsOut<$$> - return new Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }>( + return new Data<$$>( data, payloadParsers, this.#targetingPredicates, @@ -315,36 +290,31 @@ export default class Data<$ extends DT.Meta> { } > > { - 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< - $ & { - TargetingParsers: $['TargetingParsers'] & TT.ParserRecord - QueryParsers: $['QueryParsers'] & QT.ParserRecord - } - >( + return new Data<$$>( data, this.#payloadParsers, targetingPredicates, @@ -395,9 +365,11 @@ export default class Data<$ extends DT.Meta> { } > > { - type NewFallThroughTargetingParsers = - & $['FallThroughTargetingParsers'] - & FTTT.ParsersRecord + type $$ = $ & { + FallThroughTargetingParsers: + & $['FallThroughTargetingParsers'] + & FTTT.ParsersRecord + } const fallThroughTargetingParsers = this.#mergeFallThroughTargeting( targeting, @@ -407,19 +379,9 @@ export default class Data<$ extends DT.Meta> { this.#payloadParsers, this.#targetingParsers, fallThroughTargetingParsers, - ).parseAsync(this.#data)) as DataItemsOut< - $['PayloadParsers'], - $['TargetingParsers'], - NewFallThroughTargetingParsers - > + ).parseAsync(this.#data)) as DataItemsOut<$$> - return new Data< - $ & { - FallThroughTargetingParsers: - & $['FallThroughTargetingParsers'] - & FTTT.ParsersRecord - } - >( + return new Data<$$>( data, this.#payloadParsers, this.#targetingPredicates, diff --git a/packages/api/src/parsers/DataItems.ts b/packages/api/src/parsers/DataItems.ts index aa72b8493..82d37f424 100644 --- a/packages/api/src/parsers/DataItems.ts +++ b/packages/api/src/parsers/DataItems.ts @@ -1,3 +1,4 @@ +import type * as DT from '../types/Data.ts' import type { ZodPartialObject } from '../types.ts' import { type DataItemIn, @@ -5,7 +6,7 @@ import { 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. @@ -29,16 +30,12 @@ import type { $strict, $ZodShape } from 'zod/v4/core' * ) * ``` */ -export function DataItemsParser< - D extends $ZodShape, - T extends $ZodShape, - CT extends $ZodShape, ->( - payloadParsers: D, - targeting: T, - fallThroughTargeting: CT, +export function DataItemsParser<$ extends DT.Meta>( + payloadParsers: $['PayloadParsers'], + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strict = true, -): DataItemsParser { +): DataItemsParser<$> { const dataItems: Record = {} for (const [key, Payload] of Object.entries(payloadParsers)) { dataItems[key] = DataItemParser( @@ -50,44 +47,32 @@ 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, -> = ZodPartialObject< +export type DataItemsParser<$ extends DT.Meta> = ZodPartialObject< { - [Name in keyof DataTypes]: DataItemParser< - DataTypes[Name], - Targeting, - FallThroughTargeting + [Name in keyof $['PayloadParsers']]: DataItemParser< + $['PayloadParsers'][Name], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > }, $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], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > } -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], + $['TargetingParsers'], + $['FallThroughTargetingParsers'] > } 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 } From a8d1d17b23c6e5321e5701489ca40ec6be6b8146 Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 12:28:11 +0100 Subject: [PATCH 04/11] refactor: meta data typing Prior to the Meta ($) type, the Data class was difficult to maintain. Everytime a new feature was added every reference needed to change. We now contain all the Data generics in an interface. BREAKING CHANGE: Data generics have changed. --- packages/api/src/Data.ts | 69 ++++--------- packages/api/src/PromisedData.ts | 24 +---- packages/api/src/parsers/DataItem.ts | 54 +++++------ packages/api/src/parsers/DataItemRules.ts | 97 +++++++++++++------ .../src/parsers/DataItemVariablesParser.ts | 23 ++--- packages/api/src/parsers/DataItems.ts | 37 +++---- packages/api/src/types.ts | 2 + packages/api/src/types/Data.ts | 43 ++++---- .../api/src/types/FallThroughTargeting.ts | 15 ++- packages/api/src/types/Payload.ts | 20 ++-- packages/client/src/index.ts | 7 +- 11 files changed, 188 insertions(+), 203 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 9887a3a23..181f9c3f2 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -151,13 +151,7 @@ export default class Data<$ extends DT.Meta> { } } - async insert( - data: DT.InsertableData< - $['PayloadParsers'], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, - ): Promise> { + async insert(data: DT.InsertableData<$>): Promise> { const newData = { ...this.#data, ...(await DataItemsParser( @@ -207,30 +201,17 @@ export default class Data<$ extends DT.Meta> { readonly #isFallThroughRulesPayload = < Name extends keyof $['PayloadParsers'], >( - payload: PT.Payload< - $['PayloadParsers'][Name], - $['TargetingParsers'] | $['FallThroughTargetingParsers'] - >, - ): payload is FTTT.Rules< - $['PayloadParsers'][Name], - $['TargetingParsers'] | $['FallThroughTargetingParsers'] - > => typeof payload === 'object' && payload !== null && '__rules__' in payload + 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: Name, opts: - | DataItemIn< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > - | DataItemRulesIn< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + | DataItemIn<$, $['PayloadParsers'][Name]> + | DataItemRulesIn<$, $['PayloadParsers'][Name]>, ): Promise> { const dataItem = this.#data[name] || { @@ -408,13 +389,8 @@ export default class Data<$ extends DT.Meta> { async getPayloadForEachName( rawQuery: QT.Raw<$['QueryParsers']> = {}, - ): Promise< - PT.Payloads<$['PayloadParsers'], $['FallThroughTargetingParsers']> - > { - const payloads = {} as PT.Payloads< - $['PayloadParsers'], - $['FallThroughTargetingParsers'] - > + ): Promise> { + const payloads = {} as PT.Payloads<$> await Promise.all( objectKeys(this.#data).map(async (name) => { @@ -429,13 +405,13 @@ export default class Data<$ extends DT.Meta> { name: Name, rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - | PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']> + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined > { const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) let payload: - | PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']> + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined for (const rule of targetableItem.rules) { @@ -475,11 +451,7 @@ export default class Data<$ extends DT.Meta> { } async #getVariables( - targetableItem: DataItemOut< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + targetableItem: DataItemOut<$, $['PayloadParsers'][Name]>, predicate: ( rule: DataItemRule< $['PayloadParsers'][Name], @@ -508,12 +480,9 @@ export default class Data<$ extends DT.Meta> { name: Name, rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']>[] + PT.Payload<$, $['PayloadParsers'][Name]>[] > { - const payloads: PT.Payload< - $['PayloadParsers'][Name], - $['FallThroughTargetingParsers'] - >[] = [] + const payloads: PT.Payload<$, $['PayloadParsers'][Name]>[] = [] const predicate = await this.#createRulePredicate(rawQuery) const targetableItem = this.#getTargetableItem(name) for (const rule of targetableItem.rules) { @@ -534,14 +503,11 @@ export default class Data<$ extends DT.Meta> { $['TargetingParsers'], $['FallThroughTargetingParsers'] >, - ): PT.Payload | undefined { + ): 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 } @@ -584,9 +550,8 @@ export default class Data<$ extends DT.Meta> { ( this.#data as unknown as { [Name in keyof $['PayloadParsers']]: DataItemOut< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] + $, + $['PayloadParsers'][Name] > } )[name] ?? { rules: [], variables: {} } diff --git a/packages/api/src/PromisedData.ts b/packages/api/src/PromisedData.ts index 526bfba38..489c9e222 100644 --- a/packages/api/src/PromisedData.ts +++ b/packages/api/src/PromisedData.ts @@ -39,13 +39,7 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { return this.#create((data) => data.usePayload(parsers)) } - insert( - insertableData: DT.InsertableData< - $['PayloadParsers'], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, - ): PromisedData<$> { + insert(insertableData: DT.InsertableData<$>): PromisedData<$> { return this.#create((data) => data.insert(insertableData)) } @@ -54,16 +48,8 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { >( name: Name, opts: - | DataItemIn< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > - | DataItemRulesIn< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + | DataItemIn<$, $['PayloadParsers'][Name]> + | DataItemRulesIn<$, $['PayloadParsers'][Name]>, ): PromisedData<$> { return this.#create((data) => data.addRules(name, opts)) } @@ -94,7 +80,7 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { async getPayloadForEachName( rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - PT.Payloads<$['PayloadParsers'], $['FallThroughTargetingParsers']> + PT.Payloads<$> > { const data = await this return data.getPayloadForEachName(rawQuery) @@ -104,7 +90,7 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { name: Name, rawQuery: QT.Raw<$['QueryParsers']> = {}, ): Promise< - | PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']> + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined > { const data = await this diff --git a/packages/api/src/parsers/DataItem.ts b/packages/api/src/parsers/DataItem.ts index d1f1dcb9e..acadeffa6 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,12 @@ import { } from './DataItemRules.ts' import { DataItemVariablesParser } from './DataItemVariablesParser.ts' import { variablesFor } from './variablesRegistry.ts' +import type * as DT from '../types/Data.ts' + +type Meta = Pick< + DT.Meta, + 'TargetingParsers' | 'FallThroughTargetingParsers' +> /** * Parses an "item". @@ -24,15 +30,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 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 +60,12 @@ export function DataItemParser< } export type DataItemParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends Meta, + PayloadParser extends $ZodType, > = ZodMiniObject< { - rules: DataItemRulesParser< - Payload, - Targeting, - FallThroughTargeting - > - variables: ZodMiniDefault< - DataItemVariablesParser - > + rules: DataItemRulesParser<$, PayloadParser> + variables: ZodMiniDefault> }, $strict > @@ -76,14 +74,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 Meta, + PayloadParser extends $ZodType, > { - rules: DataItemRulesIn + rules: DataItemRulesIn<$, PayloadParser> variables?: Record< string, - DataItemRulesIn + DataItemRulesIn<$, ZodMiniAny> > } @@ -91,13 +88,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 Meta, + PayloadParser extends $ZodType, > { - rules: DataItemRulesOut + rules: DataItemRulesOut<$, PayloadParser> variables: Record< string, - DataItemRulesOut + DataItemRulesOut<$, ZodMiniAny> > } diff --git a/packages/api/src/parsers/DataItemRules.ts b/packages/api/src/parsers/DataItemRules.ts index 54ba62183..e9faa3ca9 100644 --- a/packages/api/src/parsers/DataItemRules.ts +++ b/packages/api/src/parsers/DataItemRules.ts @@ -21,8 +21,14 @@ 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' +type Meta = Pick< + DT.Meta, + 'TargetingParsers' | 'FallThroughTargetingParsers' +> + /** * Parses an item's `rules` field. * @@ -58,28 +64,45 @@ import type { VariablesRegistry } from './variablesRegistry.ts' * ``` */ export function DataItemRulesParser< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends 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, + $['TargetingParsers'] & $['FallThroughTargetingParsers'] + >(variablesRegistry, payloadParser, { + ...targetingParsers, + ...fallThroughTargetingParsers, + }, strictTargeting), + ), transform( ( - rules: RuleWithPayloadIn[], - ): DataItemRule[] => { + rules: RuleWithPayloadIn< + PayloadParser, + $['TargetingParsers'] & $['FallThroughTargetingParsers'] + >[], + ): DataItemRule< + PayloadParser, + $['TargetingParsers'], + $['FallThroughTargetingParsers'], + false + >[] => { const singularTargetedRules = spreadMultiTargetsToSeparateRules(rules) - let transformedRules: DataItemRule[] = [] + let transformedRules: DataItemRule< + PayloadParser, + $['TargetingParsers'], + $['FallThroughTargetingParsers'], + false + >[] = [] for (const rule of singularTargetedRules) { const prevRule = arrayLast(transformedRules) @@ -107,7 +130,12 @@ export function DataItemRulesParser< const adaptedRule = adaptRuleIntoFallThroughRule( targetingParsers, fallThroughTargetingParsers, - rule as DataItemRule, + rule as DataItemRule< + PayloadParser, + $['TargetingParsers'], + $['FallThroughTargetingParsers'], + false + >, ) adaptedPrevRule.fallThrough.push(...adaptedRule.fallThrough) @@ -130,30 +158,41 @@ export function DataItemRulesParser< } export type DataItemRulesParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends Meta, + PayloadParser extends $ZodType, > = ZodMiniPipe< ZodMiniArray< - RuleWithPayloadParser + RuleWithPayloadParser< + PayloadParser, + $['TargetingParsers'] & $['FallThroughTargetingParsers'] + > >, ZodMiniTransform< - DataItemRulesOut, - DataItemRulesIn + DataItemRulesOut<$, PayloadParser>, + DataItemRulesIn<$, PayloadParser> > > export type DataItemRulesIn< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = RuleWithPayloadIn[] + $ extends Meta, + PayloadParser extends $ZodType, +> = RuleWithPayloadIn< + PayloadParser, + $['TargetingParsers'] & $['FallThroughTargetingParsers'] +>[] export type DataItemRulesOut< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = DataItemRule[] + $ extends Pick< + DT.Meta, + 'TargetingParsers' | 'FallThroughTargetingParsers' + >, + PayloadParser extends $ZodType, +> = DataItemRule< + PayloadParser, + $['TargetingParsers'], + $['FallThroughTargetingParsers'], + false +>[] function spreadMultiTargetsToSeparateRules< P extends $ZodType, diff --git a/packages/api/src/parsers/DataItemVariablesParser.ts b/packages/api/src/parsers/DataItemVariablesParser.ts index 8028691a8..6dcae1e38 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,16 @@ 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, ->( +type Meta = Pick + +export function DataItemVariablesParser<$ extends Meta>( variablesRegistry: VariablesRegistry, - targeting: T, - fallThroughTargeting: FTT, + targeting: $['TargetingParsers'], + fallThroughTargeting: $['FallThroughTargetingParsers'], strictTargeting: boolean, -): DataItemVariablesParser { +): DataItemVariablesParser<$> { return record( string(), DataItemRulesParser( @@ -89,10 +89,7 @@ export function DataItemVariablesParser< }) } -export type DataItemVariablesParser< - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, -> = ZodMiniRecord< +export type DataItemVariablesParser<$ extends Meta> = ZodMiniRecord< ZodMiniString, - DataItemRulesParser + DataItemRulesParser<$, ZodMiniAny> > diff --git a/packages/api/src/parsers/DataItems.ts b/packages/api/src/parsers/DataItems.ts index 82d37f424..5875ca44f 100644 --- a/packages/api/src/parsers/DataItems.ts +++ b/packages/api/src/parsers/DataItems.ts @@ -1,5 +1,5 @@ import type * as DT from '../types/Data.ts' -import type { ZodPartialObject } from '../types.ts' +import type { ObjValues, ZodPartialObject } from '../types.ts' import { type DataItemIn, type DataItemOut, @@ -8,6 +8,11 @@ import { import { object, partial, strictObject } from 'zod/mini' import type { $strict } from 'zod/v4/core' +type Meta = Pick< + DT.Meta, + 'PayloadParsers' | 'TargetingParsers' | 'FallThroughTargetingParsers' +> + /** * Parses all items in a Data class. * @@ -30,7 +35,9 @@ import type { $strict } from 'zod/v4/core' * ) * ``` */ -export function DataItemsParser<$ extends DT.Meta>( +export function DataItemsParser< + $ extends Meta, +>( payloadParsers: $['PayloadParsers'], targeting: $['TargetingParsers'], fallThroughTargeting: $['FallThroughTargetingParsers'], @@ -39,7 +46,7 @@ export function DataItemsParser<$ extends DT.Meta>( const dataItems: Record = {} for (const [key, Payload] of Object.entries(payloadParsers)) { dataItems[key] = DataItemParser( - Payload, + Payload as ObjValues<$['PayloadParsers']>, targeting, fallThroughTargeting, strict, @@ -50,29 +57,25 @@ export function DataItemsParser<$ extends DT.Meta>( ) as DataItemsParser<$> } -export type DataItemsParser<$ extends DT.Meta> = ZodPartialObject< +export type DataItemsParser< + $ extends Meta, +> = ZodPartialObject< { [Name in keyof $['PayloadParsers']]: DataItemParser< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] + $, + $['PayloadParsers'][Name] > }, $strict > -export type DataItemsIn<$ extends DT.Meta> = { - [Name in keyof $['PayloadParsers']]?: DataItemIn< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - > +export type DataItemsIn<$ extends Meta> = { + [Name in keyof $['PayloadParsers']]?: DataItemIn<$, $['PayloadParsers'][Name]> } -export type DataItemsOut<$ extends DT.Meta> = { +export type DataItemsOut<$ extends Meta> = { [Name in keyof $['PayloadParsers']]?: DataItemOut< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] + $, + $['PayloadParsers'][Name] > } 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 d2207ea4e..46d89f17e 100644 --- a/packages/api/src/types/Data.ts +++ b/packages/api/src/types/Data.ts @@ -1,6 +1,6 @@ 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 @@ -58,32 +58,25 @@ export type FallThroughTargetingParsers = D extends Data ? $['FallThroughTargetingParsers'] : never -/** - * Create the fall through Data type from a Data type - */ -export type FallThrough = Data<{ - PayloadParsers: PayloadParsers - TargetingParsers: { [K in keyof FallThroughTargetingParsers]: $ZodType } - QueryParsers: Omit, keyof TargetingParsers> - FallThroughTargetingParsers: {} -}> - -/** - * 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, + $ extends Pick< + Meta, + 'PayloadParsers' | 'TargetingParsers' | 'FallThroughTargetingParsers' + >, > = 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..3ff60491d 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 Pick, + 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..c75c55334 100644 --- a/packages/api/src/types/Payload.ts +++ b/packages/api/src/types/Payload.ts @@ -1,16 +1,16 @@ -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 Pick, + PayloadParser extends $ZodType, +> = + | output + | FTTT.Rules<$, PayloadParser> export type Payloads< - PayloadParsers extends $ZodShape, - FallThroughTargetingParsers extends $ZodShape, + $ extends Pick, > = { - [Name in keyof PayloadParsers]?: Payload< - PayloadParsers[Name], - FallThroughTargetingParsers - > + [Name in keyof $['PayloadParsers']]?: Payload<$, $['PayloadParsers'][Name]> } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 5d5b744f0..81a4f8a88 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -24,7 +24,7 @@ export class Client<$ extends DT.Meta> { name: Name, rawQuery: Partial> = {}, ): Promise< - | PT.Payload<$['PayloadParsers'][Name], $['FallThroughTargetingParsers']> + | PT.Payload<$, $['PayloadParsers'][Name]> | void > { const query = this.#data.QueryParser.parse(rawQuery) @@ -68,10 +68,7 @@ export class Client<$ extends DT.Meta> { Partial< { [Name in keyof $['PayloadParsers']]: - | PT.Payload< - $['PayloadParsers'][Name], - $['FallThroughTargetingParsers'] - > + | PT.Payload<$, $['PayloadParsers'][Name]> | undefined } > From 5513fe3b089cdcf56de958b35a988ddc8d4d2b07 Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 14:44:18 +0100 Subject: [PATCH 05/11] refactor: meta data typing Prior to the Meta ($) type, the Data class was difficult to maintain. Everytime a new feature was added every reference needed to change. We now contain all the Data generics in an interface. BREAKING CHANGE: Data generics have changed. --- packages/api/src/Data.ts | 18 +-- packages/api/src/parsers/DataItemRule.ts | 144 ++++++++++------------ packages/api/src/parsers/DataItemRules.ts | 103 ++++++---------- 3 files changed, 104 insertions(+), 161 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 181f9c3f2..4c35afc8d 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -453,11 +453,7 @@ export default class Data<$ extends DT.Meta> { async #getVariables( targetableItem: DataItemOut<$, $['PayloadParsers'][Name]>, predicate: ( - rule: DataItemRule< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + rule: DataItemRule<$, $['PayloadParsers'][Name]>, ) => Promise, ) { const variables: Record = {} @@ -498,11 +494,7 @@ export default class Data<$ extends DT.Meta> { } #mapRule( - rule: DataItemRule< - PayloadParser, - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + rule: DataItemRule<$, PayloadParser>, ): PT.Payload<$, PayloadParser> | undefined { return hasPayload(rule) ? rule.payload as output @@ -517,11 +509,7 @@ export default class Data<$ extends DT.Meta> { const query = await this.#QueryParser.parseAsync(rawQuery) return ( - rule: DataItemRule< - $['PayloadParsers'][Name], - $['TargetingParsers'], - $['FallThroughTargetingParsers'] - >, + rule: DataItemRule<$, $['PayloadParsers'][Name]>, ) => ( !('targeting' in rule) || diff --git a/packages/api/src/parsers/DataItemRule.ts b/packages/api/src/parsers/DataItemRule.ts index f241ff301..08dea4ebc 100644 --- a/packages/api/src/parsers/DataItemRule.ts +++ b/packages/api/src/parsers/DataItemRule.ts @@ -17,6 +17,9 @@ import { type RecursiveVariableResolver, } from './attachVariableResolver.ts' import type { VariablesRegistry } from './variablesRegistry.ts' +import type * as DT from '../types/Data.ts' + +type Meta = Pick /** * Parses a single item rule. @@ -88,21 +91,19 @@ import type { VariablesRegistry } from './variablesRegistry.ts' * ``` */ export function DataItemRuleParser< - P extends $ZodType, - T extends $ZodShape, - CT extends $ZodShape, - V extends Record, + $ extends 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 +122,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 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 +143,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 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 +182,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 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 +215,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 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 +253,20 @@ export interface RuleWithPayload< } export type RuleWithFallThroughParser< - Payload extends $ZodType, - Targeting extends $ZodShape, - FallThroughTargeting extends $ZodShape, + $ extends 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 +278,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 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 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 +321,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 e9faa3ca9..232324a13 100644 --- a/packages/api/src/parsers/DataItemRules.ts +++ b/packages/api/src/parsers/DataItemRules.ts @@ -75,34 +75,27 @@ export function DataItemRulesParser< ): DataItemRulesParser<$, PayloadParser> { return pipe( array( - RuleWithPayloadParser< - PayloadParser, - $['TargetingParsers'] & $['FallThroughTargetingParsers'] - >(variablesRegistry, payloadParser, { - ...targetingParsers, - ...fallThroughTargetingParsers, - }, strictTargeting), + RuleWithPayloadParser<$, PayloadParser>( + variablesRegistry, + payloadParser, + { + ...targetingParsers, + ...fallThroughTargetingParsers, + }, + strictTargeting, + ), ), transform( ( - rules: RuleWithPayloadIn< - PayloadParser, - $['TargetingParsers'] & $['FallThroughTargetingParsers'] - >[], + rules: RuleWithPayloadIn<$, PayloadParser>[], ): DataItemRule< + $, PayloadParser, - $['TargetingParsers'], - $['FallThroughTargetingParsers'], false >[] => { const singularTargetedRules = spreadMultiTargetsToSeparateRules(rules) - let transformedRules: DataItemRule< - PayloadParser, - $['TargetingParsers'], - $['FallThroughTargetingParsers'], - false - >[] = [] + let transformedRules: DataItemRule<$, PayloadParser, false>[] = [] for (const rule of singularTargetedRules) { const prevRule = arrayLast(transformedRules) @@ -130,12 +123,7 @@ export function DataItemRulesParser< const adaptedRule = adaptRuleIntoFallThroughRule( targetingParsers, fallThroughTargetingParsers, - rule as DataItemRule< - PayloadParser, - $['TargetingParsers'], - $['FallThroughTargetingParsers'], - false - >, + rule as DataItemRule<$, PayloadParser, false>, ) adaptedPrevRule.fallThrough.push(...adaptedRule.fallThrough) @@ -161,12 +149,7 @@ export type DataItemRulesParser< $ extends Meta, PayloadParser extends $ZodType, > = ZodMiniPipe< - ZodMiniArray< - RuleWithPayloadParser< - PayloadParser, - $['TargetingParsers'] & $['FallThroughTargetingParsers'] - > - >, + ZodMiniArray>, ZodMiniTransform< DataItemRulesOut<$, PayloadParser>, DataItemRulesIn<$, PayloadParser> @@ -176,10 +159,7 @@ export type DataItemRulesParser< export type DataItemRulesIn< $ extends Meta, PayloadParser extends $ZodType, -> = RuleWithPayloadIn< - PayloadParser, - $['TargetingParsers'] & $['FallThroughTargetingParsers'] ->[] +> = RuleWithPayloadIn<$, PayloadParser>[] export type DataItemRulesOut< $ extends Pick< @@ -187,18 +167,13 @@ export type DataItemRulesOut< 'TargetingParsers' | 'FallThroughTargetingParsers' >, PayloadParser extends $ZodType, -> = DataItemRule< - PayloadParser, - $['TargetingParsers'], - $['FallThroughTargetingParsers'], - false ->[] +> = DataItemRule<$, PayloadParser, false>[] function spreadMultiTargetsToSeparateRules< - P extends $ZodType, - T extends $ZodShape, ->(rules: RuleWithPayloadIn[]) { - return rules.reduce[]>( + $ extends Meta, + PayloadParser extends $ZodType, +>(rules: RuleWithPayloadIn<$, PayloadParser>[]) { + return rules.reduce[]>( (rules, rule) => { if (Array.isArray(rule.targeting)) { for (const targeting of rule.targeting) { @@ -208,7 +183,7 @@ function spreadMultiTargetsToSeparateRules< }) } } else { - rules.push(rule as RuleWithPayloadIn) + rules.push(rule as RuleWithPayloadIn<$, PayloadParser, false>) } return rules }, @@ -218,8 +193,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) @@ -242,40 +217,38 @@ function canRulesCombine( } function adaptRule< - P extends $ZodType, - T extends $ZodShape, - FTT extends $ZodShape, + $ extends 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 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, @@ -285,5 +258,5 @@ function adaptRuleIntoFallThroughRule< ), }, ], - } as RuleWithFallThrough + } as RuleWithFallThrough<$, PayloadParser, false> } From 3d0d7783ead1e600b48cb8c78f152c6de6de6b29 Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 14:46:52 +0100 Subject: [PATCH 06/11] style: remove whitespace --- packages/api/src/Data.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 4c35afc8d..54d70f176 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -117,9 +117,7 @@ export default class Data<$ extends DT.Meta> { return this.#fallThroughTargetingParsers } - async usePayload( - parsers: Parsers, - ): Promise< + async usePayload(parsers: Parsers): Promise< Data<$ & { PayloadParsers: $['PayloadParsers'] & Parsers }> > { type $$ = $ & { PayloadParsers: $['PayloadParsers'] & Parsers } From 908e310bba36cd7195ef2311948bf8730c3676cb Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 14:58:15 +0100 Subject: [PATCH 07/11] refactor: remove unused generics --- packages/server/src/middleware/castQueryArrayProps.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/src/middleware/castQueryArrayProps.ts b/packages/server/src/middleware/castQueryArrayProps.ts index db1593a83..ce6a6938a 100644 --- a/packages/server/src/middleware/castQueryArrayProps.ts +++ b/packages/server/src/middleware/castQueryArrayProps.ts @@ -1,17 +1,16 @@ -import type { Data, DT } from '@targetd/api' +import type { DT } from '@targetd/api' import type { RequestHandler } from 'express' import type { MaybePromise } from '../types.ts' import type { ParsedQs } from './castQueryProp.ts' export function castQueryArrayProps< - $ extends DT.Meta, P extends Record, ResBody, ReqBody, ReqQuery, Locals extends { query: ParsedQs }, >( - getData: () => MaybePromise>, + getData: () => MaybePromise, ): RequestHandler { return async (_req, res, next) => { const { queryParsers } = await getData() From c3967585019a26b951ddbd7156f7f6d3e949453f Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 11 Oct 2025 15:03:00 +0100 Subject: [PATCH 08/11] refactor: dont narrow meta Keep things simple and use a full meta interface --- packages/api/src/parsers/DataItem.ts | 13 +++------- packages/api/src/parsers/DataItemRule.ts | 18 ++++++------- packages/api/src/parsers/DataItemRules.ts | 26 +++++++------------ .../src/parsers/DataItemVariablesParser.ts | 6 ++--- packages/api/src/parsers/DataItems.ts | 13 +++------- packages/api/src/types/Data.ts | 7 +---- .../api/src/types/FallThroughTargeting.ts | 2 +- packages/api/src/types/Payload.ts | 6 ++--- 8 files changed, 31 insertions(+), 60 deletions(-) diff --git a/packages/api/src/parsers/DataItem.ts b/packages/api/src/parsers/DataItem.ts index acadeffa6..3f9bcbeb8 100644 --- a/packages/api/src/parsers/DataItem.ts +++ b/packages/api/src/parsers/DataItem.ts @@ -15,11 +15,6 @@ import { DataItemVariablesParser } from './DataItemVariablesParser.ts' import { variablesFor } from './variablesRegistry.ts' import type * as DT from '../types/Data.ts' -type Meta = Pick< - DT.Meta, - 'TargetingParsers' | 'FallThroughTargetingParsers' -> - /** * Parses an "item". * @@ -30,7 +25,7 @@ type Meta = Pick< * Currently this is just restricted to `rules`. */ export function DataItemParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, >( Payload: PayloadParser, @@ -60,7 +55,7 @@ export function DataItemParser< } export type DataItemParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, > = ZodMiniObject< { @@ -74,7 +69,7 @@ export type DataItemParser< * The data shape expected for {@link DataItemParser} inputs. */ export interface DataItemIn< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, > { rules: DataItemRulesIn<$, PayloadParser> @@ -88,7 +83,7 @@ export interface DataItemIn< * The data shape expected for {@link DataItemParser} outputs. */ export interface DataItemOut< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, > { rules: DataItemRulesOut<$, PayloadParser> diff --git a/packages/api/src/parsers/DataItemRule.ts b/packages/api/src/parsers/DataItemRule.ts index 08dea4ebc..d30749817 100644 --- a/packages/api/src/parsers/DataItemRule.ts +++ b/packages/api/src/parsers/DataItemRule.ts @@ -19,8 +19,6 @@ import { import type { VariablesRegistry } from './variablesRegistry.ts' import type * as DT from '../types/Data.ts' -type Meta = Pick - /** * Parses a single item rule. * @@ -91,7 +89,7 @@ type Meta = Pick * ``` */ export function DataItemRuleParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, VariableRegistry extends Record, AllowMultipleTargeting extends boolean = true, @@ -129,7 +127,7 @@ export function DataItemRuleParser< } export type DataItemRuleParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = ZodMiniUnion< @@ -143,7 +141,7 @@ export type DataItemRuleParser< * @see https://github.com/colinhacks/zod/issues/4698 */ export type DataItemRule< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = @@ -198,7 +196,7 @@ export type RuleWithPayloadParser< > export function RuleWithPayloadParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, >( @@ -222,7 +220,7 @@ export function RuleWithPayloadParser< * @see https://github.com/colinhacks/zod/issues/4698 */ export interface RuleWithPayloadIn< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > { @@ -253,7 +251,7 @@ export interface RuleWithPayload< } export type RuleWithFallThroughParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > = ZodMiniObject< @@ -278,7 +276,7 @@ export type RuleWithFallThroughParser< * @see https://github.com/colinhacks/zod/issues/4698 */ export interface RuleWithFallThrough< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, AllowMultipleTargeting extends boolean = true, > { @@ -295,7 +293,7 @@ export interface RuleWithFallThrough< } export function RuleWithFallThroughParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, Variables extends Record, AllowMultipleTargeting extends boolean = true, diff --git a/packages/api/src/parsers/DataItemRules.ts b/packages/api/src/parsers/DataItemRules.ts index 232324a13..ec4374cc6 100644 --- a/packages/api/src/parsers/DataItemRules.ts +++ b/packages/api/src/parsers/DataItemRules.ts @@ -24,11 +24,6 @@ import type { ZodPartialInferObject } from '../types.ts' import type * as DT from '../types/Data.ts' import type { VariablesRegistry } from './variablesRegistry.ts' -type Meta = Pick< - DT.Meta, - 'TargetingParsers' | 'FallThroughTargetingParsers' -> - /** * Parses an item's `rules` field. * @@ -64,7 +59,7 @@ type Meta = Pick< * ``` */ export function DataItemRulesParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, >( variablesRegistry: VariablesRegistry, @@ -146,7 +141,7 @@ export function DataItemRulesParser< } export type DataItemRulesParser< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, > = ZodMiniPipe< ZodMiniArray>, @@ -157,20 +152,17 @@ export type DataItemRulesParser< > export type DataItemRulesIn< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, > = RuleWithPayloadIn<$, PayloadParser>[] export type DataItemRulesOut< - $ extends Pick< - DT.Meta, - 'TargetingParsers' | 'FallThroughTargetingParsers' - >, + $ extends DT.Meta, PayloadParser extends $ZodType, > = DataItemRule<$, PayloadParser, false>[] function spreadMultiTargetsToSeparateRules< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, >(rules: RuleWithPayloadIn<$, PayloadParser>[]) { return rules.reduce[]>( @@ -193,8 +185,8 @@ function spreadMultiTargetsToSeparateRules< function canRulesCombine( targetingParsers: $ZodShape, - a: DataItemRule, - b: DataItemRule, + a: DataItemRule, + b: DataItemRule, ) { const aTargeting = a.targeting ? intersection(a.targeting, targetingParsers) @@ -217,7 +209,7 @@ function canRulesCombine( } function adaptRule< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, >( targetingParsers: $['TargetingParsers'], @@ -236,7 +228,7 @@ function adaptRule< } function adaptRuleIntoFallThroughRule< - $ extends Meta, + $ extends DT.Meta, PayloadParser extends $ZodType, >( targetingParsers: $['TargetingParsers'], diff --git a/packages/api/src/parsers/DataItemVariablesParser.ts b/packages/api/src/parsers/DataItemVariablesParser.ts index 6dcae1e38..cf3cca7cb 100644 --- a/packages/api/src/parsers/DataItemVariablesParser.ts +++ b/packages/api/src/parsers/DataItemVariablesParser.ts @@ -12,9 +12,7 @@ import { DataItemRulesParser } from './DataItemRules.ts' import type { VariablesRegistry } from './variablesRegistry.ts' import type * as DT from '../types/Data.ts' -type Meta = Pick - -export function DataItemVariablesParser<$ extends Meta>( +export function DataItemVariablesParser<$ extends DT.Meta>( variablesRegistry: VariablesRegistry, targeting: $['TargetingParsers'], fallThroughTargeting: $['FallThroughTargetingParsers'], @@ -89,7 +87,7 @@ export function DataItemVariablesParser<$ extends Meta>( }) } -export type DataItemVariablesParser<$ extends Meta> = ZodMiniRecord< +export type DataItemVariablesParser<$ extends DT.Meta> = ZodMiniRecord< ZodMiniString, DataItemRulesParser<$, ZodMiniAny> > diff --git a/packages/api/src/parsers/DataItems.ts b/packages/api/src/parsers/DataItems.ts index 5875ca44f..09bb683c6 100644 --- a/packages/api/src/parsers/DataItems.ts +++ b/packages/api/src/parsers/DataItems.ts @@ -8,11 +8,6 @@ import { import { object, partial, strictObject } from 'zod/mini' import type { $strict } from 'zod/v4/core' -type Meta = Pick< - DT.Meta, - 'PayloadParsers' | 'TargetingParsers' | 'FallThroughTargetingParsers' -> - /** * Parses all items in a Data class. * @@ -36,7 +31,7 @@ type Meta = Pick< * ``` */ export function DataItemsParser< - $ extends Meta, + $ extends DT.Meta, >( payloadParsers: $['PayloadParsers'], targeting: $['TargetingParsers'], @@ -58,7 +53,7 @@ export function DataItemsParser< } export type DataItemsParser< - $ extends Meta, + $ extends DT.Meta, > = ZodPartialObject< { [Name in keyof $['PayloadParsers']]: DataItemParser< @@ -69,11 +64,11 @@ export type DataItemsParser< $strict > -export type DataItemsIn<$ extends Meta> = { +export type DataItemsIn<$ extends DT.Meta> = { [Name in keyof $['PayloadParsers']]?: DataItemIn<$, $['PayloadParsers'][Name]> } -export type DataItemsOut<$ extends Meta> = { +export type DataItemsOut<$ extends DT.Meta> = { [Name in keyof $['PayloadParsers']]?: DataItemOut< $, $['PayloadParsers'][Name] diff --git a/packages/api/src/types/Data.ts b/packages/api/src/types/Data.ts index 46d89f17e..974884e71 100644 --- a/packages/api/src/types/Data.ts +++ b/packages/api/src/types/Data.ts @@ -58,12 +58,7 @@ export type FallThroughTargetingParsers = D extends Data ? $['FallThroughTargetingParsers'] : never -export type InsertableData< - $ extends Pick< - Meta, - 'PayloadParsers' | 'TargetingParsers' | 'FallThroughTargetingParsers' - >, -> = Partial< +export type InsertableData<$ extends Meta> = Partial< { [Name in keyof $['PayloadParsers']]: | output<$['PayloadParsers'][Name]> diff --git a/packages/api/src/types/FallThroughTargeting.ts b/packages/api/src/types/FallThroughTargeting.ts index 3ff60491d..91b0ed96c 100644 --- a/packages/api/src/types/FallThroughTargeting.ts +++ b/packages/api/src/types/FallThroughTargeting.ts @@ -33,7 +33,7 @@ export type ParsersRecord = { * The data shape of a set of fallthrough rules */ export type Rules< - $ extends Pick, + $ extends DT.Meta, PayloadParser extends $ZodType, > = { __rules__: RuleWithPayload[] diff --git a/packages/api/src/types/Payload.ts b/packages/api/src/types/Payload.ts index c75c55334..1c791f87d 100644 --- a/packages/api/src/types/Payload.ts +++ b/packages/api/src/types/Payload.ts @@ -3,14 +3,12 @@ import type * as FTTT from './FallThroughTargeting.ts' import type * as DT from './Data.ts' export type Payload< - $ extends Pick, + $ extends DT.Meta, PayloadParser extends $ZodType, > = | output | FTTT.Rules<$, PayloadParser> -export type Payloads< - $ extends Pick, -> = { +export type Payloads<$ extends DT.Meta> = { [Name in keyof $['PayloadParsers']]?: Payload<$, $['PayloadParsers'][Name]> } From 97c6a6578dc7e013f854562b687c2a0cb719335a Mon Sep 17 00:00:00 2001 From: John Wright Date: Mon, 13 Oct 2025 10:04:46 +0100 Subject: [PATCH 09/11] style: less whitespace --- packages/api/src/Data.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 54d70f176..19b2293a1 100644 --- a/packages/api/src/Data.ts +++ b/packages/api/src/Data.ts @@ -259,9 +259,7 @@ export default class Data<$ extends DT.Meta> { ) } - async useTargeting( - targeting: TDs, - ): Promise< + async useTargeting(targeting: TDs): Promise< Data< $ & { TargetingParsers: $['TargetingParsers'] & TT.ParserRecord From 514ed7b26a1049082aa0597389dc40a969dd2dbe Mon Sep 17 00:00:00 2001 From: John Wright Date: Mon, 13 Oct 2025 10:40:15 +0100 Subject: [PATCH 10/11] refactor: use a data interface --- packages/api/src/Data.ts | 6 +++- packages/api/src/IData.ts | 54 ++++++++++++++++++++++++++++++++ packages/api/src/PromisedData.ts | 22 +++++++++---- 3 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 packages/api/src/IData.ts diff --git a/packages/api/src/Data.ts b/packages/api/src/Data.ts index 19b2293a1..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,7 +58,7 @@ import { resolveVariables } from './parsers/DataItemVariableResolver.ts' * ) * ``` */ -export default class Data<$ extends DT.Meta> { +export default class Data<$ extends DT.Meta> implements IData<$> { readonly #fallThroughTargetingParsers: $['FallThroughTargetingParsers'] readonly #data: DataItemsOut<$> readonly #payloadParsers: $['PayloadParsers'] @@ -70,6 +71,9 @@ export default class Data<$ extends DT.Meta> { return PromisedData.create(new Data({}, {}, {}, {}, {}, {})) } + /** + * @see {@link Data.create} + */ private constructor( data: DataItemsOut<$>, payloadParsers: $['PayloadParsers'], 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 489c9e222..9e1b896c8 100644 --- a/packages/api/src/PromisedData.ts +++ b/packages/api/src/PromisedData.ts @@ -8,8 +8,10 @@ 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<$ extends DT.Meta> extends Promise> { +export class PromisedData<$ extends DT.Meta> extends Promise> + implements IData<$> { constructor( executor: ( resolve: (value: MaybePromise>) => void, @@ -19,9 +21,9 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { super(executor) } - static create( - promisedData: MaybePromise>, - ): PromisedData { + static create<$ extends DT.Meta>( + promisedData: MaybePromise>, + ): PromisedData<$> { return new PromisedData((resolve) => resolve(promisedData)) } @@ -78,7 +80,7 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { } async getPayloadForEachName( - rawQuery: QT.Raw<$['QueryParsers']> = {}, + rawQuery?: QT.Raw<$['QueryParsers']>, ): Promise< PT.Payloads<$> > { @@ -88,7 +90,7 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { async getPayload( name: Name, - rawQuery: QT.Raw<$['QueryParsers']> = {}, + rawQuery?: QT.Raw<$['QueryParsers']>, ): Promise< | PT.Payload<$, $['PayloadParsers'][Name]> | undefined @@ -97,6 +99,14 @@ export class PromisedData<$ extends DT.Meta> extends Promise> { return data.getPayload(name, rawQuery) } + async getPayloads( + name: Name, + rawQuery?: QT.Raw<$['QueryParsers']>, + ) { + const data = await this + return data.getPayloads(name, rawQuery) + } + removeAllRules(): Promise> { return this.#create((data) => data.removeAllRules()) } From 2d5e5cfa739cec3aef9917928d45eb84e368c05e Mon Sep 17 00:00:00 2001 From: John Wright Date: Fri, 12 Dec 2025 09:00:27 +0000 Subject: [PATCH 11/11] refactor: add an explicit return type --- packages/api/src/PromisedData.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/src/PromisedData.ts b/packages/api/src/PromisedData.ts index 9e1b896c8..070f633dc 100644 --- a/packages/api/src/PromisedData.ts +++ b/packages/api/src/PromisedData.ts @@ -102,7 +102,9 @@ export class PromisedData<$ extends DT.Meta> extends Promise> async getPayloads( name: Name, rawQuery?: QT.Raw<$['QueryParsers']>, - ) { + ): Promise< + PT.Payload<$, $['PayloadParsers'][Name]>[] + > { const data = await this return data.getPayloads(name, rawQuery) }