cj-tauri 2.0.0 → 2.1.0

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.
Files changed (158) hide show
  1. package/README.md +113 -99
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +63 -52
  4. package/cli/cjpm.toml +22 -22
  5. package/cli/src/doctor.cj +308 -306
  6. package/cli/src/main.cj +129 -129
  7. package/cli/src/manifest.cj +632 -558
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -770
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +369 -369
  12. package/cli/src/sync.cj +526 -526
  13. package/cli/src/util.cj +168 -168
  14. package/cli/templates/app/README.md +51 -48
  15. package/cli/templates/app/assets/capabilities/default.json +7 -7
  16. package/cli/templates/app/assets/ui/index.html +120 -118
  17. package/cli/templates/app/cj-tauri.toml +32 -22
  18. package/cli/templates/app/cjpm.toml +34 -34
  19. package/cli/templates/app/src/main.cj +101 -90
  20. package/cli/templates/app-react/README.md +56 -56
  21. package/cli/templates/app-react/assets/capabilities/default.json +7 -7
  22. package/cli/templates/app-react/assets/ui/index.html +25 -25
  23. package/cli/templates/app-react/assets/ui/package.json +19 -19
  24. package/cli/templates/app-react/assets/ui/src/App.css +67 -67
  25. package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
  26. package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
  27. package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
  28. package/cli/templates/app-react/cj-tauri.toml +33 -23
  29. package/cli/templates/app-react/cjpm.toml +34 -34
  30. package/cli/templates/app-react/src/main.cj +91 -91
  31. package/cli/templates/app-vue/README.md +56 -56
  32. package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
  33. package/cli/templates/app-vue/assets/ui/index.html +25 -25
  34. package/cli/templates/app-vue/assets/ui/package.json +18 -18
  35. package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
  36. package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
  37. package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
  38. package/cli/templates/app-vue/cj-tauri.toml +33 -23
  39. package/cli/templates/app-vue/cjpm.toml +34 -34
  40. package/cli/templates/app-vue/src/main.cj +91 -91
  41. package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
  42. package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
  43. package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
  44. package/cli/templates/ohos-skeleton/README.md +84 -79
  45. package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
  46. package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
  47. package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
  48. package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
  49. package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
  50. package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
  51. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
  52. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
  53. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  54. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
  55. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
  56. package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
  57. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
  58. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
  59. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
  60. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
  61. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
  62. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
  63. package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
  64. package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
  65. package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
  66. package/native/build_win.bat +62 -62
  67. package/native/core/bridge_core.c +1681 -1626
  68. package/native/core/bridge_core.h +675 -618
  69. package/native/core/bridge_js.h +108 -108
  70. package/native/platforms/harmonyos/README.md +115 -6
  71. package/native/platforms/linux/bridge_linux.c +2787 -2485
  72. package/native/platforms/windows/bridge_win.c +3740 -3248
  73. package/native/tests/gtk_threading_probe.c +143 -143
  74. package/native/tests/test_bridge_core.c +1339 -1228
  75. package/package.json +53 -53
  76. package/prebuilt/linux-x64/cj-tauri +0 -0
  77. package/src/api_system.cj +67 -67
  78. package/src/app.cj +325 -969
  79. package/src/app_events.cj +166 -0
  80. package/src/app_host_ops.cj +287 -0
  81. package/src/app_run.cj +283 -0
  82. package/src/app_spec.cj +76 -76
  83. package/src/arkweb_menu.cj +216 -0
  84. package/src/arkweb_script.cj +142 -142
  85. package/src/capability.cj +409 -409
  86. package/src/clipboard_bridge.cj +178 -0
  87. package/src/dialog_spec.cj +79 -0
  88. package/src/dragdrop.cj +26 -26
  89. package/src/embedded_assets.cj +98 -98
  90. package/src/host.cj +752 -607
  91. package/src/host_arkweb.cj +924 -778
  92. package/src/host_webkit.cj +663 -570
  93. package/src/host_webview2.cj +663 -570
  94. package/src/hotkey.cj +66 -66
  95. package/src/ipc_hub.cj +337 -337
  96. package/src/ipc_message.cj +286 -286
  97. package/src/launch.cj +62 -62
  98. package/src/logging.cj +127 -127
  99. package/src/menu.cj +197 -197
  100. package/src/platform/platform_app_context.cj +77 -77
  101. package/src/platform/platform_assets.cj +33 -33
  102. package/src/platform/platform_discover.cj +176 -176
  103. package/src/platform/platform_env.cj +29 -29
  104. package/src/platform/platform_feature.cj +31 -31
  105. package/src/platform/platform_page_source.cj +87 -87
  106. package/src/platform/platform_target_info.cj +32 -32
  107. package/src/platform_host_provider.cj +87 -87
  108. package/src/plugin.cj +333 -333
  109. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  110. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  111. package/src/plugins/fs/plugin_fs.cj +105 -1
  112. package/src/plugins/http/http_backend.cj +131 -0
  113. package/src/plugins/http/http_desktop.cj +195 -0
  114. package/src/plugins/http/http_ohos.cj +204 -0
  115. package/src/plugins/http/plugin_http.cj +310 -459
  116. package/src/plugins/log/plugin_log.cj +105 -105
  117. package/src/plugins/menu/plugin_menu.cj +158 -158
  118. package/src/plugins/notification/plugin_notification.cj +154 -154
  119. package/src/plugins/serial/plugin_serial.cj +458 -454
  120. package/src/plugins/serial/serial_bridge.cj +194 -194
  121. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  122. package/src/plugins/serial/serial_ohos.cj +529 -0
  123. package/src/plugins/shell/plugin_shell.cj +258 -258
  124. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  125. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  126. package/src/plugins/ws/plugin_ws.cj +488 -470
  127. package/src/process_hidden.cj +174 -174
  128. package/src/scope.cj +391 -391
  129. package/src/shell_event.cj +69 -69
  130. package/src/tests/app_spec_test.cj +126 -127
  131. package/src/tests/capability_test.cj +243 -243
  132. package/src/tests/dialog_spec_test.cj +73 -0
  133. package/src/tests/fake_host.cj +201 -201
  134. package/src/tests/ipc_hub_test.cj +487 -487
  135. package/src/tests/logging_test.cj +62 -62
  136. package/src/tests/platform_contract_test.cj +251 -251
  137. package/src/tests/plugin_clipboard_test.cj +222 -222
  138. package/src/tests/plugin_dialog_test.cj +237 -237
  139. package/src/tests/plugin_fs_test.cj +207 -0
  140. package/src/tests/plugin_http_test.cj +124 -124
  141. package/src/tests/plugin_notification_test.cj +205 -205
  142. package/src/tests/plugin_serial_test.cj +48 -0
  143. package/src/tests/plugin_test.cj +605 -585
  144. package/src/tests/plugin_ws_test.cj +40 -40
  145. package/src/tests/process_hidden_test.cj +129 -129
  146. package/src/tests/scope_test.cj +350 -350
  147. package/src/tests/system_host_test.cj +99 -81
  148. package/src/tests/urlutil_test.cj +53 -53
  149. package/src/tests/version_test.cj +75 -75
  150. package/src/tests/window_registry_test.cj +123 -123
  151. package/src/tray.cj +90 -90
  152. package/src/urlutil.cj +159 -159
  153. package/src/version.cj +44 -44
  154. package/src/window.cj +51 -51
  155. package/src/window_registry.cj +116 -116
  156. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  157. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  158. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
@@ -1,211 +1,206 @@
1
- package cjTauri.plugins.sqlite
2
-
3
- import cjTauri.*
4
-
5
- /**
6
- * SQLite C API 的 FFI 声明与最小包装(`sqlite` 插件的地基)
7
- *
8
- * **为什么直连系统库而不进 C 桥**(docs/设计决策记录.md D-018 第 2 条):现有 `cj_bridge_*` 之所以存在,
9
- * 是因为平台差异集中在 C 桥里;SQLite 相反——三平台同一套 C API、没有平台分支,再包一层只是多一份要
10
- * 维护的转发代码。代价是「链接谁」从「桥提供」变成「消费方各自提供」:Linux 链发行版系统库
11
- * (`-lsqlite3`,本机 3.45.1),Windows 侧未落地(见 D-018 第 8 条)。
12
- *
13
- * **FFI 语法是实测出来的**(三条都进 AGENTS §4):
14
- * 1. 仓颉**没有** `&x` 取地址(写它会报 `expected expression after ','`);C 出参在**调用处**写
15
- * `inout <var>`,自动取址成 `CPointer<T>`——修饰对象只能是 `var`,且指针仅在本次调用期间有效。
16
- * 2. 「可选传 NULL 的 C 字符串」(这里的 `zVfs`)声明成 `CPointer<UInt8>` 并传 `CPointer<UInt8>()`;
17
- * 传空 `CString` 会被 SQLite 当成「名字为空的 VFS」,报的是 `no such vfs`(实测踩过)。
18
- * 3. C 回调声明成 `CFunc<(...) -> 返回类型>`,回调本体是顶层 `@C func`(本仓 `cj_bridge_init` 先例)。
19
- */
20
-
21
- /** SQLite 返回码(sqlite3.h 的子集,只列我们判过的) */
22
- let SQLITE_OK: Int32 = 0
23
- let SQLITE_ROW: Int32 = 100
24
- let SQLITE_DONE: Int32 = 101
25
- /** sqlite3_exec 在授权回调拒绝时给的是 SQLITE_AUTH(列出来便于断言时看明白) */
26
- let SQLITE_AUTH: Int32 = 23
27
-
28
- /** 列类型(sqlite3_column_type):界面要能说清「这格是数字还是文本」,所以逐格判 */
29
- let SQLITE_INTEGER: Int32 = 1
30
- let SQLITE_FLOAT: Int32 = 2
31
- let SQLITE_TEXT: Int32 = 3
32
- let SQLITE_BLOB: Int32 = 4
33
- let SQLITE_NULL: Int32 = 5
34
-
35
- /** 打开标志(sqlite3_open_v2):只读走 READONLY,写路径要显式给 READWRITE */
36
- let SQLITE_OPEN_READONLY: Int32 = 1
37
- let SQLITE_OPEN_READWRITE: Int32 = 2
38
- let SQLITE_OPEN_CREATE: Int32 = 4
39
-
40
- /** 授权动作码(sqlite3_set_authorizer):只列我们要拒的三类 */
41
- let SQLITE_ATTACH: Int32 = 24
42
- let SQLITE_DETACH: Int32 = 25
43
- let SQLITE_FUNCTION: Int32 = 31
44
-
45
- /** 授权回调返回值 */
46
- let SQLITE_DENY: Int32 = 1
47
-
48
- /** busy 等待上限(毫秒):另一个进程正写这个库时等着,而不是当场报 SQLITE_BUSY */
49
- let SQLITE_BUSY_TIMEOUT_MS: Int32 = 3000
50
-
51
- @C
52
- foreign func sqlite3_libversion(): CString
53
-
54
- @C
55
- foreign func sqlite3_open_v2(filename: CString, ppDb: CPointer<CPointer<Unit>>, flags: Int32,
56
- zVfs: CPointer<UInt8>): Int32
57
-
58
- @C
59
- foreign func sqlite3_close(db: CPointer<Unit>): Int32
60
-
61
- @C
62
- foreign func sqlite3_errmsg(db: CPointer<Unit>): CString
63
-
64
- @C
65
- foreign func sqlite3_exec(db: CPointer<Unit>, sql: CString, cb: CPointer<Unit>, arg: CPointer<Unit>,
66
- errmsg: CPointer<Unit>): Int32
67
-
68
- @C
69
- foreign func sqlite3_changes(db: CPointer<Unit>): Int32
70
-
71
- @C
72
- foreign func sqlite3_last_insert_rowid(db: CPointer<Unit>): Int64
73
-
74
- @C
75
- foreign func sqlite3_busy_timeout(db: CPointer<Unit>, ms: Int32): Int32
76
-
77
- @C
78
- foreign func sqlite3_prepare_v2(db: CPointer<Unit>, sql: CString, nByte: Int32,
79
- ppStmt: CPointer<CPointer<Unit>>, pzTail: CPointer<Unit>): Int32
80
-
81
- @C
82
- foreign func sqlite3_step(stmt: CPointer<Unit>): Int32
83
-
84
- @C
85
- foreign func sqlite3_finalize(stmt: CPointer<Unit>): Int32
86
-
87
- @C
88
- foreign func sqlite3_column_count(stmt: CPointer<Unit>): Int32
89
-
90
- @C
91
- foreign func sqlite3_column_name(stmt: CPointer<Unit>, i: Int32): CString
92
-
93
- @C
94
- foreign func sqlite3_column_type(stmt: CPointer<Unit>, i: Int32): Int32
95
-
96
- @C
97
- foreign func sqlite3_column_int64(stmt: CPointer<Unit>, i: Int32): Int64
98
-
99
- @C
100
- foreign func sqlite3_column_double(stmt: CPointer<Unit>, i: Int32): Float64
101
-
102
- @C
103
- foreign func sqlite3_column_text(stmt: CPointer<Unit>, i: Int32): CString
104
-
105
- @C
106
- foreign func sqlite3_column_bytes(stmt: CPointer<Unit>, i: Int32): Int32
107
-
108
- @C
109
- foreign func sqlite3_set_authorizer(
110
- db: CPointer<Unit>,
111
- cb: CFunc<(CPointer<Unit>, Int32, CString, CString, CString, CString) -> Int32>,
112
- userdata: CPointer<Unit>): Int32
113
-
114
- @C
115
- foreign func sqlite3_enable_load_extension(db: CPointer<Unit>, onoff: Int32): Int32
116
-
117
- /**
118
- * 授权回调:拒掉「跑到别的文件去」的动作——`ATTACH` / `DETACH` 换库文件、
119
- * `load_extension()` 加载任意原生库。**必须按语句判**:路径 scope 只管得住本次命令收到的那个路径,
120
- * 管不住 SQL 里写死的另一个路径(D-018 第 4 条)。被拒时留一行 stderr,取证时能对上账。
121
- */
122
- @When[env != "ohos"]
123
- @C
124
- func sqliteAuthorizer(app: CPointer<Unit>, code: Int32, arg1: CString, arg2: CString,
125
- dbName: CString, triggerName: CString): Int32 {
126
- let _ = app
127
- let _ = arg2
128
- let _ = dbName
129
- let _ = triggerName
130
- if (code == SQLITE_ATTACH || code == SQLITE_DETACH) {
131
- eprintln("[cj-tauri] sqlite denied: attach/detach is not allowed (code=${code})")
132
- return SQLITE_DENY
133
- }
134
- if (code == SQLITE_FUNCTION) {
135
- // arg1 是被调用的函数名:只拒能加载原生代码的那个,其余内置函数照常放行
136
- let fnName = sqliteCString(arg1)
137
- if (fnName == "load_extension") {
138
- eprintln("[cj-tauri] sqlite denied: load_extension() is not allowed")
139
- return SQLITE_DENY
140
- }
141
- }
142
- return SQLITE_OK
143
- }
144
-
145
- /** C 字符串 → 仓颉字符串;NULL 给空串(调用方要区分 NULL 与空串时另判归属) */
146
- func sqliteCString(s: CString): String {
147
- if (s.isNull()) {
148
- return ""
149
- }
150
- return s.toString()
151
- }
152
-
153
- /** 取最近一次错误文案;db 为空时给固定文案,避免收尾路径上再炸一次 */
154
- @When[env != "ohos"]
155
- func sqliteLastError(db: CPointer<Unit>): String {
156
- if (db.isNull()) {
157
- return "unknown error (null db handle)"
158
- }
159
- return sqliteCString(unsafe { sqlite3_errmsg(db) })
160
- }
161
-
162
- /** 关掉句柄:对空指针安全(出错路径也要能收尾),关不掉只记一行 stderr 不抛 */
163
- @When[env != "ohos"]
164
- func sqliteCloseQuietly(db: CPointer<Unit>): Unit {
165
- if (db.isNull()) {
166
- return
167
- }
168
- let rc = unsafe { sqlite3_close(db) }
169
- if (rc != SQLITE_OK) {
170
- eprintln("[cj-tauri] sqlite close returned ${rc}")
171
- }
172
- }
173
-
174
- /**
175
- * 打开数据库并装上护栏(每次命令短连接,见 D-018 第 9 条)。
176
- *
177
- * `readonly=true` 走 `SQLITE_OPEN_READONLY`——只读是**内核级**的,不是我们写的检查;
178
- * 不存在的文件 + 只读 = 打开失败(不会顺手造一个空库)。
179
- * 打开失败与护栏装不上都抛 `CommandException`(前端拿到可读文案)。
180
- */
181
- @When[env != "ohos"]
182
- func sqliteOpenGuarded(path: String, readonly: Bool): CPointer<Unit> {
183
- var db: CPointer<Unit> = CPointer<Unit>()
184
- let flags = if (readonly) {
185
- SQLITE_OPEN_READONLY
186
- } else {
187
- SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
188
- }
189
- // inout db 取的是「栈上那个指针变量」的地址:C 侧写入的 handle 直接落到 db 上
190
- let rc = unsafe { sqlite3_open_v2(LibC.mallocCString(path), inout db, flags, CPointer<UInt8>()) }
191
- if (rc != SQLITE_OK) {
192
- let msg = sqliteLastError(db)
193
- sqliteCloseQuietly(db)
194
- throw CommandException("sqlite: cannot open ${path}: ${msg}")
195
- }
196
- // 双保险之一:先关掉扩展加载(authorizer 之外再加一道)
197
- let _ = unsafe { sqlite3_enable_load_extension(db, 0) }
198
- let rcAuthorizer = unsafe { sqlite3_set_authorizer(db, sqliteAuthorizer, CPointer<Unit>()) }
199
- if (rcAuthorizer != SQLITE_OK) {
200
- sqliteCloseQuietly(db)
201
- throw CommandException("sqlite: cannot install authorizer (rc=${rcAuthorizer})")
202
- }
203
- let _ = unsafe { sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS) }
204
- return db
205
- }
206
-
207
- /** 本机链接的 SQLite 版本(`sqlite:info` 回投给页面,对账时也用它) */
208
- @When[env != "ohos"]
209
- func sqliteLibVersion(): String {
210
- return sqliteCString(unsafe { sqlite3_libversion() })
211
- }
1
+ package cjTauri.plugins.sqlite
2
+
3
+ import cjTauri.*
4
+
5
+ /**
6
+ * SQLite C API 的 FFI 声明与最小包装(`sqlite` 插件的地基)
7
+ *
8
+ * **为什么直连系统库而不进 C 桥**(docs/设计决策记录.md D-018 第 2 条):现有 `cj_bridge_*` 之所以存在,
9
+ * 是因为平台差异集中在 C 桥里;SQLite 相反——三平台同一套 C API、没有平台分支,再包一层只是多一份要
10
+ * 维护的转发代码。代价是「链接谁」从「桥提供」变成「消费方各自提供」:Linux 链发行版系统库
11
+ * (`-lsqlite3`,本机 3.45.1),Windows 侧未落地(见 D-018 第 8 条)。
12
+ *
13
+ * **FFI 语法是实测出来的**(三条都进 AGENTS §4):
14
+ * 1. 仓颉**没有** `&x` 取地址(写它会报 `expected expression after ','`);C 出参在**调用处**写
15
+ * `inout <var>`,自动取址成 `CPointer<T>`——修饰对象只能是 `var`,且指针仅在本次调用期间有效。
16
+ * 2. 「可选传 NULL 的 C 字符串」(这里的 `zVfs`)声明成 `CPointer<UInt8>` 并传 `CPointer<UInt8>()`;
17
+ * 传空 `CString` 会被 SQLite 当成「名字为空的 VFS」,报的是 `no such vfs`(实测踩过)。
18
+ * 3. C 回调声明成 `CFunc<(...) -> 返回类型>`,回调本体是顶层 `@C func`(本仓 `cj_bridge_init` 先例)。
19
+ */
20
+
21
+ /** SQLite 返回码(sqlite3.h 的子集,只列我们判过的) */
22
+ let SQLITE_OK: Int32 = 0
23
+ let SQLITE_ROW: Int32 = 100
24
+ let SQLITE_DONE: Int32 = 101
25
+ /** sqlite3_exec 在授权回调拒绝时给的是 SQLITE_AUTH(列出来便于断言时看明白) */
26
+ let SQLITE_AUTH: Int32 = 23
27
+
28
+ /** 列类型(sqlite3_column_type):界面要能说清「这格是数字还是文本」,所以逐格判 */
29
+ let SQLITE_INTEGER: Int32 = 1
30
+ let SQLITE_FLOAT: Int32 = 2
31
+ let SQLITE_TEXT: Int32 = 3
32
+ let SQLITE_BLOB: Int32 = 4
33
+ let SQLITE_NULL: Int32 = 5
34
+
35
+ /** 打开标志(sqlite3_open_v2):只读走 READONLY,写路径要显式给 READWRITE */
36
+ let SQLITE_OPEN_READONLY: Int32 = 1
37
+ let SQLITE_OPEN_READWRITE: Int32 = 2
38
+ let SQLITE_OPEN_CREATE: Int32 = 4
39
+
40
+ /** 授权动作码(sqlite3_set_authorizer):只列我们要拒的三类 */
41
+ let SQLITE_ATTACH: Int32 = 24
42
+ let SQLITE_DETACH: Int32 = 25
43
+ let SQLITE_FUNCTION: Int32 = 31
44
+
45
+ /** 授权回调返回值 */
46
+ let SQLITE_DENY: Int32 = 1
47
+
48
+ /** busy 等待上限(毫秒):另一个进程正写这个库时等着,而不是当场报 SQLITE_BUSY */
49
+ let SQLITE_BUSY_TIMEOUT_MS: Int32 = 3000
50
+
51
+ @C
52
+ foreign func sqlite3_libversion(): CString
53
+
54
+ @C
55
+ foreign func sqlite3_open_v2(filename: CString, ppDb: CPointer<CPointer<Unit>>, flags: Int32,
56
+ zVfs: CPointer<UInt8>): Int32
57
+
58
+ @C
59
+ foreign func sqlite3_close(db: CPointer<Unit>): Int32
60
+
61
+ @C
62
+ foreign func sqlite3_errmsg(db: CPointer<Unit>): CString
63
+
64
+ @C
65
+ foreign func sqlite3_exec(db: CPointer<Unit>, sql: CString, cb: CPointer<Unit>, arg: CPointer<Unit>,
66
+ errmsg: CPointer<Unit>): Int32
67
+
68
+ @C
69
+ foreign func sqlite3_changes(db: CPointer<Unit>): Int32
70
+
71
+ @C
72
+ foreign func sqlite3_last_insert_rowid(db: CPointer<Unit>): Int64
73
+
74
+ @C
75
+ foreign func sqlite3_busy_timeout(db: CPointer<Unit>, ms: Int32): Int32
76
+
77
+ @C
78
+ foreign func sqlite3_prepare_v2(db: CPointer<Unit>, sql: CString, nByte: Int32,
79
+ ppStmt: CPointer<CPointer<Unit>>, pzTail: CPointer<Unit>): Int32
80
+
81
+ @C
82
+ foreign func sqlite3_step(stmt: CPointer<Unit>): Int32
83
+
84
+ @C
85
+ foreign func sqlite3_finalize(stmt: CPointer<Unit>): Int32
86
+
87
+ @C
88
+ foreign func sqlite3_column_count(stmt: CPointer<Unit>): Int32
89
+
90
+ @C
91
+ foreign func sqlite3_column_name(stmt: CPointer<Unit>, i: Int32): CString
92
+
93
+ @C
94
+ foreign func sqlite3_column_type(stmt: CPointer<Unit>, i: Int32): Int32
95
+
96
+ @C
97
+ foreign func sqlite3_column_int64(stmt: CPointer<Unit>, i: Int32): Int64
98
+
99
+ @C
100
+ foreign func sqlite3_column_double(stmt: CPointer<Unit>, i: Int32): Float64
101
+
102
+ @C
103
+ foreign func sqlite3_column_text(stmt: CPointer<Unit>, i: Int32): CString
104
+
105
+ @C
106
+ foreign func sqlite3_column_bytes(stmt: CPointer<Unit>, i: Int32): Int32
107
+
108
+ @C
109
+ foreign func sqlite3_set_authorizer(
110
+ db: CPointer<Unit>,
111
+ cb: CFunc<(CPointer<Unit>, Int32, CString, CString, CString, CString) -> Int32>,
112
+ userdata: CPointer<Unit>): Int32
113
+
114
+ @C
115
+ foreign func sqlite3_enable_load_extension(db: CPointer<Unit>, onoff: Int32): Int32
116
+
117
+ /**
118
+ * 授权回调:拒掉「跑到别的文件去」的动作——`ATTACH` / `DETACH` 换库文件、
119
+ * `load_extension()` 加载任意原生库。**必须按语句判**:路径 scope 只管得住本次命令收到的那个路径,
120
+ * 管不住 SQL 里写死的另一个路径(D-018 第 4 条)。被拒时留一行 stderr,取证时能对上账。
121
+ */
122
+ @C
123
+ func sqliteAuthorizer(app: CPointer<Unit>, code: Int32, arg1: CString, arg2: CString,
124
+ dbName: CString, triggerName: CString): Int32 {
125
+ let _ = app
126
+ let _ = arg2
127
+ let _ = dbName
128
+ let _ = triggerName
129
+ if (code == SQLITE_ATTACH || code == SQLITE_DETACH) {
130
+ eprintln("[cj-tauri] sqlite denied: attach/detach is not allowed (code=${code})")
131
+ return SQLITE_DENY
132
+ }
133
+ if (code == SQLITE_FUNCTION) {
134
+ // arg1 是被调用的函数名:只拒能加载原生代码的那个,其余内置函数照常放行
135
+ let fnName = sqliteCString(arg1)
136
+ if (fnName == "load_extension") {
137
+ eprintln("[cj-tauri] sqlite denied: load_extension() is not allowed")
138
+ return SQLITE_DENY
139
+ }
140
+ }
141
+ return SQLITE_OK
142
+ }
143
+
144
+ /** C 字符串 → 仓颉字符串;NULL 给空串(调用方要区分 NULL 与空串时另判归属) */
145
+ func sqliteCString(s: CString): String {
146
+ if (s.isNull()) {
147
+ return ""
148
+ }
149
+ return s.toString()
150
+ }
151
+
152
+ /** 取最近一次错误文案;db 为空时给固定文案,避免收尾路径上再炸一次 */
153
+ func sqliteLastError(db: CPointer<Unit>): String {
154
+ if (db.isNull()) {
155
+ return "unknown error (null db handle)"
156
+ }
157
+ return sqliteCString(unsafe { sqlite3_errmsg(db) })
158
+ }
159
+
160
+ /** 关掉句柄:对空指针安全(出错路径也要能收尾),关不掉只记一行 stderr 不抛 */
161
+ func sqliteCloseQuietly(db: CPointer<Unit>): Unit {
162
+ if (db.isNull()) {
163
+ return
164
+ }
165
+ let rc = unsafe { sqlite3_close(db) }
166
+ if (rc != SQLITE_OK) {
167
+ eprintln("[cj-tauri] sqlite close returned ${rc}")
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 打开数据库并装上护栏(每次命令短连接,见 D-018 第 9 条)。
173
+ *
174
+ * `readonly=true` 走 `SQLITE_OPEN_READONLY`——只读是**内核级**的,不是我们写的检查;
175
+ * 不存在的文件 + 只读 = 打开失败(不会顺手造一个空库)。
176
+ * 打开失败与护栏装不上都抛 `CommandException`(前端拿到可读文案)。
177
+ */
178
+ func sqliteOpenGuarded(path: String, readonly: Bool): CPointer<Unit> {
179
+ var db: CPointer<Unit> = CPointer<Unit>()
180
+ let flags = if (readonly) {
181
+ SQLITE_OPEN_READONLY
182
+ } else {
183
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
184
+ }
185
+ // inout db 取的是「栈上那个指针变量」的地址:C 侧写入的 handle 直接落到 db 上
186
+ let rc = unsafe { sqlite3_open_v2(LibC.mallocCString(path), inout db, flags, CPointer<UInt8>()) }
187
+ if (rc != SQLITE_OK) {
188
+ let msg = sqliteLastError(db)
189
+ sqliteCloseQuietly(db)
190
+ throw CommandException("sqlite: cannot open ${path}: ${msg}")
191
+ }
192
+ // 双保险之一:先关掉扩展加载(authorizer 之外再加一道)
193
+ let _ = unsafe { sqlite3_enable_load_extension(db, 0) }
194
+ let rcAuthorizer = unsafe { sqlite3_set_authorizer(db, sqliteAuthorizer, CPointer<Unit>()) }
195
+ if (rcAuthorizer != SQLITE_OK) {
196
+ sqliteCloseQuietly(db)
197
+ throw CommandException("sqlite: cannot install authorizer (rc=${rcAuthorizer})")
198
+ }
199
+ let _ = unsafe { sqlite3_busy_timeout(db, SQLITE_BUSY_TIMEOUT_MS) }
200
+ return db
201
+ }
202
+
203
+ /** 本机链接的 SQLite 版本(`sqlite:info` 回投给页面,对账时也用它) */
204
+ func sqliteLibVersion(): String {
205
+ return sqliteCString(unsafe { sqlite3_libversion() })
206
+ }