UNPKG

@pisell/pisellos

Version:

一个可扩展的前端模块化SDK框架,支持插件系统

559 lines (557 loc) 21.5 kB
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 });