@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.
Files changed (37) hide show
  1. package/CapgoCapacitorFastSql.podspec +18 -0
  2. package/Package.swift +30 -0
  3. package/README.md +340 -0
  4. package/android/build.gradle +60 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +220 -0
  7. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +231 -0
  8. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +229 -0
  9. package/dist/esm/definitions.d.ts +225 -0
  10. package/dist/esm/definitions.d.ts.map +1 -0
  11. package/dist/esm/definitions.js +10 -0
  12. package/dist/esm/helpers.d.ts +7 -0
  13. package/dist/esm/helpers.d.ts.map +1 -0
  14. package/dist/esm/helpers.js +6 -0
  15. package/dist/esm/index.d.ts +5 -0
  16. package/dist/esm/index.d.ts.map +1 -0
  17. package/dist/esm/index.js +5 -0
  18. package/dist/esm/native-sql.d.ts +42 -0
  19. package/dist/esm/native-sql.d.ts.map +1 -0
  20. package/dist/esm/native-sql.js +69 -0
  21. package/dist/esm/plugin.d.ts +3 -0
  22. package/dist/esm/plugin.d.ts.map +1 -0
  23. package/dist/esm/plugin.js +4 -0
  24. package/dist/esm/sql-connection.d.ts +94 -0
  25. package/dist/esm/sql-connection.d.ts.map +1 -0
  26. package/dist/esm/sql-connection.js +246 -0
  27. package/dist/esm/web.d.ts +67 -0
  28. package/dist/esm/web.d.ts.map +1 -0
  29. package/dist/esm/web.js +215 -0
  30. package/dist/plugin.cjs.js +557 -0
  31. package/dist/plugin.cjs.js.map +1 -0
  32. package/dist/plugin.js +560 -0
  33. package/dist/plugin.js.map +1 -0
  34. package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +202 -0
  35. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +215 -0
  36. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +311 -0
  37. package/package.json +90 -0
@@ -0,0 +1,202 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import SQLite3
4
+
5
+ /**
6
+ * Native SQL Plugin for iOS
7
+ *
8
+ * This plugin uses a custom HTTP server for efficient data transfer,
9
+ * bypassing Capacitor's standard bridge for better performance.
10
+ */
11
+ @objc(CapgoCapacitorFastSqlPlugin)
12
+ public class CapgoCapacitorFastSqlPlugin: CAPPlugin {
13
+ private var databases: [String: SQLDatabase] = [:]
14
+ private var server: SQLHTTPServer?
15
+
16
+ @objc func connect(_ call: CAPPluginCall) {
17
+ guard let database = call.getString("database") else {
18
+ call.reject("Database name is required")
19
+ return
20
+ }
21
+
22
+ // Check if already connected
23
+ if databases[database] != nil {
24
+ if let server = server {
25
+ call.resolve([
26
+ "port": server.port,
27
+ "token": server.token,
28
+ "database": database
29
+ ])
30
+ return
31
+ }
32
+ }
33
+
34
+ do {
35
+ // Get database path
36
+ let dbPath = try getDatabasePath(database)
37
+
38
+ // Open database
39
+ let db = try SQLDatabase(path: dbPath)
40
+ databases[database] = db
41
+
42
+ // Start HTTP server if not already running
43
+ if server == nil {
44
+ server = try SQLHTTPServer(databases: databases)
45
+ try server?.start()
46
+ }
47
+
48
+ guard let server = server else {
49
+ call.reject("Failed to start HTTP server")
50
+ return
51
+ }
52
+
53
+ call.resolve([
54
+ "port": server.port,
55
+ "token": server.token,
56
+ "database": database
57
+ ])
58
+ } catch {
59
+ call.reject("Failed to connect to database: \(error.localizedDescription)")
60
+ }
61
+ }
62
+
63
+ @objc func disconnect(_ call: CAPPluginCall) {
64
+ guard let database = call.getString("database") else {
65
+ call.reject("Database name is required")
66
+ return
67
+ }
68
+
69
+ guard let db = databases[database] else {
70
+ call.reject("Database '\(database)' is not connected")
71
+ return
72
+ }
73
+
74
+ db.close()
75
+ databases.removeValue(forKey: database)
76
+
77
+ // Stop server if no more databases
78
+ if databases.isEmpty {
79
+ server?.stop()
80
+ server = nil
81
+ }
82
+
83
+ call.resolve()
84
+ }
85
+
86
+ @objc func getServerInfo(_ call: CAPPluginCall) {
87
+ guard let database = call.getString("database") else {
88
+ call.reject("Database name is required")
89
+ return
90
+ }
91
+
92
+ guard databases[database] != nil else {
93
+ call.reject("Database '\(database)' is not connected")
94
+ return
95
+ }
96
+
97
+ guard let server = server else {
98
+ call.reject("Server is not running")
99
+ return
100
+ }
101
+
102
+ call.resolve([
103
+ "port": server.port,
104
+ "token": server.token
105
+ ])
106
+ }
107
+
108
+ @objc func execute(_ call: CAPPluginCall) {
109
+ guard let database = call.getString("database") else {
110
+ call.reject("Database name is required")
111
+ return
112
+ }
113
+
114
+ guard let statement = call.getString("statement") else {
115
+ call.reject("Statement is required")
116
+ return
117
+ }
118
+
119
+ guard let db = databases[database] else {
120
+ call.reject("Database '\(database)' is not connected")
121
+ return
122
+ }
123
+
124
+ let params = call.getArray("params", JSValue.self) ?? []
125
+
126
+ do {
127
+ let result = try db.execute(statement: statement, params: params)
128
+ call.resolve(result)
129
+ } catch {
130
+ call.reject("Failed to execute statement: \(error.localizedDescription)")
131
+ }
132
+ }
133
+
134
+ @objc func beginTransaction(_ call: CAPPluginCall) {
135
+ guard let database = call.getString("database") else {
136
+ call.reject("Database name is required")
137
+ return
138
+ }
139
+
140
+ guard let db = databases[database] else {
141
+ call.reject("Database '\(database)' is not connected")
142
+ return
143
+ }
144
+
145
+ do {
146
+ try db.beginTransaction()
147
+ call.resolve()
148
+ } catch {
149
+ call.reject("Failed to begin transaction: \(error.localizedDescription)")
150
+ }
151
+ }
152
+
153
+ @objc func commitTransaction(_ call: CAPPluginCall) {
154
+ guard let database = call.getString("database") else {
155
+ call.reject("Database name is required")
156
+ return
157
+ }
158
+
159
+ guard let db = databases[database] else {
160
+ call.reject("Database '\(database)' is not connected")
161
+ return
162
+ }
163
+
164
+ do {
165
+ try db.commitTransaction()
166
+ call.resolve()
167
+ } catch {
168
+ call.reject("Failed to commit transaction: \(error.localizedDescription)")
169
+ }
170
+ }
171
+
172
+ @objc func rollbackTransaction(_ call: CAPPluginCall) {
173
+ guard let database = call.getString("database") else {
174
+ call.reject("Database name is required")
175
+ return
176
+ }
177
+
178
+ guard let db = databases[database] else {
179
+ call.reject("Database '\(database)' is not connected")
180
+ return
181
+ }
182
+
183
+ do {
184
+ try db.rollbackTransaction()
185
+ call.resolve()
186
+ } catch {
187
+ call.reject("Failed to rollback transaction: \(error.localizedDescription)")
188
+ }
189
+ }
190
+
191
+ private func getDatabasePath(_ database: String) throws -> String {
192
+ let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
193
+ guard let documentsDirectory = paths.first else {
194
+ throw NSError(domain: "CapgoCapacitorNativeSQL", code: 1, userInfo: [
195
+ NSLocalizedDescriptionKey: "Could not find documents directory"
196
+ ])
197
+ }
198
+
199
+ let dbPath = (documentsDirectory as NSString).appendingPathComponent("\(database).db")
200
+ return dbPath
201
+ }
202
+ }
@@ -0,0 +1,215 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import SQLite3
4
+
5
+ // SQLite constants that aren't imported to Swift
6
+ private let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
7
+ private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
8
+
9
+ /**
10
+ * SQLite database wrapper for iOS
11
+ */
12
+ class SQLDatabase {
13
+ private var db: OpaquePointer?
14
+ private let path: String
15
+ private var inTransaction = false
16
+
17
+ init(path: String) throws {
18
+ self.path = path
19
+
20
+ let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
21
+ let result = sqlite3_open_v2(path, &db, flags, nil)
22
+
23
+ guard result == SQLITE_OK else {
24
+ throw SQLError.openFailed(message: String(cString: sqlite3_errmsg(db)))
25
+ }
26
+
27
+ // Enable foreign keys
28
+ try execute(statement: "PRAGMA foreign_keys = ON", params: [])
29
+ }
30
+
31
+ func close() {
32
+ if let db = db {
33
+ sqlite3_close(db)
34
+ self.db = nil
35
+ }
36
+ }
37
+
38
+ func execute(statement: String, params: [Any]) throws -> [String: Any] {
39
+ guard let db = db else {
40
+ throw SQLError.notOpen
41
+ }
42
+
43
+ var stmt: OpaquePointer?
44
+ let result = sqlite3_prepare_v2(db, statement, -1, &stmt, nil)
45
+
46
+ guard result == SQLITE_OK else {
47
+ let error = String(cString: sqlite3_errmsg(db))
48
+ throw SQLError.prepareFailed(message: error)
49
+ }
50
+
51
+ defer {
52
+ sqlite3_finalize(stmt)
53
+ }
54
+
55
+ // Bind parameters
56
+ for (index, param) in params.enumerated() {
57
+ let bindResult = try bindParameter(stmt: stmt, index: Int32(index + 1), value: param)
58
+ if bindResult != SQLITE_OK {
59
+ let error = String(cString: sqlite3_errmsg(db))
60
+ throw SQLError.bindFailed(message: error)
61
+ }
62
+ }
63
+
64
+ // Execute and collect rows
65
+ var rows: [[String: Any]] = []
66
+ var columnNames: [String] = []
67
+ var columnCount: Int32 = 0
68
+
69
+ while true {
70
+ let stepResult = sqlite3_step(stmt)
71
+
72
+ if stepResult == SQLITE_ROW {
73
+ if columnNames.isEmpty {
74
+ columnCount = sqlite3_column_count(stmt)
75
+ for i in 0..<columnCount {
76
+ if let name = sqlite3_column_name(stmt, i) {
77
+ columnNames.append(String(cString: name))
78
+ }
79
+ }
80
+ }
81
+
82
+ var row: [String: Any] = [:]
83
+ for i in 0..<columnCount {
84
+ let columnName = columnNames[Int(i)]
85
+ let value = getColumnValue(stmt: stmt, index: i)
86
+ row[columnName] = value
87
+ }
88
+ rows.append(row)
89
+ } else if stepResult == SQLITE_DONE {
90
+ break
91
+ } else {
92
+ let error = String(cString: sqlite3_errmsg(db))
93
+ throw SQLError.executeFailed(message: error)
94
+ }
95
+ }
96
+
97
+ let changes = sqlite3_changes(db)
98
+ let lastInsertId = sqlite3_last_insert_rowid(db)
99
+
100
+ return [
101
+ "rows": rows,
102
+ "rowsAffected": changes,
103
+ "insertId": lastInsertId > 0 ? lastInsertId : NSNull()
104
+ ]
105
+ }
106
+
107
+ func beginTransaction() throws {
108
+ guard !inTransaction else {
109
+ throw SQLError.transactionAlreadyActive
110
+ }
111
+ try execute(statement: "BEGIN TRANSACTION", params: [])
112
+ inTransaction = true
113
+ }
114
+
115
+ func commitTransaction() throws {
116
+ guard inTransaction else {
117
+ throw SQLError.noTransactionActive
118
+ }
119
+ try execute(statement: "COMMIT", params: [])
120
+ inTransaction = false
121
+ }
122
+
123
+ func rollbackTransaction() throws {
124
+ guard inTransaction else {
125
+ throw SQLError.noTransactionActive
126
+ }
127
+ try execute(statement: "ROLLBACK", params: [])
128
+ inTransaction = false
129
+ }
130
+
131
+ private func bindParameter(stmt: OpaquePointer?, index: Int32, value: Any) throws -> Int32 {
132
+ guard let stmt = stmt else {
133
+ return SQLITE_ERROR
134
+ }
135
+
136
+ // Handle null
137
+ if value is NSNull {
138
+ return sqlite3_bind_null(stmt, index)
139
+ }
140
+
141
+ // Handle different types
142
+ if let str = value as? String {
143
+ return sqlite3_bind_text(stmt, index, str, -1, SQLITE_TRANSIENT)
144
+ } else if let num = value as? NSNumber {
145
+ // Check if it's a boolean
146
+ if CFGetTypeID(num as CFTypeRef) == CFBooleanGetTypeID() {
147
+ return sqlite3_bind_int(stmt, index, num.boolValue ? 1 : 0)
148
+ }
149
+ // Check if it's a double/float
150
+ if num.doubleValue != Double(num.intValue) {
151
+ return sqlite3_bind_double(stmt, index, num.doubleValue)
152
+ }
153
+ // It's an integer
154
+ return sqlite3_bind_int64(stmt, index, num.int64Value)
155
+ } else if let dict = value as? [String: Any],
156
+ let type = dict["_type"] as? String,
157
+ type == "binary",
158
+ let base64 = dict["_data"] as? String,
159
+ let data = Data(base64Encoded: base64) {
160
+ // Handle binary data
161
+ return data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
162
+ sqlite3_bind_blob(stmt, index, bytes.baseAddress, Int32(data.count), SQLITE_TRANSIENT)
163
+ }
164
+ }
165
+
166
+ return SQLITE_ERROR
167
+ }
168
+
169
+ private func getColumnValue(stmt: OpaquePointer?, index: Int32) -> Any {
170
+ guard let stmt = stmt else {
171
+ return NSNull()
172
+ }
173
+
174
+ let type = sqlite3_column_type(stmt, index)
175
+
176
+ switch type {
177
+ case SQLITE_INTEGER:
178
+ return sqlite3_column_int64(stmt, index)
179
+ case SQLITE_FLOAT:
180
+ return sqlite3_column_double(stmt, index)
181
+ case SQLITE_TEXT:
182
+ if let text = sqlite3_column_text(stmt, index) {
183
+ return String(cString: text)
184
+ }
185
+ return NSNull()
186
+ case SQLITE_BLOB:
187
+ if let blob = sqlite3_column_blob(stmt, index) {
188
+ let size = sqlite3_column_bytes(stmt, index)
189
+ let data = Data(bytes: blob, count: Int(size))
190
+ return [
191
+ "_type": "binary",
192
+ "_data": data.base64EncodedString()
193
+ ]
194
+ }
195
+ return NSNull()
196
+ case SQLITE_NULL:
197
+ return NSNull()
198
+ default:
199
+ return NSNull()
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * SQL error types
206
+ */
207
+ enum SQLError: Error {
208
+ case notOpen
209
+ case openFailed(message: String)
210
+ case prepareFailed(message: String)
211
+ case bindFailed(message: String)
212
+ case executeFailed(message: String)
213
+ case transactionAlreadyActive
214
+ case noTransactionActive
215
+ }