added chainable functions in em()

This commit is contained in:
dswbx
2024-12-19 16:12:30 +01:00
parent d7ee13011f
commit a7e3ce878a
5 changed files with 82 additions and 28 deletions

View File

@@ -186,7 +186,7 @@ describe("Mutator simple", async () => {
const newCount = (await em.repo(items).count()).count;
expect(newCount).toBe(oldCount + inserts.length);
const { data: data2 } = await em.repo(items).findMany();
const { data: data2 } = await em.repo(items).findMany({ offset: oldCount });
expect(data2).toEqual(data);
});
});

View File

@@ -3,6 +3,7 @@ import {
BooleanField,
DateField,
Entity,
EntityIndex,
EntityManager,
EnumField,
JsonField,
@@ -278,23 +279,32 @@ describe("prototype", () => {
test("schema", async () => {
const _em = em(
{
posts: entity("posts", { name: text() }),
comments: entity("comments", { some: text() })
posts: entity("posts", { name: text(), slug: text().required() }),
comments: entity("comments", { some: text() }),
users: entity("users", { email: text() })
},
(relation, { posts, comments }) => {
relation(posts).manyToOne(comments);
({ relation, index }, { posts, comments, users }) => {
relation(posts).manyToOne(comments).manyToOne(users);
index(posts).on(["name"]).on(["slug"], true);
}
);
type LocalDb = (typeof _em)["DB"];
const es = [
new Entity("posts", [new TextField("name")]),
new Entity("comments", [new TextField("some")])
new Entity("posts", [new TextField("name"), new TextField("slug", { required: true })]),
new Entity("comments", [new TextField("some")]),
new Entity("users", [new TextField("email")])
];
const _em2 = new EntityManager(es, new DummyConnection(), [
new ManyToOneRelation(es[0], es[1])
]);
const _em2 = new EntityManager(
es,
new DummyConnection(),
[new ManyToOneRelation(es[0], es[1]), new ManyToOneRelation(es[0], es[2])],
[
new EntityIndex(es[0], [es[0].field("name")!]),
new EntityIndex(es[0], [es[0].field("slug")!], true)
]
);
// @ts-ignore
expect(_em2.toJSON()).toEqual(_em.toJSON());

View File

@@ -220,7 +220,8 @@ export class Entity<
readOnly: !field.isFillable("update") ? true : undefined,
writeOnly: !field.isFillable("create") ? true : undefined,
...field.toJsonSchema()
}))
})),
{ additionalProperties: false }
);
return clean ? JSON.parse(JSON.stringify(schema)) : schema;

View File

@@ -104,6 +104,12 @@ export class TextField<Required extends true | false = false> extends Field<
);
}
if (this.config.pattern && value && !new RegExp(this.config.pattern).test(value)) {
throw new TransformPersistFailedException(
`Field "${this.name}" must match the pattern ${this.config.pattern}`
);
}
return value;
}

View File

@@ -10,6 +10,7 @@ import {
type DateFieldConfig,
Entity,
type EntityConfig,
EntityIndex,
type EntityRelation,
EnumField,
type EnumFieldConfig,
@@ -244,47 +245,83 @@ export function relation<Local extends Entity>(local: Local) {
};
}
export function index<E extends Entity>(entity: E) {
return {
on: (fields: (keyof InsertSchema<E>)[], unique?: boolean) => {
const _fields = fields.map((f) => {
const field = entity.field(f as any);
if (!field) {
throw new Error(`Field "${String(f)}" not found on entity "${entity.name}"`);
}
return field;
});
return new EntityIndex(entity, _fields, unique);
}
};
}
class EntityManagerPrototype<Entities extends Record<string, Entity>> extends EntityManager<
Schema<Entities>
> {
constructor(
public __entities: Entities,
relations: EntityRelation[]
relations: EntityRelation[] = [],
indices: EntityIndex[] = []
) {
super(Object.values(__entities), new DummyConnection(), relations);
super(Object.values(__entities), new DummyConnection(), relations, indices);
}
}
type Chained<Fn extends (...args: any[]) => any, Rt = ReturnType<Fn>> = <E extends Entity>(
e: E
) => {
[K in keyof Rt]: Rt[K] extends (...args: any[]) => any
? (...args: Parameters<Rt[K]>) => Rt
: never;
};
export function em<Entities extends Record<string, Entity>>(
entities: Entities,
schema?: (rel: typeof relation, entities: Entities) => void
schema?: (
fns: { relation: Chained<typeof relation>; index: Chained<typeof index> },
entities: Entities
) => void
) {
const relations: EntityRelation[] = [];
const relationProxy = (local: Entity) => {
return new Proxy(relation(local), {
const indices: EntityIndex[] = [];
const relationProxy = (e: Entity) => {
return new Proxy(relation(e), {
get(target, prop) {
if (typeof target[prop] === "function") {
return (...args: any[]) => {
const result = target[prop](...args);
relations.push(result);
return result;
};
}
return target[prop];
return (...args: any[]) => {
relations.push(target[prop](...args));
return relationProxy(e);
};
}
});
}) as any;
};
const indexProxy = (e: Entity) => {
return new Proxy(index(e), {
get(target, prop) {
return (...args: any[]) => {
indices.push(target[prop](...args));
return indexProxy(e);
};
}
}) as any;
};
if (schema) {
schema(relationProxy, entities);
schema({ relation: relationProxy, index: indexProxy }, entities);
}
const e = new EntityManagerPrototype(entities, relations);
const e = new EntityManagerPrototype(entities, relations, indices);
return {
DB: e.__entities as unknown as Schemas<Entities>,
entities: e.__entities,
relations,
indices: [],
indices,
toJSON: () =>
e.toJSON() as unknown as Pick<ModuleConfigs["data"], "entities" | "relations" | "indices">
};