@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
559 lines (557 loc) • 21.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/server/modules/resource/index.ts
var resource_exports = {};
__export(resource_exports, {
ResourceHooks: () => import_types2.ResourceHooks,
ResourceModule: () => ResourceModule,
resourceModule: () => resourceModule
});
module.exports = __toCommonJS(resource_exports);
var import_lodash_es = require("lodash-es");
var import_BaseModule = require("../../../modules/BaseModule");
var import_types = require("./types");
var import_types2 = require("./types");
var RESOURCE_STORE_NAME = "resources";
var DEFAULT_PAGE_SIZE = 999;
var RESOURCE_SYNC_DEBOUNCE_MS = 1e4;
var ResourceModule = class extends import_BaseModule.BaseModule {
constructor(name, version) {
super(name, version);
this.defaultName = "resource";
this.defaultVersion = "1.0.0";
/** 倒排索引: resourceId -> ResourceBooking[] */
this.resourceIdIndex = /* @__PURE__ */ new Map();
/** 待处理的同步消息队列 */
this.pendingSyncMessages = [];
}
// ─────────────────────────────────────────────────────────────────
// 初始化 & 生命周期
// ─────────────────────────────────────────────────────────────────
async initialize(core, options) {
var _a, _b;
this.core = core;
this.store = options == null ? void 0 : options.store;
if (Array.isArray((_a = options == null ? void 0 : options.initialState) == null ? void 0 : _a.list)) {
this.store.list = ((_b = options == null ? void 0 : options.initialState) == null ? void 0 : _b.list).map(
(item) => this.normalizeResource(item)
);
this.syncResourcesMap();
this.core.effects.emit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
} else {
this.store.list = [];
this.store.map = /* @__PURE__ */ new Map();
}
if (!Array.isArray(this.store.bookings)) {
this.store.bookings = [];
}
const appPlugin = core.getPlugin("app");
if (appPlugin) {
const app = appPlugin.getApp();
this.dbManager = app.sqlite || app.dbManager;
this.logger = app.logger;
}
this.initResourceDataSource();
this.setupResourceSync();
}
logInfo(title, metadata) {
if (this.logger) {
this.logger.addLog({
type: "info",
title: `[ResourceModule] ${title}`,
metadata: metadata || {}
});
}
}
logError(title, error, metadata) {
if (this.logger) {
this.logger.addLog({
type: "error",
title: `[ResourceModule] ${title}`,
metadata: metadata || {}
});
}
}
async preload() {
const cachedResources = await this.loadResourcesFromSQLite();
if (cachedResources.length > 0) {
this.store.list = (0, import_lodash_es.cloneDeep)(cachedResources).map((item) => this.normalizeResource(item));
this.syncResourcesMap();
await this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
} else {
const resources = await this.loadResourcesByServer();
if (resources.length > 0) {
this.store.list = (0, import_lodash_es.cloneDeep)(resources).map((item) => this.normalizeResource(item));
this.syncResourcesMap();
await this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
}
}
}
getRoutes() {
return [];
}
destroy() {
var _a;
if (this.syncTimer) {
clearTimeout(this.syncTimer);
this.syncTimer = void 0;
}
this.pendingSyncMessages = [];
if ((_a = this.resourceDataSource) == null ? void 0 : _a.destroy)
this.resourceDataSource.destroy();
super.destroy();
}
// ─────────────────────────────────────────────────────────────────
// Resource CRUD
// ─────────────────────────────────────────────────────────────────
/**
* 获取所有资源(支持 includeBookings 附加预订信息)
*/
getResources(queryOptions) {
this.logInfo("getResources", {
queryOptions
});
if (!(queryOptions == null ? void 0 : queryOptions.includeBookings)) {
return this.store.list;
}
return this.store.list.map((resource) => this.attachBookingsToResource(resource));
}
/**
* 根据 ID 获取单个资源
*/
getResourceById(id, queryOptions) {
const resource = this.store.map.get(id);
if (!resource)
return void 0;
if (queryOptions == null ? void 0 : queryOptions.includeBookings) {
return this.attachBookingsToResource(resource);
}
return resource;
}
/**
* 创建资源
*/
createResource(data) {
const id = (data == null ? void 0 : data.id) ?? (data == null ? void 0 : data.form_record_id) ?? Date.now();
const resource = this.normalizeResource({ ...data, id });
this.store.list.push(resource);
this.store.map.set(resource.id, resource);
this.saveResourcesToSQLite(this.store.list).catch(() => {
});
this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
return resource;
}
/**
* 更新资源
*/
updateResource(id, data) {
const index = this.store.list.findIndex((r) => this.getIdKey(r.id) === this.getIdKey(id));
if (index === -1)
return void 0;
const updated = this.normalizeResource({ ...this.store.list[index], ...data, id });
this.store.list[index] = updated;
this.store.map.set(updated.id, updated);
this.saveResourcesToSQLite(this.store.list).catch(() => {
});
this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
return updated;
}
/**
* 删除资源
*/
deleteResource(id) {
const key = this.getIdKey(id);
const index = this.store.list.findIndex((r) => this.getIdKey(r.id) === key);
if (index === -1)
return false;
this.store.list.splice(index, 1);
this.store.map.delete(id);
this.saveResourcesToSQLite(this.store.list).catch(() => {
});
this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
return true;
}
// ─────────────────────────────────────────────────────────────────
// Booking CRUD
// ─────────────────────────────────────────────────────────────────
/**
* 根据资源 ID 获取预订列表
*/
getBookingsByResourceId(resourceId) {
return this.resourceIdIndex.get(resourceId) || [];
}
/**
* 创建预订
*/
// createBooking(booking: Partial<ResourceBooking>): ResourceBooking {
// const id = booking?.id ?? Date.now()
// const normalized: ResourceBooking = {
// id,
// ...booking,
// } as ResourceBooking
// this.store.bookings.push(normalized)
// const rid = normalized.resource_id ?? normalized.resourceId
// if (rid !== undefined) {
// const existing = this.resourceIdIndex.get(rid) || []
// existing.push(normalized)
// this.resourceIdIndex.set(rid, existing)
// }
// this.saveBookingsToSQLite(this.store.bookings).catch(() => {})
// this.safeEmit(ResourceHooks.onBookingsChanged, this.store.bookings)
// return normalized
// }
/**
* 更新预订
*/
// updateBooking(id: ResourceId, data: Partial<ResourceBooking>): ResourceBooking | undefined {
// const index = this.store.bookings.findIndex(b => this.getIdKey(b.id) === this.getIdKey(id))
// if (index === -1) return undefined
// const old = this.store.bookings[index]
// const updated: ResourceBooking = { ...old, ...data, id: old.id } as ResourceBooking
// this.store.bookings[index] = updated
// this.rebuildBookingsIndex()
// this.saveBookingsToSQLite(this.store.bookings).catch(() => {})
// this.safeEmit(ResourceHooks.onBookingsChanged, this.store.bookings)
// return updated
// }
/**
* 删除预订
*/
// deleteBooking(id: ResourceId): boolean {
// const index = this.store.bookings.findIndex(b => this.getIdKey(b.id) === this.getIdKey(id))
// if (index === -1) return false
// this.store.bookings.splice(index, 1)
// this.rebuildBookingsIndex()
// this.saveBookingsToSQLite(this.store.bookings).catch(() => {})
// this.safeEmit(ResourceHooks.onBookingsChanged, this.store.bookings)
// return true
// }
/**
* 清空缓存
*/
async clear() {
this.store.list = [];
this.store.map.clear();
this.store.bookings = [];
this.resourceIdIndex.clear();
if (this.dbManager) {
try {
await this.dbManager.clear(RESOURCE_STORE_NAME);
} catch {
}
}
await this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
await this.safeEmit(import_types.ResourceHooks.onBookingsChanged, this.store.bookings);
}
// ─────────────────────────────────────────────────────────────────
// 内部工具方法
// ─────────────────────────────────────────────────────────────────
attachBookingsToResource(resource) {
return {
...resource,
bookings: this.getBookingsByResourceId(resource.id)
};
}
normalizeResource(resource) {
const normalized = {
...resource,
id: (resource == null ? void 0 : resource.id) ?? (resource == null ? void 0 : resource.form_record_id) ?? ""
};
if (normalized.form_record_id === void 0 && normalized.id !== "") {
normalized.form_record_id = normalized.id;
}
if (normalized.resource_form_id === void 0 || normalized.resource_form_id === null) {
normalized.resource_form_id = "";
}
if (normalized.schedule === void 0 || normalized.schedule === null) {
normalized.schedule = "";
}
if (Array.isArray(normalized.times)) {
normalized.times = normalized.times.map((timeSlot) => {
if (!timeSlot || typeof timeSlot !== "object")
return timeSlot;
const startAt = timeSlot.start_at ?? timeSlot.start;
const endAt = timeSlot.end_at ?? timeSlot.end;
return {
...timeSlot,
start_at: startAt,
end_at: endAt,
start: timeSlot.start ?? startAt,
end: timeSlot.end ?? endAt
};
});
}
if (Array.isArray(normalized.children)) {
normalized.children = normalized.children.map((child) => this.normalizeResource(child));
}
return normalized;
}
async safeEmit(event, payload) {
try {
await this.core.effects.emit(event, payload);
} catch (error) {
console.error(`[ResourceModule] 事件派发失败: ${event}`, error);
}
}
syncResourcesMap() {
this.store.map = /* @__PURE__ */ new Map();
for (const resource of this.store.list) {
this.store.map.set(resource.id, resource);
}
}
syncBookingsIndex() {
this.resourceIdIndex.clear();
for (const booking of this.store.bookings) {
const rid = booking.resource_id ?? booking.resourceId;
if (rid === void 0)
continue;
const existing = this.resourceIdIndex.get(rid) || [];
existing.push(booking);
this.resourceIdIndex.set(rid, existing);
}
}
rebuildBookingsIndex() {
this.syncBookingsIndex();
}
getIdKey(id) {
return String(id);
}
// ─────────────────────────────────────────────────────────────────
// 数据加载
// ─────────────────────────────────────────────────────────────────
async loadResourcesByServer() {
if (!this.resourceDataSource)
return [];
try {
const response = await this.resourceDataSource.getResourcePage({
num: DEFAULT_PAGE_SIZE,
skip: 1
});
const resourceList = Array.isArray(response == null ? void 0 : response.list) ? response.list : [];
const normalizedList = resourceList.map((item) => this.normalizeResource(item));
await this.saveResourcesToSQLite(normalizedList);
await this.safeEmit(import_types.ResourceHooks.onResourcesLoaded, normalizedList);
return normalizedList;
} catch {
return [];
}
}
// ─────────────────────────────────────────────────────────────────
// SQLite 持久化
// ─────────────────────────────────────────────────────────────────
async loadResourcesFromSQLite() {
if (!this.dbManager)
return [];
try {
const resources = await this.dbManager.getAll(RESOURCE_STORE_NAME);
return resources || [];
} catch {
return [];
}
}
async saveResourcesToSQLite(resourceList) {
if (!this.dbManager)
return;
try {
await this.dbManager.clear(RESOURCE_STORE_NAME);
if (resourceList.length === 0)
return;
if (this.dbManager.bulkAdd) {
await this.dbManager.bulkAdd(RESOURCE_STORE_NAME, resourceList);
return;
}
await Promise.all(resourceList.map((r) => this.dbManager.add(RESOURCE_STORE_NAME, r)));
} catch (error) {
console.error("saveResourcesToSQLite error", error);
this.logError("保存资源到 SQLite 失败", {
error: error instanceof Error ? error.message : String(error),
resourceList: resourceList.length
});
}
}
// private async loadBookingsFromSQLite(): Promise<ResourceBooking[]> {
// if (!this.dbManager) return []
// try {
// const bookings = await this.dbManager.getAll(BOOKING_STORE_NAME)
// return bookings || []
// } catch {
// return []
// }
// }
// private async saveBookingsToSQLite(bookings: ResourceBooking[]): Promise<void> {
// if (!this.dbManager) return
// try {
// await this.dbManager.clear(BOOKING_STORE_NAME)
// if (bookings.length === 0) return
// if (this.dbManager.bulkAdd) {
// await this.dbManager.bulkAdd(BOOKING_STORE_NAME, bookings)
// return
// }
// await Promise.all(bookings.map(b => this.dbManager.add(BOOKING_STORE_NAME, b)))
// } catch {
// // 忽略 SQLite 异常
// }
// }
// ─────────────────────────────────────────────────────────────────
// pubsub 同步
// ─────────────────────────────────────────────────────────────────
initResourceDataSource() {
var _a, _b;
const ResourceDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.ResourceDataSource;
if (!ResourceDataSourceClass)
return;
this.resourceDataSource = new ResourceDataSourceClass();
}
async setupResourceSync() {
var _a, _b, _c;
if (!this.resourceDataSource)
return;
const result = await this.resourceDataSource.run({
pubsub: {
callback: (res) => {
const data = (res == null ? void 0 : res.data) || res;
if (!data)
return;
const channelKey = data.module || "resource";
this.pendingSyncMessages.push({ ...data, _channelKey: channelKey });
if (this.syncTimer)
clearTimeout(this.syncTimer);
this.syncTimer = setTimeout(() => {
this.processSyncMessages();
}, RESOURCE_SYNC_DEBOUNCE_MS);
}
}
}).catch(() => {
});
console.log("result", result);
if ((_b = (_a = result == null ? void 0 : result.data) == null ? void 0 : _a.list) == null ? void 0 : _b.length) {
await this.mergeResourcesToStore((_c = result == null ? void 0 : result.data) == null ? void 0 : _c.list);
}
}
async processSyncMessages() {
var _a, _b, _c;
const messages = [...this.pendingSyncMessages];
this.pendingSyncMessages = [];
if (messages.length === 0)
return;
const deleteIds = [];
const bodyUpdates = /* @__PURE__ */ new Map();
const sseRefreshIds = [];
for (const msg of messages) {
if (msg.operation === "delete" || msg.action === "delete") {
if ((_a = msg.ids) == null ? void 0 : _a.length)
deleteIds.push(...msg.ids);
else if (msg.id !== void 0)
deleteIds.push(msg.id);
continue;
}
if (msg.body) {
const bodyId = msg.body.id ?? msg.id;
if (bodyId === void 0)
continue;
bodyUpdates.set(this.getIdKey(bodyId), { ...msg.body, id: bodyId });
continue;
}
if ((_b = msg.ids) == null ? void 0 : _b.length) {
sseRefreshIds.push(...msg.ids);
} else if (msg.id !== void 0) {
sseRefreshIds.push(msg.id);
} else if ((_c = msg.relation_resource_ids) == null ? void 0 : _c.length) {
sseRefreshIds.push(...msg.relation_resource_ids);
}
}
const uniqueDeleteIds = this.uniqueResourceIds(deleteIds);
const uniqueSSEIds = this.uniqueResourceIds(sseRefreshIds);
const bodyList = [...bodyUpdates.values()];
if (uniqueDeleteIds.length > 0)
await this.removeResourcesByIds(uniqueDeleteIds);
if (bodyList.length > 0)
await this.mergeResourcesToStore(bodyList);
if (uniqueSSEIds.length > 0) {
const freshResources = await this.fetchResourcesBySSE(uniqueSSEIds);
if (freshResources.length > 0)
await this.mergeResourcesToStore(freshResources);
}
if (uniqueDeleteIds.length === 0 && bodyList.length === 0 && uniqueSSEIds.length === 0)
return;
await this.core.effects.emit(import_types.ResourceHooks.onResourcesSyncCompleted, null);
}
async fetchResourcesBySSE(ids) {
if (!this.resourceDataSource)
return [];
try {
const list = await this.resourceDataSource.run({
sse: { query: { type: "resource", ids } }
});
return list || [];
} catch {
return [];
}
}
async removeResourcesByIds(ids) {
const keySet = new Set(ids.map((id) => this.getIdKey(id)));
this.store.list = this.store.list.filter((r) => !keySet.has(this.getIdKey(r.id)));
this.syncResourcesMap();
if (this.dbManager) {
try {
for (const id of ids) {
await this.dbManager.delete(RESOURCE_STORE_NAME, id);
}
} catch {
}
}
await this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
}
async mergeResourcesToStore(freshResources) {
const freshMap = /* @__PURE__ */ new Map();
for (const r of freshResources) {
if ((r == null ? void 0 : r.id) === void 0)
continue;
freshMap.set(this.getIdKey(r.id), this.normalizeResource(r));
}
const updatedList = this.store.list.map((r) => {
const key = this.getIdKey(r.id);
if (!freshMap.has(key))
return r;
const fresh = freshMap.get(key);
freshMap.delete(key);
return fresh;
});
for (const r of freshMap.values()) {
updatedList.push(r);
}
this.store.list = updatedList;
this.syncResourcesMap();
await this.saveResourcesToSQLite(this.store.list);
await this.safeEmit(import_types.ResourceHooks.onResourcesChanged, this.store.list);
}
uniqueResourceIds(ids) {
const idMap = /* @__PURE__ */ new Map();
for (const id of ids) {
idMap.set(this.getIdKey(id), id);
}
return [...idMap.values()];
}
};
var resourceModule = new ResourceModule();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ResourceHooks,
ResourceModule,
resourceModule
});