6 Commits
6 changed files with 64 additions and 8 deletions
+13 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { Entity } from "data/entities"; import { Entity } from "data/entities";
import { NumberField, TextField } from "data/fields"; import { NumberField, TextField, PrimaryField } from "data/fields";
describe("[data] Entity", async () => { describe("[data] Entity", async () => {
const entity = new Entity("test", [ const entity = new Entity("test", [
@@ -47,4 +47,16 @@ describe("[data] Entity", async () => {
entity.addField(field); entity.addField(field);
expect(entity.getField("new_field")).toBe(field); expect(entity.getField("new_field")).toBe(field);
}); });
test("custom primary", async () => {
const customPrimaryEntity = new Entity("custom", [
new PrimaryField("custom", { format: "uuid" }),
new TextField("name"),
]);
expect(customPrimaryEntity.getPrimaryField().name).toEqual("custom");
expect(customPrimaryEntity.getPrimaryField().format).toEqual("uuid");
expect(customPrimaryEntity.getSelect()).toEqual(["custom", "name"]);
expect(customPrimaryEntity.getField("id")).toBeUndefined(); // no auto id
});
}); });
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
import { PrimaryField } from "data/fields"; import { PrimaryField } from "data/fields";
describe("[data] PrimaryField", async () => { describe("[data] PrimaryField", async () => {
const field = new PrimaryField("primary"); const field = new PrimaryField("primary", { fillable: false });
test("name", async () => { test("name", async () => {
expect(field.name).toBe("primary"); expect(field.name).toBe("primary");
@@ -64,3 +64,23 @@ describe("[data] PrimaryField", async () => {
}); });
}); });
}); });
describe("[data] PrimaryField (fillable)", async () => {
const field = new PrimaryField("primary", { fillable: true });
test("isFillable", async () => {
expect(field.isFillable()).toBe(true);
});
test("transformPersist/Retrieve", async () => {
expect(field.transformPersist(null)).resolves.toBeUndefined();
expect(field.transformRetrieve(1)).toBe(1);
});
test("format", () => {
const uuid = new PrimaryField("integer", { format: "uuid" });
expect(uuid.transformPersist(null)).toBeDefined();
});
});
+7 -3
View File
@@ -15,7 +15,7 @@ export const entityConfigSchema = s
name: s.string(), name: s.string(),
name_singular: s.string(), name_singular: s.string(),
description: s.string(), description: s.string(),
sort_field: s.string({ default: config.data.default_primary_field }), sort_field: s.string(),
sort_dir: s.string({ enum: ["asc", "desc"], default: "asc" }), sort_dir: s.string({ enum: ["asc", "desc"], default: "asc" }),
primary_format: s.string({ enum: primaryFieldTypes }), primary_format: s.string({ enum: primaryFieldTypes }),
}, },
@@ -82,6 +82,10 @@ export class Entity<
fields.forEach((field) => this.addField(field)); fields.forEach((field) => this.addField(field));
} }
if (!this.config.sort_field) {
this.config.sort_field = this.getPrimaryField().name;
}
if (type) this.type = type; if (type) this.type = type;
this[ENTITY_SYMBOL] = true; this[ENTITY_SYMBOL] = true;
} }
@@ -115,7 +119,7 @@ export class Entity<
getDefaultSort() { getDefaultSort() {
return { return {
by: this.config.sort_field ?? "id", by: this.config.sort_field ?? this.getPrimaryField().name,
dir: this.config.sort_dir ?? "asc", dir: this.config.sort_dir ?? "asc",
}; };
} }
@@ -167,7 +171,7 @@ export class Entity<
} }
getPrimaryField(): PrimaryField { getPrimaryField(): PrimaryField {
return this.fields[0] as PrimaryField; return this.fields.find((field) => field.type === "primary") as PrimaryField;
} }
id(): PrimaryField { id(): PrimaryField {
+12 -2
View File
@@ -24,7 +24,11 @@ export class PrimaryField<Required extends true | false = false> extends Field<
override readonly type = "primary"; override readonly type = "primary";
constructor(name: string = config.data.default_primary_field, cfg?: PrimaryFieldConfig) { constructor(name: string = config.data.default_primary_field, cfg?: PrimaryFieldConfig) {
super(name, { ...cfg, fillable: false, required: false }); super(name, {
...cfg,
fillable: cfg?.fillable ?? name !== config.data.default_primary_field,
required: false,
});
} }
override isRequired(): boolean { override isRequired(): boolean {
@@ -60,7 +64,13 @@ export class PrimaryField<Required extends true | false = false> extends Field<
return undefined; return undefined;
} }
override async transformPersist(value: any): Promise<number> { override async transformPersist(value: any): Promise<any> {
if (this.isFillable()) {
if (value == null) {
return this.getNewValue();
}
return value;
}
throw new Error("PrimaryField: This function should not be called"); throw new Error("PrimaryField: This function should not be called");
} }
+10 -1
View File
@@ -21,6 +21,8 @@ import {
type NumberFieldConfig, type NumberFieldConfig,
TextField, TextField,
type TextFieldConfig, type TextFieldConfig,
PrimaryField,
type PrimaryFieldConfig,
} from "data/fields"; } from "data/fields";
import { Entity, type EntityConfig, type TEntityType } from "data/entities"; import { Entity, type EntityConfig, type TEntityType } from "data/entities";
@@ -51,6 +53,8 @@ type Options<Config = any> = {
}; };
const FieldMap = { const FieldMap = {
primary: (o: Omit<Options, "required">) =>
new PrimaryField(o.field_name, { ...o.config, required: true }),
text: (o: Options) => new TextField(o.field_name, { ...o.config, required: o.is_required }), text: (o: Options) => new TextField(o.field_name, { ...o.config, required: o.is_required }),
number: (o: Options) => new NumberField(o.field_name, { ...o.config, required: o.is_required }), number: (o: Options) => new NumberField(o.field_name, { ...o.config, required: o.is_required }),
date: (o: Options) => new DateField(o.field_name, { ...o.config, required: o.is_required }), date: (o: Options) => new DateField(o.field_name, { ...o.config, required: o.is_required }),
@@ -108,6 +112,11 @@ export class FieldPrototype {
} }
} }
export function primary(
config?: Omit<PrimaryFieldConfig, "required">,
): PrimaryField<false> & { required: () => PrimaryField<true> } {
return new FieldPrototype("primary", config, false) as any;
}
export function text( export function text(
config?: Omit<TextFieldConfig, "required">, config?: Omit<TextFieldConfig, "required">,
): TextField<false> & { required: () => TextField<true> } { ): TextField<false> & { required: () => TextField<true> } {
@@ -410,5 +419,5 @@ export type Schemas<T extends Record<string, Entity>> = {
}; };
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> | string } & InsertSchema<T>>;
export type FieldSchema<T> = Simplify<OptionalUndefined<InferFields<T>>>; export type FieldSchema<T> = Simplify<OptionalUndefined<InferFields<T>>>;
+1
View File
@@ -173,6 +173,7 @@ export {
// data prototype // data prototype
export { export {
primary,
text, text,
number, number,
date, date,