added new em() shorthand for prototyping, added insertMany mutator function for seeding

This commit is contained in:
dswbx
2024-12-19 07:38:40 +01:00
parent 602235b372
commit 386c0d3ff0
6 changed files with 145 additions and 17 deletions

View File

@@ -16,7 +16,7 @@ describe("Mutator simple", async () => {
new TextField("label", { required: true, minLength: 1 }), new TextField("label", { required: true, minLength: 1 }),
new NumberField("count", { default_value: 0 }) new NumberField("count", { default_value: 0 })
]); ]);
const em = new EntityManager([items], connection); const em = new EntityManager<any>([items], connection);
await em.connection.kysely.schema await em.connection.kysely.schema
.createTable("items") .createTable("items")
@@ -175,4 +175,18 @@ describe("Mutator simple", async () => {
{ id: 8, label: "keep", count: 0 } { id: 8, label: "keep", count: 0 }
]); ]);
}); });
test("insertMany", async () => {
const oldCount = (await em.repo(items).count()).count;
const inserts = [{ label: "insert 1" }, { label: "insert 2" }];
const { data } = await em.mutator(items).insertMany(inserts);
expect(data.length).toBe(2);
expect(data.map((d) => ({ label: d.label }))).toEqual(inserts);
const newCount = (await em.repo(items).count()).count;
expect(newCount).toBe(oldCount + inserts.length);
const { data: data2 } = await em.repo(items).findMany();
expect(data2).toEqual(data);
});
}); });

View File

@@ -13,6 +13,7 @@ import {
PolymorphicRelation, PolymorphicRelation,
TextField TextField
} from "../../src/data"; } from "../../src/data";
import { DummyConnection } from "../../src/data/connection/DummyConnection";
import { import {
FieldPrototype, FieldPrototype,
type FieldSchema, type FieldSchema,
@@ -21,6 +22,7 @@ import {
boolean, boolean,
date, date,
datetime, datetime,
em,
entity, entity,
enumm, enumm,
json, json,
@@ -272,4 +274,29 @@ describe("prototype", () => {
const obj: Schema<typeof test> = {} as any; const obj: Schema<typeof test> = {} as any;
}); });
test("schema", async () => {
const _em = em(
{
posts: entity("posts", { name: text() }),
comments: entity("comments", { some: text() })
},
(relation, { posts, comments }) => {
relation(posts).manyToOne(comments);
}
);
type LocalDb = (typeof _em)["DB"];
const es = [
new Entity("posts", [new TextField("name")]),
new Entity("comments", [new TextField("some")])
];
const _em2 = new EntityManager(es, new DummyConnection(), [
new ManyToOneRelation(es[0], es[1])
]);
// @ts-ignore
expect(_em2.toJSON()).toEqual(_em.toJSON());
});
}); });

View File

@@ -22,7 +22,7 @@ describe("[data] Mutator (base)", async () => {
new TextField("hidden", { hidden: true }), new TextField("hidden", { hidden: true }),
new TextField("not_fillable", { fillable: false }) new TextField("not_fillable", { fillable: false })
]); ]);
const em = new EntityManager([entity], dummyConnection); const em = new EntityManager<any>([entity], dummyConnection);
await em.schema().sync({ force: true }); await em.schema().sync({ force: true });
const payload = { label: "item 1", count: 1 }; const payload = { label: "item 1", count: 1 };
@@ -61,7 +61,7 @@ describe("[data] Mutator (ManyToOne)", async () => {
const posts = new Entity("posts", [new TextField("title")]); const posts = new Entity("posts", [new TextField("title")]);
const users = new Entity("users", [new TextField("username")]); const users = new Entity("users", [new TextField("username")]);
const relations = [new ManyToOneRelation(posts, users)]; const relations = [new ManyToOneRelation(posts, users)];
const em = new EntityManager([posts, users], dummyConnection, relations); const em = new EntityManager<any>([posts, users], dummyConnection, relations);
await em.schema().sync({ force: true }); await em.schema().sync({ force: true });
test("RelationMutator", async () => { test("RelationMutator", async () => {
@@ -192,7 +192,7 @@ describe("[data] Mutator (OneToOne)", async () => {
const users = new Entity("users", [new TextField("username")]); const users = new Entity("users", [new TextField("username")]);
const settings = new Entity("settings", [new TextField("theme")]); const settings = new Entity("settings", [new TextField("theme")]);
const relations = [new OneToOneRelation(users, settings)]; const relations = [new OneToOneRelation(users, settings)];
const em = new EntityManager([users, settings], dummyConnection, relations); const em = new EntityManager<any>([users, settings], dummyConnection, relations);
await em.schema().sync({ force: true }); await em.schema().sync({ force: true });
test("insertOne: missing ref", async () => { test("insertOne: missing ref", async () => {
@@ -276,7 +276,7 @@ describe("[data] Mutator (ManyToMany)", async () => {
describe("[data] Mutator (Events)", async () => { describe("[data] Mutator (Events)", async () => {
const entity = new Entity("test", [new TextField("label")]); const entity = new Entity("test", [new TextField("label")]);
const em = new EntityManager([entity], dummyConnection); const em = new EntityManager<any>([entity], dummyConnection);
await em.schema().sync({ force: true }); await em.schema().sync({ force: true });
const events = new Map<string, any>(); const events = new Map<string, any>();

View File

@@ -0,0 +1,7 @@
import { Connection } from "./Connection";
export class DummyConnection extends Connection {
constructor() {
super(undefined as any);
}
}

View File

@@ -276,4 +276,39 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
return (await this.many(query)) as any; return (await this.many(query)) as any;
} }
async insertMany(data: Data[]): Promise<MutatorResponse<DB[TB][]>> {
const entity = this.entity;
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
}
const validated: any[] = [];
for (const row of data) {
const validatedData = {
...entity.getDefaultObject(),
...(await this.getValidatedData(row, "create"))
};
// check if required fields are present
const required = entity.getRequiredFields();
for (const field of required) {
if (
typeof validatedData[field.name] === "undefined" ||
validatedData[field.name] === null
) {
throw new Error(`Field "${field.name}" is required`);
}
}
validated.push(validatedData);
}
const query = this.conn
.insertInto(entity.name)
.values(validated)
.returning(entity.getSelect());
return (await this.many(query)) as any;
}
} }

View File

@@ -1,3 +1,5 @@
import { DummyConnection } from "data/connection/DummyConnection";
import { EntityManager } from "data/entities/EntityManager";
import type { Generated } from "kysely"; import type { Generated } from "kysely";
import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
import { import {
@@ -7,6 +9,7 @@ import {
type DateFieldConfig, type DateFieldConfig,
Entity, Entity,
type EntityConfig, type EntityConfig,
type EntityRelation,
EnumField, EnumField,
type EnumFieldConfig, type EnumFieldConfig,
type Field, type Field,
@@ -240,6 +243,57 @@ export function relation<Local extends Entity>(local: Local) {
}; };
} }
class EntityManagerPrototype<Entities extends Record<string, Entity>> extends EntityManager<
Schema<Entities>
> {
constructor(
public __entities: Entities,
relations: EntityRelation[]
) {
super(Object.values(__entities), new DummyConnection(), relations);
}
}
export function em<Entities extends Record<string, Entity>>(
entities: Entities,
schema?: (rel: typeof relation, entities: Entities) => void
) {
const relations: EntityRelation[] = [];
const relationProxy = (local: Entity) => {
return new Proxy(relation(local), {
get(target, prop) {
if (typeof target[prop] === "function") {
return (...args: any[]) => {
const result = target[prop](...args);
relations.push(result);
return result;
};
}
return target[prop];
}
});
};
if (schema) {
schema(relationProxy, entities);
}
const e = new EntityManagerPrototype(entities, relations);
return {
DB: e.__entities as unknown as Schemas<Entities>,
entities: e.__entities,
relations,
indices: [],
toJSON: () => {
return e.toJSON() as unknown as {
entities: Schemas<Entities>;
relations: EntityRelation[];
indices: any[];
};
}
};
}
export type InferEntityFields<T> = T extends Entity<infer _N, infer Fields> export type InferEntityFields<T> = T extends Entity<infer _N, infer Fields>
? { ? {
[K in keyof Fields]: Fields[K] extends { _type: infer Type; _required: infer Required } [K in keyof Fields]: Fields[K] extends { _type: infer Type; _required: infer Required }
@@ -291,18 +345,9 @@ export type InferField<Field> = Field extends { _type: infer Type; _required: in
: Type | undefined : Type | undefined
: never; : never;
const n = number(); export type Schemas<T extends Record<string, Entity>> = {
type T2 = InferField<typeof n>; [K in keyof T]: Schema<T[K]>;
};
const users = entity("users", {
name: text(),
email: text(),
created_at: datetime(),
updated_at: datetime()
});
type TUsersFields = InferEntityFields<typeof users>;
type TUsers = Schema<typeof users>;
type TUsers2 = Simplify<OptionalUndefined<Schema<typeof users>>>;
export type InsertSchema<T> = Simplify<OptionalUndefined<InferEntityFields<T>>>; export type InsertSchema<T> = Simplify<OptionalUndefined<InferEntityFields<T>>>;
export type Schema<T> = Simplify<{ id: Generated<number> } & InsertSchema<T>>; export type Schema<T> = Simplify<{ id: Generated<number> } & InsertSchema<T>>;