mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
refactored mutator to listen for returned data from event listeners
This commit is contained in:
@@ -5,8 +5,13 @@ import type { Generated } from "kysely";
|
|||||||
|
|
||||||
export type PrimaryFieldType = number | Generated<number>;
|
export type PrimaryFieldType = number | Generated<number>;
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noEmptyInterface: <explanation>
|
export interface DB {
|
||||||
export interface DB {}
|
// make sure to make unknown as "any"
|
||||||
|
[key: string]: {
|
||||||
|
id: PrimaryFieldType;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export type EventClass = {
|
||||||
|
new (params: any): Event<any, any>;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
||||||
export abstract class Event<Params = any, Returning = void> {
|
export abstract class Event<Params = any, Returning = void> {
|
||||||
_returning!: Returning;
|
_returning!: Returning;
|
||||||
|
|
||||||
@@ -9,7 +14,9 @@ export abstract class Event<Params = any, Returning = void> {
|
|||||||
params: Params;
|
params: Params;
|
||||||
returned: boolean = false;
|
returned: boolean = false;
|
||||||
|
|
||||||
validate(value: Returning): Event<Params, Returning> | void {}
|
validate(value: Returning): Event<Params, Returning> | void {
|
||||||
|
throw new EventReturnedWithoutValidation(this as any, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected clone<This extends Event<Params, Returning> = Event<Params, Returning>>(
|
protected clone<This extends Event<Params, Returning> = Event<Params, Returning>>(
|
||||||
this: This,
|
this: This,
|
||||||
@@ -39,3 +46,13 @@ export class InvalidEventReturn extends Error {
|
|||||||
super(`Expected "${expected}", got "${given}"`);
|
super(`Expected "${expected}", got "${given}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class EventReturnedWithoutValidation extends Error {
|
||||||
|
constructor(
|
||||||
|
event: EventClass,
|
||||||
|
public data: any
|
||||||
|
) {
|
||||||
|
// @ts-expect-error slug is static
|
||||||
|
super(`Event "${event.constructor.slug}" returned without validation`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Event, InvalidEventReturn } from "./Event";
|
import { type Event, type EventClass, InvalidEventReturn } from "./Event";
|
||||||
import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener";
|
import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener";
|
||||||
|
|
||||||
export type RegisterListenerConfig =
|
export type RegisterListenerConfig =
|
||||||
@@ -12,10 +12,8 @@ export interface EmitsEvents {
|
|||||||
emgr: EventManager;
|
emgr: EventManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventClass = {
|
// for compatibility, moved it to Event.ts
|
||||||
new (params: any): Event<any, any>;
|
export type { EventClass };
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class EventManager<
|
export class EventManager<
|
||||||
RegisteredEvents extends Record<string, EventClass> = Record<string, EventClass>
|
RegisteredEvents extends Record<string, EventClass> = Record<string, EventClass>
|
||||||
|
|||||||
@@ -192,10 +192,26 @@ export class Entity<
|
|||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @todo: add tests
|
||||||
isValidData(data: EntityData, context: TActionContext, explain?: boolean): boolean {
|
isValidData(data: EntityData, context: TActionContext, explain?: boolean): boolean {
|
||||||
|
if (typeof data !== "object") {
|
||||||
|
if (explain) {
|
||||||
|
throw new Error(`Entity "${this.name}" data must be an object`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fields = this.getFillableFields(context, false);
|
const fields = this.getFillableFields(context, false);
|
||||||
//const fields = this.fields;
|
const field_names = fields.map((f) => f.name);
|
||||||
//console.log("data", data);
|
const given_keys = Object.keys(data);
|
||||||
|
|
||||||
|
if (given_keys.some((key) => !field_names.includes(key))) {
|
||||||
|
if (explain) {
|
||||||
|
throw new Error(
|
||||||
|
`Entity "${this.name}" data must only contain known keys, got: "${given_keys}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (!field.isValid(data[field.name], context)) {
|
if (!field.isValid(data[field.name], context)) {
|
||||||
console.log("Entity.isValidData:invalid", context, field.name, data[field.name]);
|
console.log("Entity.isValidData:invalid", context, field.name, data[field.name]);
|
||||||
|
|||||||
@@ -132,14 +132,17 @@ export class Mutator<
|
|||||||
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: establish the original order from "data"
|
const result = await this.emgr.emit(
|
||||||
|
new Mutator.Events.MutatorInsertBefore({ entity, data: data as any })
|
||||||
|
);
|
||||||
|
|
||||||
|
// if listener returned, take what's returned
|
||||||
|
const _data = result.returned ? result.params.data : data;
|
||||||
const validatedData = {
|
const validatedData = {
|
||||||
...entity.getDefaultObject(),
|
...entity.getDefaultObject(),
|
||||||
...(await this.getValidatedData(data, "create"))
|
...(await this.getValidatedData(_data, "create"))
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.emgr.emit(new Mutator.Events.MutatorInsertBefore({ entity, data: validatedData }));
|
|
||||||
|
|
||||||
// check if required fields are present
|
// check if required fields are present
|
||||||
const required = entity.getRequiredFields();
|
const required = entity.getRequiredFields();
|
||||||
for (const field of required) {
|
for (const field of required) {
|
||||||
@@ -169,16 +172,17 @@ export class Mutator<
|
|||||||
throw new Error("ID must be provided for update");
|
throw new Error("ID must be provided for update");
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedData = await this.getValidatedData(data, "update");
|
const result = await this.emgr.emit(
|
||||||
|
|
||||||
await this.emgr.emit(
|
|
||||||
new Mutator.Events.MutatorUpdateBefore({
|
new Mutator.Events.MutatorUpdateBefore({
|
||||||
entity,
|
entity,
|
||||||
entityId: id,
|
entityId: id,
|
||||||
data: validatedData as any
|
data
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const _data = result.returned ? result.params.data : data;
|
||||||
|
const validatedData = await this.getValidatedData(_data, "update");
|
||||||
|
|
||||||
const query = this.conn
|
const query = this.conn
|
||||||
.updateTable(entity.name)
|
.updateTable(entity.name)
|
||||||
.set(validatedData as any)
|
.set(validatedData as any)
|
||||||
|
|||||||
@@ -1,20 +1,48 @@
|
|||||||
import type { PrimaryFieldType } from "core";
|
import type { PrimaryFieldType } from "core";
|
||||||
import { Event } from "core/events";
|
import { Event, InvalidEventReturn } from "core/events";
|
||||||
import type { Entity, EntityData } from "../entities";
|
import type { Entity, EntityData } from "../entities";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/data-query-impl";
|
||||||
|
|
||||||
export class MutatorInsertBefore extends Event<{ entity: Entity; data: EntityData }> {
|
export class MutatorInsertBefore extends Event<{ entity: Entity; data: EntityData }, EntityData> {
|
||||||
static override slug = "mutator-insert-before";
|
static override slug = "mutator-insert-before";
|
||||||
|
|
||||||
|
override validate(data: EntityData) {
|
||||||
|
const { entity } = this.params;
|
||||||
|
if (!entity.isValidData(data, "create")) {
|
||||||
|
throw new InvalidEventReturn("EntityData", "invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clone({
|
||||||
|
entity,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export class MutatorInsertAfter extends Event<{ entity: Entity; data: EntityData }> {
|
export class MutatorInsertAfter extends Event<{ entity: Entity; data: EntityData }> {
|
||||||
static override slug = "mutator-insert-after";
|
static override slug = "mutator-insert-after";
|
||||||
}
|
}
|
||||||
export class MutatorUpdateBefore extends Event<{
|
export class MutatorUpdateBefore extends Event<
|
||||||
|
{
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
entityId: PrimaryFieldType;
|
entityId: PrimaryFieldType;
|
||||||
data: EntityData;
|
data: EntityData;
|
||||||
}> {
|
},
|
||||||
|
EntityData
|
||||||
|
> {
|
||||||
static override slug = "mutator-update-before";
|
static override slug = "mutator-update-before";
|
||||||
|
|
||||||
|
override validate(data: EntityData) {
|
||||||
|
const { entity, ...rest } = this.params;
|
||||||
|
if (!entity.isValidData(data, "update")) {
|
||||||
|
throw new InvalidEventReturn("EntityData", "invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clone({
|
||||||
|
...rest,
|
||||||
|
entity,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export class MutatorUpdateAfter extends Event<{
|
export class MutatorUpdateAfter extends Event<{
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const useEntityQuery = <
|
|||||||
options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean }
|
options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean }
|
||||||
) => {
|
) => {
|
||||||
const api = useApi().data;
|
const api = useApi().data;
|
||||||
const key = makeKey(api, entity, id, query);
|
const key = makeKey(api, entity as string, id, query);
|
||||||
const { read, ...actions } = useEntity<Entity, Id>(entity, id);
|
const { read, ...actions } = useEntity<Entity, Id>(entity, id);
|
||||||
const fetcher = () => read(query);
|
const fetcher = () => read(query);
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ export const useEntityQuery = <
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mutateAll = async () => {
|
const mutateAll = async () => {
|
||||||
const entityKey = makeKey(api, entity);
|
const entityKey = makeKey(api, entity as string);
|
||||||
return mutate((key) => typeof key === "string" && key.startsWith(entityKey), undefined, {
|
return mutate((key) => typeof key === "string" && key.startsWith(entityKey), undefined, {
|
||||||
revalidate: true
|
revalidate: true
|
||||||
});
|
});
|
||||||
@@ -167,7 +167,7 @@ export async function mutateEntityCache<
|
|||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityKey = makeKey(api, entity);
|
const entityKey = makeKey(api, entity as string);
|
||||||
|
|
||||||
return mutate(
|
return mutate(
|
||||||
(key) => typeof key === "string" && key.startsWith(entityKey),
|
(key) => typeof key === "string" && key.startsWith(entityKey),
|
||||||
|
|||||||
Reference in New Issue
Block a user