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; const newCount = (await em.repo(items).count()).count;
expect(newCount).toBe(oldCount + inserts.length); 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); expect(data2).toEqual(data);
}); });
}); });

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import {
type DateFieldConfig, type DateFieldConfig,
Entity, Entity,
type EntityConfig, type EntityConfig,
EntityIndex,
type EntityRelation, type EntityRelation,
EnumField, EnumField,
type EnumFieldConfig, 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< class EntityManagerPrototype<Entities extends Record<string, Entity>> extends EntityManager<
Schema<Entities> Schema<Entities>
> { > {
constructor( constructor(
public __entities: Entities, 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>>( export function em<Entities extends Record<string, Entity>>(
entities: Entities, 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 relations: EntityRelation[] = [];
const relationProxy = (local: Entity) => { const indices: EntityIndex[] = [];
return new Proxy(relation(local), {
const relationProxy = (e: Entity) => {
return new Proxy(relation(e), {
get(target, prop) { get(target, prop) {
if (typeof target[prop] === "function") {
return (...args: any[]) => { return (...args: any[]) => {
const result = target[prop](...args); relations.push(target[prop](...args));
relations.push(result); return relationProxy(e);
return result;
}; };
} }
return target[prop]; }) 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) { 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 { return {
DB: e.__entities as unknown as Schemas<Entities>, DB: e.__entities as unknown as Schemas<Entities>,
entities: e.__entities, entities: e.__entities,
relations, relations,
indices: [], indices,
toJSON: () => toJSON: () =>
e.toJSON() as unknown as Pick<ModuleConfigs["data"], "entities" | "relations" | "indices"> e.toJSON() as unknown as Pick<ModuleConfigs["data"], "entities" | "relations" | "indices">
}; };