@scenemesh/entity-engine
Version:
一个“元数据驱动 + 组件适配 + 动态关系 + 视图管线”式的实体引擎。以 **Model + View + FieldType + SuiteAdapter + DataSource** 为五大支点,统一 CRUD / 查询 / 引用管理 / 视图渲染 / 扩展注册,支持在运行期无侵入拼装出 **表单、网格、主从、看板、仪表盘、流程/树形视图** 等多形态界面。
510 lines (496 loc) • 16 kB
JavaScript
"use client";
import {
BuildinModule,
EntityActionRegistry,
EntityComponentRegistry,
EntityDataSourceFactory,
EntityEngine,
EntityEngineProvider,
EntityEngineThemeProvider,
EntityEventRegistry,
EntityFieldDelegate,
EntityMetaRegistry,
EntityModelDelegate,
EntityNamedRenderer,
EntityPermissionGuard,
EntitySessionManager,
EntitySuiteAdapterProvider,
EntityView,
EntityViewContainer,
EntityViewDelegate,
EntityViewFieldDelegate,
EntityWidget,
EntityWidgetRenderer,
ModelFieldTyperRegistry,
TRPCEntityObjectDataSource,
ViewContainerProvider,
entityEngineDefaultTheme,
getEntityEngine,
toDataSourceHook,
useContainerRouter,
useEntityEngine,
useEntityEngineRouter,
useEntityEngineTheme,
useEntityPermissionGuard,
useEntitySession,
useEntitySuiteAdapter,
useMasterDetailViewContainer
} from "./chunk-2MQEKMBU.mjs";
// src/core/types/session.types.ts
var EntitySession = class {
constructor(sessionId, userInfo, update = () => {
}) {
this.sessionId = sessionId;
this.userInfo = userInfo;
this.update = update;
}
isAuthenticated() {
return !!this.userInfo;
}
};
// src/components/renderers/view-inspector/index.tsx
import React2 from "react";
import { modals } from "@mantine/modals";
import JsonView from "@uiw/react-json-view";
import { Tabs, ActionIcon } from "@mantine/core";
import { ViewIcon, DatabaseIcon, Settings2Icon } from "lucide-react";
// src/lib/hooks/utils/use-async/use-async.ts
import { useState, useDeferredValue } from "react";
// src/lib/hooks/utils/use-async-effect/use-async-effect.ts
import { useRef, useEffect } from "react";
function useAsyncEffect(effect, deps) {
const abortRef = useRef(null);
useEffect(() => {
abortRef.current = new AbortController();
return () => {
abortRef.current?.abort();
};
}, []);
useEffect(() => {
let canceled = false;
let result;
const load = async () => {
const signal = abortRef.current.signal;
if (canceled || signal.aborted) {
return;
}
result = await effect(signal);
};
queueMicrotask(load);
return () => {
canceled = true;
if (result) {
result();
}
};
}, deps);
}
// src/lib/hooks/utils/use-async/use-async.ts
function useAsync(func, deps) {
const [{ state, data, error }, setState] = useState({});
useAsyncEffect(async () => {
setState((draft) => ({ ...draft, state: "loading" }));
try {
const result = await func();
setState({ state: "hasData", data: result });
} catch (e) {
setState((draft) => ({ ...draft, state: "hasError", error: e }));
}
}, deps);
return {
state: state ?? "loading",
data,
error
};
}
function useAsyncWithCache(func, deps) {
const [internalState, setInternalState] = useState({ state: "loading" });
const deferredResult = useDeferredValue(internalState);
const [loadingState, setLoadingState] = useState(false);
useAsyncEffect(async () => {
setLoadingState(true);
try {
const result = await func();
setInternalState({ state: "hasData", data: result, error: void 0 });
} catch (e) {
setInternalState({ state: "hasError", data: void 0, error: e });
} finally {
setLoadingState(false);
}
}, deps);
return {
state: loadingState ? "loading" : deferredResult.state ?? "loading",
data: deferredResult.data,
error: deferredResult.error
};
}
// src/services/api/trpc/react.tsx
import * as React from "react";
import SuperJSON2 from "superjson";
import { createTRPCReact } from "@trpc/react-query";
import { loggerLink, httpBatchStreamLink } from "@trpc/client";
import { QueryClientProvider } from "@tanstack/react-query";
// src/services/api/trpc/query-client.ts
import SuperJSON from "superjson";
import { QueryClient, defaultShouldDehydrateQuery } from "@tanstack/react-query";
var createQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 30 * 1e3
},
dehydrate: {
serializeData: SuperJSON.serialize,
shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending"
},
hydrate: {
deserializeData: SuperJSON.deserialize
}
}
});
// src/services/api/trpc/react.tsx
import { jsx } from "react/jsx-runtime";
var clientQueryClientSingleton = void 0;
var getQueryClient = () => {
if (typeof window === "undefined") {
return createQueryClient();
}
clientQueryClientSingleton ?? (clientQueryClientSingleton = createQueryClient());
return clientQueryClientSingleton;
};
var api = createTRPCReact();
function TRPCReactProvider(props) {
const engine = useEntityEngine();
const queryClient = getQueryClient();
const [trpcClient] = React.useState(
() => api.createClient({
links: [
loggerLink({
enabled: (op) => false
// process.env.NODE_ENV === 'development' ||
// (op.direction === 'down' && op.result instanceof Error),
}),
httpBatchStreamLink({
transformer: SuperJSON2,
url: engine.settings.getUrl("/trpc"),
///api/ee/trpc
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
}
})
]
})
);
return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(api.Provider, { client: trpcClient, queryClient, children: props.children }) });
}
// src/core/engine/engine.initializer.ts
var EngineInitializer = class {
constructor(args) {
this._config = args.config;
this._suiteAdapters = args.suiteAdapters;
this._renderers = args.renderers;
this._fieldTypers = args.fieldTypers;
this._settings = args.settings;
this._modules = args.modules;
this._sessionProvider = args.sessionProvider;
}
async init(engine) {
if (this._config) {
const { models, views } = this._config;
if (models.length > 0) {
for (const model of models) {
engine.metaRegistry.registerModel(model);
}
}
if (views.length > 0) {
for (const view of views) {
engine.metaRegistry.registerView(view);
}
}
}
if (this._suiteAdapters && this._suiteAdapters.length > 0) {
for (const adapter of this._suiteAdapters) {
engine.componentRegistry.registerAdapter(adapter);
}
}
if (this._renderers && this._renderers.length > 0) {
for (const renderer of this._renderers) {
engine.componentRegistry.registerRenderer(renderer);
}
}
if (this._fieldTypers && this._fieldTypers.length > 0) {
for (const typer of this._fieldTypers) {
engine.fieldTyperRegistry.registerFieldTyper(typer);
}
}
if (this._modules && this._modules.length > 0) {
for (const mod of this._modules) {
await engine.moduleRegistry.registerModule(mod, true);
}
}
if (this._sessionProvider) {
engine.sessionManager.setProvider(this._sessionProvider);
}
if (this._settings) {
engine.settings.setBaseUrl(this._settings.baseUrl || "");
engine.settings.setEndpoint(
this._settings.endpoint || process.env.EE_SERVICE_ROOT_PATH || "/api/ee"
);
engine.settings.authenticationEnabled = this._settings.authenticationEnabled || false;
}
console.log(
`Entity Engine initialized with models(${this._config?.models.length || 0}) and views(${this._config?.views.length || 0})`
);
}
};
// src/uikit/provider/entity-engine-provider-factory.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
function createEntityEngineProvider(config) {
process.env.EE_SERVICE_ROOT_PATH = config.settings?.endpoint || "/api/ee";
const initializer = new EngineInitializer({
config: config.config,
suiteAdapters: config.suiteAdapters,
renderers: config.renderers,
fieldTypers: config.fieldTypers,
modules: config.modules,
settings: config.settings
});
const ConfiguredProvider = ({ children }) => /* @__PURE__ */ jsx2(
EntitySuiteAdapterProvider,
{
adapter: config.suiteAdapter || { suiteName: "build-in", suiteVersion: "1.0.0" },
children: /* @__PURE__ */ jsx2(
EntityEngineProvider,
{
initializer,
loading: config.loading,
router: config.router,
permissionGuard: config.permissionGuard,
children: /* @__PURE__ */ jsx2(EntityEngineThemeProvider, { theme: config.theme, children: /* @__PURE__ */ jsx2(TRPCReactProvider, { children }) })
}
)
}
);
ConfiguredProvider.displayName = "EntityEngineProvider(Configured)";
return ConfiguredProvider;
}
// src/uikit/surface/empty-symbol-comp.tsx
function EmptySymbol() {
return null;
}
// src/uikit/consumer/entity-object-consumer.tsx
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
function EntityObjectsConsumer(props) {
const { modelName, query, withReference, objectsRenderer, onError, loading } = props;
const engine = useEntityEngine();
const model = engine.metaRegistry.getModel(modelName);
if (!model) {
return null;
}
const datasource = engine.datasourceFactory.getDataSource();
const dsHook = toDataSourceHook(datasource);
const {
data,
loading: isLoading,
error
} = dsHook.useFindMany({
modelName,
query,
withAllReferences: withReference || false
});
if (isLoading || !data) {
return loading || /* @__PURE__ */ jsx3("div", { children: "Loading..." });
}
if (error) {
if (onError) {
return onError(error);
}
return /* @__PURE__ */ jsxs("div", { style: { color: "red" }, children: [
"Error: ",
error.message
] });
}
return /* @__PURE__ */ jsx3(Fragment, { children: objectsRenderer(data.count, data.data) });
}
function EntityObjectConsumer(props) {
const { modelName, objectId, withReference, objectRenderer, onError, loading } = props;
const engine = useEntityEngine();
const datasource = engine.datasourceFactory.getDataSource();
const dsHook = toDataSourceHook(datasource);
const { data, error, state } = useAsync(async () => {
if (withReference) {
return await datasource.findOneWithReferences({
modelName,
id: objectId,
includeFieldNames: void 0
});
} else {
return await datasource.findOne({
modelName,
id: objectId
});
}
}, [objectId, withReference]);
if (state === "loading" || !data) {
return loading || /* @__PURE__ */ jsx3("div", { children: "Loading..." });
}
if (error) {
if (onError) {
return onError(error);
}
return /* @__PURE__ */ jsxs("div", { style: { color: "red" }, children: [
"Error: ",
error.message
] });
}
return /* @__PURE__ */ jsx3(Fragment, { children: objectRenderer(data) });
}
// src/components/renderers/view-inspector/index.tsx
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
var EntityViewInspector = {
name: "buildin-view-inspector",
slotName: "view-inspector",
disabled: false,
renderer: (props) => /* @__PURE__ */ jsx4(EntityViewInspectorComp, { ...props })
};
function EntityViewInspectorComp(props) {
const { model, viewData } = props;
const handleAction = () => {
modals.open({
title: "\u89C6\u56FE\u914D\u7F6E\u6570\u636E",
children: /* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(InnerEntityViewInspectorComp, { ...props, model, viewData }) }),
size: "80%",
centered: true,
closeOnClickOutside: true,
closeOnEscape: true,
onClose: () => {
console.log("Settings modal closed");
}
});
};
return /* @__PURE__ */ jsx4(ActionIcon, { variant: "subtle", "aria-label": "Settings", onClick: handleAction, children: /* @__PURE__ */ jsx4(Settings2Icon, { size: 14, strokeWidth: 2 }) });
}
function InnerEntityViewInspectorComp(props) {
const { model, viewData, ...otherProps } = props;
const [tab, setTab] = React2.useState("model");
const engine = useEntityEngine();
const handleTabChange = (value) => {
setTab(value || "model");
};
return /* @__PURE__ */ jsxs2(Tabs, { value: tab, variant: "outline", color: "blue", radius: "md", onChange: handleTabChange, children: [
/* @__PURE__ */ jsxs2(Tabs.List, { children: [
/* @__PURE__ */ jsx4(Tabs.Tab, { value: "model", leftSection: /* @__PURE__ */ jsx4(ViewIcon, { size: 14, strokeWidth: 2 }), children: "\u6A21\u578B\u914D\u7F6E" }),
/* @__PURE__ */ jsx4(Tabs.Tab, { value: "view", leftSection: /* @__PURE__ */ jsx4(DatabaseIcon, { size: 14, strokeWidth: 2 }), children: "\u89C6\u56FE\u914D\u7F6E" }),
/* @__PURE__ */ jsx4(Tabs.Tab, { value: "other", leftSection: /* @__PURE__ */ jsx4(Settings2Icon, { size: 14, strokeWidth: 2 }), children: "\u5176\u4ED6\u6570\u636E" })
] }),
/* @__PURE__ */ jsx4(Tabs.Panel, { value: "model", pt: "xs", children: /* @__PURE__ */ jsx4(
JsonView,
{
value: JSON.parse(
JSON.stringify(model, (key, value) => {
if (typeof key === "string" && key.startsWith("_metaRegistry")) {
return void 0;
}
return value;
})
)
}
) }),
/* @__PURE__ */ jsx4(Tabs.Panel, { value: "view", pt: "xs", children: /* @__PURE__ */ jsx4(
JsonView,
{
value: JSON.parse(
JSON.stringify(viewData, (key, value) => {
if (typeof key === "string" && key.startsWith("_metaRegistry")) {
return void 0;
}
return value;
})
)
}
) }),
/* @__PURE__ */ jsx4(Tabs.Panel, { value: "other", pt: "xs", children: /* @__PURE__ */ jsx4(
JsonView,
{
value: JSON.parse(
JSON.stringify(otherProps, (key, value) => {
if (typeof key === "string" && key.startsWith("_metaRegistry")) {
return void 0;
}
return value;
})
)
}
) })
] });
}
// src/types/data.types.ts
var QueryOperator = /* @__PURE__ */ ((QueryOperator2) => {
QueryOperator2["NONE"] = "none";
QueryOperator2["EQ"] = "eq";
QueryOperator2["NE"] = "ne";
QueryOperator2["GT"] = "gt";
QueryOperator2["GTE"] = "gte";
QueryOperator2["LT"] = "lt";
QueryOperator2["LTE"] = "lte";
QueryOperator2["CONTAINS"] = "contains";
QueryOperator2["STARTS_WITH"] = "startsWith";
QueryOperator2["ENDS_WITH"] = "endsWith";
QueryOperator2["IN"] = "in";
QueryOperator2["NOT_IN"] = "notIn";
QueryOperator2["IS_NULL"] = "isNull";
QueryOperator2["IS_NOT_NULL"] = "isNotNull";
QueryOperator2["BETWEEN"] = "between";
return QueryOperator2;
})(QueryOperator || {});
export {
BuildinModule,
EmptySymbol,
EntityActionRegistry,
EntityComponentRegistry,
EntityDataSourceFactory,
EntityEngine,
EntityEngineProvider,
EntityEngineThemeProvider,
EntityEventRegistry,
EntityFieldDelegate,
EntityMetaRegistry,
EntityModelDelegate,
EntityNamedRenderer,
EntityObjectConsumer,
EntityObjectsConsumer,
EntityPermissionGuard,
EntitySession,
EntitySessionManager,
EntitySuiteAdapterProvider,
EntityView,
EntityViewContainer,
EntityViewDelegate,
EntityViewFieldDelegate,
EntityViewInspector,
EntityWidget,
EntityWidgetRenderer,
ModelFieldTyperRegistry,
QueryOperator,
TRPCEntityObjectDataSource,
ViewContainerProvider,
createEntityEngineProvider,
entityEngineDefaultTheme,
getEntityEngine,
toDataSourceHook,
useAsync,
useAsyncEffect,
useAsyncWithCache,
useContainerRouter,
useEntityEngine,
useEntityEngineRouter,
useEntityEngineTheme,
useEntityPermissionGuard,
useEntitySession,
useEntitySuiteAdapter,
useMasterDetailViewContainer
};
//# sourceMappingURL=index.mjs.map