Initial commit

This commit is contained in:
2026-06-24 23:47:55 -07:00
commit d134b480a0
297 changed files with 30726 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
# Serializers
Serializers take model data, and add or remove fields. They are used to convert a database representation of a model to a front-end representation of a model. This might include removing fields that should not be exposed to the front-end, or adding fields that are derived from the database representation.
Serializers are used in controllers to convert from a database representation to a front-end data packet. Serializers should not be used for general data formating such as date or money formatting, as formatting those kinds of things in the front-end is generally more flexible.
e.g. Usage in a Controller might look like this
Note that the BaseSerializer supports passing either an array or a single model to the `perform` method.
```typescript
import { isNil } from "lodash"
import logger from "@/utils/logger"
import { User } from "@/models"
import { UsersPolicy } from "@/policies"
import { CreateService } from "@/services/users"
import { IndexSerializer } from "@/serializers/users"
import BaseController from "@/controllers/base-controller"
export class FormsController extends BaseController {
async index() {
try {
const where = this.buildWhere()
const scopes = this.buildFilterScopes()
const scopedUsers = UsersPolicy.applyScope(scopes, this.currentUser)
const totalCount = await scopedUsers.count({ where })
const users = await scopedUsers.findAll({
where,
limit: this.pagination.limit,
offset: this.pagination.offset,
})
const serializedUsers = IndexSerializer.perform(users)
return this.response.json({
users: serializedUsers,
totalCount,
})
} catch (error) {
logger.error("Error fetching users" + error)
return this.response.status(400).json({
message: `Error fetching users: ${error}`,
})
}
}
async show() {
try {
const user = await this.loadUser()
if (isNil(user)) {
return this.response.status(404).json({
message: "User not found",
})
}
const policy = this.buildPolicy(user)
if (!policy.show()) {
return this.response.status(403).json({
message: "You are not authorized to view this user",
})
}
return this.response.json({ user, policy })
} catch (error) {
logger.error("Error fetching user" + error)
return this.response.status(400).json({
message: `Error fetching user: ${error}`,
})
}
}
}
```
+78
View File
@@ -0,0 +1,78 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type RemainingConstructorParameters<C extends new (...args: any[]) => any> = C extends new (
head: any,
...tail: infer TT
) => any
? TT
: []
/**
* BaseSerializer is a generic class that provides a common interface for all serializers.
* It is designed to be extended by other serializers, and provides a static `perform` method
* that can be used to serialize a single record or an array of records.
*
* The `perform` method is overloaded to handle both cases, and will return the serialized
* record or records based on the input. The return type is determined as the return type of the
* `perform` instance method of the subclass as a single value or an array of values.
*
* The `perform` takes its signature from the constructor of the subclass, while also allowing
* for an array of records to be passed in as the first argument.
*
* @param M The model type that the serializer is designed to handle
*
* @example
* class TableSerializer extends BaseSerializer<Dataset> {
* constructor(
* protected record: Dataset,
* protected currentUser: User
* ) {
* super(record)
* }
*
* perform(): DatasetTableView {}
* }
*
* TableSerializer.perform(dataset, currentUser) // => DatasetTableView
* TableSerializer.perform([dataset1, dataset2], currentUser) // => [DatasetTableView, DatasetTableView]
*/
export class BaseSerializer<Model> {
constructor(protected record: Model) {}
// Overload for handling a single record
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
this: C,
...args: ConstructorParameters<C>
): ReturnType<InstanceType<C>["perform"]>
// Overload for handling an array of records
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
this: C,
...args: [ConstructorParameters<C>[0][], ...RemainingConstructorParameters<C>]
): ReturnType<InstanceType<C>["perform"]>[]
// Implementation of the perform method
static perform<T extends BaseSerializer<any>, C extends new (...args: any[]) => T>(
this: C,
...args:
| ConstructorParameters<C>
| [ConstructorParameters<C>[0][], ...RemainingConstructorParameters<C>]
): ReturnType<InstanceType<C>["perform"]> | ReturnType<InstanceType<C>["perform"]>[] {
if (Array.isArray(args[0])) {
const records = args[0] as ConstructorParameters<C>[0][]
return records.map((record) => {
const instance = new this(record, ...args.slice(1))
return instance.perform()
}) as ReturnType<InstanceType<C>["perform"]>[]
} else {
const instance = new this(...args)
return instance.perform() as ReturnType<InstanceType<C>["perform"]>
}
}
perform(): any {
throw new Error("Not Implemented")
}
}
export default BaseSerializer
@@ -0,0 +1 @@
export { ShowSerializer } from "./show-serializer"
@@ -0,0 +1,28 @@
import { pick } from "lodash"
import { User } from "@/models"
import BaseSerializer from "@/serializers/base-serializer"
export type UserShowView = Pick<
User,
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt"
>
export class ShowSerializer extends BaseSerializer<User> {
perform(): UserShowView {
return {
...pick(this.record, [
"id",
"email",
"firstName",
"lastName",
"displayName",
"roles",
"createdAt",
"updatedAt",
]),
}
}
}
export default ShowSerializer
+2
View File
@@ -0,0 +1,2 @@
// Bundled exports
export * as Users from "./users"
@@ -0,0 +1,19 @@
import { pick } from "lodash"
import { User } from "@/models"
import BaseSerializer from "@/serializers/base-serializer"
export type UserIndexView = Pick<
User,
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles"
>
export class IndexSerializer extends BaseSerializer<User> {
perform(): UserIndexView {
return {
...pick(this.record, ["id", "email", "firstName", "lastName", "displayName", "roles"]),
}
}
}
export default IndexSerializer
+2
View File
@@ -0,0 +1,2 @@
export { ShowSerializer } from "./show-serializer"
export { IndexSerializer } from "./index-serializer"
@@ -0,0 +1,28 @@
import { pick } from "lodash"
import { User } from "@/models"
import BaseSerializer from "@/serializers/base-serializer"
export type UserShowView = Pick<
User,
"id" | "email" | "firstName" | "lastName" | "displayName" | "roles" | "createdAt" | "updatedAt"
>
export class ShowSerializer extends BaseSerializer<User> {
perform(): UserShowView {
return {
...pick(this.record, [
"id",
"email",
"firstName",
"lastName",
"displayName",
"roles",
"createdAt",
"updatedAt",
]),
}
}
}
export default ShowSerializer