mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
feat: improved abilities of plugins, moved schema fns to ctx
This commit is contained in:
@@ -2,19 +2,10 @@ import type { Guard } from "auth";
|
||||
import { type DebugLogger, SchemaObject } from "core";
|
||||
import type { EventManager } from "core/events";
|
||||
import type { Static, TSchema } from "core/utils";
|
||||
import {
|
||||
type Connection,
|
||||
type EntityIndex,
|
||||
type EntityManager,
|
||||
type Field,
|
||||
FieldPrototype,
|
||||
make,
|
||||
type em as prototypeEm,
|
||||
} from "data";
|
||||
import { Entity } from "data";
|
||||
import type { Connection, EntityManager } from "data";
|
||||
import type { Hono } from "hono";
|
||||
import { isEqual } from "lodash-es";
|
||||
import type { ServerEnv } from "modules/Controller";
|
||||
import type { ModuleHelper } from "./ModuleHelper";
|
||||
|
||||
export type ModuleBuildContext = {
|
||||
connection: Connection;
|
||||
@@ -24,6 +15,7 @@ export type ModuleBuildContext = {
|
||||
guard: Guard;
|
||||
logger: DebugLogger;
|
||||
flags: (typeof Module)["ctx_flags"];
|
||||
helper: ModuleHelper;
|
||||
};
|
||||
|
||||
export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = Static<Schema>> {
|
||||
@@ -141,80 +133,4 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
||||
toJSON(secrets?: boolean): Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
protected ensureEntity(entity: Entity) {
|
||||
const instance = this.ctx.em.entity(entity.name, true);
|
||||
|
||||
// check fields
|
||||
if (!instance) {
|
||||
this.ctx.em.addEntity(entity);
|
||||
this.ctx.flags.sync_required = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if exists, check all fields required are there
|
||||
// @todo: check if the field also equal
|
||||
for (const field of entity.fields) {
|
||||
const instanceField = instance.field(field.name);
|
||||
if (!instanceField) {
|
||||
instance.addField(field);
|
||||
this.ctx.flags.sync_required = true;
|
||||
} else {
|
||||
const changes = this.setEntityFieldConfigs(field, instanceField);
|
||||
if (changes > 0) {
|
||||
this.ctx.flags.sync_required = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replace entity (mainly to keep the ensured type)
|
||||
this.ctx.em.__replaceEntity(
|
||||
new Entity(instance.name, instance.fields, instance.config, entity.type),
|
||||
);
|
||||
}
|
||||
|
||||
protected ensureIndex(index: EntityIndex) {
|
||||
if (!this.ctx.em.hasIndex(index)) {
|
||||
this.ctx.em.addIndex(index);
|
||||
this.ctx.flags.sync_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected ensureSchema<Schema extends ReturnType<typeof prototypeEm>>(schema: Schema): Schema {
|
||||
Object.values(schema.entities ?? {}).forEach(this.ensureEntity.bind(this));
|
||||
schema.indices?.forEach(this.ensureIndex.bind(this));
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
protected setEntityFieldConfigs(
|
||||
parent: Field,
|
||||
child: Field,
|
||||
props: string[] = ["hidden", "fillable", "required"],
|
||||
) {
|
||||
let changes = 0;
|
||||
for (const prop of props) {
|
||||
if (!isEqual(child.config[prop], parent.config[prop])) {
|
||||
child.config[prop] = parent.config[prop];
|
||||
changes++;
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
protected replaceEntityField(
|
||||
_entity: string | Entity,
|
||||
field: Field | string,
|
||||
_newField: Field | FieldPrototype,
|
||||
) {
|
||||
const entity = this.ctx.em.entity(_entity);
|
||||
const name = typeof field === "string" ? field : field.name;
|
||||
const newField =
|
||||
_newField instanceof FieldPrototype ? make(name, _newField as any) : _newField;
|
||||
|
||||
// ensure keeping vital config
|
||||
this.setEntityFieldConfigs(entity.field(name)!, newField);
|
||||
|
||||
entity.__replaceField(name, newField);
|
||||
}
|
||||
}
|
||||
|
||||
113
app/src/modules/ModuleHelper.ts
Normal file
113
app/src/modules/ModuleHelper.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
type EntityIndex,
|
||||
type EntityRelation,
|
||||
type Field,
|
||||
type em as prototypeEm,
|
||||
FieldPrototype,
|
||||
make,
|
||||
Entity,
|
||||
entityTypes,
|
||||
} from "data";
|
||||
import { isEqual } from "lodash-es";
|
||||
import type { ModuleBuildContext } from "./Module";
|
||||
|
||||
export class ModuleHelper {
|
||||
constructor(protected ctx: Omit<ModuleBuildContext, "helper">) {}
|
||||
|
||||
get em() {
|
||||
return this.ctx.em;
|
||||
}
|
||||
|
||||
get flags() {
|
||||
return this.ctx.flags;
|
||||
}
|
||||
|
||||
ensureEntity(entity: Entity) {
|
||||
const instance = this.em.entity(entity.name, true);
|
||||
|
||||
// check fields
|
||||
if (!instance) {
|
||||
this.em.addEntity(entity);
|
||||
this.flags.sync_required = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if exists, check all fields required are there
|
||||
// @todo: potentially identify system and generated entities and take that as instance
|
||||
// @todo: check if the field also equal
|
||||
for (const field of entity.fields) {
|
||||
const instanceField = instance.field(field.name);
|
||||
if (!instanceField) {
|
||||
instance.addField(field);
|
||||
this.flags.sync_required = true;
|
||||
} else {
|
||||
const changes = this.setEntityFieldConfigs(field, instanceField);
|
||||
if (changes > 0) {
|
||||
this.flags.sync_required = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if type is different, keep the highest
|
||||
if (instance.type !== entity.type) {
|
||||
const instance_i = entityTypes.indexOf(instance.type);
|
||||
const entity_i = entityTypes.indexOf(entity.type);
|
||||
const type = entity_i > instance_i ? entity.type : instance.type;
|
||||
|
||||
this.em.__replaceEntity(new Entity(instance.name, instance.fields, instance.config, type));
|
||||
}
|
||||
}
|
||||
|
||||
ensureIndex(index: EntityIndex) {
|
||||
if (!this.em.hasIndex(index)) {
|
||||
this.em.addIndex(index);
|
||||
this.flags.sync_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
ensureRelation(relation: EntityRelation) {
|
||||
if (!this.em.relations.exists(relation)) {
|
||||
this.em.addRelation(relation);
|
||||
this.flags.sync_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
ensureSchema<Schema extends ReturnType<typeof prototypeEm>>(schema: Schema): Schema {
|
||||
Object.values(schema.entities ?? {}).forEach(this.ensureEntity.bind(this));
|
||||
schema.indices?.forEach(this.ensureIndex.bind(this));
|
||||
schema.relations?.forEach(this.ensureRelation.bind(this));
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
setEntityFieldConfigs(
|
||||
parent: Field,
|
||||
child: Field,
|
||||
props: string[] = ["hidden", "fillable", "required"],
|
||||
) {
|
||||
let changes = 0;
|
||||
for (const prop of props) {
|
||||
if (!isEqual(child.config[prop], parent.config[prop])) {
|
||||
child.config[prop] = parent.config[prop];
|
||||
changes++;
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
replaceEntityField(
|
||||
_entity: string | Entity,
|
||||
field: Field | string,
|
||||
_newField: Field | FieldPrototype,
|
||||
) {
|
||||
const entity = this.em.entity(_entity);
|
||||
const name = typeof field === "string" ? field : field.name;
|
||||
const newField =
|
||||
_newField instanceof FieldPrototype ? make(name, _newField as any) : _newField;
|
||||
|
||||
// ensure keeping vital config
|
||||
this.setEntityFieldConfigs(entity.field(name)!, newField);
|
||||
|
||||
entity.__replaceField(name, newField);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { AppMedia } from "../media/AppMedia";
|
||||
import type { ServerEnv } from "./Controller";
|
||||
import { Module, type ModuleBuildContext } from "./Module";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
import { ModuleHelper } from "./ModuleHelper";
|
||||
const { Type } = tbbox;
|
||||
|
||||
export type { ModuleBuildContext };
|
||||
@@ -92,6 +93,8 @@ export type ModuleManagerOptions = {
|
||||
trustFetched?: boolean;
|
||||
// runs when initial config provided on a fresh database
|
||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||
// called right after modules are built, before finish
|
||||
onModulesBuilt?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||
/** @deprecated */
|
||||
verbosity?: Verbosity;
|
||||
};
|
||||
@@ -267,7 +270,7 @@ export class ModuleManager {
|
||||
this.guard = new Guard();
|
||||
}
|
||||
|
||||
return {
|
||||
const ctx = {
|
||||
connection: this.connection,
|
||||
server: this.server,
|
||||
em: this.em,
|
||||
@@ -276,6 +279,11 @@ export class ModuleManager {
|
||||
flags: Module.ctx_flags,
|
||||
logger: this.logger,
|
||||
};
|
||||
|
||||
return {
|
||||
...ctx,
|
||||
helper: new ModuleHelper(ctx),
|
||||
};
|
||||
}
|
||||
|
||||
private async fetch(): Promise<ConfigTable | undefined> {
|
||||
@@ -549,6 +557,10 @@ export class ModuleManager {
|
||||
this._built = state.built = true;
|
||||
this.logger.log("modules built", ctx.flags);
|
||||
|
||||
if (this.options?.onModulesBuilt) {
|
||||
await this.options.onModulesBuilt(ctx);
|
||||
}
|
||||
|
||||
if (options?.ignoreFlags !== true) {
|
||||
if (ctx.flags.sync_required) {
|
||||
ctx.flags.sync_required = false;
|
||||
|
||||
Reference in New Issue
Block a user