@geekron/hono 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract.d.ts +9 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +14 -3
- package/dist/core/api/common.d.ts +2 -0
- package/dist/core/api/common.d.ts.map +1 -0
- package/dist/core/api/common.js +38 -0
- package/dist/core/api/inquiries.d.ts +2 -0
- package/dist/core/api/inquiries.d.ts.map +1 -0
- package/dist/core/api/inquiries.js +116 -0
- package/dist/core/api/inquiry.d.ts +2 -0
- package/dist/core/api/inquiry.d.ts.map +1 -0
- package/dist/core/api/inquiry.js +33 -0
- package/dist/core/api/visitor.d.ts +2 -0
- package/dist/core/api/visitor.d.ts.map +1 -0
- package/dist/core/api/visitor.js +152 -0
- package/dist/core/components/InquiryEmailTemplate.d.ts +30 -0
- package/dist/core/components/InquiryEmailTemplate.d.ts.map +1 -0
- package/dist/core/components/InquiryEmailTemplate.js +13 -0
- package/dist/core/components/Seo.d.ts +97 -0
- package/dist/core/components/Seo.d.ts.map +1 -0
- package/dist/core/components/Seo.js +365 -0
- package/dist/core/components/index.d.ts +3 -0
- package/dist/core/components/index.d.ts.map +1 -0
- package/dist/core/components/index.js +2 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/core/lib/constants/inquiry.d.ts +52 -0
- package/dist/core/lib/constants/inquiry.d.ts.map +1 -0
- package/dist/core/lib/constants/inquiry.js +51 -0
- package/dist/core/lib/constants/visitor.d.ts +29 -0
- package/dist/core/lib/constants/visitor.d.ts.map +1 -0
- package/dist/core/lib/constants/visitor.js +28 -0
- package/dist/core/lib/database/constants/database.d.ts +15 -0
- package/dist/core/lib/database/constants/database.d.ts.map +1 -0
- package/dist/core/lib/database/constants/database.js +14 -0
- package/dist/core/lib/database/database.service.d.ts +63 -0
- package/dist/core/lib/database/database.service.d.ts.map +1 -0
- package/dist/core/lib/database/database.service.js +108 -0
- package/dist/core/lib/database/index.d.ts +8 -0
- package/dist/core/lib/database/index.d.ts.map +1 -0
- package/dist/core/lib/database/index.js +7 -0
- package/dist/core/lib/database/managers/sqlite.d.ts +29 -0
- package/dist/core/lib/database/managers/sqlite.d.ts.map +1 -0
- package/dist/core/lib/database/managers/sqlite.js +55 -0
- package/dist/core/lib/database/managers/visitor-shard.d.ts +29 -0
- package/dist/core/lib/database/managers/visitor-shard.d.ts.map +1 -0
- package/dist/core/lib/database/managers/visitor-shard.js +83 -0
- package/dist/core/lib/database/repositories/inquiry.repository.d.ts +33 -0
- package/dist/core/lib/database/repositories/inquiry.repository.d.ts.map +1 -0
- package/dist/core/lib/database/repositories/inquiry.repository.js +173 -0
- package/dist/core/lib/database/repositories/visitor.repository.d.ts +51 -0
- package/dist/core/lib/database/repositories/visitor.repository.d.ts.map +1 -0
- package/dist/core/lib/database/repositories/visitor.repository.js +559 -0
- package/dist/core/lib/database/schemas/inquiry.schema.d.ts +323 -0
- package/dist/core/lib/database/schemas/inquiry.schema.d.ts.map +1 -0
- package/dist/core/lib/database/schemas/inquiry.schema.js +52 -0
- package/dist/core/lib/database/schemas/visitor.schema.d.ts +623 -0
- package/dist/core/lib/database/schemas/visitor.schema.d.ts.map +1 -0
- package/dist/core/lib/database/schemas/visitor.schema.js +162 -0
- package/dist/core/lib/services/captcha.service.d.ts +13 -0
- package/dist/core/lib/services/captcha.service.d.ts.map +1 -0
- package/dist/core/lib/services/captcha.service.js +40 -0
- package/dist/core/lib/services/geoip.service.d.ts +79 -0
- package/dist/core/lib/services/geoip.service.d.ts.map +1 -0
- package/dist/core/lib/services/geoip.service.js +144 -0
- package/dist/core/lib/services/index.d.ts +9 -0
- package/dist/core/lib/services/index.d.ts.map +1 -0
- package/dist/core/lib/services/index.js +8 -0
- package/dist/core/lib/services/inquiry.service.d.ts +56 -0
- package/dist/core/lib/services/inquiry.service.d.ts.map +1 -0
- package/dist/core/lib/services/inquiry.service.js +129 -0
- package/dist/core/lib/services/mailer.service.d.ts +23 -0
- package/dist/core/lib/services/mailer.service.d.ts.map +1 -0
- package/dist/core/lib/services/mailer.service.js +96 -0
- package/dist/core/lib/types/inquiry.d.ts +95 -0
- package/dist/core/lib/types/inquiry.d.ts.map +1 -0
- package/dist/core/lib/types/inquiry.js +1 -0
- package/dist/core/lib/types/visitor.d.ts +135 -0
- package/dist/core/lib/types/visitor.d.ts.map +1 -0
- package/dist/core/lib/types/visitor.js +1 -0
- package/dist/core/lib/utils/api.d.ts +25 -0
- package/dist/core/lib/utils/api.d.ts.map +1 -0
- package/dist/core/lib/utils/api.js +48 -0
- package/dist/core/lib/utils/request.d.ts +18 -0
- package/dist/core/lib/utils/request.d.ts.map +1 -0
- package/dist/core/lib/utils/request.js +25 -0
- package/dist/core/middlewares/api-guard.d.ts +39 -0
- package/dist/core/middlewares/api-guard.d.ts.map +1 -0
- package/dist/core/middlewares/api-guard.js +154 -0
- package/dist/core/middlewares/html-minifier.d.ts +55 -0
- package/dist/core/middlewares/html-minifier.d.ts.map +1 -0
- package/dist/core/middlewares/html-minifier.js +140 -0
- package/dist/core/middlewares/index.d.ts +5 -0
- package/dist/core/middlewares/index.d.ts.map +1 -0
- package/dist/core/middlewares/index.js +4 -0
- package/dist/core/middlewares/initializer.d.ts +11 -0
- package/dist/core/middlewares/initializer.d.ts.map +1 -0
- package/dist/core/middlewares/initializer.js +13 -0
- package/dist/core/middlewares/visitor-tracker.d.ts +85 -0
- package/dist/core/middlewares/visitor-tracker.d.ts.map +1 -0
- package/dist/core/middlewares/visitor-tracker.js +253 -0
- package/dist/core/template.d.ts +4 -0
- package/dist/core/template.d.ts.map +1 -0
- package/dist/core/template.js +7 -0
- package/dist/core/utils/formatDate.d.ts +3 -0
- package/dist/core/utils/formatDate.d.ts.map +1 -0
- package/dist/core/utils/formatDate.js +2 -0
- package/dist/core/utils/fullUrl.d.ts +2 -0
- package/dist/core/utils/fullUrl.d.ts.map +1 -0
- package/dist/core/utils/fullUrl.js +9 -0
- package/dist/core/utils/index.d.ts +6 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/index.js +5 -0
- package/dist/core/utils/markdownify.d.ts +2 -0
- package/dist/core/utils/markdownify.d.ts.map +1 -0
- package/dist/core/utils/markdownify.js +4 -0
- package/dist/core/utils/params.d.ts +5 -0
- package/dist/core/utils/params.d.ts.map +1 -0
- package/dist/core/utils/params.js +14 -0
- package/dist/core/utils/routeUrl.d.ts +20 -0
- package/dist/core/utils/routeUrl.d.ts.map +1 -0
- package/dist/core/utils/routeUrl.js +111 -0
- package/dist/route/methods.d.ts +15 -0
- package/dist/route/methods.d.ts.map +1 -1
- package/dist/route/methods.js +45 -18
- package/dist/route/setup.d.ts +11 -12
- package/dist/route/setup.d.ts.map +1 -1
- package/dist/route/setup.js +26 -16
- package/dist/strapi/api/site.d.ts +2 -0
- package/dist/strapi/api/site.d.ts.map +1 -0
- package/dist/strapi/api/site.js +153 -0
- package/dist/strapi/cms/cms.js +1 -1
- package/dist/strapi/cms/common.d.ts.map +1 -1
- package/dist/strapi/cms/common.js +3 -1
- package/dist/strapi/cms/menu.d.ts +0 -9
- package/dist/strapi/cms/menu.d.ts.map +1 -1
- package/dist/strapi/cms/menu.js +3 -62
- package/dist/strapi/cms/setup.d.ts +2 -1
- package/dist/strapi/cms/setup.d.ts.map +1 -1
- package/dist/strapi/cms/setup.js +5 -0
- package/dist/strapi/cms/site.d.ts.map +1 -1
- package/dist/strapi/cms/site.js +3 -1
- package/dist/strapi/cms/translations.d.ts.map +1 -1
- package/dist/strapi/cms/translations.js +6 -2
- package/dist/strapi/i18n.d.ts +3 -0
- package/dist/strapi/i18n.d.ts.map +1 -0
- package/dist/strapi/i18n.js +27 -0
- package/dist/strapi/index.d.ts +1 -0
- package/dist/strapi/index.d.ts.map +1 -1
- package/dist/strapi/index.js +1 -0
- package/dist/strapi/interfaces/index.d.ts +1 -0
- package/dist/strapi/interfaces/index.d.ts.map +1 -1
- package/dist/strapi/interfaces/index.js +1 -0
- package/dist/strapi/interfaces/sitemap.d.ts +19 -0
- package/dist/strapi/interfaces/sitemap.d.ts.map +1 -0
- package/dist/strapi/interfaces/sitemap.js +1 -0
- package/dist/strapi/pages/sitemap/index.d.ts +2 -0
- package/dist/strapi/pages/sitemap/index.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/index.js +50 -0
- package/dist/strapi/pages/sitemap/index.xml +11 -0
- package/dist/strapi/pages/sitemap/list.d.ts +2 -0
- package/dist/strapi/pages/sitemap/list.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/list.js +123 -0
- package/dist/strapi/pages/sitemap/list.xml +28 -0
- package/dist/strapi/pages/sitemap/robots.d.ts +2 -0
- package/dist/strapi/pages/sitemap/robots.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/robots.js +19 -0
- package/dist/strapi/pages/sitemap/robots.txt +7 -0
- package/dist/strapi/pages/sitemap/style.d.ts +2 -0
- package/dist/strapi/pages/sitemap/style.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/style.js +17 -0
- package/dist/strapi/pages/sitemap/style.xsl +602 -0
- package/dist/strapi/utils/index.d.ts +1 -0
- package/dist/strapi/utils/index.d.ts.map +1 -1
- package/dist/strapi/utils/index.js +1 -0
- package/dist/strapi/utils/trans.d.ts +5 -0
- package/dist/strapi/utils/trans.d.ts.map +1 -0
- package/dist/strapi/utils/trans.js +7 -0
- package/package.json +20 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { DATA_DIR } from './constants/database';
|
|
3
|
+
import { Sqlite } from './managers/sqlite';
|
|
4
|
+
import { InquiryRepository } from './repositories/inquiry.repository';
|
|
5
|
+
import { VisitorRepository } from './repositories/visitor.repository';
|
|
6
|
+
/**
|
|
7
|
+
* DatabaseService:聚合仓储,提供对外的业务友好 API。
|
|
8
|
+
*/
|
|
9
|
+
export class DatabaseService {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.initialized = false;
|
|
12
|
+
this.manager = new Sqlite();
|
|
13
|
+
this.inquiryRepo = new InquiryRepository(this.manager);
|
|
14
|
+
this.visitorRepo = new VisitorRepository(this.manager);
|
|
15
|
+
}
|
|
16
|
+
/** 初始化数据目录及各表结构 */
|
|
17
|
+
async init() {
|
|
18
|
+
if (this.initialized)
|
|
19
|
+
return;
|
|
20
|
+
if (!fs.existsSync(DATA_DIR))
|
|
21
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
22
|
+
await this.inquiryRepo.initSchema();
|
|
23
|
+
await this.visitorRepo.initShardSchemas();
|
|
24
|
+
this.initialized = true;
|
|
25
|
+
}
|
|
26
|
+
/** 询盘:创建 */
|
|
27
|
+
addInquiry(data) {
|
|
28
|
+
return this.inquiryRepo.create(data);
|
|
29
|
+
}
|
|
30
|
+
/** 询盘:按 ID 查询 */
|
|
31
|
+
getInquiryById(id) {
|
|
32
|
+
return this.inquiryRepo.findById(id);
|
|
33
|
+
}
|
|
34
|
+
/** 询盘:列表 */
|
|
35
|
+
listInquiries(params = {}) {
|
|
36
|
+
return this.inquiryRepo.list(params);
|
|
37
|
+
}
|
|
38
|
+
/** 询盘:计数 */
|
|
39
|
+
countInquiries(params = {}) {
|
|
40
|
+
return this.inquiryRepo.count(params);
|
|
41
|
+
}
|
|
42
|
+
/** 询盘:统计未读数量 */
|
|
43
|
+
countUnreadInquiries() {
|
|
44
|
+
return this.inquiryRepo.countUnread();
|
|
45
|
+
}
|
|
46
|
+
/** 询盘:标记已读 */
|
|
47
|
+
markInquiryAsRead(id, operatorId) {
|
|
48
|
+
return this.inquiryRepo.markAsRead(id, operatorId);
|
|
49
|
+
}
|
|
50
|
+
/** 访客:写入 */
|
|
51
|
+
addVisitor(data) {
|
|
52
|
+
return this.visitorRepo.insert(data);
|
|
53
|
+
}
|
|
54
|
+
/** 访客:查询页面浏览记录 */
|
|
55
|
+
queryPageViews(filter = {}) {
|
|
56
|
+
return this.visitorRepo.findPageViews(filter);
|
|
57
|
+
}
|
|
58
|
+
/** 访客:查询访客会话 */
|
|
59
|
+
queryVisitorSessions(filter = {}) {
|
|
60
|
+
return this.visitorRepo.findVisitorSessions(filter);
|
|
61
|
+
}
|
|
62
|
+
/** 访客:计数(页面浏览数) */
|
|
63
|
+
countVisitors(filter = {}) {
|
|
64
|
+
return this.visitorRepo.count(filter);
|
|
65
|
+
}
|
|
66
|
+
/** 访客:统计(向后兼容) */
|
|
67
|
+
visitorStats(by, filter = {}) {
|
|
68
|
+
return this.visitorRepo.stats(by, filter);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 统计独立访客数(UV)
|
|
72
|
+
*/
|
|
73
|
+
getUVStats(filter = {}, byDate = false) {
|
|
74
|
+
return this.visitorRepo.getUVStats(filter, byDate);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 统计页面总浏览量(PV)
|
|
78
|
+
*/
|
|
79
|
+
getPVStats(filter = {}, groupBy) {
|
|
80
|
+
return this.visitorRepo.getPVStats(filter, groupBy);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 统计每个页面每日的访问次数
|
|
84
|
+
*/
|
|
85
|
+
getDailyPageViewStats(filter = {}) {
|
|
86
|
+
return this.visitorRepo.getDailyPageViewStats(filter);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 基于 IP 的页面访问次数统计
|
|
90
|
+
*/
|
|
91
|
+
getIpPageViewStats(filter = {}) {
|
|
92
|
+
return this.visitorRepo.getIpPageViewStats(filter);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 全局单例数据库服务
|
|
97
|
+
*/
|
|
98
|
+
let dbInstance = null;
|
|
99
|
+
/**
|
|
100
|
+
* 获取数据库服务实例(单例)
|
|
101
|
+
*/
|
|
102
|
+
export async function getDatabase() {
|
|
103
|
+
if (!dbInstance) {
|
|
104
|
+
dbInstance = new DatabaseService();
|
|
105
|
+
await dbInstance.init();
|
|
106
|
+
}
|
|
107
|
+
return dbInstance;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/database/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
3
|
+
/**
|
|
4
|
+
* SQLite 连接管理器,负责按文件路径复用 bun:sqlite 实例并执行连接优化。
|
|
5
|
+
*/
|
|
6
|
+
export declare class Sqlite {
|
|
7
|
+
private readonly cache;
|
|
8
|
+
private readonly dbCache;
|
|
9
|
+
/**
|
|
10
|
+
* 获取指定数据库文件的 Drizzle 实例(如不存在则创建)。
|
|
11
|
+
*/
|
|
12
|
+
get(dbPath: string): BunSQLiteDatabase;
|
|
13
|
+
/**
|
|
14
|
+
* 获取原始 Database 实例(用于 DDL 操作)
|
|
15
|
+
*/
|
|
16
|
+
getRaw(dbPath: string): Database;
|
|
17
|
+
/**
|
|
18
|
+
* 连接后执行的优化配置(WAL、外键、超时等)。
|
|
19
|
+
*/
|
|
20
|
+
private afterConnect;
|
|
21
|
+
/**
|
|
22
|
+
* 当前已打开的连接列表。
|
|
23
|
+
*/
|
|
24
|
+
listOpen(): {
|
|
25
|
+
path: string;
|
|
26
|
+
db: BunSQLiteDatabase;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/managers/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAI/D;;GAEG;AACH,qBAAa,MAAM;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAEtD;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAYtC;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAGe,QAAQ;IAG5C;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,QAAQ,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,iBAAiB,CAAA;KAAE,EAAE;CAMrD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
4
|
+
import { DATA_DIR } from '../../../../core/lib/database';
|
|
5
|
+
/**
|
|
6
|
+
* SQLite 连接管理器,负责按文件路径复用 bun:sqlite 实例并执行连接优化。
|
|
7
|
+
*/
|
|
8
|
+
export class Sqlite {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.cache = new Map();
|
|
11
|
+
this.dbCache = new Map();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 获取指定数据库文件的 Drizzle 实例(如不存在则创建)。
|
|
15
|
+
*/
|
|
16
|
+
get(dbPath) {
|
|
17
|
+
const existing = this.cache.get(dbPath);
|
|
18
|
+
if (existing)
|
|
19
|
+
return existing;
|
|
20
|
+
if (!fs.existsSync(DATA_DIR))
|
|
21
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
22
|
+
const db = new Database(dbPath, { create: true });
|
|
23
|
+
this.afterConnect(db);
|
|
24
|
+
const drizzleDb = drizzle(db);
|
|
25
|
+
this.cache.set(dbPath, drizzleDb);
|
|
26
|
+
this.dbCache.set(dbPath, db);
|
|
27
|
+
return drizzleDb;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取原始 Database 实例(用于 DDL 操作)
|
|
31
|
+
*/
|
|
32
|
+
getRaw(dbPath) {
|
|
33
|
+
// 确保 drizzle 实例已创建
|
|
34
|
+
this.get(dbPath);
|
|
35
|
+
return this.dbCache.get(dbPath);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 连接后执行的优化配置(WAL、外键、超时等)。
|
|
39
|
+
*/
|
|
40
|
+
afterConnect(db) {
|
|
41
|
+
db.run('PRAGMA journal_mode = WAL');
|
|
42
|
+
db.run('PRAGMA synchronous = NORMAL');
|
|
43
|
+
db.run('PRAGMA foreign_keys = ON');
|
|
44
|
+
db.run('PRAGMA busy_timeout = 2000');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 当前已打开的连接列表。
|
|
48
|
+
*/
|
|
49
|
+
listOpen() {
|
|
50
|
+
return Array.from(this.cache.entries()).map(([p, db]) => ({
|
|
51
|
+
path: p,
|
|
52
|
+
db: db,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
2
|
+
import type { VisitorShardStrategy } from '../../../../core/lib/database';
|
|
3
|
+
import type { Sqlite } from './sqlite';
|
|
4
|
+
/**
|
|
5
|
+
* 访客分片管理器:按顺序或日期管理 SQLite 分片数据库文件,并提供对应的 Drizzle 实例。
|
|
6
|
+
*/
|
|
7
|
+
export declare class VisitorShard {
|
|
8
|
+
private readonly manager;
|
|
9
|
+
private strategy;
|
|
10
|
+
constructor(manager: Sqlite);
|
|
11
|
+
/** 切换分片策略 */
|
|
12
|
+
setStrategy(strategy: VisitorShardStrategy): void;
|
|
13
|
+
/** 当前写入分片文件路径(不足容量则复用,否则生成新分片)。 */
|
|
14
|
+
currentShardPath(): string;
|
|
15
|
+
/** 列出所有分片文件名 */
|
|
16
|
+
listShardFiles(): string[];
|
|
17
|
+
/** 列出所有分片绝对路径 */
|
|
18
|
+
allShardPaths(): string[];
|
|
19
|
+
/** 获取当前分片的 Drizzle 实例 */
|
|
20
|
+
getCurrentDb(): BunSQLiteDatabase;
|
|
21
|
+
/** 获取已存在的全部分片 Drizzle 实例(若无则创建当前分片) */
|
|
22
|
+
getAllDbs(): {
|
|
23
|
+
path: string;
|
|
24
|
+
db: BunSQLiteDatabase;
|
|
25
|
+
}[];
|
|
26
|
+
/** 到达容量上限时触发旋转(销毁旧连接,下一次写入自动落到新分片)。 */
|
|
27
|
+
rotateIfNeeded(shardPath: string): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=visitor-shard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-shard.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/managers/visitor-shard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC;;GAEG;AACH,qBAAa,YAAY;IAEZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,OAAO,CAAC,QAAQ,CAAmC;gBACtB,OAAO,EAAE,MAAM;IAE5C,aAAa;IACb,WAAW,CAAC,QAAQ,EAAE,oBAAoB;IAI1C,mCAAmC;IACnC,gBAAgB,IAAI,MAAM;IA+B1B,gBAAgB;IAChB,cAAc,IAAI,MAAM,EAAE;IAM1B,iBAAiB;IACjB,aAAa,IAAI,MAAM,EAAE;IAIzB,yBAAyB;IACzB,YAAY,IAAI,iBAAiB;IAKjC,uCAAuC;IACvC,SAAS,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,iBAAiB,CAAA;KAAE,EAAE;IAStD,uCAAuC;IACvC,cAAc,CAAC,SAAS,EAAE,MAAM;CAUhC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { DATA_DIR, MAX_DB_SIZE_BYTES } from '../../../../core/lib/database';
|
|
5
|
+
/**
|
|
6
|
+
* 访客分片管理器:按顺序或日期管理 SQLite 分片数据库文件,并提供对应的 Drizzle 实例。
|
|
7
|
+
*/
|
|
8
|
+
export class VisitorShard {
|
|
9
|
+
constructor(manager) {
|
|
10
|
+
this.manager = manager;
|
|
11
|
+
this.strategy = 'sequence';
|
|
12
|
+
}
|
|
13
|
+
/** 切换分片策略 */
|
|
14
|
+
setStrategy(strategy) {
|
|
15
|
+
this.strategy = strategy;
|
|
16
|
+
}
|
|
17
|
+
/** 当前写入分片文件路径(不足容量则复用,否则生成新分片)。 */
|
|
18
|
+
currentShardPath() {
|
|
19
|
+
if (this.strategy === 'date') {
|
|
20
|
+
const base = `visitor_${dayjs().format('YYYYMMDD')}.db`;
|
|
21
|
+
const p = path.join(DATA_DIR, base);
|
|
22
|
+
if (fs.existsSync(p)) {
|
|
23
|
+
const s = fs.statSync(p).size;
|
|
24
|
+
if (s < MAX_DB_SIZE_BYTES)
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const files = this.listShardFiles();
|
|
29
|
+
const seqNumbers = files
|
|
30
|
+
.map((f) => {
|
|
31
|
+
const m = f.match(/^visitor_(\d{3})\.db$/);
|
|
32
|
+
return m ? parseInt(m[1], 10) : null;
|
|
33
|
+
})
|
|
34
|
+
.filter((n) => typeof n === 'number');
|
|
35
|
+
const last = seqNumbers.length ? Math.max(...seqNumbers) : 0;
|
|
36
|
+
const lastFile = last
|
|
37
|
+
? path.join(DATA_DIR, `visitor_${String(last).padStart(3, '0')}.db`)
|
|
38
|
+
: null;
|
|
39
|
+
if (lastFile &&
|
|
40
|
+
fs.existsSync(lastFile) &&
|
|
41
|
+
fs.statSync(lastFile).size < MAX_DB_SIZE_BYTES) {
|
|
42
|
+
return lastFile;
|
|
43
|
+
}
|
|
44
|
+
const next = String(last + 1).padStart(3, '0');
|
|
45
|
+
return path.join(DATA_DIR, `visitor_${next}.db`);
|
|
46
|
+
}
|
|
47
|
+
/** 列出所有分片文件名 */
|
|
48
|
+
listShardFiles() {
|
|
49
|
+
if (!fs.existsSync(DATA_DIR))
|
|
50
|
+
return [];
|
|
51
|
+
const names = fs.readdirSync(DATA_DIR);
|
|
52
|
+
return names.filter((n) => n.startsWith('visitor_') && n.endsWith('.db'));
|
|
53
|
+
}
|
|
54
|
+
/** 列出所有分片绝对路径 */
|
|
55
|
+
allShardPaths() {
|
|
56
|
+
return this.listShardFiles().map((n) => path.join(DATA_DIR, n));
|
|
57
|
+
}
|
|
58
|
+
/** 获取当前分片的 Drizzle 实例 */
|
|
59
|
+
getCurrentDb() {
|
|
60
|
+
const p = this.currentShardPath();
|
|
61
|
+
return this.manager.get(p);
|
|
62
|
+
}
|
|
63
|
+
/** 获取已存在的全部分片 Drizzle 实例(若无则创建当前分片) */
|
|
64
|
+
getAllDbs() {
|
|
65
|
+
const paths = this.allShardPaths();
|
|
66
|
+
if (!paths.length) {
|
|
67
|
+
const db = this.getCurrentDb();
|
|
68
|
+
return [{ path: this.currentShardPath(), db }];
|
|
69
|
+
}
|
|
70
|
+
return paths.map((p) => ({ path: p, db: this.manager.get(p) }));
|
|
71
|
+
}
|
|
72
|
+
/** 到达容量上限时触发旋转(销毁旧连接,下一次写入自动落到新分片)。 */
|
|
73
|
+
rotateIfNeeded(shardPath) {
|
|
74
|
+
if (fs.existsSync(shardPath)) {
|
|
75
|
+
const size = fs.statSync(shardPath).size;
|
|
76
|
+
if (size >= MAX_DB_SIZE_BYTES) {
|
|
77
|
+
// 获取原始 Database 实例进行关闭
|
|
78
|
+
const db = this.manager.getRaw(shardPath);
|
|
79
|
+
db.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Inquiry, InquiryCreate, InquiryListParams } from '../../../../core/lib/database';
|
|
2
|
+
import type { Sqlite } from '../managers/sqlite';
|
|
3
|
+
/**
|
|
4
|
+
* 询盘仓储:封装对 SQLite 的 CRUD 操作。
|
|
5
|
+
*/
|
|
6
|
+
export declare class InquiryRepository {
|
|
7
|
+
private readonly manager;
|
|
8
|
+
constructor(manager: Sqlite);
|
|
9
|
+
/** 获取询盘数据库连接 */
|
|
10
|
+
private get db();
|
|
11
|
+
/** 获取原始 Database 实例 */
|
|
12
|
+
private get rawDb();
|
|
13
|
+
/** 初始化表结构(幂等) */
|
|
14
|
+
initSchema(): Promise<void>;
|
|
15
|
+
/** 创建询盘(事务) */
|
|
16
|
+
create(data: InquiryCreate): Promise<number>;
|
|
17
|
+
/** 根据 ID 查询询盘 */
|
|
18
|
+
findById(id: number): Promise<Inquiry | null>;
|
|
19
|
+
/** 列表查询 */
|
|
20
|
+
list(params?: InquiryListParams): Promise<Inquiry[]>;
|
|
21
|
+
count(params?: InquiryListParams): Promise<number>;
|
|
22
|
+
/**
|
|
23
|
+
* 统计未读询盘数量
|
|
24
|
+
*/
|
|
25
|
+
countUnread(): Promise<number>;
|
|
26
|
+
/**
|
|
27
|
+
* 标记询盘为已读
|
|
28
|
+
* @param id 询盘 ID
|
|
29
|
+
* @param operatorId 操作人 ID
|
|
30
|
+
*/
|
|
31
|
+
markAsRead(id: number, operatorId: number): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=inquiry.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inquiry.repository.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/repositories/inquiry.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,MAAM,qBAAqB,CAAA;AAM5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD;;GAEG;AACH,qBAAa,iBAAiB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAE5C,gBAAgB;IAChB,OAAO,KAAK,EAAE,GAEb;IAED,uBAAuB;IACvB,OAAO,KAAK,KAAK,GAEhB;IAED,iBAAiB;IACX,UAAU;IAIhB,eAAe;IACT,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BlD,iBAAiB;IACX,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA2BnD,WAAW;IACL,IAAI,CAAC,MAAM,GAAE,iBAAsB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IA6DxD,KAAK,CAAC,MAAM,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5D;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IASpC;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAalE"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { and, asc, count, desc, eq, gte, like, lt } from 'drizzle-orm';
|
|
3
|
+
import { ensureInquirySchema, INQUIRY_DB_PATH, inquiries, } from '../../../../core/lib/database';
|
|
4
|
+
/**
|
|
5
|
+
* 询盘仓储:封装对 SQLite 的 CRUD 操作。
|
|
6
|
+
*/
|
|
7
|
+
export class InquiryRepository {
|
|
8
|
+
constructor(manager) {
|
|
9
|
+
this.manager = manager;
|
|
10
|
+
}
|
|
11
|
+
/** 获取询盘数据库连接 */
|
|
12
|
+
get db() {
|
|
13
|
+
return this.manager.get(INQUIRY_DB_PATH);
|
|
14
|
+
}
|
|
15
|
+
/** 获取原始 Database 实例 */
|
|
16
|
+
get rawDb() {
|
|
17
|
+
return this.manager.getRaw(INQUIRY_DB_PATH);
|
|
18
|
+
}
|
|
19
|
+
/** 初始化表结构(幂等) */
|
|
20
|
+
async initSchema() {
|
|
21
|
+
ensureInquirySchema(this.rawDb);
|
|
22
|
+
}
|
|
23
|
+
/** 创建询盘(事务) */
|
|
24
|
+
async create(data) {
|
|
25
|
+
const createdAt = data.createdAt
|
|
26
|
+
? dayjs(data.createdAt).toISOString()
|
|
27
|
+
: dayjs().toISOString();
|
|
28
|
+
const result = this.db
|
|
29
|
+
.insert(inquiries)
|
|
30
|
+
.values({
|
|
31
|
+
firstName: data.firstName,
|
|
32
|
+
lastName: data.lastName,
|
|
33
|
+
email: data.email,
|
|
34
|
+
phone: data.phone,
|
|
35
|
+
message: data.message,
|
|
36
|
+
userAgent: data.userAgent ?? null,
|
|
37
|
+
ip: data.ip ?? null,
|
|
38
|
+
data: data.data ? JSON.stringify(data.data) : null,
|
|
39
|
+
referer: data.referer ?? null,
|
|
40
|
+
geo: data.geo ? JSON.stringify(data.geo) : null,
|
|
41
|
+
createdAt,
|
|
42
|
+
})
|
|
43
|
+
.returning({ id: inquiries.id });
|
|
44
|
+
const rows = result.all();
|
|
45
|
+
return rows[0]?.id ?? 0;
|
|
46
|
+
}
|
|
47
|
+
/** 根据 ID 查询询盘 */
|
|
48
|
+
async findById(id) {
|
|
49
|
+
const row = this.db
|
|
50
|
+
.select()
|
|
51
|
+
.from(inquiries)
|
|
52
|
+
.where(eq(inquiries.id, id))
|
|
53
|
+
.get();
|
|
54
|
+
if (!row)
|
|
55
|
+
return null;
|
|
56
|
+
return {
|
|
57
|
+
id: row.id,
|
|
58
|
+
firstName: row.firstName,
|
|
59
|
+
lastName: row.lastName,
|
|
60
|
+
email: row.email,
|
|
61
|
+
phone: row.phone,
|
|
62
|
+
message: row.message,
|
|
63
|
+
userAgent: row.userAgent ?? null,
|
|
64
|
+
ip: row.ip ?? null,
|
|
65
|
+
data: row.data ? JSON.parse(row.data) : null,
|
|
66
|
+
referer: row.referer ?? null,
|
|
67
|
+
geo: row.geo ? JSON.parse(row.geo) : null,
|
|
68
|
+
isRead: row.isRead ?? false,
|
|
69
|
+
operatorId: row.operatorId ?? null,
|
|
70
|
+
createdAt: row.createdAt,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** 列表查询 */
|
|
74
|
+
async list(params = {}) {
|
|
75
|
+
const conditions = [];
|
|
76
|
+
if (params.start) {
|
|
77
|
+
conditions.push(gte(inquiries.createdAt, dayjs(params.start).toISOString()));
|
|
78
|
+
}
|
|
79
|
+
if (params.end) {
|
|
80
|
+
conditions.push(lt(inquiries.createdAt, dayjs(params.end).toISOString()));
|
|
81
|
+
}
|
|
82
|
+
if (params.keyword) {
|
|
83
|
+
conditions.push(like(inquiries.message, `%${params.keyword}%`));
|
|
84
|
+
}
|
|
85
|
+
let query = this.db.select().from(inquiries).$dynamic();
|
|
86
|
+
if (conditions.length > 0) {
|
|
87
|
+
query = query.where(and(...conditions));
|
|
88
|
+
}
|
|
89
|
+
// 排序
|
|
90
|
+
if (params.sortBy === 'email') {
|
|
91
|
+
query =
|
|
92
|
+
params.order === 'asc'
|
|
93
|
+
? query.orderBy(asc(inquiries.email))
|
|
94
|
+
: query.orderBy(desc(inquiries.email));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
query =
|
|
98
|
+
params.order === 'asc'
|
|
99
|
+
? query.orderBy(asc(inquiries.createdAt))
|
|
100
|
+
: query.orderBy(desc(inquiries.createdAt));
|
|
101
|
+
}
|
|
102
|
+
// 分页
|
|
103
|
+
if (params.limit) {
|
|
104
|
+
query = query.limit(params.limit);
|
|
105
|
+
}
|
|
106
|
+
if (params.offset) {
|
|
107
|
+
query = query.offset(params.offset);
|
|
108
|
+
}
|
|
109
|
+
const rows = query.all();
|
|
110
|
+
return rows.map((row) => ({
|
|
111
|
+
id: row.id,
|
|
112
|
+
firstName: row.firstName,
|
|
113
|
+
lastName: row.lastName,
|
|
114
|
+
email: row.email,
|
|
115
|
+
phone: row.phone,
|
|
116
|
+
message: row.message,
|
|
117
|
+
userAgent: row.userAgent ?? null,
|
|
118
|
+
ip: row.ip ?? null,
|
|
119
|
+
data: row.data ? JSON.parse(row.data) : null,
|
|
120
|
+
referer: row.referer ?? null,
|
|
121
|
+
geo: row.geo ? JSON.parse(row.geo) : null,
|
|
122
|
+
isRead: row.isRead ?? false,
|
|
123
|
+
operatorId: row.operatorId ?? null,
|
|
124
|
+
createdAt: row.createdAt,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
async count(params = {}) {
|
|
128
|
+
const conditions = [];
|
|
129
|
+
if (params.start) {
|
|
130
|
+
conditions.push(gte(inquiries.createdAt, dayjs(params.start).toISOString()));
|
|
131
|
+
}
|
|
132
|
+
if (params.end) {
|
|
133
|
+
conditions.push(lt(inquiries.createdAt, dayjs(params.end).toISOString()));
|
|
134
|
+
}
|
|
135
|
+
if (params.keyword) {
|
|
136
|
+
conditions.push(like(inquiries.message, `%${params.keyword}%`));
|
|
137
|
+
}
|
|
138
|
+
let query = this.db.select({ c: count() }).from(inquiries);
|
|
139
|
+
if (conditions.length > 0) {
|
|
140
|
+
query = query.where(and(...conditions));
|
|
141
|
+
}
|
|
142
|
+
const result = query.get();
|
|
143
|
+
return result?.c ?? 0;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 统计未读询盘数量
|
|
147
|
+
*/
|
|
148
|
+
async countUnread() {
|
|
149
|
+
const result = this.db
|
|
150
|
+
.select({ c: count() })
|
|
151
|
+
.from(inquiries)
|
|
152
|
+
.where(eq(inquiries.isRead, false))
|
|
153
|
+
.get();
|
|
154
|
+
return result?.c ?? 0;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 标记询盘为已读
|
|
158
|
+
* @param id 询盘 ID
|
|
159
|
+
* @param operatorId 操作人 ID
|
|
160
|
+
*/
|
|
161
|
+
async markAsRead(id, operatorId) {
|
|
162
|
+
const result = this.db
|
|
163
|
+
.update(inquiries)
|
|
164
|
+
.set({
|
|
165
|
+
isRead: true,
|
|
166
|
+
operatorId,
|
|
167
|
+
})
|
|
168
|
+
.where(eq(inquiries.id, id))
|
|
169
|
+
.returning({ id: inquiries.id });
|
|
170
|
+
const rows = result.all();
|
|
171
|
+
return rows.length > 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DailyPageViewStats, IpPageViewStats, PageView, PVStats, UVStats, VisitorCreate, VisitorQuery, VisitorSession, VisitorStatsBy } from '../../../../core/lib/database';
|
|
2
|
+
import type { Sqlite } from '../managers/sqlite';
|
|
3
|
+
/**
|
|
4
|
+
* 访客仓储:封装跨分片的读写与统计。
|
|
5
|
+
*/
|
|
6
|
+
export declare class VisitorRepository {
|
|
7
|
+
private readonly manager;
|
|
8
|
+
private readonly shardManager;
|
|
9
|
+
constructor(manager: Sqlite);
|
|
10
|
+
/** 初始化已有分片的表结构(幂等),若无分片则创建当前分片 */
|
|
11
|
+
initShardSchemas(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* 写入访客记录(事务),并在必要时触发分片旋转。
|
|
14
|
+
*/
|
|
15
|
+
insert(data: VisitorCreate): Promise<{
|
|
16
|
+
id: number;
|
|
17
|
+
shardPath: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* 统计独立访客数(UV)
|
|
21
|
+
*/
|
|
22
|
+
getUVStats(filter?: VisitorQuery, byDate?: boolean): Promise<UVStats>;
|
|
23
|
+
/**
|
|
24
|
+
* 统计页面总浏览量(PV)
|
|
25
|
+
*/
|
|
26
|
+
getPVStats(filter?: VisitorQuery, groupBy?: 'page' | 'date'): Promise<PVStats>;
|
|
27
|
+
/**
|
|
28
|
+
* 统计每个页面每日的访问次数(从预聚合表查询)
|
|
29
|
+
*/
|
|
30
|
+
getDailyPageViewStats(filter?: VisitorQuery): Promise<DailyPageViewStats[]>;
|
|
31
|
+
/**
|
|
32
|
+
* 统计基于 IP 的页面访问次数(从预聚合表查询)
|
|
33
|
+
*/
|
|
34
|
+
getIpPageViewStats(filter?: VisitorQuery): Promise<IpPageViewStats[]>;
|
|
35
|
+
/**
|
|
36
|
+
* 跨分片查询页面浏览记录
|
|
37
|
+
*/
|
|
38
|
+
findPageViews(filter?: VisitorQuery): Promise<PageView[]>;
|
|
39
|
+
/**
|
|
40
|
+
* 跨分片查询访客会话
|
|
41
|
+
*/
|
|
42
|
+
findVisitorSessions(filter?: VisitorQuery): Promise<VisitorSession[]>;
|
|
43
|
+
/** 跨分片计数 */
|
|
44
|
+
count(filter?: VisitorQuery): Promise<number>;
|
|
45
|
+
/** 跨分片统计(向后兼容) */
|
|
46
|
+
stats(by: VisitorStatsBy, filter?: VisitorQuery): Promise<{
|
|
47
|
+
key: string;
|
|
48
|
+
count: number;
|
|
49
|
+
}[]>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=visitor.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor.repository.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/repositories/visitor.repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,MAAM,qBAAqB,CAAA;AAS5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD;;GAEG;AACH,qBAAa,iBAAiB;IAGjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;gBAEd,OAAO,EAAE,MAAM;IAI5C,kCAAkC;IAC5B,gBAAgB;IAQtB;;OAEG;IACG,MAAM,CACX,IAAI,EAAE,aAAa,GACjB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAkK7C;;OAEG;IACG,UAAU,CACf,MAAM,GAAE,YAAiB,EACzB,MAAM,UAAQ,GACZ,OAAO,CAAC,OAAO,CAAC;IA0FnB;;OAEG;IACG,UAAU,CACf,MAAM,GAAE,YAAiB,EACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC;IAqInB;;OAEG;IACG,qBAAqB,CAC1B,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAkDhC;;OAEG;IACG,kBAAkB,CACvB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC;IA6C7B;;OAEG;IACG,aAAa,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA+CnE;;OAEG;IACG,mBAAmB,CACxB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC;IA6C5B,YAAY;IACN,KAAK,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiCvD,kBAAkB;IACZ,KAAK,CACV,EAAE,EAAE,cAAc,EAClB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAwB5C"}
|