@sfmc-bds/db-server 0.2.0-beta.7 → 0.2.0-beta.8

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.
@@ -1,22 +1,21 @@
1
1
  /**
2
- * domain/schema.ts — 数据库 Schema 初始化定义(平台内嵌表唯一基线)
2
+ * domain/schema.ts — 数据库 Schema 初始化定义(平台核心底座表)
3
3
  *
4
- * 集中管理平台核心数据表的 `CREATE TABLE IF NOT EXISTS` 与 `CREATE INDEX IF NOT EXISTS` DDL 语句。
5
- * 调用方式:`initSchema(db)` —— 在 `openDatabase` 之后、业务查询启动前执行。
4
+ * 集中管理平台核心数据表的 CREATE TABLE IF NOT EXISTS 与 CREATE INDEX IF NOT EXISTS DDL 语句。
5
+ * 调用方式:initSchema(db) —— 在 openDatabase 之后、业务查询启动前执行。
6
6
  *
7
- * 业务数据域分布:
8
- * (1) 世界备份 (2) 玩家备份 (3) 计分板
9
- * (4) 玩家行为日志 (5) 聊天频道 (6) 聊天消息
10
- * (7) 红包 (8) 合作社主体/成员/邀请/账户
11
- * (9) 合作社商店项/组 (10) 合作社流水/审计
12
- * (11) 领地主体/成员 (12) 领地邀请/审计/幂等
13
- * (13) 指令用量 (14) 价格指数
14
- * (15) 每日任务/统计 (16) 经济账户/流水/幂等
15
- * (17) QQ 绑定与申请 (18) 模块自声明元数据表
7
+ * 架构约定:
8
+ * 业务模块(activity-log, coop, land, chat, economy 等)私有数据表均由各自模块在 SAPI 运行期通过
9
+ * db.defineTable(...) 自主声明与维护,不在此处静态硬编码,避免列定义与索引冲突。
10
+ *
11
+ * 此处仅定义平台核心运行、跨服务共享(qq-bridge / HTTP 端点 / status 探活)的基础底座表:
12
+ * 1. 世界与玩家运行期状态快照(sfmc_world, sfmc_players)
13
+ * 2. 跨进程聊天消息聚合(sfmc_chat_messages)
14
+ * 3. QQ 身份绑定与审批队列(sfmc_qq_*)
16
15
  */
17
16
  import type { DatabaseSync } from "node:sqlite";
18
17
  /**
19
- * 初始化数据库结构,创建所有平台内嵌表与索引。
18
+ * 初始化数据库结构,创建平台核心底座表与索引。
20
19
  *
21
20
  * @param db SQLite 数据库连接实例。
22
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAofjD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoGjD"}
@@ -3,519 +3,66 @@ function initSchema(db) {
3
3
  /* sql */
4
4
  `
5
5
  CREATE TABLE IF NOT EXISTS sfmc_world (
6
- allow_cheats INTEGER NOT NULL DEFAULT 0, -- \u5141\u8BB8\u4F5C\u5F0A\u7684\u9009\u9879
7
- game_rules TEXT NOT NULL DEFAULT '', -- \u4E16\u754C\u89C4\u5219(json)
8
- seed TEXT DEFAULT '', -- \u79CD\u5B50
9
- default_spawn_location TEXT NOT NULL DEFAULT '', -- \u9ED8\u8BA4\u51FA\u751F\u70B9
10
- difficulty TEXT NOT NULL DEFAULT '', -- \u96BE\u5EA6
11
- day INTEGER NOT NULL DEFAULT 0, -- \u6E38\u620F\u5929\u6570\uFF08\u4E16\u754C\uFF09
12
- ticking_areas_count INTEGER DEFAULT 0, -- \u5E38\u52A0\u8F7D\u533A\u57DF\u6570\u91CF
13
- absolute_time INTEGER NOT NULL DEFAULT 0, -- \u6E38\u620F\u5185\u65F6\u95F4\uFF08\u5F53\u524D\uFF09
14
- structures_from_addon TEXT DEFAULT '', -- \u6765\u81EA\u6A21\u7EC4\u7684\u7ED3\u6784\uFF08\u5728\u5BF9\u5E94\u884C\u4E3A\u5305\u5185\uFF09
15
- structures_from_world TEXT DEFAULT '', -- \u6765\u81EA\u4E16\u754C\u7684\u7ED3\u6784 \u5305\u542B\u4FDD\u5B58\u5728\u5185\u5B58\u4E0E\u78C1\u76D8\u4E2D\u7684
16
- dynamic_property_total_byte_count INTEGER DEFAULT 0, -- \u52A8\u6001\u6570\u636E\uFF08\u4E16\u754C\uFF09\u5B57\u8282\u6570
17
- moon_phase INTEGER NOT NULL DEFAULT 0, -- \u6708\u76F8\uFF08\u5F53\u524D\uFF09
18
- updated_at TEXT NOT NULL DEFAULT '' -- \u66F4\u65B0\u65F6\u95F4\u6233
6
+ id TEXT PRIMARY KEY,
7
+ seed TEXT DEFAULT '',
8
+ difficulty TEXT DEFAULT '',
9
+ spawn_x INTEGER DEFAULT 0,
10
+ spawn_y INTEGER DEFAULT 0,
11
+ spawn_z INTEGER DEFAULT 0,
12
+ gamerules TEXT DEFAULT '{}',
13
+ day INTEGER DEFAULT 0,
14
+ absolute_time INTEGER DEFAULT 0,
15
+ moon_phase INTEGER DEFAULT 0,
16
+ updated_at INTEGER DEFAULT 0
19
17
  )`
20
18
  );
21
19
  db.exec(
22
20
  /* sql */
23
21
  `
24
22
  CREATE TABLE IF NOT EXISTS sfmc_players (
25
- id TEXT NOT NULL, -- \u5B9E\u4F53id\u6807\u8BC6\u7B26
26
- name TEXT NOT NULL, -- \u540D\u79F0
27
- permission INTEGER DEFAULT 0, -- \u6743\u9650\u7B49\u7EA7\uFF080=\u8BBF\u5BA2,1=\u6210\u5458,2=OP,3=\u7BA1\u7406\u5458\uFF09TODO: \u6682\u672A\u9002\u914D\u81EA\u5B9A\u4E49\u811A\u672C\u6743\u9650
28
- client_system_info_local TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u672C\u5730\u5316\u8BED\u8A00
29
- client_system_info_maxRenderDistance INTEGER DEFAULT 0, -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u6700\u5927\u6E32\u67D3\u8DDD\u79BB
30
- client_system_info_memoryTier_level TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u4FE1\u606F->\u5185\u5B58\u7B49\u7EA7 \u8BE6\u7EC6\u8BF7\u67E5\u770B\u5B98\u65B9\u6587\u6863
31
- client_system_info_PlatformType TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u5E73\u53F0\u7C7B\u578B
32
- graphicsMode TEXT DEFAULT '', -- \u56FE\u5F62\u6A21\u5F0F
33
- dynamicPropertyTotalByteCount INTEGER DEFAULT 0, -- \u52A8\u6001\u5C5E\u6027\u603B\u5B57\u8282\u6570
34
- ping INTEGER DEFAULT 0, -- \u5EF6\u8FDF\uFF08ms\uFF09
35
- spawnPoint TEXT DEFAULT '', -- \u51FA\u751F\u70B9\u5750\u6807
36
- tags TEXT DEFAULT '', -- \u73A9\u5BB6\u6807\u7B7E
37
- level INTEGER DEFAULT 0, -- \u7B49\u7EA7
38
- totalXp INTEGER DEFAULT 0, -- \u603B\u7ECF\u9A8C\u503C
39
- afk_step INTEGER DEFAULT 0, -- AFK\u7D2F\u8BA1\u6B65\u6570
40
- afk_last_location TEXT DEFAULT '', -- AFK\u6700\u540E\u4F4D\u7F6E
41
- onlinetime_session INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u672C\u6B21\u4F1A\u8BDD\uFF08\u79D2\uFF09
42
- onlinetime_today INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4ECA\u65E5\uFF08\u79D2\uFF09
43
- onlinetime_month INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u672C\u6708\uFF08\u79D2\uFF09
44
- onlinetime_total INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u7D2F\u8BA1\uFF08\u79D2\uFF09
45
- onlinetime_last_date INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4E0A\u6B21\u8BB0\u5F55\u65E5\u671F
46
- onlinetime_last_month INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4E0A\u6B21\u8BB0\u5F55\u6708\u4EFD
47
- active_channel TEXT NOT NULL DEFAULT '', -- \u5F53\u524D\u6D3B\u8DC3\u804A\u5929\u9891\u9053
48
- subscribed_channels TEXT DEFAULT '', -- \u8BA2\u9605\u7684\u9891\u9053\u5217\u8868
49
- updated_at INTEGER NOT NULL, -- \u73A9\u5BB6\u6570\u636E\u66F4\u65B0\u65F6\u95F4\u6233
50
- PRIMARY KEY (id, name) -- \u8054\u5408\u4E3B\u952E\uFF1A\u5B9E\u4F53id+\u540D\u79F0
23
+ id TEXT PRIMARY KEY,
24
+ name TEXT NOT NULL DEFAULT '',
25
+ xuid TEXT DEFAULT '',
26
+ last_online INTEGER DEFAULT 0,
27
+ level INTEGER DEFAULT 0,
28
+ total_xp INTEGER DEFAULT 0,
29
+ tags TEXT DEFAULT '',
30
+ updated_at INTEGER DEFAULT 0
51
31
  )`
52
32
  );
53
33
  db.exec(
54
34
  /* sql */
55
35
  `
56
- CREATE INDEX IF NOT EXISTS idx_players_id ON sfmc_players(id);
57
36
  CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`
58
37
  );
59
- db.exec(
60
- /* sql */
61
- `
62
- CREATE TABLE IF NOT EXISTS sfmc_scoreboards (
63
- objective_id TEXT NOT NULL, -- \u8BA1\u5206\u677F\u76EE\u6807id\uFF08\u4E3B\u952E\uFF09
64
- objective_display TEXT NOT NULL DEFAULT '', -- \u8BA1\u5206\u677F\u663E\u793A\u540D\u79F0
65
- participants TEXT DEFAULT '', -- \u53C2\u4E0E\u8005\u5217\u8868\uFF08json\uFF09
66
- updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233
67
- PRIMARY KEY (objective_id) -- \u4E3B\u952E
68
- )`
69
- );
70
- db.exec(
71
- /* sql */
72
- `
73
- CREATE TABLE IF NOT EXISTS sfmc_activities (
74
- id TEXT PRIMARY KEY, -- \u552F\u4E00id
75
- timestamp INTEGER NOT NULL, -- \u4E8B\u4EF6\u53D1\u751F\u65F6\u95F4\u6233
76
- dimension TEXT NOT NULL DEFAULT '', -- \u7EF4\u5EA6\uFF08overworld/nether/end\uFF09
77
- source_type TEXT NOT NULL, -- \u4E8B\u4EF6\u6E90\u7C7B\u578B\uFF08\u5982player\u3001entity\u3001block\u7B49\uFF09
78
- source_id TEXT DEFAULT '', -- \u4E8B\u4EF6\u6E90\u5B9E\u4F53/\u65B9\u5757id
79
- source_name TEXT NOT NULL DEFAULT '', -- \u4E8B\u4EF6\u6E90\u540D\u79F0
80
- source_x REAL, -- \u4E8B\u4EF6\u6E90x\u5750\u6807
81
- source_y REAL, -- \u4E8B\u4EF6\u6E90y\u5750\u6807
82
- source_z REAL, -- \u4E8B\u4EF6\u6E90z\u5750\u6807
83
- event_type TEXT NOT NULL, -- \u4E8B\u4EF6\u7C7B\u578B\uFF08\u5982chat\u3001command\u3001death\u3001join\u7B49\uFF09
84
- target_type TEXT DEFAULT '', -- \u76EE\u6807\u7C7B\u578B
85
- target_id TEXT DEFAULT '', -- \u76EE\u6807\u5B9E\u4F53id
86
- target_name TEXT NOT NULL DEFAULT '', -- \u76EE\u6807\u540D\u79F0
87
- target_x REAL, -- \u76EE\u6807x\u5750\u6807
88
- target_y REAL, -- \u76EE\u6807y\u5750\u6807
89
- target_z REAL, -- \u76EE\u6807z\u5750\u6807
90
- detail TEXT DEFAULT '{}', -- \u4E8B\u4EF6\u8BE6\u7EC6\u4FE1\u606F\uFF08json\uFF09
91
- created_at INTEGER NOT NULL -- \u8BB0\u5F55\u521B\u5EFA\u65F6\u95F4\u6233
92
- )`
93
- );
94
- db.exec(
95
- /* sql */
96
- `
97
- CREATE INDEX IF NOT EXISTS idx_sfmc_act_source ON sfmc_activities(source_id, timestamp);
98
- CREATE INDEX IF NOT EXISTS idx_sfmc_act_event ON sfmc_activities(event_type, timestamp);
99
- CREATE INDEX IF NOT EXISTS idx_sfmc_act_target ON sfmc_activities(target_id, timestamp)`
100
- );
101
- db.exec(
102
- /* sql */
103
- `
104
- CREATE TABLE IF NOT EXISTS sfmc_chat_channels (
105
- id TEXT NOT NULL, -- \u9891\u9053\u552F\u4E00id
106
- name TEXT NOT NULL, -- \u9891\u9053\u540D\u79F0
107
- type TEXT NOT NULL, -- \u9891\u9053\u7C7B\u578B
108
- prefix TEXT NOT NULL, -- \u9891\u9053\u524D\u7F00\u7B26\u53F7
109
- owner_id TEXT DEFAULT '', -- \u9891\u9053\u6240\u6709\u8005\u73A9\u5BB6id
110
- created_at INTEGER NOT NULL, -- \u9891\u9053\u521B\u5EFA\u65F6\u95F4\u6233
111
- config_allow_chat INTEGER NOT NULL DEFAULT 1, -- \u914D\u7F6E->\u662F\u5426\u5141\u8BB8\u804A\u5929
112
- config_slow_mode INTEGER NOT NULL DEFAULT 0, -- \u914D\u7F6E->\u6162\u6A21\u5F0F\u95F4\u9694\uFF08\u79D2\uFF09
113
- config_is_broadcast INTEGER NOT NULL DEFAULT 0, -- \u914D\u7F6E->\u662F\u5426\u4E3A\u5E7F\u64AD\u9891\u9053
114
- updated_at INTEGER NOT NULL, -- \u9891\u9053\u914D\u7F6E\u66F4\u65B0\u65F6\u95F4\u6233
115
- PRIMARY KEY (id, name) -- \u8054\u5408\u4E3B\u952E
116
- )`
117
- );
118
- db.exec(
119
- /* sql */
120
- `
121
- CREATE INDEX IF NOT EXISTS idx_channels_id ON sfmc_chat_channels(id);
122
- CREATE INDEX IF NOT EXISTS idx_channels_name ON sfmc_chat_channels(name, created_at ASC)`
123
- );
124
38
  db.exec(
125
39
  /* sql */
126
40
  `
127
41
  CREATE TABLE IF NOT EXISTS sfmc_chat_messages (
128
- id TEXT PRIMARY KEY, -- \u6D88\u606F\u552F\u4E00id
129
- channel_id TEXT NOT NULL, -- \u6240\u5C5E\u9891\u9053id
130
- from_id TEXT NOT NULL, -- \u53D1\u9001\u8005\u73A9\u5BB6id
131
- from_name TEXT NOT NULL, -- \u53D1\u9001\u8005\u540D\u79F0
132
- type TEXT NOT NULL DEFAULT 'text', -- \u6D88\u606F\u7C7B\u578B
133
- content TEXT NOT NULL, -- \u6D88\u606F\u5185\u5BB9
134
- attachment TEXT, -- \u9644\u4EF6\u4FE1\u606F
135
- show_timestamp INTEGER NOT NULL DEFAULT 0, -- \u662F\u5426\u5728\u6D88\u606F\u5217\u8868\u663E\u793A\u65F6\u95F4\u6233
136
- created_at INTEGER NOT NULL -- \u6D88\u606F\u53D1\u9001\u65F6\u95F4\u6233
137
- )`
138
- );
139
- db.exec(
140
- /* sql */
141
- `
142
- CREATE INDEX IF NOT EXISTS idx_messages_channel ON sfmc_chat_messages(channel_id, created_at ASC)`
143
- );
144
- db.exec(
145
- /* sql */
146
- `
147
- CREATE TABLE IF NOT EXISTS sfmc_chat_redpackets (
148
- id TEXT PRIMARY KEY, -- \u7EA2\u5305\u552F\u4E00id
149
- sender_id TEXT NOT NULL, -- \u53D1\u7EA2\u5305\u73A9\u5BB6id
150
- sender_name TEXT NOT NULL, -- \u53D1\u7EA2\u5305\u73A9\u5BB6\u540D\u79F0
151
- total_amount REAL NOT NULL, -- \u7EA2\u5305\u603B\u91D1\u989D
152
- remaining_amount REAL NOT NULL, -- \u5269\u4F59\u91D1\u989D
153
- total_count INTEGER NOT NULL, -- \u7EA2\u5305\u603B\u4E2A\u6570
154
- remaining_count INTEGER NOT NULL, -- \u5269\u4F59\u4E2A\u6570
155
- receivers TEXT NOT NULL DEFAULT '[]', -- \u5DF2\u9886\u53D6\u73A9\u5BB6\u5217\u8868\uFF08json\uFF09
156
- target_type TEXT NOT NULL, -- \u7EA2\u5305\u76EE\u6807\u7C7B\u578B\uFF08all/channel/player\u7B49\uFF09
157
- target_id TEXT NOT NULL, -- \u7EA2\u5305\u76EE\u6807id
158
- created_at INTEGER NOT NULL, -- \u7EA2\u5305\u521B\u5EFA\u65F6\u95F4\u6233
159
- expires_at INTEGER NOT NULL -- \u7EA2\u5305\u8FC7\u671F\u65F6\u95F4\u6233
160
- )`
161
- );
162
- db.exec(
163
- /* sql */
164
- `
165
- CREATE INDEX IF NOT EXISTS idx_redpackets_id ON sfmc_chat_redpackets(id)`
166
- );
167
- db.exec(
168
- /* sql */
169
- `
170
- CREATE TABLE IF NOT EXISTS sfmc_coops (
171
- cid TEXT PRIMARY KEY, -- \u5408\u4F5C\u793E\u552F\u4E00id
172
- name TEXT NOT NULL, -- \u5408\u4F5C\u793E\u540D\u79F0
173
- owner_player_id TEXT NOT NULL, -- \u793E\u957F\u73A9\u5BB6id
174
- owner_name_snapshot TEXT NOT NULL DEFAULT '', -- \u793E\u957F\u540D\u79F0\u5FEB\u7167
175
- status TEXT NOT NULL DEFAULT 'active', -- \u72B6\u6001\uFF08active/inactive/dissolved\uFF09
176
- notice TEXT DEFAULT '', -- \u5408\u4F5C\u793E\u516C\u544A
177
- created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233
178
- updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233
179
- fee_bps INTEGER NOT NULL DEFAULT 500, -- \u7A0E\u7387\uFF08\u57FA\u70B9\uFF0C500=5%\uFF09
180
- version INTEGER NOT NULL DEFAULT 1 -- \u4E50\u89C2\u9501\u7248\u672C\u53F7
181
- )`
182
- );
183
- db.exec(
184
- /* sql */
185
- `
186
- CREATE TABLE IF NOT EXISTS sfmc_coop_members (
187
- cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid
188
- player_id TEXT NOT NULL, -- \u6210\u5458\u73A9\u5BB6id
189
- player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6210\u5458\u540D\u79F0\u5FEB\u7167
190
- role TEXT NOT NULL DEFAULT 'member', -- \u89D2\u8272\uFF08member/admin/owner\uFF09
191
- joined_at INTEGER NOT NULL, -- \u52A0\u5165\u65F6\u95F4\u6233
192
- expires_at INTEGER, -- \u6210\u5458\u8D44\u683C\u8FC7\u671F\u65F6\u95F4\u6233\uFF08null=\u6C38\u4E0D\u8FC7\u671F\uFF09
193
- status TEXT NOT NULL DEFAULT 'active', -- \u72B6\u6001\uFF08active/expired/kicked\uFF09
194
- version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7
195
- PRIMARY KEY (cid, player_id), -- \u8054\u5408\u4E3B\u952E
196
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
197
- )`
198
- );
199
- db.exec(
200
- /* sql */
201
- `
202
- CREATE UNIQUE INDEX IF NOT EXISTS idx_sfmc_one_active_coop_member ON sfmc_coop_members(player_id) WHERE status='active'`
203
- );
204
- db.exec(
205
- /* sql */
206
- `
207
- CREATE TABLE IF NOT EXISTS sfmc_coop_invites (
208
- id TEXT PRIMARY KEY, -- \u9080\u8BF7\u552F\u4E00id
209
- cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid
210
- inviter_id TEXT NOT NULL, -- \u9080\u8BF7\u4EBA\u73A9\u5BB6id
211
- invitee_id TEXT NOT NULL, -- \u88AB\u9080\u8BF7\u4EBA\u73A9\u5BB6id
212
- invitee_name_snapshot TEXT NOT NULL DEFAULT '', -- \u88AB\u9080\u8BF7\u4EBA\u540D\u79F0\u5FEB\u7167
213
- role TEXT NOT NULL DEFAULT 'member', -- \u9080\u8BF7\u89D2\u8272
214
- status TEXT NOT NULL DEFAULT 'pending', -- \u72B6\u6001\uFF08pending/accepted/rejected/expired\uFF09
215
- expires_at INTEGER NOT NULL, -- \u9080\u8BF7\u8FC7\u671F\u65F6\u95F4\u6233
216
- created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233
217
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
218
- )`
219
- );
220
- db.exec(
221
- /* sql */
222
- `
223
- CREATE INDEX IF NOT EXISTS idx_sfmc_coop_invites_target ON sfmc_coop_invites(invitee_id,status,expires_at)`
224
- );
225
- db.exec(
226
- /* sql */
227
- `
228
- CREATE TABLE IF NOT EXISTS sfmc_coop_accounts (
229
- cid TEXT PRIMARY KEY, -- \u5408\u4F5C\u793Eid
230
- balance INTEGER NOT NULL DEFAULT 0, -- \u8D26\u6237\u4F59\u989D
231
- version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7
232
- updated_at INTEGER NOT NULL, -- \u8D26\u6237\u66F4\u65B0\u65F6\u95F4\u6233
233
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
234
- )`
235
- );
236
- db.exec(
237
- /* sql */
238
- `
239
- CREATE TABLE IF NOT EXISTS sfmc_coop_shop_items (
240
- id TEXT PRIMARY KEY, -- \u7269\u54C1\u552F\u4E00id
241
- cid TEXT NOT NULL, -- \u6240\u5C5E\u5408\u4F5C\u793Eid
242
- name TEXT NOT NULL, -- \u7269\u54C1\u540D\u79F0
243
- item_type TEXT NOT NULL, -- \u7269\u54C1\u7C7B\u578B\u6807\u8BC6\u7B26\uFF08\u5982minecraft:diamond\uFF09
244
- item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C/\u6570\u636E\u503C
245
- item_nbt TEXT DEFAULT '', -- \u7269\u54C1NBT\u6570\u636E
246
- type INTEGER NOT NULL, -- \u7269\u54C1\u4EA4\u6613\u7C7B\u578B\uFF080=\u6536\u8D2D,1=\u51FA\u552E,2=\u4EA4\u6362\uFF09
247
- groups TEXT DEFAULT '[]', -- \u6240\u5C5E\u5546\u54C1\u5206\u7EC4
248
- des TEXT DEFAULT '', -- \u7269\u54C1\u63CF\u8FF0
249
- num INTEGER DEFAULT 0, -- \u5F53\u524D\u5E93\u5B58\u6570\u91CF
250
- sv INTEGER DEFAULT 0, -- \u9500\u552E/\u6536\u8D2D\u9650\u5236
251
- money INTEGER DEFAULT 0, -- \u5355\u4EF7
252
- is_true INTEGER DEFAULT 1, -- \u662F\u5426\u4E0A\u67B6\uFF081=\u4E0A\u67B6,0=\u4E0B\u67B6\uFF09
253
- created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233
254
- updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233
255
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
256
- )`
257
- );
258
- db.exec(
259
- /* sql */
260
- `
261
- CREATE TABLE IF NOT EXISTS sfmc_coop_bank_log (
262
- id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u6D41\u6C34id\uFF08\u81EA\u589E\uFF09
263
- cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid
264
- actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u4EBA\u73A9\u5BB6id
265
- actor_name_snapshot TEXT NOT NULL DEFAULT '', -- \u64CD\u4F5C\u4EBA\u540D\u79F0\u5FEB\u7167
266
- type INTEGER NOT NULL, -- \u6D41\u6C34\u7C7B\u578B\uFF080=\u5B58\u5165,1=\u53D6\u51FA,2=\u4EA4\u6613\u6536\u5165,3=\u4EA4\u6613\u652F\u51FA\uFF09
267
- amount INTEGER NOT NULL, -- \u53D8\u52A8\u91D1\u989D
268
- note TEXT DEFAULT '', -- \u5907\u6CE8
269
- transaction_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E8B\u52A1id
270
- created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233
271
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
272
- )`
273
- );
274
- db.exec(
275
- /* sql */
276
- `
277
- CREATE TABLE IF NOT EXISTS sfmc_coop_audit_logs (
278
- id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u65E5\u5FD7id\uFF08\u81EA\u589E\uFF09
279
- cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid
280
- actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id
281
- target_id TEXT NOT NULL DEFAULT '', -- \u88AB\u64CD\u4F5C\u76EE\u6807id
282
- action TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982create/update/delete/transfer\u7B49\uFF09
283
- before_state TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u524D\u72B6\u6001\uFF08json\uFF09
284
- after_state TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u540E\u72B6\u6001\uFF08json\uFF09
285
- transaction_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E8B\u52A1id
286
- created_at INTEGER NOT NULL, -- \u65E5\u5FD7\u521B\u5EFA\u65F6\u95F4\u6233
287
- FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
288
- )`
289
- );
290
- db.exec(
291
- /* sql */
292
- `
293
- CREATE TABLE IF NOT EXISTS sfmc_coop_shop_groups (
294
- groupid TEXT PRIMARY KEY, -- \u5206\u7EC4\u552F\u4E00id
295
- displayname TEXT NOT NULL, -- \u5206\u7EC4\u663E\u793A\u540D\u79F0
296
- displaydescribe TEXT DEFAULT '', -- \u5206\u7EC4\u63CF\u8FF0
297
- icon TEXT DEFAULT '', -- \u5206\u7EC4\u56FE\u6807
298
- type_function TEXT DEFAULT '' -- \u5206\u7EC4\u529F\u80FD\u7C7B\u578B
299
- )`
300
- );
301
- db.exec(
302
- /* sql */
303
- `
304
- CREATE TABLE IF NOT EXISTS sfmc_lands (
305
- id TEXT PRIMARY KEY, -- \u9886\u5730\u552F\u4E00id
306
- owner_player_id TEXT NOT NULL, -- \u9886\u5730\u6240\u6709\u8005\u73A9\u5BB6id
307
- owner_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6240\u6709\u8005\u540D\u79F0\u5FEB\u7167
308
- dimension INTEGER NOT NULL, -- \u6240\u5728\u7EF4\u5EA6\uFF080=\u4E3B\u4E16\u754C,1=\u4E0B\u754C,2=\u672B\u5730\uFF09
309
- min_x INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fx\u5750\u6807
310
- min_y INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fy\u5750\u6807
311
- min_z INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fz\u5750\u6807
312
- max_x INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927x\u5750\u6807
313
- max_y INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927y\u5750\u6807
314
- max_z INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927z\u5750\u6807
315
- name TEXT NOT NULL DEFAULT '', -- \u9886\u5730\u540D\u79F0
316
- status TEXT NOT NULL DEFAULT 'active', -- \u9886\u5730\u72B6\u6001\uFF08active/locked/expired/auction\uFF09
317
- created_at INTEGER NOT NULL, -- \u9886\u5730\u521B\u5EFA\u65F6\u95F4\u6233
318
- updated_at INTEGER NOT NULL, -- \u9886\u5730\u66F4\u65B0\u65F6\u95F4\u6233
319
- expires_at INTEGER, -- \u9886\u5730\u8FC7\u671F\u65F6\u95F4\u6233\uFF08null=\u6C38\u4E0D\u8FC7\u671F\uFF09
320
- protection_profile TEXT NOT NULL DEFAULT '{}', -- \u4FDD\u62A4\u914D\u7F6E\uFF08json\uFF09
321
- version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7
322
- purchase_price INTEGER NOT NULL DEFAULT 0, -- \u8D2D\u4E70\u4EF7\u683C
323
- refund_rate REAL NOT NULL DEFAULT 0.7, -- \u9000\u6B3E\u6BD4\u4F8B\uFF080~1\uFF09
324
- tax_rate INTEGER NOT NULL DEFAULT 0, -- \u7A0E\u7387\uFF08\u767E\u5206\u6BD4\uFF09
325
- tax_due_at INTEGER, -- \u4E0B\u4E00\u4E2A\u7F34\u7A0E\u65F6\u95F4\u6233
326
- tax_frozen INTEGER NOT NULL DEFAULT 0 -- \u7A0E\u52A1\u51BB\u7ED3\u72B6\u6001\uFF080=\u6B63\u5E38,1=\u51BB\u7ED3\uFF09
327
- )`
328
- );
329
- db.exec(
330
- /* sql */
331
- `
332
- CREATE TABLE IF NOT EXISTS sfmc_land_members (
333
- land_id TEXT NOT NULL, -- \u9886\u5730id
334
- player_id TEXT NOT NULL, -- \u6210\u5458\u73A9\u5BB6id
335
- player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6210\u5458\u540D\u79F0\u5FEB\u7167
336
- role TEXT NOT NULL DEFAULT 'admin', -- \u6210\u5458\u89D2\u8272\uFF08member/trusted/admin/owner\uFF09
337
- created_at INTEGER NOT NULL, -- \u52A0\u5165\u65F6\u95F4\u6233
338
- expires_at INTEGER, -- \u6210\u5458\u8D44\u683C\u8FC7\u671F\u65F6\u95F4\u6233
339
- PRIMARY KEY (land_id, player_id), -- \u8054\u5408\u4E3B\u952E
340
- FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
341
- )`
342
- );
343
- db.exec(
344
- /* sql */
345
- `
346
- CREATE TABLE IF NOT EXISTS sfmc_land_permissions (
347
- land_id TEXT NOT NULL, -- \u9886\u5730id
348
- permission_key TEXT NOT NULL, -- \u6743\u9650\u952E\uFF08\u5982 buildbreak.interact \u7B49\uFF09
349
- subject_type TEXT NOT NULL, -- \u6743\u9650\u4E3B\u4F53\u7C7B\u578B\uFF08player/group/role/all\uFF09
350
- subject_id TEXT NOT NULL, -- \u6743\u9650\u4E3B\u4F53id
351
- allowed INTEGER NOT NULL DEFAULT 0, -- \u662F\u5426\u5141\u8BB8\uFF081=\u5141\u8BB8,0=\u62D2\u7EDD,-1=\u672A\u8BBE\u7F6E\uFF09
352
- updated_at INTEGER NOT NULL, -- \u6743\u9650\u66F4\u65B0\u65F6\u95F4\u6233
353
- PRIMARY KEY (land_id, permission_key, subject_type, subject_id), -- \u8054\u5408\u4E3B\u952E
354
- FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
355
- )`
356
- );
357
- db.exec(
358
- /* sql */
359
- `
360
- CREATE TABLE IF NOT EXISTS sfmc_land_invites (
361
- id TEXT PRIMARY KEY, -- \u9080\u8BF7\u552F\u4E00id
362
- land_id TEXT NOT NULL, -- \u9886\u5730id
363
- inviter_id TEXT NOT NULL, -- \u9080\u8BF7\u4EBAid
364
- invitee_id TEXT NOT NULL, -- \u88AB\u9080\u8BF7\u4EBAid
365
- role TEXT NOT NULL, -- \u9080\u8BF7\u89D2\u8272
366
- expires_at INTEGER NOT NULL, -- \u9080\u8BF7\u8FC7\u671F\u65F6\u95F4\u6233
367
- status TEXT NOT NULL DEFAULT 'pending', -- \u72B6\u6001\uFF08pending/accepted/rejected/expired\uFF09
368
- created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233
369
- FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
370
- )`
371
- );
372
- db.exec(
373
- /* sql */
374
- `
375
- CREATE INDEX IF NOT EXISTS idx_sfmc_land_invites_target ON sfmc_land_invites(invitee_id, status, expires_at)`
376
- );
377
- db.exec(
378
- /* sql */
379
- `
380
- CREATE TABLE IF NOT EXISTS sfmc_land_audit_logs (
381
- id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u65E5\u5FD7id\uFF08\u81EA\u589E\uFF09
382
- land_id TEXT NOT NULL, -- \u9886\u5730id
383
- actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id
384
- action TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982 claim/release/transfer/updat \u7B49\uFF09
385
- payload TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u8F7D\u8377\uFF08json\uFF09
386
- created_at INTEGER NOT NULL, -- \u65E5\u5FD7\u521B\u5EFA\u65F6\u95F4\u6233
387
- FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664
388
- )`
389
- );
390
- db.exec(
391
- /* sql */
392
- `
393
- CREATE TABLE IF NOT EXISTS sfmc_land_operations (
394
- request_id TEXT PRIMARY KEY, -- \u8BF7\u6C42\u552F\u4E00id
395
- operation_type TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982 claim/release/upgrade \u7B49\uFF09
396
- actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u53D1\u8D77\u4EBAid
397
- land_id TEXT, -- \u5173\u8054\u9886\u5730id\uFF08\u53EF\u9009\uFF09
398
- status TEXT NOT NULL, -- \u8BF7\u6C42\u72B6\u6001\uFF08pending/processing/completed/failed\uFF09
399
- response_json TEXT NOT NULL, -- \u64CD\u4F5C\u54CD\u5E94\u7ED3\u679C\uFF08json\uFF09
400
- created_at INTEGER NOT NULL -- \u8BF7\u6C42\u521B\u5EFA\u65F6\u95F4\u6233
401
- )`
402
- );
403
- db.exec(
404
- /* sql */
405
- `
406
- CREATE TABLE IF NOT EXISTS sfmc_player_command_usage (
407
- id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u8BB0\u5F55id\uFF08\u81EA\u589E\uFF09
408
- player_id TEXT NOT NULL, -- \u73A9\u5BB6id
409
- command TEXT NOT NULL, -- \u547D\u4EE4\u540D\u79F0\uFF08\u4E0D\u542B/\u524D\u7F00\uFF09
410
- date TEXT NOT NULL, -- \u7EDF\u8BA1\u65E5\u671F\uFF08YYYY-MM-DD\u683C\u5F0F\uFF09
411
- count INTEGER DEFAULT 0, -- \u5F53\u65E5\u8BE5\u547D\u4EE4\u4F7F\u7528\u6B21\u6570
412
- UNIQUE(player_id, command, date) -- \u552F\u4E00\u7EA6\u675F\uFF1A\u540C\u4E00\u73A9\u5BB6\u540C\u4E00\u547D\u4EE4\u540C\u4E00\u5929
413
- )`
414
- );
415
- db.exec(
416
- /* sql */
417
- `
418
- CREATE TABLE IF NOT EXISTS sfmc_economy_price_index (
419
- item_type TEXT NOT NULL, -- \u7269\u54C1\u7C7B\u578B\u6807\u8BC6\u7B26
420
- item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C/\u6570\u636E\u503C
421
- base_buy_price INTEGER NOT NULL DEFAULT 0, -- \u57FA\u7840\u6536\u8D2D\u4EF7\u683C
422
- base_sell_price INTEGER NOT NULL DEFAULT 0, -- \u57FA\u7840\u51FA\u552E\u4EF7\u683C
423
- current_buy_price INTEGER NOT NULL DEFAULT 0, -- \u5F53\u524D\u6536\u8D2D\u4EF7\u683C
424
- current_sell_price INTEGER NOT NULL DEFAULT 0, -- \u5F53\u524D\u51FA\u552E\u4EF7\u683C
425
- elasticity REAL DEFAULT 0.3, -- \u4EF7\u683C\u5F39\u6027\u7CFB\u6570
426
- weekly_acquisition_cap INTEGER, -- \u5468\u6536\u8D2D\u4E0A\u9650
427
- weekly_acquired INTEGER DEFAULT 0, -- \u672C\u5468\u5DF2\u6536\u8D2D\u6570\u91CF
428
- week_start INTEGER, -- \u672C\u5468\u5F00\u59CB\u65F6\u95F4\u6233
429
- rarity TEXT DEFAULT 'common', -- \u7A00\u6709\u5EA6\uFF08common/uncommon/rare/epic/legendary\uFF09
430
- is_renewable INTEGER DEFAULT 1, -- \u662F\u5426\u53EF\u518D\u751F
431
- updated_at INTEGER, -- \u7D22\u5F15\u66F4\u65B0\u65F6\u95F4\u6233
432
- PRIMARY KEY (item_type, item_aux) -- \u8054\u5408\u4E3B\u952E
433
- )`
434
- );
435
- db.exec(
436
- /* sql */
437
- `
438
- CREATE TABLE IF NOT EXISTS sfmc_economy_daily_tasks (
439
- id TEXT PRIMARY KEY, -- \u4EFB\u52A1\u552F\u4E00id
440
- item_type TEXT NOT NULL, -- \u76EE\u6807\u7269\u54C1\u7C7B\u578B
441
- item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C
442
- target_qty INTEGER NOT NULL, -- \u76EE\u6807\u6570\u91CF
443
- filled_qty INTEGER DEFAULT 0, -- \u5DF2\u5B8C\u6210\u6570\u91CF
444
- unit_reward INTEGER NOT NULL, -- \u5355\u4F4D\u5956\u52B1\u91D1\u989D
445
- created_at INTEGER, -- \u4EFB\u52A1\u521B\u5EFA\u65F6\u95F4\u6233
446
- expires_at INTEGER, -- \u4EFB\u52A1\u8FC7\u671F\u65F6\u95F4\u6233
447
- status TEXT DEFAULT 'active' -- \u4EFB\u52A1\u72B6\u6001\uFF08active/completed/expired\uFF09
448
- )`
449
- );
450
- db.exec(
451
- /* sql */
452
- `
453
- CREATE TABLE IF NOT EXISTS sfmc_economy_stats (
454
- id TEXT PRIMARY KEY, -- \u7EDF\u8BA1\u9879id\uFF08\u76EE\u524D\u56FA\u5B9A\u4E3A'global'\uFF09
455
- total_issued INTEGER DEFAULT 0, -- \u7D2F\u8BA1\u53D1\u884C\u8D27\u5E01\u91CF
456
- total_destroyed INTEGER DEFAULT 0, -- \u7D2F\u8BA1\u9500\u6BC1\u8D27\u5E01\u91CF
457
- total_supply INTEGER DEFAULT 0, -- \u5F53\u524D\u603B\u4F9B\u5E94\u91CF
458
- active_accounts INTEGER DEFAULT 0, -- \u6D3B\u8DC3\u8D26\u6237\u6570
459
- computed_at INTEGER -- \u7EDF\u8BA1\u8BA1\u7B97\u65F6\u95F4\u6233
460
- )`
461
- );
462
- db.exec(
463
- /* sql */
464
- `
465
- CREATE TABLE IF NOT EXISTS sfmc_economy_accounts (
466
- player_id TEXT PRIMARY KEY, -- \u73A9\u5BB6id\uFF08\u4E3B\u952E\uFF09
467
- player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u73A9\u5BB6\u540D\u79F0\u5FEB\u7167
468
- balance INTEGER NOT NULL DEFAULT 0 CHECK(balance >= 0), -- \u8D26\u6237\u4F59\u989D\uFF08>=0\uFF09
469
- version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7
470
- created_at INTEGER NOT NULL, -- \u8D26\u6237\u521B\u5EFA\u65F6\u95F4\u6233
471
- updated_at INTEGER NOT NULL -- \u8D26\u6237\u66F4\u65B0\u65F6\u95F4\u6233
472
- )`
473
- );
474
- db.exec(
475
- /* sql */
476
- `
477
- CREATE TABLE IF NOT EXISTS sfmc_economy_transactions (
478
- id TEXT PRIMARY KEY, -- \u4EA4\u6613\u552F\u4E00id
479
- transaction_type TEXT NOT NULL, -- \u4EA4\u6613\u7C7B\u578B\uFF08transfer/payment/reward/purchase\u7B49\uFF09
480
- actor_id TEXT NOT NULL, -- \u4EA4\u6613\u53D1\u8D77\u4EBAid
481
- source_player_id TEXT, -- \u6E90\u73A9\u5BB6id\uFF08\u4ED8\u6B3E\u65B9\uFF09
482
- target_player_id TEXT, -- \u76EE\u6807\u73A9\u5BB6id\uFF08\u6536\u6B3E\u65B9\uFF09
483
- amount INTEGER NOT NULL CHECK(amount > 0), -- \u4EA4\u6613\u91D1\u989D\uFF08>0\uFF09
484
- balance_before INTEGER, -- \u4EA4\u6613\u524D\u4F59\u989D
485
- balance_after INTEGER, -- \u4EA4\u6613\u540E\u4F59\u989D
486
- reference_type TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E1A\u52A1\u7C7B\u578B
487
- reference_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E1A\u52A1id
488
- reason TEXT NOT NULL DEFAULT '', -- \u4EA4\u6613\u539F\u56E0/\u5907\u6CE8
489
- created_at INTEGER NOT NULL -- \u4EA4\u6613\u65F6\u95F4\u6233
490
- )`
491
- );
492
- db.exec(
493
- /* sql */
494
- `
495
- CREATE TABLE IF NOT EXISTS sfmc_economy_idempotency (
496
- actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id
497
- idempotency_key TEXT NOT NULL, -- \u5E42\u7B49\u952E
498
- transaction_id TEXT NOT NULL, -- \u5DF2\u5904\u7406\u7684\u4EA4\u6613id
499
- response_json TEXT NOT NULL, -- \u5904\u7406\u7ED3\u679C\uFF08json\uFF09
500
- created_at INTEGER NOT NULL, -- \u8BB0\u5F55\u521B\u5EFA\u65F6\u95F4\u6233
501
- PRIMARY KEY (actor_id, idempotency_key) -- \u8054\u5408\u4E3B\u952E
42
+ id TEXT PRIMARY KEY NOT NULL,
43
+ channel_id TEXT NOT NULL DEFAULT '',
44
+ from_id TEXT NOT NULL DEFAULT '',
45
+ from_name TEXT NOT NULL DEFAULT '',
46
+ type TEXT NOT NULL DEFAULT 'text',
47
+ content TEXT NOT NULL DEFAULT '',
48
+ attachment TEXT DEFAULT NULL,
49
+ show_timestamp INTEGER NOT NULL DEFAULT 0,
50
+ created_at INTEGER NOT NULL
502
51
  )`
503
52
  );
504
53
  db.exec(
505
54
  /* sql */
506
55
  `
507
- CREATE INDEX IF NOT EXISTS idx_economy_transactions_player ON sfmc_economy_transactions(source_player_id, created_at DESC);
508
- CREATE INDEX IF NOT EXISTS idx_economy_transactions_target ON sfmc_economy_transactions(target_player_id, created_at DESC);
509
- CREATE INDEX IF NOT EXISTS idx_sfmc_lands_owner ON sfmc_lands(owner_player_id, status);
510
- CREATE INDEX IF NOT EXISTS idx_sfmc_lands_location ON sfmc_lands(dimension, min_x, max_x, min_z, max_z, status)`
56
+ CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);
57
+ CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`
511
58
  );
512
59
  db.exec(
513
60
  /* sql */
514
61
  `
515
62
  CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (
516
- code TEXT PRIMARY KEY NOT NULL, -- \u77ED\u7801
517
- qq_user_openid TEXT NOT NULL, -- QQ \u4FA7\u7528\u6237 openid / llbot user id
518
- qq_backend TEXT NOT NULL DEFAULT 'official', -- official | llbot
63
+ code TEXT PRIMARY KEY NOT NULL,
64
+ qq_user_openid TEXT NOT NULL,
65
+ qq_backend TEXT NOT NULL DEFAULT 'official',
519
66
  created_at INTEGER NOT NULL,
520
67
  expires_at INTEGER NOT NULL
521
68
  )`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/domain/schema.ts"],
4
- "sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u5185\u5D4C\u8868\u552F\u4E00\u57FA\u7EBF\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 `CREATE TABLE IF NOT EXISTS` \u4E0E `CREATE INDEX IF NOT EXISTS` DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1A`initSchema(db)` \u2014\u2014 \u5728 `openDatabase` \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u4E1A\u52A1\u6570\u636E\u57DF\u5206\u5E03\uFF1A\n * (1) \u4E16\u754C\u5907\u4EFD (2) \u73A9\u5BB6\u5907\u4EFD (3) \u8BA1\u5206\u677F\n * (4) \u73A9\u5BB6\u884C\u4E3A\u65E5\u5FD7 (5) \u804A\u5929\u9891\u9053 (6) \u804A\u5929\u6D88\u606F\n * (7) \u7EA2\u5305 (8) \u5408\u4F5C\u793E\u4E3B\u4F53/\u6210\u5458/\u9080\u8BF7/\u8D26\u6237\n * (9) \u5408\u4F5C\u793E\u5546\u5E97\u9879/\u7EC4 (10) \u5408\u4F5C\u793E\u6D41\u6C34/\u5BA1\u8BA1\n * (11) \u9886\u5730\u4E3B\u4F53/\u6210\u5458 (12) \u9886\u5730\u9080\u8BF7/\u5BA1\u8BA1/\u5E42\u7B49\n * (13) \u6307\u4EE4\u7528\u91CF (14) \u4EF7\u683C\u6307\u6570\n * (15) \u6BCF\u65E5\u4EFB\u52A1/\u7EDF\u8BA1 (16) \u7ECF\u6D4E\u8D26\u6237/\u6D41\u6C34/\u5E42\u7B49\n * (17) QQ \u7ED1\u5B9A\u4E0E\u7533\u8BF7 (18) \u6A21\u5757\u81EA\u58F0\u660E\u5143\u6570\u636E\u8868\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u6240\u6709\u5E73\u53F0\u5185\u5D4C\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n\n // (1 \u4E16\u754C\u5907\u4EFD\u6570\u636E\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n allow_cheats INTEGER NOT NULL DEFAULT 0, -- \u5141\u8BB8\u4F5C\u5F0A\u7684\u9009\u9879\n game_rules TEXT NOT NULL DEFAULT '', -- \u4E16\u754C\u89C4\u5219(json)\n seed TEXT DEFAULT '', -- \u79CD\u5B50\n default_spawn_location TEXT NOT NULL DEFAULT '', -- \u9ED8\u8BA4\u51FA\u751F\u70B9\n difficulty TEXT NOT NULL DEFAULT '', -- \u96BE\u5EA6\n day INTEGER NOT NULL DEFAULT 0, -- \u6E38\u620F\u5929\u6570\uFF08\u4E16\u754C\uFF09\n ticking_areas_count INTEGER DEFAULT 0, -- \u5E38\u52A0\u8F7D\u533A\u57DF\u6570\u91CF\n absolute_time INTEGER NOT NULL DEFAULT 0, -- \u6E38\u620F\u5185\u65F6\u95F4\uFF08\u5F53\u524D\uFF09\n structures_from_addon TEXT DEFAULT '', -- \u6765\u81EA\u6A21\u7EC4\u7684\u7ED3\u6784\uFF08\u5728\u5BF9\u5E94\u884C\u4E3A\u5305\u5185\uFF09\n structures_from_world TEXT DEFAULT '', -- \u6765\u81EA\u4E16\u754C\u7684\u7ED3\u6784 \u5305\u542B\u4FDD\u5B58\u5728\u5185\u5B58\u4E0E\u78C1\u76D8\u4E2D\u7684\n dynamic_property_total_byte_count INTEGER DEFAULT 0, -- \u52A8\u6001\u6570\u636E\uFF08\u4E16\u754C\uFF09\u5B57\u8282\u6570\n moon_phase INTEGER NOT NULL DEFAULT 0, -- \u6708\u76F8\uFF08\u5F53\u524D\uFF09\n updated_at TEXT NOT NULL DEFAULT '' -- \u66F4\u65B0\u65F6\u95F4\u6233\n )`);\n\n // (2 \u73A9\u5BB6\u5907\u4EFD\u6570\u636E\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT NOT NULL, -- \u5B9E\u4F53id\u6807\u8BC6\u7B26\n name TEXT NOT NULL, -- \u540D\u79F0\n permission INTEGER DEFAULT 0, -- \u6743\u9650\u7B49\u7EA7\uFF080=\u8BBF\u5BA2,1=\u6210\u5458,2=OP,3=\u7BA1\u7406\u5458\uFF09TODO: \u6682\u672A\u9002\u914D\u81EA\u5B9A\u4E49\u811A\u672C\u6743\u9650\n client_system_info_local TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u672C\u5730\u5316\u8BED\u8A00\n client_system_info_maxRenderDistance INTEGER DEFAULT 0, -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u6700\u5927\u6E32\u67D3\u8DDD\u79BB\n client_system_info_memoryTier_level TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u4FE1\u606F->\u5185\u5B58\u7B49\u7EA7 \u8BE6\u7EC6\u8BF7\u67E5\u770B\u5B98\u65B9\u6587\u6863\n client_system_info_PlatformType TEXT DEFAULT '', -- \u5BA2\u6237\u7AEF\u7CFB\u7EDF\u4FE1\u606F->\u5E73\u53F0\u7C7B\u578B\n graphicsMode TEXT DEFAULT '', -- \u56FE\u5F62\u6A21\u5F0F\n dynamicPropertyTotalByteCount INTEGER DEFAULT 0, -- \u52A8\u6001\u5C5E\u6027\u603B\u5B57\u8282\u6570\n ping INTEGER DEFAULT 0, -- \u5EF6\u8FDF\uFF08ms\uFF09\n spawnPoint TEXT DEFAULT '', -- \u51FA\u751F\u70B9\u5750\u6807\n tags TEXT DEFAULT '', -- \u73A9\u5BB6\u6807\u7B7E\n level INTEGER DEFAULT 0, -- \u7B49\u7EA7\n totalXp INTEGER DEFAULT 0, -- \u603B\u7ECF\u9A8C\u503C\n afk_step INTEGER DEFAULT 0, -- AFK\u7D2F\u8BA1\u6B65\u6570\n afk_last_location TEXT DEFAULT '', -- AFK\u6700\u540E\u4F4D\u7F6E\n onlinetime_session INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u672C\u6B21\u4F1A\u8BDD\uFF08\u79D2\uFF09\n onlinetime_today INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4ECA\u65E5\uFF08\u79D2\uFF09\n onlinetime_month INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u672C\u6708\uFF08\u79D2\uFF09\n onlinetime_total INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u7D2F\u8BA1\uFF08\u79D2\uFF09\n onlinetime_last_date INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4E0A\u6B21\u8BB0\u5F55\u65E5\u671F\n onlinetime_last_month INTEGER DEFAULT 0, -- \u5728\u7EBF\u65F6\u95F4->\u4E0A\u6B21\u8BB0\u5F55\u6708\u4EFD\n active_channel TEXT NOT NULL DEFAULT '', -- \u5F53\u524D\u6D3B\u8DC3\u804A\u5929\u9891\u9053\n subscribed_channels TEXT DEFAULT '', -- \u8BA2\u9605\u7684\u9891\u9053\u5217\u8868\n updated_at INTEGER NOT NULL, -- \u73A9\u5BB6\u6570\u636E\u66F4\u65B0\u65F6\u95F4\u6233\n PRIMARY KEY (id, name) -- \u8054\u5408\u4E3B\u952E\uFF1A\u5B9E\u4F53id+\u540D\u79F0\n )`);\n // \u73A9\u5BB6\u8868\u7D22\u5F15\uFF1A\u6309id\u67E5\u3001\u6309name\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_id ON sfmc_players(id);\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3 \u8BA1\u5206\u677F\u6570\u636E\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_scoreboards (\n objective_id TEXT NOT NULL, -- \u8BA1\u5206\u677F\u76EE\u6807id\uFF08\u4E3B\u952E\uFF09\n objective_display TEXT NOT NULL DEFAULT '', -- \u8BA1\u5206\u677F\u663E\u793A\u540D\u79F0\n participants TEXT DEFAULT '', -- \u53C2\u4E0E\u8005\u5217\u8868\uFF08json\uFF09\n updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233\n PRIMARY KEY (objective_id) -- \u4E3B\u952E\n )`);\n\n // (4 \u73A9\u5BB6\u884C\u4E3A\u65E5\u5FD7\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_activities (\n id TEXT PRIMARY KEY, -- \u552F\u4E00id\n timestamp INTEGER NOT NULL, -- \u4E8B\u4EF6\u53D1\u751F\u65F6\u95F4\u6233\n dimension TEXT NOT NULL DEFAULT '', -- \u7EF4\u5EA6\uFF08overworld/nether/end\uFF09\n source_type TEXT NOT NULL, -- \u4E8B\u4EF6\u6E90\u7C7B\u578B\uFF08\u5982player\u3001entity\u3001block\u7B49\uFF09\n source_id TEXT DEFAULT '', -- \u4E8B\u4EF6\u6E90\u5B9E\u4F53/\u65B9\u5757id\n source_name TEXT NOT NULL DEFAULT '', -- \u4E8B\u4EF6\u6E90\u540D\u79F0\n source_x REAL, -- \u4E8B\u4EF6\u6E90x\u5750\u6807\n source_y REAL, -- \u4E8B\u4EF6\u6E90y\u5750\u6807\n source_z REAL, -- \u4E8B\u4EF6\u6E90z\u5750\u6807\n event_type TEXT NOT NULL, -- \u4E8B\u4EF6\u7C7B\u578B\uFF08\u5982chat\u3001command\u3001death\u3001join\u7B49\uFF09\n target_type TEXT DEFAULT '', -- \u76EE\u6807\u7C7B\u578B\n target_id TEXT DEFAULT '', -- \u76EE\u6807\u5B9E\u4F53id\n target_name TEXT NOT NULL DEFAULT '', -- \u76EE\u6807\u540D\u79F0\n target_x REAL, -- \u76EE\u6807x\u5750\u6807\n target_y REAL, -- \u76EE\u6807y\u5750\u6807\n target_z REAL, -- \u76EE\u6807z\u5750\u6807\n detail TEXT DEFAULT '{}', -- \u4E8B\u4EF6\u8BE6\u7EC6\u4FE1\u606F\uFF08json\uFF09\n created_at INTEGER NOT NULL -- \u8BB0\u5F55\u521B\u5EFA\u65F6\u95F4\u6233\n )`);\n // \u6D3B\u52A8\u8868\u7D22\u5F15\uFF1A\u6309\u6E90id\u67E5\u3001\u6309\u4E8B\u4EF6\u7C7B\u578B\u67E5\u3001\u6309\u76EE\u6807id\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_sfmc_act_source ON sfmc_activities(source_id, timestamp);\n CREATE INDEX IF NOT EXISTS idx_sfmc_act_event ON sfmc_activities(event_type, timestamp);\n CREATE INDEX IF NOT EXISTS idx_sfmc_act_target ON sfmc_activities(target_id, timestamp)`);\n\n // (5 \u804A\u5929\u9891\u9053\u914D\u7F6E\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_channels (\n id TEXT NOT NULL, -- \u9891\u9053\u552F\u4E00id\n name TEXT NOT NULL, -- \u9891\u9053\u540D\u79F0\n type TEXT NOT NULL, -- \u9891\u9053\u7C7B\u578B\n prefix TEXT NOT NULL, -- \u9891\u9053\u524D\u7F00\u7B26\u53F7\n owner_id TEXT DEFAULT '', -- \u9891\u9053\u6240\u6709\u8005\u73A9\u5BB6id\n created_at INTEGER NOT NULL, -- \u9891\u9053\u521B\u5EFA\u65F6\u95F4\u6233\n config_allow_chat INTEGER NOT NULL DEFAULT 1, -- \u914D\u7F6E->\u662F\u5426\u5141\u8BB8\u804A\u5929\n config_slow_mode INTEGER NOT NULL DEFAULT 0, -- \u914D\u7F6E->\u6162\u6A21\u5F0F\u95F4\u9694\uFF08\u79D2\uFF09\n config_is_broadcast INTEGER NOT NULL DEFAULT 0, -- \u914D\u7F6E->\u662F\u5426\u4E3A\u5E7F\u64AD\u9891\u9053\n updated_at INTEGER NOT NULL, -- \u9891\u9053\u914D\u7F6E\u66F4\u65B0\u65F6\u95F4\u6233\n PRIMARY KEY (id, name) -- \u8054\u5408\u4E3B\u952E\n )`);\n // \u9891\u9053\u8868\u7D22\u5F15\uFF1A\u6309id\u67E5\u3001\u6309name+\u521B\u5EFA\u65F6\u95F4\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_channels_id ON sfmc_chat_channels(id);\n CREATE INDEX IF NOT EXISTS idx_channels_name ON sfmc_chat_channels(name, created_at ASC)`);\n\n // (6 \u804A\u5929\u6D88\u606F\u8BB0\u5F55\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY, -- \u6D88\u606F\u552F\u4E00id\n channel_id TEXT NOT NULL, -- \u6240\u5C5E\u9891\u9053id\n from_id TEXT NOT NULL, -- \u53D1\u9001\u8005\u73A9\u5BB6id\n from_name TEXT NOT NULL, -- \u53D1\u9001\u8005\u540D\u79F0\n type TEXT NOT NULL DEFAULT 'text', -- \u6D88\u606F\u7C7B\u578B\n content TEXT NOT NULL, -- \u6D88\u606F\u5185\u5BB9\n attachment TEXT, -- \u9644\u4EF6\u4FE1\u606F\n show_timestamp INTEGER NOT NULL DEFAULT 0, -- \u662F\u5426\u5728\u6D88\u606F\u5217\u8868\u663E\u793A\u65F6\u95F4\u6233\n created_at INTEGER NOT NULL -- \u6D88\u606F\u53D1\u9001\u65F6\u95F4\u6233\n )`);\n // \u6D88\u606F\u8868\u7D22\u5F15\uFF1A\u6309\u9891\u9053+\u65F6\u95F4\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_messages_channel ON sfmc_chat_messages(channel_id, created_at ASC)`);\n\n // (7 \u804A\u5929\u7EA2\u5305\u8BB0\u5F55\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_redpackets (\n id TEXT PRIMARY KEY, -- \u7EA2\u5305\u552F\u4E00id\n sender_id TEXT NOT NULL, -- \u53D1\u7EA2\u5305\u73A9\u5BB6id\n sender_name TEXT NOT NULL, -- \u53D1\u7EA2\u5305\u73A9\u5BB6\u540D\u79F0\n total_amount REAL NOT NULL, -- \u7EA2\u5305\u603B\u91D1\u989D\n remaining_amount REAL NOT NULL, -- \u5269\u4F59\u91D1\u989D\n total_count INTEGER NOT NULL, -- \u7EA2\u5305\u603B\u4E2A\u6570\n remaining_count INTEGER NOT NULL, -- \u5269\u4F59\u4E2A\u6570\n receivers TEXT NOT NULL DEFAULT '[]', -- \u5DF2\u9886\u53D6\u73A9\u5BB6\u5217\u8868\uFF08json\uFF09\n target_type TEXT NOT NULL, -- \u7EA2\u5305\u76EE\u6807\u7C7B\u578B\uFF08all/channel/player\u7B49\uFF09\n target_id TEXT NOT NULL, -- \u7EA2\u5305\u76EE\u6807id\n created_at INTEGER NOT NULL, -- \u7EA2\u5305\u521B\u5EFA\u65F6\u95F4\u6233\n expires_at INTEGER NOT NULL -- \u7EA2\u5305\u8FC7\u671F\u65F6\u95F4\u6233\n )`);\n // \u7EA2\u5305\u8868\u7D22\u5F15\uFF1A\u6309id\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_redpackets_id ON sfmc_chat_redpackets(id)`);\n\n // (8 \u5408\u4F5C\u793E\u6838\u5FC3\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coops (\n cid TEXT PRIMARY KEY, -- \u5408\u4F5C\u793E\u552F\u4E00id\n name TEXT NOT NULL, -- \u5408\u4F5C\u793E\u540D\u79F0\n owner_player_id TEXT NOT NULL, -- \u793E\u957F\u73A9\u5BB6id\n owner_name_snapshot TEXT NOT NULL DEFAULT '', -- \u793E\u957F\u540D\u79F0\u5FEB\u7167\n status TEXT NOT NULL DEFAULT 'active', -- \u72B6\u6001\uFF08active/inactive/dissolved\uFF09\n notice TEXT DEFAULT '', -- \u5408\u4F5C\u793E\u516C\u544A\n created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233\n updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233\n fee_bps INTEGER NOT NULL DEFAULT 500, -- \u7A0E\u7387\uFF08\u57FA\u70B9\uFF0C500=5%\uFF09\n version INTEGER NOT NULL DEFAULT 1 -- \u4E50\u89C2\u9501\u7248\u672C\u53F7\n )`);\n // \u5408\u4F5C\u793E\u6210\u5458\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_members (\n cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid\n player_id TEXT NOT NULL, -- \u6210\u5458\u73A9\u5BB6id\n player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6210\u5458\u540D\u79F0\u5FEB\u7167\n role TEXT NOT NULL DEFAULT 'member', -- \u89D2\u8272\uFF08member/admin/owner\uFF09\n joined_at INTEGER NOT NULL, -- \u52A0\u5165\u65F6\u95F4\u6233\n expires_at INTEGER, -- \u6210\u5458\u8D44\u683C\u8FC7\u671F\u65F6\u95F4\u6233\uFF08null=\u6C38\u4E0D\u8FC7\u671F\uFF09\n status TEXT NOT NULL DEFAULT 'active', -- \u72B6\u6001\uFF08active/expired/kicked\uFF09\n version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7\n PRIMARY KEY (cid, player_id), -- \u8054\u5408\u4E3B\u952E\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u786E\u4FDD\u6BCF\u4E2A\u73A9\u5BB6\u53EA\u6709\u4E00\u4E2Aactive\u5408\u4F5C\u793E\u6210\u5458\u8BB0\u5F55\n db.exec(/* sql */ `\n CREATE UNIQUE INDEX IF NOT EXISTS idx_sfmc_one_active_coop_member ON sfmc_coop_members(player_id) WHERE status='active'`);\n // \u5408\u4F5C\u793E\u9080\u8BF7\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_invites (\n id TEXT PRIMARY KEY, -- \u9080\u8BF7\u552F\u4E00id\n cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid\n inviter_id TEXT NOT NULL, -- \u9080\u8BF7\u4EBA\u73A9\u5BB6id\n invitee_id TEXT NOT NULL, -- \u88AB\u9080\u8BF7\u4EBA\u73A9\u5BB6id\n invitee_name_snapshot TEXT NOT NULL DEFAULT '', -- \u88AB\u9080\u8BF7\u4EBA\u540D\u79F0\u5FEB\u7167\n role TEXT NOT NULL DEFAULT 'member', -- \u9080\u8BF7\u89D2\u8272\n status TEXT NOT NULL DEFAULT 'pending', -- \u72B6\u6001\uFF08pending/accepted/rejected/expired\uFF09\n expires_at INTEGER NOT NULL, -- \u9080\u8BF7\u8FC7\u671F\u65F6\u95F4\u6233\n created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u9080\u8BF7\u8868\u7D22\u5F15\uFF1A\u6309\u88AB\u9080\u8BF7\u4EBA+\u72B6\u6001+\u8FC7\u671F\u65F6\u95F4\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_sfmc_coop_invites_target ON sfmc_coop_invites(invitee_id,status,expires_at)`);\n // \u5408\u4F5C\u793E\u8D26\u6237\u8868\uFF08\u7ECF\u6D4E\u7CFB\u7EDF\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_accounts (\n cid TEXT PRIMARY KEY, -- \u5408\u4F5C\u793Eid\n balance INTEGER NOT NULL DEFAULT 0, -- \u8D26\u6237\u4F59\u989D\n version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7\n updated_at INTEGER NOT NULL, -- \u8D26\u6237\u66F4\u65B0\u65F6\u95F4\u6233\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u5408\u4F5C\u793E\u5546\u5E97\u7269\u54C1\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_shop_items (\n id TEXT PRIMARY KEY, -- \u7269\u54C1\u552F\u4E00id\n cid TEXT NOT NULL, -- \u6240\u5C5E\u5408\u4F5C\u793Eid\n name TEXT NOT NULL, -- \u7269\u54C1\u540D\u79F0\n item_type TEXT NOT NULL, -- \u7269\u54C1\u7C7B\u578B\u6807\u8BC6\u7B26\uFF08\u5982minecraft:diamond\uFF09\n item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C/\u6570\u636E\u503C\n item_nbt TEXT DEFAULT '', -- \u7269\u54C1NBT\u6570\u636E\n type INTEGER NOT NULL, -- \u7269\u54C1\u4EA4\u6613\u7C7B\u578B\uFF080=\u6536\u8D2D,1=\u51FA\u552E,2=\u4EA4\u6362\uFF09\n groups TEXT DEFAULT '[]', -- \u6240\u5C5E\u5546\u54C1\u5206\u7EC4\n des TEXT DEFAULT '', -- \u7269\u54C1\u63CF\u8FF0\n num INTEGER DEFAULT 0, -- \u5F53\u524D\u5E93\u5B58\u6570\u91CF\n sv INTEGER DEFAULT 0, -- \u9500\u552E/\u6536\u8D2D\u9650\u5236\n money INTEGER DEFAULT 0, -- \u5355\u4EF7\n is_true INTEGER DEFAULT 1, -- \u662F\u5426\u4E0A\u67B6\uFF081=\u4E0A\u67B6,0=\u4E0B\u67B6\uFF09\n created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233\n updated_at INTEGER NOT NULL, -- \u66F4\u65B0\u65F6\u95F4\u6233\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u5408\u4F5C\u793E\u94F6\u884C\u6D41\u6C34\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_bank_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u6D41\u6C34id\uFF08\u81EA\u589E\uFF09\n cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid\n actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u4EBA\u73A9\u5BB6id\n actor_name_snapshot TEXT NOT NULL DEFAULT '', -- \u64CD\u4F5C\u4EBA\u540D\u79F0\u5FEB\u7167\n type INTEGER NOT NULL, -- \u6D41\u6C34\u7C7B\u578B\uFF080=\u5B58\u5165,1=\u53D6\u51FA,2=\u4EA4\u6613\u6536\u5165,3=\u4EA4\u6613\u652F\u51FA\uFF09\n amount INTEGER NOT NULL, -- \u53D8\u52A8\u91D1\u989D\n note TEXT DEFAULT '', -- \u5907\u6CE8\n transaction_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E8B\u52A1id\n created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u5408\u4F5C\u793E\u5BA1\u8BA1\u65E5\u5FD7\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_audit_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u65E5\u5FD7id\uFF08\u81EA\u589E\uFF09\n cid TEXT NOT NULL, -- \u5408\u4F5C\u793Eid\n actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id\n target_id TEXT NOT NULL DEFAULT '', -- \u88AB\u64CD\u4F5C\u76EE\u6807id\n action TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982create/update/delete/transfer\u7B49\uFF09\n before_state TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u524D\u72B6\u6001\uFF08json\uFF09\n after_state TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u540E\u72B6\u6001\uFF08json\uFF09\n transaction_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E8B\u52A1id\n created_at INTEGER NOT NULL, -- \u65E5\u5FD7\u521B\u5EFA\u65F6\u95F4\u6233\n FOREIGN KEY (cid) REFERENCES sfmc_coops(cid) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u5408\u4F5C\u793E\u5546\u5E97\u5206\u7EC4\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_coop_shop_groups (\n groupid TEXT PRIMARY KEY, -- \u5206\u7EC4\u552F\u4E00id\n displayname TEXT NOT NULL, -- \u5206\u7EC4\u663E\u793A\u540D\u79F0\n displaydescribe TEXT DEFAULT '', -- \u5206\u7EC4\u63CF\u8FF0\n icon TEXT DEFAULT '', -- \u5206\u7EC4\u56FE\u6807\n type_function TEXT DEFAULT '' -- \u5206\u7EC4\u529F\u80FD\u7C7B\u578B\n )`);\n\n // (9 \u9886\u5730\uFF08 LAND \uFF09\u6838\u5FC3\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_lands (\n id TEXT PRIMARY KEY, -- \u9886\u5730\u552F\u4E00id\n owner_player_id TEXT NOT NULL, -- \u9886\u5730\u6240\u6709\u8005\u73A9\u5BB6id\n owner_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6240\u6709\u8005\u540D\u79F0\u5FEB\u7167\n dimension INTEGER NOT NULL, -- \u6240\u5728\u7EF4\u5EA6\uFF080=\u4E3B\u4E16\u754C,1=\u4E0B\u754C,2=\u672B\u5730\uFF09\n min_x INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fx\u5750\u6807\n min_y INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fy\u5750\u6807\n min_z INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5C0Fz\u5750\u6807\n max_x INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927x\u5750\u6807\n max_y INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927y\u5750\u6807\n max_z INTEGER NOT NULL, -- \u9886\u5730\u8303\u56F4\u6700\u5927z\u5750\u6807\n name TEXT NOT NULL DEFAULT '', -- \u9886\u5730\u540D\u79F0\n status TEXT NOT NULL DEFAULT 'active', -- \u9886\u5730\u72B6\u6001\uFF08active/locked/expired/auction\uFF09\n created_at INTEGER NOT NULL, -- \u9886\u5730\u521B\u5EFA\u65F6\u95F4\u6233\n updated_at INTEGER NOT NULL, -- \u9886\u5730\u66F4\u65B0\u65F6\u95F4\u6233\n expires_at INTEGER, -- \u9886\u5730\u8FC7\u671F\u65F6\u95F4\u6233\uFF08null=\u6C38\u4E0D\u8FC7\u671F\uFF09\n protection_profile TEXT NOT NULL DEFAULT '{}', -- \u4FDD\u62A4\u914D\u7F6E\uFF08json\uFF09\n version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7\n purchase_price INTEGER NOT NULL DEFAULT 0, -- \u8D2D\u4E70\u4EF7\u683C\n refund_rate REAL NOT NULL DEFAULT 0.7, -- \u9000\u6B3E\u6BD4\u4F8B\uFF080~1\uFF09\n tax_rate INTEGER NOT NULL DEFAULT 0, -- \u7A0E\u7387\uFF08\u767E\u5206\u6BD4\uFF09\n tax_due_at INTEGER, -- \u4E0B\u4E00\u4E2A\u7F34\u7A0E\u65F6\u95F4\u6233\n tax_frozen INTEGER NOT NULL DEFAULT 0 -- \u7A0E\u52A1\u51BB\u7ED3\u72B6\u6001\uFF080=\u6B63\u5E38,1=\u51BB\u7ED3\uFF09\n )`);\n // \u9886\u5730\u6210\u5458\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_land_members (\n land_id TEXT NOT NULL, -- \u9886\u5730id\n player_id TEXT NOT NULL, -- \u6210\u5458\u73A9\u5BB6id\n player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u6210\u5458\u540D\u79F0\u5FEB\u7167\n role TEXT NOT NULL DEFAULT 'admin', -- \u6210\u5458\u89D2\u8272\uFF08member/trusted/admin/owner\uFF09\n created_at INTEGER NOT NULL, -- \u52A0\u5165\u65F6\u95F4\u6233\n expires_at INTEGER, -- \u6210\u5458\u8D44\u683C\u8FC7\u671F\u65F6\u95F4\u6233\n PRIMARY KEY (land_id, player_id), -- \u8054\u5408\u4E3B\u952E\n FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u9886\u5730\u6743\u9650\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_land_permissions (\n land_id TEXT NOT NULL, -- \u9886\u5730id\n permission_key TEXT NOT NULL, -- \u6743\u9650\u952E\uFF08\u5982 buildbreak.interact \u7B49\uFF09\n subject_type TEXT NOT NULL, -- \u6743\u9650\u4E3B\u4F53\u7C7B\u578B\uFF08player/group/role/all\uFF09\n subject_id TEXT NOT NULL, -- \u6743\u9650\u4E3B\u4F53id\n allowed INTEGER NOT NULL DEFAULT 0, -- \u662F\u5426\u5141\u8BB8\uFF081=\u5141\u8BB8,0=\u62D2\u7EDD,-1=\u672A\u8BBE\u7F6E\uFF09\n updated_at INTEGER NOT NULL, -- \u6743\u9650\u66F4\u65B0\u65F6\u95F4\u6233\n PRIMARY KEY (land_id, permission_key, subject_type, subject_id), -- \u8054\u5408\u4E3B\u952E\n FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u9886\u5730\u9080\u8BF7\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_land_invites (\n id TEXT PRIMARY KEY, -- \u9080\u8BF7\u552F\u4E00id\n land_id TEXT NOT NULL, -- \u9886\u5730id\n inviter_id TEXT NOT NULL, -- \u9080\u8BF7\u4EBAid\n invitee_id TEXT NOT NULL, -- \u88AB\u9080\u8BF7\u4EBAid\n role TEXT NOT NULL, -- \u9080\u8BF7\u89D2\u8272\n expires_at INTEGER NOT NULL, -- \u9080\u8BF7\u8FC7\u671F\u65F6\u95F4\u6233\n status TEXT NOT NULL DEFAULT 'pending', -- \u72B6\u6001\uFF08pending/accepted/rejected/expired\uFF09\n created_at INTEGER NOT NULL, -- \u521B\u5EFA\u65F6\u95F4\u6233\n FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u9886\u5730\u9080\u8BF7\u8868\u7D22\u5F15\uFF1A\u6309\u88AB\u9080\u8BF7\u4EBA+\u72B6\u6001+\u8FC7\u671F\u65F6\u95F4\u67E5\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_sfmc_land_invites_target ON sfmc_land_invites(invitee_id, status, expires_at)`);\n // \u9886\u5730\u64CD\u4F5C\u5BA1\u8BA1\u65E5\u5FD7\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_land_audit_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u65E5\u5FD7id\uFF08\u81EA\u589E\uFF09\n land_id TEXT NOT NULL, -- \u9886\u5730id\n actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id\n action TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982 claim/release/transfer/updat \u7B49\uFF09\n payload TEXT NOT NULL DEFAULT '{}', -- \u64CD\u4F5C\u8F7D\u8377\uFF08json\uFF09\n created_at INTEGER NOT NULL, -- \u65E5\u5FD7\u521B\u5EFA\u65F6\u95F4\u6233\n FOREIGN KEY (land_id) REFERENCES sfmc_lands(id) ON DELETE CASCADE -- \u7EA7\u8054\u5220\u9664\n )`);\n // \u9886\u5730\u64CD\u4F5C\u8BF7\u6C42\u8868\uFF08\u5F02\u6B65\u64CD\u4F5C\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_land_operations (\n request_id TEXT PRIMARY KEY, -- \u8BF7\u6C42\u552F\u4E00id\n operation_type TEXT NOT NULL, -- \u64CD\u4F5C\u7C7B\u578B\uFF08\u5982 claim/release/upgrade \u7B49\uFF09\n actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u53D1\u8D77\u4EBAid\n land_id TEXT, -- \u5173\u8054\u9886\u5730id\uFF08\u53EF\u9009\uFF09\n status TEXT NOT NULL, -- \u8BF7\u6C42\u72B6\u6001\uFF08pending/processing/completed/failed\uFF09\n response_json TEXT NOT NULL, -- \u64CD\u4F5C\u54CD\u5E94\u7ED3\u679C\uFF08json\uFF09\n created_at INTEGER NOT NULL -- \u8BF7\u6C42\u521B\u5EFA\u65F6\u95F4\u6233\n )`);\n\n // (10 \u73A9\u5BB6\u547D\u4EE4\u4F7F\u7528\u7EDF\u8BA1\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_player_command_usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- \u8BB0\u5F55id\uFF08\u81EA\u589E\uFF09\n player_id TEXT NOT NULL, -- \u73A9\u5BB6id\n command TEXT NOT NULL, -- \u547D\u4EE4\u540D\u79F0\uFF08\u4E0D\u542B/\u524D\u7F00\uFF09\n date TEXT NOT NULL, -- \u7EDF\u8BA1\u65E5\u671F\uFF08YYYY-MM-DD\u683C\u5F0F\uFF09\n count INTEGER DEFAULT 0, -- \u5F53\u65E5\u8BE5\u547D\u4EE4\u4F7F\u7528\u6B21\u6570\n UNIQUE(player_id, command, date) -- \u552F\u4E00\u7EA6\u675F\uFF1A\u540C\u4E00\u73A9\u5BB6\u540C\u4E00\u547D\u4EE4\u540C\u4E00\u5929\n )`);\n\n // (11 \u7ECF\u6D4E\u7CFB\u7EDF\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_price_index (\n item_type TEXT NOT NULL, -- \u7269\u54C1\u7C7B\u578B\u6807\u8BC6\u7B26\n item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C/\u6570\u636E\u503C\n base_buy_price INTEGER NOT NULL DEFAULT 0, -- \u57FA\u7840\u6536\u8D2D\u4EF7\u683C\n base_sell_price INTEGER NOT NULL DEFAULT 0, -- \u57FA\u7840\u51FA\u552E\u4EF7\u683C\n current_buy_price INTEGER NOT NULL DEFAULT 0, -- \u5F53\u524D\u6536\u8D2D\u4EF7\u683C\n current_sell_price INTEGER NOT NULL DEFAULT 0, -- \u5F53\u524D\u51FA\u552E\u4EF7\u683C\n elasticity REAL DEFAULT 0.3, -- \u4EF7\u683C\u5F39\u6027\u7CFB\u6570\n weekly_acquisition_cap INTEGER, -- \u5468\u6536\u8D2D\u4E0A\u9650\n weekly_acquired INTEGER DEFAULT 0, -- \u672C\u5468\u5DF2\u6536\u8D2D\u6570\u91CF\n week_start INTEGER, -- \u672C\u5468\u5F00\u59CB\u65F6\u95F4\u6233\n rarity TEXT DEFAULT 'common', -- \u7A00\u6709\u5EA6\uFF08common/uncommon/rare/epic/legendary\uFF09\n is_renewable INTEGER DEFAULT 1, -- \u662F\u5426\u53EF\u518D\u751F\n updated_at INTEGER, -- \u7D22\u5F15\u66F4\u65B0\u65F6\u95F4\u6233\n PRIMARY KEY (item_type, item_aux) -- \u8054\u5408\u4E3B\u952E\n )`);\n // \u7ECF\u6D4E\u65E5\u5E38\u4EFB\u52A1\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_daily_tasks (\n id TEXT PRIMARY KEY, -- \u4EFB\u52A1\u552F\u4E00id\n item_type TEXT NOT NULL, -- \u76EE\u6807\u7269\u54C1\u7C7B\u578B\n item_aux INTEGER DEFAULT 0, -- \u7269\u54C1\u9644\u52A0\u503C\n target_qty INTEGER NOT NULL, -- \u76EE\u6807\u6570\u91CF\n filled_qty INTEGER DEFAULT 0, -- \u5DF2\u5B8C\u6210\u6570\u91CF\n unit_reward INTEGER NOT NULL, -- \u5355\u4F4D\u5956\u52B1\u91D1\u989D\n created_at INTEGER, -- \u4EFB\u52A1\u521B\u5EFA\u65F6\u95F4\u6233\n expires_at INTEGER, -- \u4EFB\u52A1\u8FC7\u671F\u65F6\u95F4\u6233\n status TEXT DEFAULT 'active' -- \u4EFB\u52A1\u72B6\u6001\uFF08active/completed/expired\uFF09\n )`);\n // \u7ECF\u6D4E\u7CFB\u7EDF\u5168\u5C40\u7EDF\u8BA1\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_stats (\n id TEXT PRIMARY KEY, -- \u7EDF\u8BA1\u9879id\uFF08\u76EE\u524D\u56FA\u5B9A\u4E3A'global'\uFF09\n total_issued INTEGER DEFAULT 0, -- \u7D2F\u8BA1\u53D1\u884C\u8D27\u5E01\u91CF\n total_destroyed INTEGER DEFAULT 0, -- \u7D2F\u8BA1\u9500\u6BC1\u8D27\u5E01\u91CF\n total_supply INTEGER DEFAULT 0, -- \u5F53\u524D\u603B\u4F9B\u5E94\u91CF\n active_accounts INTEGER DEFAULT 0, -- \u6D3B\u8DC3\u8D26\u6237\u6570\n computed_at INTEGER -- \u7EDF\u8BA1\u8BA1\u7B97\u65F6\u95F4\u6233\n )`);\n // \u7ECF\u6D4E\u8D26\u6237\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_accounts (\n player_id TEXT PRIMARY KEY, -- \u73A9\u5BB6id\uFF08\u4E3B\u952E\uFF09\n player_name_snapshot TEXT NOT NULL DEFAULT '', -- \u73A9\u5BB6\u540D\u79F0\u5FEB\u7167\n balance INTEGER NOT NULL DEFAULT 0 CHECK(balance >= 0), -- \u8D26\u6237\u4F59\u989D\uFF08>=0\uFF09\n version INTEGER NOT NULL DEFAULT 1, -- \u4E50\u89C2\u9501\u7248\u672C\u53F7\n created_at INTEGER NOT NULL, -- \u8D26\u6237\u521B\u5EFA\u65F6\u95F4\u6233\n updated_at INTEGER NOT NULL -- \u8D26\u6237\u66F4\u65B0\u65F6\u95F4\u6233\n )`);\n // \u7ECF\u6D4E\u4EA4\u6613\u6D41\u6C34\u8868\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_transactions (\n id TEXT PRIMARY KEY, -- \u4EA4\u6613\u552F\u4E00id\n transaction_type TEXT NOT NULL, -- \u4EA4\u6613\u7C7B\u578B\uFF08transfer/payment/reward/purchase\u7B49\uFF09\n actor_id TEXT NOT NULL, -- \u4EA4\u6613\u53D1\u8D77\u4EBAid\n source_player_id TEXT, -- \u6E90\u73A9\u5BB6id\uFF08\u4ED8\u6B3E\u65B9\uFF09\n target_player_id TEXT, -- \u76EE\u6807\u73A9\u5BB6id\uFF08\u6536\u6B3E\u65B9\uFF09\n amount INTEGER NOT NULL CHECK(amount > 0), -- \u4EA4\u6613\u91D1\u989D\uFF08>0\uFF09\n balance_before INTEGER, -- \u4EA4\u6613\u524D\u4F59\u989D\n balance_after INTEGER, -- \u4EA4\u6613\u540E\u4F59\u989D\n reference_type TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E1A\u52A1\u7C7B\u578B\n reference_id TEXT NOT NULL DEFAULT '', -- \u5173\u8054\u4E1A\u52A1id\n reason TEXT NOT NULL DEFAULT '', -- \u4EA4\u6613\u539F\u56E0/\u5907\u6CE8\n created_at INTEGER NOT NULL -- \u4EA4\u6613\u65F6\u95F4\u6233\n )`);\n // \u7ECF\u6D4E\u5E42\u7B49\u8868\uFF08\u9632\u6B62\u91CD\u590D\u4EA4\u6613\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_economy_idempotency (\n actor_id TEXT NOT NULL, -- \u64CD\u4F5C\u8005id\n idempotency_key TEXT NOT NULL, -- \u5E42\u7B49\u952E\n transaction_id TEXT NOT NULL, -- \u5DF2\u5904\u7406\u7684\u4EA4\u6613id\n response_json TEXT NOT NULL, -- \u5904\u7406\u7ED3\u679C\uFF08json\uFF09\n created_at INTEGER NOT NULL, -- \u8BB0\u5F55\u521B\u5EFA\u65F6\u95F4\u6233\n PRIMARY KEY (actor_id, idempotency_key) -- \u8054\u5408\u4E3B\u952E\n )`);\n // \u7ECF\u6D4E\u76F8\u5173\u7D22\u5F15\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_economy_transactions_player ON sfmc_economy_transactions(source_player_id, created_at DESC);\n CREATE INDEX IF NOT EXISTS idx_economy_transactions_target ON sfmc_economy_transactions(target_player_id, created_at DESC);\n CREATE INDEX IF NOT EXISTS idx_sfmc_lands_owner ON sfmc_lands(owner_player_id, status);\n CREATE INDEX IF NOT EXISTS idx_sfmc_lands_location ON sfmc_lands(dimension, min_x, max_x, min_z, max_z, status)`);\n\n // QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL, -- \u77ED\u7801\n qq_user_openid TEXT NOT NULL, -- QQ \u4FA7\u7528\u6237 openid / llbot user id\n qq_backend TEXT NOT NULL DEFAULT 'official', -- official | llbot\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // QQ \u5165\u670D\u5BA1\u6279 + \u7BA1\u7406\u52A8\u4F5C\u961F\u5217\uFF08\u5E73\u53F0\u8868\uFF1BBDS \u751F\u6548\u7531 SAPI + server-admin\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (\n id TEXT PRIMARY KEY NOT NULL,\n applicant_openid TEXT NOT NULL,\n player_name TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n decided_at INTEGER,\n decided_by TEXT,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (\n id TEXT PRIMARY KEY NOT NULL,\n kind TEXT NOT NULL,\n target_name TEXT NOT NULL,\n reason TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`);\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * domain/schema.ts \u2014 \u6570\u636E\u5E93 Schema \u521D\u59CB\u5316\u5B9A\u4E49\uFF08\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\uFF09\n *\n * \u96C6\u4E2D\u7BA1\u7406\u5E73\u53F0\u6838\u5FC3\u6570\u636E\u8868\u7684 CREATE TABLE IF NOT EXISTS \u4E0E CREATE INDEX IF NOT EXISTS DDL \u8BED\u53E5\u3002\n * \u8C03\u7528\u65B9\u5F0F\uFF1AinitSchema(db) \u2014\u2014 \u5728 openDatabase \u4E4B\u540E\u3001\u4E1A\u52A1\u67E5\u8BE2\u542F\u52A8\u524D\u6267\u884C\u3002\n *\n * \u67B6\u6784\u7EA6\u5B9A\uFF1A\n * \u4E1A\u52A1\u6A21\u5757\uFF08activity-log, coop, land, chat, economy \u7B49\uFF09\u79C1\u6709\u6570\u636E\u8868\u5747\u7531\u5404\u81EA\u6A21\u5757\u5728 SAPI \u8FD0\u884C\u671F\u901A\u8FC7\n * db.defineTable(...) \u81EA\u4E3B\u58F0\u660E\u4E0E\u7EF4\u62A4\uFF0C\u4E0D\u5728\u6B64\u5904\u9759\u6001\u786C\u7F16\u7801\uFF0C\u907F\u514D\u5217\u5B9A\u4E49\u4E0E\u7D22\u5F15\u51B2\u7A81\u3002\n *\n * \u6B64\u5904\u4EC5\u5B9A\u4E49\u5E73\u53F0\u6838\u5FC3\u8FD0\u884C\u3001\u8DE8\u670D\u52A1\u5171\u4EAB\uFF08qq-bridge / HTTP \u7AEF\u70B9 / status \u63A2\u6D3B\uFF09\u7684\u57FA\u7840\u5E95\u5EA7\u8868\uFF1A\n * 1. \u4E16\u754C\u4E0E\u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\u5FEB\u7167\uFF08sfmc_world, sfmc_players\uFF09\n * 2. \u8DE8\u8FDB\u7A0B\u804A\u5929\u6D88\u606F\u805A\u5408\uFF08sfmc_chat_messages\uFF09\n * 3. QQ \u8EAB\u4EFD\u7ED1\u5B9A\u4E0E\u5BA1\u6279\u961F\u5217\uFF08sfmc_qq_*\uFF09\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * \u521D\u59CB\u5316\u6570\u636E\u5E93\u7ED3\u6784\uFF0C\u521B\u5EFA\u5E73\u53F0\u6838\u5FC3\u5E95\u5EA7\u8868\u4E0E\u7D22\u5F15\u3002\n *\n * @param db SQLite \u6570\u636E\u5E93\u8FDE\u63A5\u5B9E\u4F8B\u3002\n */\nexport function initSchema(db: DatabaseSync): void {\n // (1) \u4E16\u754C\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_world (\n id TEXT PRIMARY KEY,\n seed TEXT DEFAULT '',\n difficulty TEXT DEFAULT '',\n spawn_x INTEGER DEFAULT 0,\n spawn_y INTEGER DEFAULT 0,\n spawn_z INTEGER DEFAULT 0,\n gamerules TEXT DEFAULT '{}',\n day INTEGER DEFAULT 0,\n absolute_time INTEGER DEFAULT 0,\n moon_phase INTEGER DEFAULT 0,\n updated_at INTEGER DEFAULT 0\n )`);\n\n // (2) \u73A9\u5BB6\u8FD0\u884C\u671F\u72B6\u6001\uFF08\u4F9B status \u8DEF\u7531\u4E0E qq-bridge \u8BFB\u53D6\uFF0C\u5B57\u6BB5\u4E0E data-backup \u517C\u5BB9\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_players (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n xuid TEXT DEFAULT '',\n last_online INTEGER DEFAULT 0,\n level INTEGER DEFAULT 0,\n total_xp INTEGER DEFAULT 0,\n tags TEXT DEFAULT '',\n updated_at INTEGER DEFAULT 0\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_players_name ON sfmc_players(name)`);\n\n // (3) \u804A\u5929\u6D88\u606F\u805A\u5408\uFF08\u4F9B routes/messages \u4E0E qq-bridge \u53CC\u5411\u540C\u6B65\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_chat_messages (\n id TEXT PRIMARY KEY NOT NULL,\n channel_id TEXT NOT NULL DEFAULT '',\n from_id TEXT NOT NULL DEFAULT '',\n from_name TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT 'text',\n content TEXT NOT NULL DEFAULT '',\n attachment TEXT DEFAULT NULL,\n show_timestamp INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_chat_messages_channel_time ON sfmc_chat_messages(channel_id, created_at ASC);\n CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON sfmc_chat_messages(created_at ASC)`);\n\n // (4) QQ\u2194MC \u8EAB\u4EFD\u7ED1\u5B9A\uFF08\u5E73\u53F0\u8868\uFF1Bqq-bridge / \u6E38\u620F\u6A21\u5757\u5171\u7528 HTTP\uFF0C\u975E\u6A21\u5757\u79C1\u6709\u8868\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bind_pending (\n code TEXT PRIMARY KEY NOT NULL,\n qq_user_openid TEXT NOT NULL,\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_bindings (\n qq_user_openid TEXT PRIMARY KEY NOT NULL,\n player_xuid TEXT NOT NULL UNIQUE,\n player_name TEXT NOT NULL DEFAULT '',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n bound_at INTEGER NOT NULL\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_bind_pending_openid ON sfmc_qq_bind_pending(qq_user_openid);\n CREATE INDEX IF NOT EXISTS idx_qq_bindings_xuid ON sfmc_qq_bindings(player_xuid)`);\n\n // (5) QQ \u5165\u670D\u5BA1\u6279 + \u7BA1\u7406\u52A8\u4F5C\u961F\u5217\uFF08\u5E73\u53F0\u8868\uFF1BBDS \u751F\u6548\u7531 SAPI + server-admin\uFF09\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_join_requests (\n id TEXT PRIMARY KEY NOT NULL,\n applicant_openid TEXT NOT NULL,\n player_name TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n qq_backend TEXT NOT NULL DEFAULT 'official',\n created_at INTEGER NOT NULL,\n decided_at INTEGER,\n decided_by TEXT,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_join_status ON sfmc_qq_join_requests(status, created_at)`);\n db.exec(/* sql */ `\n CREATE TABLE IF NOT EXISTS sfmc_qq_admin_actions (\n id TEXT PRIMARY KEY NOT NULL,\n kind TEXT NOT NULL,\n target_name TEXT NOT NULL,\n reason TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL DEFAULT 'pending',\n created_at INTEGER NOT NULL,\n applied_at INTEGER,\n apply_error TEXT\n )`);\n db.exec(/* sql */ `\n CREATE INDEX IF NOT EXISTS idx_qq_admin_actions_status ON sfmc_qq_admin_actions(status, created_at)`);\n}\n"],
5
+ "mappings": "AAuBO,SAAS,WAAW,IAAwB;AAEjD,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAad;AAGJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACkD;AAGpE,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAE8E;AAGhG,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA,EAEiE;AAGnF,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EAC2E;AAC7F,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWd;AACJ,KAAG;AAAA;AAAA,IAAe;AAAA;AAAA,EACoF;AACxG;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAEV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CA4KrB"}
1
+ {"version":3,"file":"db-routes.d.ts","sourceRoot":"","sources":["../../src/routes/db-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAEV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,WAAW,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,SAOvC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,eAAe,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,CAAC,CA6KrB"}
@@ -1,4 +1,5 @@
1
1
  import { json as defaultJson } from "../lib/http.js";
2
+ import { log } from "../lib/log.js";
2
3
  import { jsonV2Fail, jsonV2Ok } from "./_shared.js";
3
4
  import { PermissionDeniedError } from "../permission-gate.js";
4
5
  function createDbRoutes(depsIn) {
@@ -25,6 +26,7 @@ function createDbRoutes(depsIn) {
25
26
  const result = deps.schemaRegistry.define(moduleId, req2);
26
27
  jsonV2Ok(res, { table: result.table, created: result.created });
27
28
  } catch (e) {
29
+ log.warn(`[define-table failed] ${moduleId} table=${body?.name}: ${e.message}`);
28
30
  jsonV2Fail(res, e.message, 400);
29
31
  }
30
32
  return true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/routes/db-routes.ts"],
4
- "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\nimport type {\n DefineTableRequest,\n SchemaRegistry,\n} from \"../schema-registry.js\";\nimport type {\n TxRequest,\n TxRunner,\n TxStep,\n} from \"../tx-runner.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(\n moduleId,\n String(body.action),\n String(body.key)\n );\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(\n moduleId,\n String(body.action),\n String(body.key),\n body.value\n );\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * routes/db-routes.ts \u2014 /api/sfmc/db/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * POST /api/sfmc/db/define-table body {name, columns, softDelete?} \u2192 {table, created}\n * POST /api/sfmc/db/tx body {steps[]} \u2192 TxResponse | TxError (\u6279\u91CF)\n * POST /api/sfmc/db/tx/begin body {} \u2192 {txId}\n * POST /api/sfmc/db/tx/step body {txId, step} \u2192 {result}\n * POST /api/sfmc/db/tx/commit body {txId} \u2192 TxResponse\n * POST /api/sfmc/db/tx/rollback body {txId} \u2192 {ok}\n * POST /api/sfmc/db/query body {table, opts?} \u2192 {rows}\n * ...\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { log } from \"../lib/log.js\";\nimport { jsonV2Fail, jsonV2Ok, type ModuleAuth } from \"./_shared.js\";\nimport type {\n DefineTableRequest,\n SchemaRegistry,\n} from \"../schema-registry.js\";\nimport type {\n TxRequest,\n TxRunner,\n TxStep,\n} from \"../tx-runner.js\";\nimport { PermissionDeniedError } from \"../permission-gate.js\";\nimport type { IdempotencyStore } from \"../lib/idempotency-store.js\";\n\nexport interface DbRoutesDeps {\n schemaRegistry: SchemaRegistry;\n txRunner: TxRunner;\n idempotent: IdempotencyStore;\n json?: typeof defaultJson;\n}\n\n/**\n * \u521B\u5EFA `/api/sfmc/db/*` \u6570\u636E\u5E93\u8DEF\u7531\u8BF7\u6C42\u5904\u7406\u5668\u3002\n *\n * @param depsIn \u4F9D\u8D56\u6CE8\u5165\u9879\uFF08\u5305\u542B schemaRegistry\u3001txRunner\u3001idempotent \u7B49\uFF09\u3002\n * @returns \u5F02\u6B65\u8DEF\u7531\u5904\u7406\u51FD\u6570\u3002\n */\nexport function createDbRoutes(depsIn: Partial<DbRoutesDeps>) {\n const deps = depsIn as Partial<DbRoutesDeps>;\n if (!deps.schemaRegistry || !deps.txRunner || !deps.idempotent) {\n throw new Error(\"createDbRoutes: \u7F3A\u5C11 schemaRegistry / txRunner / idempotent\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n req: IncomingMessage;\n res: ServerResponse;\n body?: Promise<Record<string, unknown>> | Record<string, unknown>;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/db/\")) return false;\n if (method !== \"POST\" && method !== \"DELETE\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n const body = (await (ctx.body as Promise<Record<string, unknown>> | undefined)) || {};\n const moduleId = auth.id;\n\n if (path === \"/api/sfmc/db/define-table\") {\n try {\n const req2 = body as unknown as DefineTableRequest & { moduleId?: string };\n delete (req2 as { moduleId?: string }).moduleId;\n const result = deps.schemaRegistry!.define(moduleId, req2);\n jsonV2Ok(res, { table: result.table, created: result.created });\n } catch (e) {\n log.warn(`[define-table failed] ${moduleId} table=${(body as any)?.name}: ${(e as Error).message}`);\n jsonV2Fail(res, (e as Error).message, 400);\n }\n return true;\n }\n\n if (path === \"/api/sfmc/db/tx\") {\n try {\n const txReq = body as unknown as TxRequest;\n // \u5F3A\u5236\u4EE5\u9274\u6743\u8EAB\u4EFD\u6267\u884C\u4E8B\u52A1\uFF1A\u5FFD\u7565\u8BF7\u6C42\u4F53\u4E2D\u81EA\u5E26\u7684 moduleId\uFF0C\n // \u65E2\u675C\u7EDD\u8DE8\u6A21\u5757\u8D8A\u6743\u5047\u501F\u8EAB\u4EFD\uFF0C\u4E5F\u517C\u5BB9\u5BA2\u6237\u7AEF\u4EC5\u63D0\u4EA4 steps \u6570\u636E\u7684\u8C03\u7528\u573A\u666F\u3002\n const steps = Array.isArray(txReq.steps) ? txReq.steps : [];\n const result = await deps.txRunner!.run({ moduleId, steps });\n const status = result.ok ? 200 : 400;\n // TxResponse \u4E0E TxError \u81EA\u8EAB\u5DF2\u5305\u542B ok \u5B57\u6BB5\uFF0C\u539F\u6837\u56DE\u4F20\u4FDD\u6301\u534F\u8BAE\u5951\u7EA6\n json(res, result as unknown as Record<string, unknown>, status);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n // \u4EA4\u4E92\u5F0F\u4E8B\u52A1\u4F1A\u8BDD \u2014 \u4F9B\u5BA2\u6237\u7AEF\u5728\u4E8B\u52A1\u56DE\u8C03\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BFB\u56DE\u5B9E\u65F6 query/get/call \u7ED3\u679C\n\n type SessionReply = { ok: boolean } & Record<string, unknown>;\n const sessionOps: Array<{\n path: string;\n requireTxId?: boolean;\n requireStep?: boolean;\n run: (args: { txId: string; step?: TxStep | undefined }) => Promise<SessionReply> | SessionReply;\n }> = [\n {\n path: \"/api/sfmc/db/tx/begin\",\n run: () => deps.txRunner!.beginSession(moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/step\",\n requireTxId: true,\n requireStep: true,\n run: ({ txId, step }) => deps.txRunner!.stepSession(txId, moduleId, step!) as Promise<SessionReply>,\n },\n {\n path: \"/api/sfmc/db/tx/commit\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.commitSession(txId, moduleId) as SessionReply,\n },\n {\n path: \"/api/sfmc/db/tx/rollback\",\n requireTxId: true,\n run: ({ txId }) => deps.txRunner!.rollbackSession(txId, moduleId) as SessionReply,\n },\n ];\n for (const op of sessionOps) {\n if (path !== op.path) continue;\n try {\n const txId = String(body.txId || \"\");\n const step = body.step as TxStep | undefined;\n if (op.requireTxId && !txId) {\n jsonV2Fail(res, `${op.path.split(\"/\").pop()} \u9700\u8981 { txId }`, 400);\n return true;\n }\n if (op.requireStep && (!step || typeof step !== \"object\" || !(\"op\" in step))) {\n jsonV2Fail(res, \"tx/step \u9700\u8981 { txId, step }\", 400);\n return true;\n }\n const result = await op.run(step !== undefined ? { txId, step } : { txId });\n json(res, result as Record<string, unknown>, result.ok ? 200 : 400);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n type SingleOp = \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\";\n const singles: { match: RegExp; op: SingleOp }[] = [\n { match: /^\\/api\\/sfmc\\/db\\/query$/, op: \"query\" },\n { match: /^\\/api\\/sfmc\\/db\\/get$/, op: \"get\" },\n { match: /^\\/api\\/sfmc\\/db\\/insert$/, op: \"insert\" },\n { match: /^\\/api\\/sfmc\\/db\\/update$/, op: \"update\" },\n { match: /^\\/api\\/sfmc\\/db\\/delete$/, op: \"delete\" },\n ];\n for (const cand of singles) {\n if (cand.match.test(path)) {\n try {\n const step = buildSingleStep(cand.op, body);\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n const code = result.code === \"permission_denied\" ? 403 : 400;\n jsonV2Fail(res, result.error, code, result.code, { step: result.step });\n return true;\n }\n const first = result.results[0];\n jsonV2Ok(res, unwrapResult(first, cand.op) as Record<string, unknown>);\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n }\n\n if (path === \"/api/sfmc/db/audit\") {\n try {\n const step: TxStep = {\n op: \"audit\",\n table: String(body.table),\n rowId: String(body.rowId),\n action: String(body.action),\n ...(body.data ? { data: body.data as Record<string, unknown> } : {}),\n };\n const result = await deps.txRunner!.run({ moduleId, steps: [step] });\n if (!result.ok) {\n jsonV2Fail(res, result.error, 400, result.code);\n return true;\n }\n jsonV2Ok(res);\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/db\\/idempotent\\/(probe|commit)$/);\n if (m) {\n try {\n if (m[1] === \"probe\") {\n const r = await deps.idempotent!.probe(\n moduleId,\n String(body.action),\n String(body.key)\n );\n json(res, r as unknown as Record<string, unknown>);\n } else {\n const r = await deps.idempotent!.commit(\n moduleId,\n String(body.action),\n String(body.key),\n body.value\n );\n jsonV2Ok(res, { committed: r.ok });\n }\n } catch (e) {\n jsonV2Fail(res, (e as Error).message, 500);\n }\n return true;\n }\n\n return false;\n };\n}\n\nfunction buildSingleStep(op: \"query\" | \"get\" | \"insert\" | \"update\" | \"delete\", body: Record<string, unknown>): TxStep {\n switch (op) {\n case \"query\":\n return {\n op: \"query\",\n table: String(body.table),\n ...(body.opts ? { opts: body.opts as Parameters<typeof JSON.parse>[0] } : {}),\n } as TxStep;\n case \"get\":\n return { op: \"get\", table: String(body.table), id: String(body.id) };\n case \"insert\":\n return {\n op: \"insert\",\n table: String(body.table),\n row: body.row as Record<string, unknown>,\n };\n case \"update\":\n return {\n op: \"update\",\n table: String(body.table),\n id: String(body.id),\n patch: body.patch as Record<string, unknown>,\n };\n case \"delete\":\n return {\n op: \"delete\",\n table: String(body.table),\n id: String(body.id),\n hard: Boolean(body.hard),\n };\n }\n}\n\ninterface UnwrappedRow {\n rows?: unknown[];\n row?: unknown;\n changes?: number;\n}\n\nfunction unwrapResult(r: unknown, op: string): UnwrappedRow {\n const step = r as { op: string; rows?: unknown[]; row?: unknown; changes?: number };\n switch (op) {\n case \"query\":\n return step.rows ? { rows: step.rows } : {};\n case \"get\":\n case \"insert\":\n case \"update\":\n return step.row !== undefined ? { row: step.row } : {};\n case \"delete\":\n return typeof step.changes === \"number\" ? { changes: step.changes } : {};\n default:\n return {};\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,7 +12,7 @@
12
12
  * - 软删除支持:开启 `softDelete`(默认开启)时自动在末尾统一追加 `_deleted_at` 与乐观锁 `_version`
13
13
  */
14
14
  import type { DatabaseSync } from "node:sqlite";
15
- export type ColumnType = "text" | "integer" | "real" | "blob";
15
+ export type ColumnType = "text" | "integer" | "real" | "blob" | "TEXT" | "INTEGER" | "REAL" | "BLOB";
16
16
  export interface ColumnDef {
17
17
  type: ColumnType;
18
18
  primary?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-registry.d.ts","sourceRoot":"","sources":["../src/schema-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AASD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAkEjD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,EAAE,EAAE,YAAY,EAE3B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,iBAAiB,CAuCnE;IAED,6CAA6C;IAC7C,OAAO,CAAC,cAAc;IAWtB;;;OAGG;IACH,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CA0BtD;IAED,UAAU,IAAI,MAAM,EAAE,CAErB;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzC;CACF"}
1
+ {"version":3,"file":"schema-registry.d.ts","sourceRoot":"","sources":["../src/schema-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AASD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AA6EjD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,EAAE,EAAE,YAAY,EAE3B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,iBAAiB,CAuCnE;IAED,6CAA6C;IAC7C,OAAO,CAAC,cAAc;IAyEtB;;;OAGG;IACH,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CA0BtD;IAED,UAAU,IAAI,MAAM,EAAE,CAErB;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzC;CACF"}
@@ -1,7 +1,16 @@
1
1
  import { log } from "./lib/log.js";
2
2
  const PLATFORM_MODULE_ID = "__platform__";
3
3
  const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
- const VALID_TYPES = /* @__PURE__ */ new Set(["text", "integer", "real", "blob"]);
4
+ const VALID_TYPES = /* @__PURE__ */ new Set([
5
+ "text",
6
+ "integer",
7
+ "real",
8
+ "blob",
9
+ "TEXT",
10
+ "INTEGER",
11
+ "REAL",
12
+ "BLOB"
13
+ ]);
5
14
  function assertValid(req, moduleId) {
6
15
  if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name "${req.name}"`);
7
16
  if (!req.columns || Object.keys(req.columns).length === 0) {
@@ -10,9 +19,11 @@ function assertValid(req, moduleId) {
10
19
  let pks = 0;
11
20
  for (const [colName, def] of Object.entries(req.columns)) {
12
21
  if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name "${colName}" in ${req.name}`);
13
- if (!VALID_TYPES.has(def.type)) {
22
+ const rawType = String(def.type || "");
23
+ if (!VALID_TYPES.has(rawType)) {
14
24
  throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type "${def.type}"`);
15
25
  }
26
+ def.type = rawType.toLowerCase();
16
27
  if (def.primary) pks++;
17
28
  if (def.ref) {
18
29
  const [t, c] = def.ref.split(".");
@@ -93,12 +104,65 @@ class SchemaRegistry {
93
104
  }
94
105
  /** 幂等地物理建表 + 建索引(供 define / finalize 共用)。 */
95
106
  createPhysical(t) {
96
- const cols = buildColumnList(t.columns, t.softDelete);
97
- this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
107
+ const existingTableInfo = this.db.prepare(`PRAGMA table_info("${t.name}")`).all();
108
+ if (existingTableInfo.length > 0) {
109
+ let rowCount = 0;
110
+ try {
111
+ const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM "${t.name}"`).get();
112
+ rowCount = countRes?.c ?? 0;
113
+ } catch {
114
+ rowCount = 0;
115
+ }
116
+ const existingColNames = new Set(existingTableInfo.map((c) => c.name));
117
+ const requiredCols = Object.keys(t.columns);
118
+ const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));
119
+ const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);
120
+ const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);
121
+ if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {
122
+ try {
123
+ this.db.exec(`DROP TABLE "${t.name}"`);
124
+ } catch {
125
+ }
126
+ const cols = buildColumnList(t.columns, t.softDelete);
127
+ this.db.exec(`CREATE TABLE "${t.name}" (${cols.join(", ")})`);
128
+ } else {
129
+ for (const [colName, colDef] of Object.entries(t.columns)) {
130
+ if (!existingColNames.has(colName)) {
131
+ const clause = buildColumnClause(colName, colDef);
132
+ try {
133
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN ${clause};`);
134
+ } catch (e) {
135
+ log.warn(`[schema] ${t.moduleId}: ALTER TABLE "${t.name}" ADD COLUMN "${colName}" \u5F02\u5E38: ${e.message}`);
136
+ }
137
+ }
138
+ }
139
+ if (t.softDelete) {
140
+ if (!existingColNames.has("_deleted_at")) {
141
+ try {
142
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_deleted_at" INTEGER;`);
143
+ } catch {
144
+ }
145
+ }
146
+ if (!existingColNames.has("_version")) {
147
+ try {
148
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_version" INTEGER DEFAULT 0;`);
149
+ } catch {
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } else {
155
+ const cols = buildColumnList(t.columns, t.softDelete);
156
+ this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
157
+ }
98
158
  for (const [n, def] of Object.entries(t.columns)) {
99
159
  if (def.index) {
100
160
  const idxName = `idx_${t.name}_${n}`.slice(0, 60);
101
- this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
161
+ try {
162
+ this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
163
+ } catch (e) {
164
+ log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 "${idxName}" \u5931\u8D25: ${e.message}`);
165
+ }
102
166
  }
103
167
  }
104
168
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/schema-registry.ts"],
4
- "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u52A8\u6001\u6570\u636E\u8868\u7ED3\u6784\u6CE8\u518C\u4E0E\u521B\u5EFA\u7BA1\u7406\u5668\n *\n * \u534F\u8BAE\u4E0E\u6D41\u7A0B\uFF1A\n * 1. \u6A21\u5757\u542F\u52A8\u65F6\u8C03\u7528 `db.defineTable(name, columns, { softDelete })` \u5411\u670D\u52A1\u7AEF\u6CE8\u518C\u8868\u7ED3\u6784\u5B9A\u4E49\u3002\n * 2. `SchemaRegistry` \u6821\u9A8C\u5217\u5B9A\u4E49\u5408\u6CD5\u6027\u3001\u5916\u952E\u5F15\u7528\u4E0E\u4E3B\u952E\u552F\u4E00\u6027\u3002\n * 3. \u6821\u9A8C\u901A\u8FC7\u540E\u81EA\u52A8\u6267\u884C `CREATE TABLE IF NOT EXISTS` \u53CA\u7D22\u5F15\u521B\u5EFA\u8BED\u53E5\u3002\n *\n * \u51B2\u7A81\u4E0E\u9694\u79BB\u63A7\u5236\uFF1A\n * - \u76F8\u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49\u540C\u540D\u8868\uFF1A\u5177\u5907\u5E42\u7B49\u6027\uFF08\u8981\u6C42\u5217\u7ED3\u6784\u5B8C\u5168\u4E00\u81F4\uFF09\n * - \u4E0D\u540C\u6A21\u5757\u5B9A\u4E49\u76F8\u540C\u8868\u540D\uFF1A\u629B\u51FA\u51B2\u7A81\u5F02\u5E38\u62D2\u7EDD\u6CE8\u518C\n * - \u8F6F\u5220\u9664\u652F\u6301\uFF1A\u5F00\u542F `softDelete`\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09\u65F6\u81EA\u52A8\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0 `_deleted_at` \u4E0E\u4E50\u89C2\u9501 `_version`\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType = \"text\" | \"integer\" | \"real\" | \"blob\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<ColumnType> = new Set([\"text\", \"integer\", \"real\", \"blob\"]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n if (!VALID_TYPES.has(def.type)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u5C06\u5355\u5217\u5B9A\u4E49\u7F16\u8BD1\u4E3A SQL \u5217\u5B9A\u4E49\u5B50\u53E5\uFF08\u5982 `\"id\" TEXT PRIMARY KEY`\uFF09\u3002\n *\n * @param name \u5217\u540D\u3002\n * @param def \u5217\u5C5E\u6027\u5B9A\u4E49\u3002\n * @returns \u7F16\u8BD1\u540E\u7684 SQL \u7247\u6BB5\u3002\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u5C06\u6574\u5F20\u8868\u7684\u6240\u6709\u5217\u5B9A\u4E49\uFF08\u5305\u542B softDelete \u9690\u5F0F\u5217\uFF09\u7F16\u8BD1\u4E3A\u5217\u5B9A\u4E49\u5B57\u7B26\u4E32\u6570\u7EC4\u3002\n * \u8F6F\u5220\u9664\u5217 `_deleted_at` \u4E0E\u4E50\u89C2\u9501\u7248\u672C\u53F7 `_version` \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0\u3002\n *\n * @param columns \u5217\u7ED3\u6784\u5B9A\u4E49\u5B57\u5178\u3002\n * @param softDelete \u662F\u5426\u542F\u7528\u8F6F\u5220\u9664\u6269\u5C55\u5217\u3002\n * @returns \u7F16\u8BD1\u540E\u7684\u5217\u5B9A\u4E49\u5B50\u53E5\u6570\u7EC4\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /**\n * \u6CE8\u518C\u5E76\u521B\u5EFA\u6A21\u5757\u6570\u636E\u8868\u7ED3\u6784\u3002\n *\n * @param moduleId \u6CE8\u518C\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param req \u8868\u7ED3\u6784\u5B9A\u4E49\u8BF7\u6C42\u5BF9\u8C61\u3002\n * @returns \u5EFA\u8868\u7ED3\u679C\u4E0E\u521B\u5EFA\u7684\u7D22\u5F15\u5217\u8868\u3002\n */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u52A8\u6001\u6570\u636E\u8868\u7ED3\u6784\u6CE8\u518C\u4E0E\u521B\u5EFA\u7BA1\u7406\u5668\n *\n * \u534F\u8BAE\u4E0E\u6D41\u7A0B\uFF1A\n * 1. \u6A21\u5757\u542F\u52A8\u65F6\u8C03\u7528 `db.defineTable(name, columns, { softDelete })` \u5411\u670D\u52A1\u7AEF\u6CE8\u518C\u8868\u7ED3\u6784\u5B9A\u4E49\u3002\n * 2. `SchemaRegistry` \u6821\u9A8C\u5217\u5B9A\u4E49\u5408\u6CD5\u6027\u3001\u5916\u952E\u5F15\u7528\u4E0E\u4E3B\u952E\u552F\u4E00\u6027\u3002\n * 3. \u6821\u9A8C\u901A\u8FC7\u540E\u81EA\u52A8\u6267\u884C `CREATE TABLE IF NOT EXISTS` \u53CA\u7D22\u5F15\u521B\u5EFA\u8BED\u53E5\u3002\n *\n * \u51B2\u7A81\u4E0E\u9694\u79BB\u63A7\u5236\uFF1A\n * - \u76F8\u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49\u540C\u540D\u8868\uFF1A\u5177\u5907\u5E42\u7B49\u6027\uFF08\u8981\u6C42\u5217\u7ED3\u6784\u5B8C\u5168\u4E00\u81F4\uFF09\n * - \u4E0D\u540C\u6A21\u5757\u5B9A\u4E49\u76F8\u540C\u8868\u540D\uFF1A\u629B\u51FA\u51B2\u7A81\u5F02\u5E38\u62D2\u7EDD\u6CE8\u518C\n * - \u8F6F\u5220\u9664\u652F\u6301\uFF1A\u5F00\u542F `softDelete`\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09\u65F6\u81EA\u52A8\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0 `_deleted_at` \u4E0E\u4E50\u89C2\u9501 `_version`\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType =\n | \"text\"\n | \"integer\"\n | \"real\"\n | \"blob\"\n | \"TEXT\"\n | \"INTEGER\"\n | \"REAL\"\n | \"BLOB\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<string> = new Set([\n \"text\",\n \"integer\",\n \"real\",\n \"blob\",\n \"TEXT\",\n \"INTEGER\",\n \"REAL\",\n \"BLOB\",\n]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n const rawType = String(def.type || \"\");\n if (!VALID_TYPES.has(rawType)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n def.type = rawType.toLowerCase() as ColumnType;\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u5C06\u5355\u5217\u5B9A\u4E49\u7F16\u8BD1\u4E3A SQL \u5217\u5B9A\u4E49\u5B50\u53E5\uFF08\u5982 `\"id\" TEXT PRIMARY KEY`\uFF09\u3002\n *\n * @param name \u5217\u540D\u3002\n * @param def \u5217\u5C5E\u6027\u5B9A\u4E49\u3002\n * @returns \u7F16\u8BD1\u540E\u7684 SQL \u7247\u6BB5\u3002\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u5C06\u6574\u5F20\u8868\u7684\u6240\u6709\u5217\u5B9A\u4E49\uFF08\u5305\u542B softDelete \u9690\u5F0F\u5217\uFF09\u7F16\u8BD1\u4E3A\u5217\u5B9A\u4E49\u5B57\u7B26\u4E32\u6570\u7EC4\u3002\n * \u8F6F\u5220\u9664\u5217 `_deleted_at` \u4E0E\u4E50\u89C2\u9501\u7248\u672C\u53F7 `_version` \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0\u3002\n *\n * @param columns \u5217\u7ED3\u6784\u5B9A\u4E49\u5B57\u5178\u3002\n * @param softDelete \u662F\u5426\u542F\u7528\u8F6F\u5220\u9664\u6269\u5C55\u5217\u3002\n * @returns \u7F16\u8BD1\u540E\u7684\u5217\u5B9A\u4E49\u5B50\u53E5\u6570\u7EC4\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /**\n * \u6CE8\u518C\u5E76\u521B\u5EFA\u6A21\u5757\u6570\u636E\u8868\u7ED3\u6784\u3002\n *\n * @param moduleId \u6CE8\u518C\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param req \u8868\u7ED3\u6784\u5B9A\u4E49\u8BF7\u6C42\u5BF9\u8C61\u3002\n * @returns \u5EFA\u8868\u7ED3\u679C\u4E0E\u521B\u5EFA\u7684\u7D22\u5F15\u5217\u8868\u3002\n */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const existingTableInfo = this.db.prepare(`PRAGMA table_info(\"${t.name}\")`).all() as Array<{\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: unknown;\n pk: number;\n }>;\n\n if (existingTableInfo.length > 0) {\n let rowCount = 0;\n try {\n const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM \"${t.name}\"`).get() as { c: number } | undefined;\n rowCount = countRes?.c ?? 0;\n } catch {\n rowCount = 0;\n }\n const existingColNames = new Set(existingTableInfo.map((c) => c.name));\n const requiredCols = Object.keys(t.columns);\n const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));\n const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);\n const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);\n\n if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {\n // \u7A7A\u8868\u4E14\u7ED3\u6784\u4E0E\u5F53\u524D\u5B9A\u4E49\u4E0D\u517C\u5BB9\uFF08\u5982\u65E7\u7248\u672C\u9057\u7559\u7A7A\u8868\uFF09\uFF1A\u5B89\u5168\u5220\u9664\u5E76\u4EE5\u65B0\u7ED3\u6784\u91CD\u5EFA\n try {\n this.db.exec(`DROP TABLE \"${t.name}\"`);\n } catch {}\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE \"${t.name}\" (${cols.join(\", \")})`);\n } else {\n // \u8868\u4E2D\u6709\u6570\u636E\u6216\u7ED3\u6784\u517C\u5BB9\uFF1A\u901A\u8FC7 ALTER TABLE ADD COLUMN \u589E\u91CF\u8865\u9F50\u7F3A\u5931\u5217\n for (const [colName, colDef] of Object.entries(t.columns)) {\n if (!existingColNames.has(colName)) {\n const clause = buildColumnClause(colName, colDef);\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN ${clause};`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: ALTER TABLE \"${t.name}\" ADD COLUMN \"${colName}\" \u5F02\u5E38: ${(e as Error).message}`);\n }\n }\n }\n if (t.softDelete) {\n if (!existingColNames.has(\"_deleted_at\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_deleted_at\" INTEGER;`);\n } catch {}\n }\n if (!existingColNames.has(\"_version\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_version\" INTEGER DEFAULT 0;`);\n } catch {}\n }\n }\n }\n } else {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n }\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n try {\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 \"${idxName}\" \u5931\u8D25: ${(e as Error).message}`);\n }\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfmc-bds/db-server",
3
- "version": "0.2.0-beta.7",
3
+ "version": "0.2.0-beta.8",
4
4
  "description": "SFMC SQLite HTTP REST backend — configs, modules, gameplay data",
5
5
  "author": "Shiroha7z",
6
6
  "license": "AGPL-3.0-only",
@@ -32,10 +32,10 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "sql-template-strings": "^2.2.2",
35
- "@sfmc-bds/sdk": "0.2.0-beta.11"
35
+ "@sfmc-bds/sdk": "0.2.0-beta.12"
36
36
  },
37
37
  "devDependencies": {
38
- "@sfmc-bds/tools": "0.2.0-beta.12"
38
+ "@sfmc-bds/tools": "0.2.0-beta.13"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public",