-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from koyopro/feature/to_json
Serialization
- Loading branch information
Showing
11 changed files
with
540 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Accella Project | ||
|
||
- [Accel Record](./packages/accel-record/) | ||
- [Accel Record Factory](./packages/accel-record-factory/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import { BelongsToAssociation } from "../associations/belongsToAssociation.js"; | ||
import { Collection } from "../associations/collectionProxy.js"; | ||
import { HasManyAssociation } from "../associations/hasManyAssociation.js"; | ||
import { HasOneAssociation } from "../associations/hasOneAssociation.js"; | ||
import { Model } from "../index.js"; | ||
import { Meta } from "../meta.js"; | ||
|
||
type RetriveModel<T> = undefined extends T | ||
? NonNullable<T> | ||
: T extends Collection<infer U, infer S> | ||
? U | ||
: T; | ||
|
||
type ToUnion<T extends any[] | undefined> = undefined extends T | ||
? never | ||
: NonNullable<T>[number]; | ||
|
||
type ToHashIncludeResult<K, T, O extends ToHashOptions<any>> = K extends keyof T | ||
? undefined extends T[K] | ||
? ToHashResult<NonNullable<T[K]>, O> | undefined // HasOne | ||
: T[K] extends Collection<infer U, infer S> | ||
? ToHashResult<U, O>[] // HasMany | ||
: ToHashResult<T[K], O> // BelongsTo | ||
: {}; | ||
|
||
type ToHashInclude<O extends ToHashOptions<T>, T> = O["include"] extends string | ||
? { | ||
[K in O["include"]]: ToHashIncludeResult<K, T, {}>; | ||
} | ||
: O["include"] extends ToHashIncludeOption<T> | ||
? { | ||
[K in keyof O["include"]]: ToHashIncludeResult< | ||
K, | ||
T, | ||
// @ts-ignore | ||
NonNullable<O["include"][K]> | ||
>; | ||
} | ||
: {}; | ||
|
||
type ToHashMethods< | ||
O extends ToHashOptions<T>, | ||
T, | ||
> = undefined extends O["methods"] | ||
? {} | ||
: { | ||
[K in ToUnion<O["methods"]>]: T[Extract<K, keyof T>] extends () => any | ||
? ReturnType<T[Extract<K, keyof T>]> | ||
: never; | ||
}; | ||
|
||
export type ToHashResult<T, O extends ToHashOptions<T>> = { | ||
[K in undefined extends O["only"] | ||
? Exclude<keyof Meta<T>["OrderInput"], ToUnion<O["except"]>> | ||
: ToUnion<O["only"]>]: T[Extract<K, keyof T>]; | ||
} & ToHashInclude<O, T> & | ||
ToHashMethods<O, T>; | ||
|
||
type ToHashIncludeOption<T> = { | ||
[K in Meta<T>["AssociationKey"]]?: K extends keyof T | ||
? ToHashOptions<RetriveModel<T[K]>> | ||
: never; | ||
}; | ||
|
||
type NoArgMethods<T> = { | ||
[K in keyof T]: T[K] extends () => any ? K : never; | ||
}[keyof T]; | ||
|
||
export type ToHashOptions<T> = { | ||
only?: (keyof Meta<T>["OrderInput"])[]; | ||
except?: (keyof Meta<T>["OrderInput"])[]; | ||
methods?: NoArgMethods<T>[]; | ||
include?: Meta<T>["AssociationKey"] | ToHashIncludeOption<T>; | ||
}; | ||
|
||
/** | ||
* Represents a Serialization class that provides methods for converting a model instance to a hash object. | ||
* | ||
* This class is intended to be inherited by the Model class. | ||
*/ | ||
export class Serialization { | ||
/** | ||
* Converts the model instance to a hash object. | ||
* @param options - The options for the conversion. | ||
* @returns The hash object representing the model instance. | ||
*/ | ||
toHash<T, O extends ToHashOptions<T>>( | ||
this: T, | ||
options?: O | ||
): ToHashResult<T, O>; | ||
toHash<T extends Model, O extends ToHashOptions<T>>( | ||
this: T, | ||
options: O = {} as O | ||
) { | ||
const ret = {} as any; | ||
for (const field of this.columnFields) { | ||
if (options.only && !options.only.includes(field.name)) continue; | ||
if (options.except && options.except.includes(field.name)) continue; | ||
|
||
ret[field.name] = this[field.dbName as keyof T] ?? undefined; | ||
} | ||
if (typeof options.include === "string") { | ||
ret[options.include] = this.toHashInclude(options.include, {}); | ||
} | ||
if (typeof options.include === "object") { | ||
for (const [key, value] of Object.entries(options.include)) { | ||
ret[key] = this.toHashInclude(key, value ?? {}); | ||
} | ||
} | ||
for (const method of options.methods ?? []) { | ||
const f = this[method as keyof T]; | ||
if (typeof f !== "function") continue; | ||
ret[method] = f.bind(this)(); | ||
} | ||
return ret; | ||
} | ||
|
||
/** | ||
* Converts the associated model instance to a hash object. | ||
* @param key - The key of the association. | ||
* @param options - The options for the conversion. | ||
* @returns The hash object representing the associated model instance. | ||
*/ | ||
protected toHashInclude<T extends Model>( | ||
this: T, | ||
key: string, | ||
options: ToHashOptions<any> | ||
) { | ||
const association = this.associations.get(key); | ||
if (association instanceof HasManyAssociation) { | ||
return (this[key as keyof T] as any).map((r: Model) => r.toHash(options)); | ||
} | ||
if ( | ||
association instanceof HasOneAssociation || | ||
association instanceof BelongsToAssociation | ||
) { | ||
return (this[key as keyof T] as Model)?.toHash(options); | ||
} | ||
return undefined; | ||
} | ||
|
||
/** | ||
* Converts the model instance to a JSON string representation. | ||
* | ||
* @typeparam T - The type of the model. | ||
* @typeparam O - The type of the options for serialization. | ||
* @param options - The options for serialization. | ||
* @returns The JSON string representation of the model instance. | ||
*/ | ||
toJson<T extends Model, O extends ToHashOptions<T>>( | ||
this: T, | ||
options?: O | ||
): string { | ||
return JSON.stringify(this.toHash(options)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { User } from ".."; | ||
|
||
test("Relation#toHashArray()", () => { | ||
const users = User.all().toHashArray(); | ||
expectTypeOf(users[0]).toEqualTypeOf<{ | ||
id: number; | ||
name: string | undefined; | ||
age: number | undefined; | ||
email: string; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
}>(); | ||
|
||
const withSelect = User.select("id").toHashArray(); | ||
expectTypeOf(withSelect).toEqualTypeOf<never>(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { User } from ".."; | ||
import { $user } from "../../factories/user"; | ||
|
||
const objectContaining = expect.objectContaining; | ||
|
||
test("Relation#toHashArray()", () => { | ||
$user.create({ name: "hoge" }); | ||
$user.create({ name: "fuga", age: 20 }); | ||
|
||
const users = User.all().toHashArray(); | ||
expect(users).toHaveLength(2); | ||
expect(users[0]).toEqual( | ||
objectContaining({ | ||
name: "hoge", | ||
age: undefined, | ||
}) | ||
); | ||
expect(users[1]).toEqual( | ||
objectContaining({ | ||
name: "fuga", | ||
age: 20, | ||
}) | ||
); | ||
|
||
expect(User.all().toHashArray({ only: ["name", "age"] })[0]).toEqual({ | ||
name: "hoge", | ||
age: undefined, | ||
}); | ||
}); | ||
|
||
test("Relation#toJson()", () => { | ||
const now = new Date("Tue, 01 Dec 2024 10:00:00 GMT"); | ||
$user.create({ name: "hoge", createdAt: now }); | ||
$user.create({ name: "fuga", createdAt: now }); | ||
|
||
const result = User.all().toJson({ only: ["name", "createdAt"] }); | ||
expect(result).toEqual( | ||
JSON.stringify([ | ||
{ | ||
name: "hoge", | ||
createdAt: "2024-12-01T10:00:00.000Z", | ||
}, | ||
{ | ||
name: "fuga", | ||
createdAt: "2024-12-01T10:00:00.000Z", | ||
}, | ||
]) | ||
); | ||
}); |
Oops, something went wrong.