mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
added pausing to event manager, added context aware entity schemas, fixed typings, first boot event, improved useEntityQuery mutation behavior
This commit is contained in:
@@ -12,13 +12,17 @@ import { SystemController } from "modules/server/SystemController";
|
|||||||
|
|
||||||
export type AppPlugin<DB> = (app: App<DB>) => void;
|
export type AppPlugin<DB> = (app: App<DB>) => void;
|
||||||
|
|
||||||
export class AppConfigUpdatedEvent extends Event<{ app: App }> {
|
abstract class AppEvent<A = {}> extends Event<{ app: App } & A> {}
|
||||||
|
export class AppConfigUpdatedEvent extends AppEvent {
|
||||||
static override slug = "app-config-updated";
|
static override slug = "app-config-updated";
|
||||||
}
|
}
|
||||||
export class AppBuiltEvent extends Event<{ app: App }> {
|
export class AppBuiltEvent extends AppEvent {
|
||||||
static override slug = "app-built";
|
static override slug = "app-built";
|
||||||
}
|
}
|
||||||
export const AppEvents = { AppConfigUpdatedEvent, AppBuiltEvent } as const;
|
export class AppFirstBoot extends AppEvent {
|
||||||
|
static override slug = "app-first-boot";
|
||||||
|
}
|
||||||
|
export const AppEvents = { AppConfigUpdatedEvent, AppBuiltEvent, AppFirstBoot } as const;
|
||||||
|
|
||||||
export type CreateAppConfig = {
|
export type CreateAppConfig = {
|
||||||
connection?:
|
connection?:
|
||||||
@@ -38,6 +42,7 @@ export class App<DB = any> {
|
|||||||
modules: ModuleManager;
|
modules: ModuleManager;
|
||||||
static readonly Events = AppEvents;
|
static readonly Events = AppEvents;
|
||||||
adminController?: AdminController;
|
adminController?: AdminController;
|
||||||
|
private trigger_first_boot = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private connection: Connection,
|
private connection: Connection,
|
||||||
@@ -49,9 +54,20 @@ export class App<DB = any> {
|
|||||||
...moduleManagerOptions,
|
...moduleManagerOptions,
|
||||||
initial: _initialConfig,
|
initial: _initialConfig,
|
||||||
onUpdated: async (key, config) => {
|
onUpdated: async (key, config) => {
|
||||||
//console.log("[APP] config updated", key, config);
|
// if the EventManager was disabled, we assume we shouldn't
|
||||||
|
// respond to events, such as "onUpdated".
|
||||||
|
if (!this.emgr.enabled) {
|
||||||
|
console.warn("[APP] config updated, but event manager is disabled, skip.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[APP] config updated", key);
|
||||||
await this.build({ sync: true, save: true });
|
await this.build({ sync: true, save: true });
|
||||||
await this.emgr.emit(new AppConfigUpdatedEvent({ app: this }));
|
await this.emgr.emit(new AppConfigUpdatedEvent({ app: this }));
|
||||||
|
},
|
||||||
|
onFirstBoot: async () => {
|
||||||
|
console.log("[APP] first boot");
|
||||||
|
this.trigger_first_boot = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.modules.ctx().emgr.registerEvents(AppEvents);
|
this.modules.ctx().emgr.registerEvents(AppEvents);
|
||||||
@@ -89,6 +105,12 @@ export class App<DB = any> {
|
|||||||
if (options?.save) {
|
if (options?.save) {
|
||||||
await this.modules.save();
|
await this.modules.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// first boot is set from ModuleManager when there wasn't a config table
|
||||||
|
if (this.trigger_first_boot) {
|
||||||
|
this.trigger_first_boot = false;
|
||||||
|
await this.emgr.emit(new AppFirstBoot({ app: this }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateConfig<Module extends keyof Modules>(module: Module) {
|
mutateConfig<Module extends keyof Modules>(module: Module) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export class EventManager<
|
|||||||
> {
|
> {
|
||||||
protected events: EventClass[] = [];
|
protected events: EventClass[] = [];
|
||||||
protected listeners: EventListener[] = [];
|
protected listeners: EventListener[] = [];
|
||||||
|
enabled: boolean = true;
|
||||||
|
|
||||||
constructor(events?: RegisteredEvents, listeners?: EventListener[]) {
|
constructor(events?: RegisteredEvents, listeners?: EventListener[]) {
|
||||||
if (events) {
|
if (events) {
|
||||||
@@ -28,6 +29,16 @@ export class EventManager<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this.enabled = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.enabled = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
clearEvents() {
|
clearEvents() {
|
||||||
this.events = [];
|
this.events = [];
|
||||||
return this;
|
return this;
|
||||||
@@ -39,6 +50,10 @@ export class EventManager<
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getListeners(): EventListener[] {
|
||||||
|
return [...this.listeners];
|
||||||
|
}
|
||||||
|
|
||||||
get Events(): { [K in keyof RegisteredEvents]: RegisteredEvents[K] } {
|
get Events(): { [K in keyof RegisteredEvents]: RegisteredEvents[K] } {
|
||||||
// proxy class to access events
|
// proxy class to access events
|
||||||
return new Proxy(this, {
|
return new Proxy(this, {
|
||||||
@@ -133,6 +148,11 @@ export class EventManager<
|
|||||||
async emit(event: Event) {
|
async emit(event: Event) {
|
||||||
// @ts-expect-error slug is static
|
// @ts-expect-error slug is static
|
||||||
const slug = event.constructor.slug;
|
const slug = event.constructor.slug;
|
||||||
|
if (!this.enabled) {
|
||||||
|
console.log("EventManager disabled, not emitting", slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.eventExists(event)) {
|
if (!this.eventExists(event)) {
|
||||||
throw new Error(`Event "${slug}" not registered`);
|
throw new Error(`Event "${slug}" not registered`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class DataApi<DB> extends ModuleApi<DataApiOptions> {
|
|||||||
|
|
||||||
createOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
createOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
entity: E,
|
entity: E,
|
||||||
input: Data
|
input: Omit<Data, "id">
|
||||||
) {
|
) {
|
||||||
return this.post<RepositoryResponse<Data>>([entity as any], input);
|
return this.post<RepositoryResponse<Data>>([entity as any], input);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ export class DataApi<DB> extends ModuleApi<DataApiOptions> {
|
|||||||
updateOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
updateOne<E extends keyof DB | string, Data = E extends keyof DB ? DB[E] : EntityData>(
|
||||||
entity: E,
|
entity: E,
|
||||||
id: PrimaryFieldType,
|
id: PrimaryFieldType,
|
||||||
input: Partial<Data>
|
input: Partial<Omit<Data, "id">>
|
||||||
) {
|
) {
|
||||||
return this.patch<RepositoryResponse<Data>>([entity as any, id], input);
|
return this.patch<RepositoryResponse<Data>>([entity as any, id], input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type ClassController, isDebug, tbValidator as tb } from "core";
|
import { type ClassController, isDebug, tbValidator as tb } from "core";
|
||||||
import { Type, objectCleanEmpty, objectTransform } from "core/utils";
|
import { StringEnum, Type, objectCleanEmpty, objectTransform } from "core/utils";
|
||||||
import {
|
import {
|
||||||
DataPermissions,
|
DataPermissions,
|
||||||
type EntityData,
|
type EntityData,
|
||||||
@@ -182,19 +182,25 @@ export class DataController implements ClassController {
|
|||||||
})
|
})
|
||||||
// read schema
|
// read schema
|
||||||
.get(
|
.get(
|
||||||
"/schemas/:entity",
|
"/schemas/:entity/:context?",
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb(
|
||||||
|
"param",
|
||||||
|
Type.Object({
|
||||||
|
entity: Type.String(),
|
||||||
|
context: Type.Optional(StringEnum(["create", "update"]))
|
||||||
|
})
|
||||||
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
this.guard.throwUnlessGranted(DataPermissions.entityRead);
|
||||||
|
|
||||||
//console.log("request", c.req.raw);
|
//console.log("request", c.req.raw);
|
||||||
const { entity } = c.req.param();
|
const { entity, context } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
console.log("not found", entity, definedEntities);
|
console.log("not found", entity, definedEntities);
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
const _entity = this.em.entity(entity);
|
const _entity = this.em.entity(entity);
|
||||||
const schema = _entity.toSchema();
|
const schema = _entity.toSchema({ context } as any);
|
||||||
const url = new URL(c.req.url);
|
const url = new URL(c.req.url);
|
||||||
const base = `${url.origin}${this.config.basepath}`;
|
const base = `${url.origin}${this.config.basepath}`;
|
||||||
const $id = `${this.config.basepath}/schemas/${entity}`;
|
const $id = `${this.config.basepath}/schemas/${entity}`;
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export class Entity<
|
|||||||
}
|
}
|
||||||
|
|
||||||
get label(): string {
|
get label(): string {
|
||||||
return snakeToPascalWithSpaces(this.config.name || this.name);
|
return this.config.name ?? snakeToPascalWithSpaces(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
field(name: string): Field | undefined {
|
field(name: string): Field | undefined {
|
||||||
@@ -210,21 +210,34 @@ export class Entity<
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSchema(clean?: boolean): object {
|
toSchema(options?: { clean: boolean; context?: "create" | "update" }): object {
|
||||||
const fields = Object.fromEntries(this.fields.map((field) => [field.name, field]));
|
let fields: Field[];
|
||||||
|
switch (options?.context) {
|
||||||
|
case "create":
|
||||||
|
case "update":
|
||||||
|
fields = this.getFillableFields(options.context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fields = this.getFields(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _fields = Object.fromEntries(fields.map((field) => [field.name, field]));
|
||||||
const schema = Type.Object(
|
const schema = Type.Object(
|
||||||
transformObject(fields, (field) => ({
|
transformObject(_fields, (field) => {
|
||||||
title: field.config.label,
|
//const hidden = field.isHidden(options?.context);
|
||||||
$comment: field.config.description,
|
const fillable = field.isFillable(options?.context);
|
||||||
$field: field.type,
|
return {
|
||||||
readOnly: !field.isFillable("update") ? true : undefined,
|
title: field.config.label,
|
||||||
writeOnly: !field.isFillable("create") ? true : undefined,
|
$comment: field.config.description,
|
||||||
...field.toJsonSchema()
|
$field: field.type,
|
||||||
})),
|
readOnly: !fillable ? true : undefined,
|
||||||
|
...field.toJsonSchema()
|
||||||
|
};
|
||||||
|
}),
|
||||||
{ additionalProperties: false }
|
{ additionalProperties: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
return clean ? JSON.parse(JSON.stringify(schema)) : schema;
|
return options?.clean ? JSON.parse(JSON.stringify(schema)) : schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
|
|||||||
@@ -25,8 +25,12 @@ export type MutatorResponse<T = EntityData[]> = {
|
|||||||
data: T;
|
data: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "id">>
|
export class Mutator<
|
||||||
implements EmitsEvents
|
DB = any,
|
||||||
|
TB extends keyof DB = any,
|
||||||
|
Output = DB[TB],
|
||||||
|
Input = Omit<Output, "id">
|
||||||
|
> implements EmitsEvents
|
||||||
{
|
{
|
||||||
em: EntityManager<DB>;
|
em: EntityManager<DB>;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
@@ -122,7 +126,7 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
|
|||||||
return { ...response, data: data[0]! };
|
return { ...response, data: data[0]! };
|
||||||
}
|
}
|
||||||
|
|
||||||
async insertOne(data: Data): Promise<MutatorResponse<DB[TB]>> {
|
async insertOne(data: Input): Promise<MutatorResponse<Output>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
||||||
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
||||||
@@ -159,7 +163,7 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
|
|||||||
return res as any;
|
return res as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(id: PrimaryFieldType, data: Data): Promise<MutatorResponse<DB[TB]>> {
|
async updateOne(id: PrimaryFieldType, data: Input): Promise<MutatorResponse<Output>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!Number.isInteger(id)) {
|
||||||
throw new Error("ID must be provided for update");
|
throw new Error("ID must be provided for update");
|
||||||
@@ -190,7 +194,7 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
|
|||||||
return res as any;
|
return res as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<DB[TB]>> {
|
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<Output>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!Number.isInteger(id)) {
|
||||||
throw new Error("ID must be provided for deletion");
|
throw new Error("ID must be provided for deletion");
|
||||||
@@ -256,7 +260,7 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @todo: decide whether entries should be deleted all at once or one by one (for events)
|
// @todo: decide whether entries should be deleted all at once or one by one (for events)
|
||||||
async deleteWhere(where?: RepoQuery["where"]): Promise<MutatorResponse<DB[TB][]>> {
|
async deleteWhere(where?: RepoQuery["where"]): Promise<MutatorResponse<Output[]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
|
|
||||||
const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning(
|
const qb = this.appendWhere(this.conn.deleteFrom(entity.name), where).returning(
|
||||||
@@ -266,7 +270,7 @@ export class Mutator<DB = any, TB extends keyof DB = any, Data = Omit<DB[TB], "i
|
|||||||
return (await this.many(qb)) as any;
|
return (await this.many(qb)) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWhere(data: Data, where?: RepoQuery["where"]): Promise<MutatorResponse<DB[TB][]>> {
|
async updateWhere(data: Input, where?: RepoQuery["where"]): Promise<MutatorResponse<Output[]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
const validatedData = await this.getValidatedData(data, "update");
|
const validatedData = await this.getValidatedData(data, "update");
|
||||||
|
|
||||||
@@ -277,7 +281,7 @@ 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][]>> {
|
async insertMany(data: Input[]): Promise<MutatorResponse<Output[]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
if (entity.type === "system" && this.__unstable_disable_system_entity_creation) {
|
||||||
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
throw new Error(`Creation of system entity "${entity.name}" is disabled`);
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export type ModuleManagerOptions = {
|
|||||||
module: Module,
|
module: Module,
|
||||||
config: ModuleConfigs[Module]
|
config: ModuleConfigs[Module]
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
// triggered when no config table existed
|
||||||
|
onFirstBoot?: () => Promise<void>;
|
||||||
// base path for the hono instance
|
// base path for the hono instance
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
// doesn't perform validity checks for given/fetched config
|
// doesn't perform validity checks for given/fetched config
|
||||||
@@ -480,6 +482,9 @@ export class ModuleManager {
|
|||||||
// perform a sync
|
// perform a sync
|
||||||
await ctx.em.schema().sync({ force: true });
|
await ctx.em.schema().sync({ force: true });
|
||||||
await this.options?.seed?.(ctx);
|
await this.options?.seed?.(ctx);
|
||||||
|
|
||||||
|
// run first boot event
|
||||||
|
await this.options?.onFirstBoot?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
get<K extends keyof Modules>(key: K): Modules[K] {
|
get<K extends keyof Modules>(key: K): Modules[K] {
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import type { PrimaryFieldType } from "core";
|
import type { PrimaryFieldType } from "core";
|
||||||
import { objectTransform } from "core/utils";
|
import { encodeSearch, objectTransform } from "core/utils";
|
||||||
import type { EntityData, RepoQuery } from "data";
|
import type { EntityData, RepoQuery } from "data";
|
||||||
import type { ModuleApi, ResponseObject } from "modules/ModuleApi";
|
import type { ModuleApi, ResponseObject } from "modules/ModuleApi";
|
||||||
import useSWR, { type SWRConfiguration, useSWRConfig } from "swr";
|
import useSWR, { type SWRConfiguration, mutate } from "swr";
|
||||||
import { useApi } from "ui/client";
|
import { useApi } from "ui/client";
|
||||||
|
|
||||||
export class UseEntityApiError<Payload = any> extends Error {
|
export class UseEntityApiError<Payload = any> extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public payload: Payload,
|
public response: ResponseObject<Payload>,
|
||||||
public response: Response,
|
fallback?: string
|
||||||
message?: string
|
|
||||||
) {
|
) {
|
||||||
|
let message = fallback;
|
||||||
|
if ("error" in response) {
|
||||||
|
message = response.error as string;
|
||||||
|
if (fallback) {
|
||||||
|
message = `${fallback}: ${message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super(message ?? "UseEntityApiError");
|
super(message ?? "UseEntityApiError");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,14 +45,14 @@ export const useEntity = <
|
|||||||
create: async (input: Omit<Data, "id">) => {
|
create: async (input: Omit<Data, "id">) => {
|
||||||
const res = await api.createOne(entity, input);
|
const res = await api.createOne(entity, input);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to create entity");
|
throw new UseEntityApiError(res, `Failed to create entity "${entity}"`);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
read: async (query: Partial<RepoQuery> = {}) => {
|
read: async (query: Partial<RepoQuery> = {}) => {
|
||||||
const res = id ? await api.readOne(entity, id!, query) : await api.readMany(entity, query);
|
const res = id ? await api.readOne(entity, id!, query) : await api.readMany(entity, query);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to read entity");
|
throw new UseEntityApiError(res as any, `Failed to read entity "${entity}"`);
|
||||||
}
|
}
|
||||||
// must be manually typed
|
// must be manually typed
|
||||||
return res as unknown as Id extends undefined
|
return res as unknown as Id extends undefined
|
||||||
@@ -58,7 +65,7 @@ export const useEntity = <
|
|||||||
}
|
}
|
||||||
const res = await api.updateOne(entity, _id, input);
|
const res = await api.updateOne(entity, _id, input);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to update entity");
|
throw new UseEntityApiError(res, `Failed to update entity "${entity}"`);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
@@ -69,19 +76,26 @@ export const useEntity = <
|
|||||||
|
|
||||||
const res = await api.deleteOne(entity, _id);
|
const res = await api.deleteOne(entity, _id);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new UseEntityApiError(res.data, res.res, "Failed to delete entity");
|
throw new UseEntityApiError(res, `Failed to delete entity "${entity}"`);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function makeKey(api: ModuleApi, entity: string, id?: PrimaryFieldType) {
|
// @todo: try to get from ModuleApi directly
|
||||||
|
export function makeKey(
|
||||||
|
api: ModuleApi,
|
||||||
|
entity: string,
|
||||||
|
id?: PrimaryFieldType,
|
||||||
|
query?: Partial<RepoQuery>
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
"/" +
|
"/" +
|
||||||
[...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])]
|
[...(api.options?.basepath?.split("/") ?? []), entity, ...(id ? [id] : [])]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("/")
|
.join("/") +
|
||||||
|
(query ? "?" + encodeSearch(query) : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,29 +106,36 @@ export const useEntityQuery = <
|
|||||||
entity: Entity,
|
entity: Entity,
|
||||||
id?: Id,
|
id?: Id,
|
||||||
query?: Partial<RepoQuery>,
|
query?: Partial<RepoQuery>,
|
||||||
options?: SWRConfiguration & { enabled?: boolean }
|
options?: SWRConfiguration & { enabled?: boolean; revalidateOnMutate?: boolean }
|
||||||
) => {
|
) => {
|
||||||
const { mutate } = useSWRConfig();
|
|
||||||
const api = useApi().data;
|
const api = useApi().data;
|
||||||
const key = makeKey(api, entity, id);
|
const key = makeKey(api, entity, 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);
|
||||||
|
|
||||||
type T = Awaited<ReturnType<typeof fetcher>>;
|
type T = Awaited<ReturnType<typeof fetcher>>;
|
||||||
const swr = useSWR<T>(options?.enabled === false ? null : key, fetcher as any, {
|
const swr = useSWR<T>(options?.enabled === false ? null : key, fetcher as any, {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
keepPreviousData: false,
|
keepPreviousData: true,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mutateAll = async () => {
|
||||||
|
const entityKey = makeKey(api, entity);
|
||||||
|
return mutate((key) => typeof key === "string" && key.startsWith(entityKey), undefined, {
|
||||||
|
revalidate: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const mapped = objectTransform(actions, (action) => {
|
const mapped = objectTransform(actions, (action) => {
|
||||||
return async (...args: any) => {
|
return async (...args: any) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const res = await action(...args);
|
const res = await action(...args);
|
||||||
|
|
||||||
// mutate the key + list key
|
// mutate all keys of entity by default
|
||||||
mutate(key);
|
if (options?.revalidateOnMutate !== false) {
|
||||||
if (id) mutate(makeKey(api, entity));
|
await mutateAll();
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
|
}) as Omit<ReturnType<typeof useEntity<Entity, Id>>, "read">;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function DataEntityUpdate({ params }) {
|
|||||||
data: {
|
data: {
|
||||||
data: data as any,
|
data: data as any,
|
||||||
entity: entity.toJSON(),
|
entity: entity.toJSON(),
|
||||||
schema: entity.toSchema(true),
|
schema: entity.toSchema({ clean: true }),
|
||||||
form: Form.state.values,
|
form: Form.state.values,
|
||||||
state: Form.state
|
state: Form.state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ export default function SWRAndAPI() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!comment) return;
|
if (!comment) return;
|
||||||
|
|
||||||
await r.mutate(async () => {
|
/*await r.mutate(async () => {
|
||||||
const res = await r.api.data.updateOne("comments", comment.id, {
|
const res = await r.api.data.updateOne("comments", comment.id, {
|
||||||
content: text
|
content: text
|
||||||
});
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
});
|
});*/
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ export default function SwrAndDataApi() {
|
|||||||
|
|
||||||
function QueryDataApi() {
|
function QueryDataApi() {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const { data, update, ...r } = useEntityQuery("comments", 2);
|
const { data, update, ...r } = useEntityQuery("comments", 2, {
|
||||||
|
sort: { by: "id", dir: "desc" }
|
||||||
|
});
|
||||||
const comment = data ? data : null;
|
const comment = data ? data : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ export default {
|
|||||||
async fetch(request: Request) {
|
async fetch(request: Request) {
|
||||||
const app = App.create({ connection });
|
const app = App.create({ connection });
|
||||||
|
|
||||||
app.emgr.on(
|
app.emgr.onEvent(
|
||||||
"app-built",
|
App.Events.AppBuiltEvent,
|
||||||
async () => {
|
async () => {
|
||||||
app.registerAdminController({ forceDev: true });
|
app.registerAdminController({ forceDev: true });
|
||||||
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
|
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
|
||||||
|
|||||||
Reference in New Issue
Block a user