|
import type { LucidModel, LucidRow, ModelPaginatorContract } from '@adonisjs/lucid/types/model' |
|
import type { Maybe } from '#utils/maybe' |
|
import type { Paginate, ShapeMapper, PickColumn, PickRelations, PickShape } from '#types/common' |
|
import { ModelRelations } from '@adonisjs/lucid/types/relations' |
|
|
|
export type Serialize<T> = T extends LucidRow ? ShapeMapper<T>['$columns'] : T |
|
|
|
type Mappeable = Record<string, unknown> |
|
type Serializable = LucidRow | Mappeable |
|
type Labelable = Record<string, string> |
|
|
|
/** |
|
* Utility type to infer the output type of a Mapper's serialize method. |
|
* If the Mapper has a custom serialize method, it uses that; otherwise, it defaults to Serialize<T>. |
|
*/ |
|
type InferOutputType< |
|
MapperType extends Mapper<I, any>, |
|
I extends Serializable, |
|
> = MapperType extends { |
|
serialize(input: I): infer R |
|
} |
|
? R |
|
: I extends LucidRow |
|
? ShapeMapper<I>['$columns'] |
|
: I extends Record<string, any> |
|
? I |
|
: never |
|
|
|
type MapperOutput<T extends Mapper<any, any>> = |
|
T extends Mapper<infer E, any> ? InferOutputType<T, E> : never |
|
|
|
/** |
|
* Utility for mapping and serializing Lucid ORM entities to plain objects or other formats. |
|
* Provides methods to transform models, arrays, paginations, and also to map labels. |
|
*/ |
|
export abstract class Mapper<E extends Serializable, L extends Labelable = any> { |
|
/** |
|
* Maps an input (model, array, or pagination) to the output format defined by the Mapper. |
|
* The return type is automatically inferred based on the input type's nullability. |
|
*/ |
|
static mapTo<I extends LucidRow, P extends Mapper<I, any>>( |
|
this: new () => P, |
|
input: ModelPaginatorContract<I> |
|
): Paginate<MapperOutput<P>> |
|
|
|
static mapTo<I extends LucidRow, P extends Mapper<I, any>, T extends Maybe<I>>( |
|
this: new () => P, |
|
input: T |
|
): T extends null | undefined ? null : MapperOutput<P> |
|
|
|
static mapTo<I extends LucidRow, P extends Mapper<I, any>>( |
|
this: new () => P, |
|
input: I[] |
|
): MapperOutput<P>[] |
|
|
|
static mapTo<I extends Mappeable, P extends Mapper<I, any>, T extends Maybe<I>>( |
|
this: new () => P, |
|
input: T |
|
): T extends null | undefined ? null : MapperOutput<P> |
|
|
|
static mapTo<I extends Mappeable, P extends Mapper<I, any>>( |
|
this: new () => P, |
|
input: I[] |
|
): MapperOutput<P>[] |
|
|
|
static mapTo(this: MapperConstructor, input: unknown) { |
|
if (Array.isArray(input)) { |
|
if ('toJSON' in input && typeof input.toJSON === 'function') { |
|
const serialized = input.toJSON() |
|
|
|
if ('meta' in serialized) { |
|
const instance = new this() |
|
const data = serialized.data.map((item: any) => instance.#serialize(item)) |
|
|
|
return { |
|
data, |
|
meta: { |
|
total: serialized.meta.total, |
|
perPage: serialized.meta.perPage, |
|
currentPage: serialized.meta.currentPage, |
|
lastPage: serialized.meta.lastPage, |
|
firstPage: serialized.meta.firstPage, |
|
firstPageUrl: serialized.meta.firstPageUrl, |
|
lastPageUrl: serialized.meta.lastPageUrl, |
|
nextPageUrl: serialized.meta.nextPageUrl, |
|
previousPageUrl: serialized.meta.previousPageUrl, |
|
}, |
|
} |
|
} |
|
} |
|
|
|
return input.map((item) => new this().#serialize(item)) |
|
} |
|
|
|
return input ? new this().#serialize(input) : null |
|
} |
|
|
|
/** |
|
* Optional method to override the serialization of an entity. |
|
*/ |
|
protected serialize?(input: E): any |
|
|
|
/** |
|
* Internal serialization method that handles the actual transformation of entities. |
|
*/ |
|
#serialize(input: E) { |
|
if (!input) throw new Error('Serializable input is required') |
|
|
|
if (this.serialize) return this.serialize(input) |
|
|
|
if (typeof input === 'object') { |
|
if ('serialize' in input && typeof input.serialize === 'function') { |
|
return input.serialize() |
|
} |
|
|
|
if ('toJSON' in input && typeof input.toJSON === 'function') { |
|
return input.toJSON() |
|
} |
|
} |
|
|
|
return JSON.parse(JSON.stringify(input)) |
|
} |
|
|
|
/** |
|
* Optional method to provide custom labels for fields. |
|
*/ |
|
protected label?(): L |
|
|
|
/** |
|
* Returns the label corresponding to a key, if defined, or the key itself. |
|
*/ |
|
static label<MapperType extends Mapper<any, any>>( |
|
this: new () => MapperType, |
|
value: MapperType extends Mapper<any, infer R> ? keyof R : string |
|
): string |
|
static label<MapperType extends Mapper<any, any>>( |
|
this: new () => MapperType, |
|
value: string |
|
): string |
|
static label(this: MapperConstructor, value: any) { |
|
return new this().#label(value) |
|
} |
|
|
|
/** |
|
* Returns the label corresponding to a key, if defined, or the key itself (instance). |
|
*/ |
|
#label(value: keyof L): string |
|
#label(value: string): string |
|
#label(value: any) { |
|
return this.label?.()[value] || value |
|
} |
|
|
|
/** |
|
* Serializes only the selected fields of a model or array of models. |
|
*/ |
|
pick< |
|
T extends LucidModel | ModelRelations<any, any>, |
|
K extends PickColumn<T>, |
|
R extends Partial<PickRelations<T>>, |
|
>(input: T | null, keys: K[], relations?: R): PickShape<T, K, R> | null |
|
|
|
pick< |
|
T extends LucidModel | ModelRelations<any, any>, |
|
K extends PickColumn<T>, |
|
R extends Partial<PickRelations<T>>, |
|
>(input: T[], keys: K[], relations?: R): PickShape<T, K, R>[] |
|
|
|
pick(input: any | any[] | null, keys: string[], relations: any = {}) { |
|
if (!input) return null |
|
|
|
if (Array.isArray(input)) { |
|
return input.map((i) => { |
|
if ('serialize' in i) { |
|
return i.serialize({ fields: { pick: keys }, relations }) |
|
} |
|
return i |
|
}) |
|
} |
|
|
|
if ('serialize' in input) { |
|
return input.serialize({ fields: { pick: keys }, relations }) |
|
} |
|
return input |
|
} |
|
} |
|
|
|
/** |
|
* Helper type that represents a Mapper constructor. |
|
*/ |
|
type MapperConstructor = new (...args: any) => Mapper<any, any> |
|
|
|
/** |
|
* Utility type to infer the output type of a Mapper class. |
|
*/ |
|
export type Infer<T extends MapperConstructor> = T extends new () => infer MapperInstance |
|
? MapperInstance extends { serialize(input: any): infer R } |
|
? R |
|
: MapperInstance extends Mapper<infer E, any> |
|
? Serialize<E> |
|
: never |
|
: never |