@capgo/capacitor-fast-sql 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorFastSql.podspec +18 -0
- package/Package.swift +30 -0
- package/README.md +340 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +220 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +231 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +229 -0
- package/dist/esm/definitions.d.ts +225 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +10 -0
- package/dist/esm/helpers.d.ts +7 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +6 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/native-sql.d.ts +42 -0
- package/dist/esm/native-sql.d.ts.map +1 -0
- package/dist/esm/native-sql.js +69 -0
- package/dist/esm/plugin.d.ts +3 -0
- package/dist/esm/plugin.d.ts.map +1 -0
- package/dist/esm/plugin.js +4 -0
- package/dist/esm/sql-connection.d.ts +94 -0
- package/dist/esm/sql-connection.d.ts.map +1 -0
- package/dist/esm/sql-connection.js +246 -0
- package/dist/esm/web.d.ts +67 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +215 -0
- package/dist/plugin.cjs.js +557 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +560 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +202 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +215 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +311 -0
- package/package.json +90 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Telegraph
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HTTP server for efficient SQL operations
|
|
7
|
+
*
|
|
8
|
+
* This server handles direct HTTP requests from JavaScript, bypassing Capacitor's
|
|
9
|
+
* bridge for better performance with large datasets and sync operations.
|
|
10
|
+
*/
|
|
11
|
+
class SQLHTTPServer {
|
|
12
|
+
let port: Int
|
|
13
|
+
let token: String
|
|
14
|
+
private var databases: [String: SQLDatabase]
|
|
15
|
+
private var server: Server?
|
|
16
|
+
private var isRunning = false
|
|
17
|
+
|
|
18
|
+
init(databases: [String: SQLDatabase]) throws {
|
|
19
|
+
self.databases = databases
|
|
20
|
+
self.token = SQLHTTPServer.generateToken()
|
|
21
|
+
self.port = try SQLHTTPServer.findAvailablePort()
|
|
22
|
+
|
|
23
|
+
// Initialize Telegraph server
|
|
24
|
+
self.server = Server()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func start() throws {
|
|
28
|
+
guard !isRunning else { return }
|
|
29
|
+
guard let server = server else {
|
|
30
|
+
throw NSError(domain: "SQLHTTPServer", code: 1, userInfo: [
|
|
31
|
+
NSLocalizedDescriptionKey: "Server not initialized"
|
|
32
|
+
])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Configure routes
|
|
36
|
+
setupRoutes()
|
|
37
|
+
|
|
38
|
+
// Start server on localhost only
|
|
39
|
+
try server.start(port: port, interface: "127.0.0.1")
|
|
40
|
+
isRunning = true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func stop() {
|
|
44
|
+
server?.stop()
|
|
45
|
+
isRunning = false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - Route Setup
|
|
49
|
+
|
|
50
|
+
private func setupRoutes() {
|
|
51
|
+
guard let server = server else { return }
|
|
52
|
+
|
|
53
|
+
// Execute endpoint
|
|
54
|
+
server.route(.POST, "/execute") { [weak self] request in
|
|
55
|
+
guard let self = self else {
|
|
56
|
+
return HTTPResponse(.internalServerError)
|
|
57
|
+
}
|
|
58
|
+
return self.handleExecuteRequest(request: request)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Batch endpoint
|
|
62
|
+
server.route(.POST, "/batch") { [weak self] request in
|
|
63
|
+
guard let self = self else {
|
|
64
|
+
return HTTPResponse(.internalServerError)
|
|
65
|
+
}
|
|
66
|
+
return self.handleBatchRequest(request: request)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Transaction endpoints
|
|
70
|
+
server.route(.POST, "/transaction/begin") { [weak self] request in
|
|
71
|
+
guard let self = self else {
|
|
72
|
+
return HTTPResponse(.internalServerError)
|
|
73
|
+
}
|
|
74
|
+
return self.handleBeginTransactionRequest(request: request)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
server.route(.POST, "/transaction/commit") { [weak self] request in
|
|
78
|
+
guard let self = self else {
|
|
79
|
+
return HTTPResponse(.internalServerError)
|
|
80
|
+
}
|
|
81
|
+
return self.handleCommitTransactionRequest(request: request)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
server.route(.POST, "/transaction/rollback") { [weak self] request in
|
|
85
|
+
guard let self = self else {
|
|
86
|
+
return HTTPResponse(.internalServerError)
|
|
87
|
+
}
|
|
88
|
+
return self.handleRollbackTransactionRequest(request: request)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - Helper methods
|
|
93
|
+
|
|
94
|
+
private static func generateToken() -> String {
|
|
95
|
+
var bytes = [UInt8](repeating: 0, count: 32)
|
|
96
|
+
let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
|
97
|
+
guard result == errSecSuccess else {
|
|
98
|
+
return UUID().uuidString
|
|
99
|
+
}
|
|
100
|
+
return bytes.map { String(format: "%02hhx", $0) }.joined()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static func findAvailablePort() throws -> Int {
|
|
104
|
+
// Try ports in the range 9000-9100
|
|
105
|
+
for port in 9000..<9100 {
|
|
106
|
+
let socket = socket(AF_INET, SOCK_STREAM, 0)
|
|
107
|
+
guard socket != -1 else {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
defer {
|
|
112
|
+
close(socket)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var addr = sockaddr_in()
|
|
116
|
+
addr.sin_family = sa_family_t(AF_INET)
|
|
117
|
+
addr.sin_port = UInt16(port).bigEndian
|
|
118
|
+
addr.sin_addr.s_addr = inet_addr("127.0.0.1")
|
|
119
|
+
|
|
120
|
+
var bindAddr = addr
|
|
121
|
+
let bindResult = withUnsafePointer(to: &bindAddr) {
|
|
122
|
+
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
123
|
+
bind(socket, $0, socklen_t(MemoryLayout<sockaddr_in>.size))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if bindResult == 0 {
|
|
128
|
+
return port
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Fallback to 9000 if no port found
|
|
133
|
+
return 9000
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// MARK: - Request Handlers
|
|
137
|
+
|
|
138
|
+
private func handleExecuteRequest(request: HTTPRequest) -> HTTPResponse {
|
|
139
|
+
// Authenticate
|
|
140
|
+
guard let authHeader = request.headers["Authorization"],
|
|
141
|
+
authHeader == "Bearer \(token)" else {
|
|
142
|
+
return HTTPResponse(.unauthorized)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get database name
|
|
146
|
+
guard let database = request.headers["X-Database"] else {
|
|
147
|
+
let bodyData = "Database header required".data(using: .utf8)!
|
|
148
|
+
return HTTPResponse(.badRequest, body: bodyData)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check if database exists
|
|
152
|
+
guard let db = databases[database] else {
|
|
153
|
+
let bodyData = "Database not found".data(using: .utf8)!
|
|
154
|
+
return HTTPResponse(.notFound, body: bodyData)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Parse request body
|
|
158
|
+
let bodyData = request.body
|
|
159
|
+
guard let json = try? JSONSerialization.jsonObject(with: bodyData) as? [String: Any],
|
|
160
|
+
let statement = json["statement"] as? String else {
|
|
161
|
+
let errorData = "Invalid request body".data(using: .utf8)!
|
|
162
|
+
return HTTPResponse(.badRequest, body: errorData)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let params = (json["params"] as? [Any] ?? [])
|
|
166
|
+
|
|
167
|
+
do {
|
|
168
|
+
let result = try db.execute(statement: statement, params: params)
|
|
169
|
+
let resultData = try JSONSerialization.data(withJSONObject: result)
|
|
170
|
+
return HTTPResponse(.ok, headers: ["Content-Type": "application/json"], body: resultData)
|
|
171
|
+
} catch {
|
|
172
|
+
let errorData = "Error: \(error.localizedDescription)".data(using: .utf8)!
|
|
173
|
+
return HTTPResponse(.internalServerError, body: errorData)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func handleBatchRequest(request: HTTPRequest) -> HTTPResponse {
|
|
178
|
+
// Authenticate
|
|
179
|
+
guard let authHeader = request.headers["Authorization"],
|
|
180
|
+
authHeader == "Bearer \(token)" else {
|
|
181
|
+
return HTTPResponse(.unauthorized)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Get database name
|
|
185
|
+
guard let database = request.headers["X-Database"] else {
|
|
186
|
+
let bodyData = "Database header required".data(using: .utf8)!
|
|
187
|
+
return HTTPResponse(.badRequest, body: bodyData)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if database exists
|
|
191
|
+
guard let db = databases[database] else {
|
|
192
|
+
let bodyData = "Database not found".data(using: .utf8)!
|
|
193
|
+
return HTTPResponse(.notFound, body: bodyData)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Parse request body
|
|
197
|
+
let bodyData = request.body
|
|
198
|
+
guard let json = try? JSONSerialization.jsonObject(with: bodyData) as? [String: Any],
|
|
199
|
+
let operations = json["operations"] as? [[String: Any]] else {
|
|
200
|
+
let errorData = "Invalid request body".data(using: .utf8)!
|
|
201
|
+
return HTTPResponse(.badRequest, body: errorData)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
var results: [[String: Any]] = []
|
|
205
|
+
|
|
206
|
+
do {
|
|
207
|
+
for operation in operations {
|
|
208
|
+
guard let statement = operation["statement"] as? String else {
|
|
209
|
+
let errorData = "Invalid operation: missing statement".data(using: .utf8)!
|
|
210
|
+
return HTTPResponse(.badRequest, body: errorData)
|
|
211
|
+
}
|
|
212
|
+
let params = (operation["params"] as? [Any] ?? [])
|
|
213
|
+
let result = try db.execute(statement: statement, params: params)
|
|
214
|
+
results.append(result)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let resultData = try JSONSerialization.data(withJSONObject: results)
|
|
218
|
+
return HTTPResponse(.ok, headers: ["Content-Type": "application/json"], body: resultData)
|
|
219
|
+
} catch {
|
|
220
|
+
let errorData = "Error: \(error.localizedDescription)".data(using: .utf8)!
|
|
221
|
+
return HTTPResponse(.internalServerError, body: errorData)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private func handleBeginTransactionRequest(request: HTTPRequest) -> HTTPResponse {
|
|
226
|
+
// Authenticate
|
|
227
|
+
guard let authHeader = request.headers["Authorization"],
|
|
228
|
+
authHeader == "Bearer \(token)" else {
|
|
229
|
+
return HTTPResponse(.unauthorized)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Get database name
|
|
233
|
+
guard let database = request.headers["X-Database"] else {
|
|
234
|
+
let bodyData = "Database header required".data(using: .utf8)!
|
|
235
|
+
return HTTPResponse(.badRequest, body: bodyData)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if database exists
|
|
239
|
+
guard let db = databases[database] else {
|
|
240
|
+
let bodyData = "Database not found".data(using: .utf8)!
|
|
241
|
+
return HTTPResponse(.notFound, body: bodyData)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
do {
|
|
245
|
+
try db.beginTransaction()
|
|
246
|
+
let resultData = "{}".data(using: .utf8)!
|
|
247
|
+
return HTTPResponse(.ok, headers: ["Content-Type": "application/json"], body: resultData)
|
|
248
|
+
} catch {
|
|
249
|
+
let errorData = "Error: \(error.localizedDescription)".data(using: .utf8)!
|
|
250
|
+
return HTTPResponse(.internalServerError, body: errorData)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private func handleCommitTransactionRequest(request: HTTPRequest) -> HTTPResponse {
|
|
255
|
+
// Authenticate
|
|
256
|
+
guard let authHeader = request.headers["Authorization"],
|
|
257
|
+
authHeader == "Bearer \(token)" else {
|
|
258
|
+
return HTTPResponse(.unauthorized)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get database name
|
|
262
|
+
guard let database = request.headers["X-Database"] else {
|
|
263
|
+
let bodyData = "Database header required".data(using: .utf8)!
|
|
264
|
+
return HTTPResponse(.badRequest, body: bodyData)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check if database exists
|
|
268
|
+
guard let db = databases[database] else {
|
|
269
|
+
let bodyData = "Database not found".data(using: .utf8)!
|
|
270
|
+
return HTTPResponse(.notFound, body: bodyData)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
do {
|
|
274
|
+
try db.commitTransaction()
|
|
275
|
+
let resultData = "{}".data(using: .utf8)!
|
|
276
|
+
return HTTPResponse(.ok, headers: ["Content-Type": "application/json"], body: resultData)
|
|
277
|
+
} catch {
|
|
278
|
+
let errorData = "Error: \(error.localizedDescription)".data(using: .utf8)!
|
|
279
|
+
return HTTPResponse(.internalServerError, body: errorData)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private func handleRollbackTransactionRequest(request: HTTPRequest) -> HTTPResponse {
|
|
284
|
+
// Authenticate
|
|
285
|
+
guard let authHeader = request.headers["Authorization"],
|
|
286
|
+
authHeader == "Bearer \(token)" else {
|
|
287
|
+
return HTTPResponse(.unauthorized)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Get database name
|
|
291
|
+
guard let database = request.headers["X-Database"] else {
|
|
292
|
+
let bodyData = "Database header required".data(using: .utf8)!
|
|
293
|
+
return HTTPResponse(.badRequest, body: bodyData)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check if database exists
|
|
297
|
+
guard let db = databases[database] else {
|
|
298
|
+
let bodyData = "Database not found".data(using: .utf8)!
|
|
299
|
+
return HTTPResponse(.notFound, body: bodyData)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
do {
|
|
303
|
+
try db.rollbackTransaction()
|
|
304
|
+
let resultData = "{}".data(using: .utf8)!
|
|
305
|
+
return HTTPResponse(.ok, headers: ["Content-Type": "application/json"], body: resultData)
|
|
306
|
+
} catch {
|
|
307
|
+
let errorData = "Error: \(error.localizedDescription)".data(using: .utf8)!
|
|
308
|
+
return HTTPResponse(.internalServerError, body: errorData)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-fast-sql",
|
|
3
|
+
"version": "7.0.1",
|
|
4
|
+
"description": "High-performance native SQLite plugin with custom protocol for efficient sync operations and IndexedDB replacement",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorFastSql.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-fast-sql.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-fast-sql/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native",
|
|
31
|
+
"sqlite",
|
|
32
|
+
"sql",
|
|
33
|
+
"database",
|
|
34
|
+
"sync",
|
|
35
|
+
"indexeddb",
|
|
36
|
+
"performance"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
40
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorFastSql -destination generic/platform=iOS",
|
|
41
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
42
|
+
"verify:web": "npm run build",
|
|
43
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
44
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
45
|
+
"eslint": "eslint .",
|
|
46
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
47
|
+
"swiftlint": "node-swiftlint",
|
|
48
|
+
"docgen": "docgen --api CapgoCapacitorFastSqlPlugin --output-readme README.md --output-json dist/docs.json",
|
|
49
|
+
"build": "npm run clean && tsc && rollup -c rollup.config.mjs",
|
|
50
|
+
"clean": "rimraf ./dist",
|
|
51
|
+
"watch": "tsc --watch",
|
|
52
|
+
"prepublishOnly": "npm run build"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@capacitor/android": "^7.0.0",
|
|
56
|
+
"@capacitor/cli": "^7.0.0",
|
|
57
|
+
"@capacitor/core": "^7.0.0",
|
|
58
|
+
"@capacitor/docgen": "^0.3.0",
|
|
59
|
+
"@capacitor/ios": "^7.0.0",
|
|
60
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
61
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
62
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
63
|
+
"@types/node": "^22.13.1",
|
|
64
|
+
"eslint": "^8.57.0",
|
|
65
|
+
"eslint-plugin-import": "^2.31.0",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"prettier": "^3.4.2",
|
|
68
|
+
"prettier-plugin-java": "^2.6.7",
|
|
69
|
+
"rimraf": "^6.0.1",
|
|
70
|
+
"rollup": "^4.34.6",
|
|
71
|
+
"swiftlint": "^2.0.0",
|
|
72
|
+
"typescript": "^5.7.3"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@capacitor/core": ">=7.0.0"
|
|
76
|
+
},
|
|
77
|
+
"eslintConfig": {
|
|
78
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
79
|
+
},
|
|
80
|
+
"prettier": "@ionic/prettier-config",
|
|
81
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
82
|
+
"capacitor": {
|
|
83
|
+
"ios": {
|
|
84
|
+
"src": "ios"
|
|
85
|
+
},
|
|
86
|
+
"android": {
|
|
87
|
+
"src": "android"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|