Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-bags-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@archisquad/repository": minor
---

Introduced the branded type `EntityKey` as a type for the default identifier generated by Entity API.
17 changes: 9 additions & 8 deletions packages/repository/src/entity/factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { makeRepositoryKey } from "@/repositoryKey"
import type { ObjectSchema, Output } from "valibot"
import { boolean, cuid2, number, object, string } from "valibot"
import { boolean, cuid2, number, object, string, transform } from "valibot"
import type { TestEntityData } from "vitest"
import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest"
import { entityModelFactory } from "./factory"
import type { EntityKey } from "./interface"

describe("Entity", () => {
beforeEach((context) => {
Expand Down Expand Up @@ -176,7 +177,7 @@ describe("Entity", () => {

const entity = entityFactory.createEntity({
...fakeData,
id: "anything",
id: "anything" as EntityKey,
})

expect(entity.getIdentifier()).toEqual(fakeData.foo)
Expand All @@ -195,7 +196,7 @@ describe("Entity", () => {

const entity = entityFactory.createEntity({
...fakeData,
id: "anything",
id: "anything" as EntityKey,
})

expect(entity.getIdentifier()).toEqual(fakeData.foo)
Expand Down Expand Up @@ -349,7 +350,7 @@ describe("Entity", () => {
}) => {
const { createEntity } = entityModelFactory({
schema: {
id: "string",
id: "string" as EntityKey,
foo: "string",
bar: 1,
deep: {
Expand All @@ -363,7 +364,7 @@ describe("Entity", () => {

const entity = createEntity(fakeData)

expectTypeOf(entity).toMatchTypeOf<TestEntityData>()
expectTypeOf(entity.toObject()).toMatchTypeOf<TestEntityData>()
expect(entity.foo).toBe(fakeData.foo)
})

Expand All @@ -379,15 +380,15 @@ describe("Entity", () => {

const entity = createEntity(fakeData)

expectTypeOf(entity).toMatchTypeOf<TestEntityData>()
expectTypeOf(entity.toObject()).toMatchTypeOf<TestEntityData>()
expect(entity.foo).toBe(fakeData.foo)
})

it("Given valibot based schema, When entity model declared, Then return proper schema type", ({
fakeData,
}) => {
const valibotSchema = object({
id: string([cuid2()]),
id: transform(string([cuid2()]), (id) => id as EntityKey),
foo: string(),
bar: number(),
deep: object({
Expand All @@ -409,7 +410,7 @@ describe("Entity", () => {

const entity = createEntity(fakeData)

expectTypeOf(entity).toMatchTypeOf<TestEntityData>()
expectTypeOf(entity.toObject()).toMatchTypeOf<TestEntityData>()
expect(entity.foo).toBe(fakeData.foo)
})
})
Expand Down
3 changes: 2 additions & 1 deletion packages/repository/src/entity/generateId.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, expectTypeOf, it } from "vitest"
import { generateId } from "./generateId"
import { EntityKey } from "./interface"

describe("generateId", () => {
it("Given generateId function, When called, Then return string", () => {
const result = generateId()

expect(result).not.toBeUndefined()
expectTypeOf(result).toEqualTypeOf<string>()
expectTypeOf(result).toEqualTypeOf<EntityKey>()
})
})
5 changes: 3 additions & 2 deletions packages/repository/src/entity/generateId.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createId } from "@paralleldrive/cuid2"
import { EntityKey } from "./interface"

export function generateId(): string {
return createId()
export function generateId(): EntityKey {
return createId() as EntityKey
}
3 changes: 2 additions & 1 deletion packages/repository/src/entity/interface/data.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { TestEntityData } from "vitest"
import { describe, expectTypeOf, it } from "vitest"
import type { ResolveIdentifier } from "./data"
import { EntityKey } from "./entity"

describe("User Input Data types", () => {
it("ResolveIdentifier returns default identifier if identifier is undefined", () => {
type Test = ResolveIdentifier<TestEntityData, undefined>

expectTypeOf<Test>().toEqualTypeOf<string>()
expectTypeOf<Test>().toEqualTypeOf<EntityKey>()
})

it("ResolveIdentifier returns ReturnType if identifier is function", () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/repository/src/entity/interface/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IsStringLiteral } from "type-fest"
import { IsStringLiteral } from "type-fest"
import { EntityKey } from "./entity"

export type EntitySchema = Record<string, any>

Expand All @@ -8,7 +9,7 @@ export type Validator<TSchema, TInputSchema> = (
) => TInputSchema

export type Identifier<TSchema> = keyof TSchema | ((data: TSchema) => any)
type DefaultIdentifier = string
type DefaultIdentifier = EntityKey

/**
* To determine if the identifier is defined, we checking for union function with
Expand Down
13 changes: 5 additions & 8 deletions packages/repository/src/entity/interface/entity.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PostsRelationDefinition, TestEntityData } from "vitest"
import { describe, expectTypeOf, it } from "vitest"
import type { Entity } from "./entity"
import { DeepReadonly } from "../deepReadonly"
import type { Entity, EntityKey } from "./entity"

describe("Entity interface", () => {
it("Entity has relationship accessors according to defined relations", () => {
Expand All @@ -22,7 +23,7 @@ describe("Entity interface", () => {
it("Entity can be created without relations", () => {
type Test = Entity<TestEntityData>

expectTypeOf<Test["id"]>().toEqualTypeOf<string>()
expectTypeOf<Test["id"]>().toMatchTypeOf<DeepReadonly<EntityKey>>()
expectTypeOf<Test["foo"]>().toEqualTypeOf<TestEntityData["foo"]>()
expectTypeOf<Test["deep"]>().toMatchTypeOf<
Readonly<TestEntityData["deep"]>
Expand Down Expand Up @@ -59,7 +60,7 @@ describe("Entity interface", () => {
it("Entity haven't to identifier defined", () => {
type Test = Entity<TestEntityData, undefined, undefined, undefined>

expectTypeOf<Test["getIdentifier"]>().toEqualTypeOf<() => string>()
expectTypeOf<Test["getIdentifier"]>().toEqualTypeOf<() => EntityKey>()
})

it("Entity give readonly access to data", () => {
Expand All @@ -86,11 +87,7 @@ describe("Entity interface", () => {
it("Entity can be created without defining methods", () => {
type Test = Entity<TestEntityData>

expectTypeOf<Test>().toMatchTypeOf<
TestEntityData & {
id: string
}
>()
expectTypeOf<Test>().toMatchTypeOf<DeepReadonly<TestEntityData>>()
})

it("Defined methods can't override built-in update method", () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/repository/src/entity/interface/entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PartialDeep } from "type-fest"
import type { Opaque, PartialDeep } from "type-fest"
import type { DeepReadonly } from "../deepReadonly"
import type {
EntitySchema,
Expand All @@ -9,6 +9,8 @@ import type {
import type { Methods, ResolvedMethods } from "./methods"
import type { RelationshipsDefinitions, ResolvedRelations } from "./relations"

export type EntityKey = Opaque<string, "entity-key">

type UpdateMethod<
TSchema extends EntitySchema,
TMethods extends Methods<TSchema> | undefined,
Expand Down
3 changes: 2 additions & 1 deletion packages/repository/src/entity/interface/methods.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Except } from "type-fest"
import type { DeepReadonly } from "../deepReadonly"
import type { EntitySchema } from "./data"

export type Methods<TSchema extends EntitySchema> = Record<
string,
(this: TSchema, ...args: any[]) => any
(this: DeepReadonly<TSchema>, ...args: any[]) => any
>

export type PrototypeMethods<TSchema extends EntitySchema> = {
Expand Down
10 changes: 7 additions & 3 deletions packages/repository/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EntityKey } from "@/entity/interface"
import type { SyncKey } from "@/network/interface/sync"
import { makeSyncKey } from "@/network/sync"
import type { RepositoryKey } from "@/repositoryKey"
Expand All @@ -6,7 +7,10 @@ import { beforeEach } from "vitest"
import { z } from "zod"

const zodSchema = z.object({
id: z.string().cuid2(),
id: z
.string()
.cuid2()
.transform((id) => id as EntityKey),
foo: z.string(),
bar: z.number(),
deep: z.object({
Expand All @@ -18,7 +22,7 @@ const zodSchema = z.object({

declare module "vitest" {
export type TestRawEntityData = {
id: string
id: EntityKey
foo: string
bar: number
deep?: {
Expand Down Expand Up @@ -121,7 +125,7 @@ beforeEach((context) => {

function generateFakeObj(faker: Faker) {
return {
id: faker.string.uuid(),
id: faker.string.uuid() as EntityKey,
foo: faker.person.fullName(),
bar: faker.number.int(),
deep: {
Expand Down