mcard-js 1.0.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.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/__mocks__/better-sqlite3.js +20 -0
- package/dist/__mocks__/better-sqlite3.js.map +1 -0
- package/dist/config/config_constants.js +188 -0
- package/dist/config/config_constants.js.map +1 -0
- package/dist/config/env_parameters.js +62 -0
- package/dist/config/env_parameters.js.map +1 -0
- package/dist/content/model/content_type_detector.js +89 -0
- package/dist/content/model/content_type_detector.js.map +1 -0
- package/dist/core/card-collection.js +279 -0
- package/dist/core/card-collection.js.map +1 -0
- package/dist/core/event-producer.js +132 -0
- package/dist/core/event-producer.js.map +1 -0
- package/dist/core/g_time.js +201 -0
- package/dist/core/g_time.js.map +1 -0
- package/dist/core/hash/enums.js +19 -0
- package/dist/core/hash/enums.js.map +1 -0
- package/dist/core/hash/validator.js +260 -0
- package/dist/core/hash/validator.js.map +1 -0
- package/dist/core/mcard.js +205 -0
- package/dist/core/mcard.js.map +1 -0
- package/dist/engine/sqlite_engine.js +723 -0
- package/dist/engine/sqlite_engine.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/mcardPersistenceMiddleware.js +45 -0
- package/dist/middleware/mcardPersistenceMiddleware.js.map +1 -0
- package/dist/models/database_schemas.js +31 -0
- package/dist/models/database_schemas.js.map +1 -0
- package/dist/services/logger.js +80 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/services/mcardStorageService.js +36 -0
- package/dist/services/mcardStorageService.js.map +1 -0
- package/dist/utils/actionHelpers.js +25 -0
- package/dist/utils/actionHelpers.js.map +1 -0
- package/dist/utils/bufferContentHelper.js +393 -0
- package/dist/utils/bufferContentHelper.js.map +1 -0
- package/dist/utils/bufferPolyfill.js +198 -0
- package/dist/utils/bufferPolyfill.js.map +1 -0
- package/dist/utils/content-detection.js +74 -0
- package/dist/utils/content-detection.js.map +1 -0
- package/dist/utils/content-utils.js +269 -0
- package/dist/utils/content-utils.js.map +1 -0
- package/dist/utils/content_type_detector copy.js +480 -0
- package/dist/utils/content_type_detector copy.js.map +1 -0
- package/dist/utils/content_type_detector.js +480 -0
- package/dist/utils/content_type_detector.js.map +1 -0
- package/dist/utils/cryptoPolyfill.js +166 -0
- package/dist/utils/cryptoPolyfill.js.map +1 -0
- package/dist/utils/dotenv-browser.js +35 -0
- package/dist/utils/dotenv-browser.js.map +1 -0
- package/dist/utils/environmentDetector.js +93 -0
- package/dist/utils/environmentDetector.js.map +1 -0
- package/dist/utils/logWriter.js +27 -0
- package/dist/utils/logWriter.js.map +1 -0
- package/dist/utils/serviceWorkerManager.js +118 -0
- package/dist/utils/serviceWorkerManager.js.map +1 -0
- package/dist/utils/test-content-detection.js +79 -0
- package/dist/utils/test-content-detection.js.map +1 -0
- package/dist/utils/test-detection-fix.js +121 -0
- package/dist/utils/test-detection-fix.js.map +1 -0
- package/dist/utils/test-format-conversion.js +170 -0
- package/dist/utils/test-format-conversion.js.map +1 -0
- package/dist/utils/test-mov-viewer.js +57 -0
- package/dist/utils/test-mov-viewer.js.map +1 -0
- package/dist/utils/testDetection.js +21 -0
- package/dist/utils/testDetection.js.map +1 -0
- package/dist/utils/textEncoderPolyfill.js +87 -0
- package/dist/utils/textEncoderPolyfill.js.map +1 -0
- package/package.json +74 -0
- package/src/__mocks__/better-sqlite3.js +14 -0
- package/src/config/config_constants.js +227 -0
- package/src/config/env_parameters.js +69 -0
- package/src/content/model/content_type_detector.js +87 -0
- package/src/core/card-collection.js +300 -0
- package/src/core/event-producer.js +160 -0
- package/src/core/g_time.js +215 -0
- package/src/core/hash/enums.js +13 -0
- package/src/core/hash/validator.js +271 -0
- package/src/core/mcard.js +203 -0
- package/src/engine/sqlite_engine.js +755 -0
- package/src/index.js +10 -0
- package/src/middleware/mcardPersistenceMiddleware.js +45 -0
- package/src/models/database_schemas.js +26 -0
- package/src/services/logger.js +74 -0
- package/src/services/mcardStorageService.js +34 -0
- package/src/utils/actionHelpers.js +13 -0
- package/src/utils/bufferContentHelper.js +436 -0
- package/src/utils/bufferPolyfill.js +202 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/content-detection.js +66 -0
- package/src/utils/content-utils.js +250 -0
- package/src/utils/content_type_detector copy.js +501 -0
- package/src/utils/content_type_detector.js +501 -0
- package/src/utils/cryptoPolyfill.js +180 -0
- package/src/utils/dateUtils.ts +18 -0
- package/src/utils/dbInitializer.ts +27 -0
- package/src/utils/dotenv-browser.js +29 -0
- package/src/utils/environmentDetector.js +92 -0
- package/src/utils/logWriter.js +20 -0
- package/src/utils/serviceWorkerManager.js +122 -0
- package/src/utils/stateWatcher.ts +78 -0
- package/src/utils/storeAdapter copy.ts +157 -0
- package/src/utils/storeAdapter.ts +157 -0
- package/src/utils/test-content-detection.js +71 -0
- package/src/utils/test-detection-fix.js +136 -0
- package/src/utils/test-format-conversion.js +165 -0
- package/src/utils/test-mov-viewer.js +59 -0
- package/src/utils/testDetection.js +16 -0
- package/src/utils/textEncoderPolyfill.js +88 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.SQLiteEngine = exports.SQLiteConnection = void 0;
|
|
8
|
+
var _mcard = require("../core/mcard.js");
|
|
9
|
+
var _cardCollection = require("../core/card-collection.js");
|
|
10
|
+
var _config_constants = require("../config/config_constants.js");
|
|
11
|
+
var _database_schemas = require("../models/database_schemas.js");
|
|
12
|
+
var _content_type_detector = _interopRequireDefault(require("../content/model/content_type_detector.js"));
|
|
13
|
+
var _bufferPolyfill = require("../utils/bufferPolyfill.js");
|
|
14
|
+
var _path = _interopRequireDefault(require("path"));
|
|
15
|
+
var _betterSqlite = _interopRequireDefault(require("better-sqlite3"));
|
|
16
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
17
|
+
class SQLiteConnection {
|
|
18
|
+
/**
|
|
19
|
+
* Singleton instance management
|
|
20
|
+
*/
|
|
21
|
+
static _instance = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get singleton instance of SQLiteConnection
|
|
25
|
+
* @param {string} [dbPath] - Optional path to the SQLite database file
|
|
26
|
+
* @returns {SQLiteConnection} Singleton instance
|
|
27
|
+
*/
|
|
28
|
+
static getInstance(dbPath = null) {
|
|
29
|
+
if (!this._instance) {
|
|
30
|
+
this._instance = new SQLiteConnection(dbPath);
|
|
31
|
+
}
|
|
32
|
+
return this._instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a new SQLite database connection
|
|
37
|
+
* @param {string} [dbPath] - Optional path to the SQLite database file
|
|
38
|
+
* Prioritizes the provided path, then environment variable, then default config
|
|
39
|
+
*/
|
|
40
|
+
constructor(dbPath = null) {
|
|
41
|
+
// Determine the database path in order of priority:
|
|
42
|
+
// 1. Explicitly provided path
|
|
43
|
+
// 2. Environment variable
|
|
44
|
+
// 3. Default configuration path
|
|
45
|
+
this.dbPath = dbPath || process.env.MCARD_DB_PATH || _config_constants.CARDS_DB_PATH;
|
|
46
|
+
|
|
47
|
+
// Ensure the path is an absolute path
|
|
48
|
+
this.dbPath = _path.default.resolve(this.dbPath);
|
|
49
|
+
this.conn = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Establish a database connection
|
|
54
|
+
*/
|
|
55
|
+
connect() {
|
|
56
|
+
try {
|
|
57
|
+
// Ensure the directory exists
|
|
58
|
+
const dir = _path.default.dirname(this.dbPath);
|
|
59
|
+
if (!_fs.default.existsSync(dir)) {
|
|
60
|
+
_fs.default.mkdirSync(dir, {
|
|
61
|
+
recursive: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Open the database connection with appropriate flags
|
|
66
|
+
// Don't remove the existing database file to prevent data loss
|
|
67
|
+
this.conn = new _betterSqlite.default(this.dbPath, {
|
|
68
|
+
// Open in read-write mode, create if not exists
|
|
69
|
+
mode: _betterSqlite.default.OPEN_READWRITE | _betterSqlite.default.OPEN_CREATE,
|
|
70
|
+
// Disable verbose mode to reduce unnecessary logging
|
|
71
|
+
verbose: null
|
|
72
|
+
});
|
|
73
|
+
return this;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Database connection error: ${error.message}`);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set up the database, creating the file and table if they don't exist
|
|
82
|
+
*/
|
|
83
|
+
setup_database() {
|
|
84
|
+
try {
|
|
85
|
+
// Ensure the connection is open
|
|
86
|
+
if (!this.conn) {
|
|
87
|
+
this.connect();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if the tables already exist
|
|
91
|
+
const tableExists = this.conn.prepare(`
|
|
92
|
+
SELECT name FROM sqlite_master
|
|
93
|
+
WHERE type='table' AND name='card'
|
|
94
|
+
`).get();
|
|
95
|
+
|
|
96
|
+
// Only create tables if they don't exist
|
|
97
|
+
if (!tableExists) {
|
|
98
|
+
console.log('Creating new database tables...');
|
|
99
|
+
|
|
100
|
+
// Create the table using the schema
|
|
101
|
+
this.conn.exec(_database_schemas.MCARD_TABLE_SCHEMA);
|
|
102
|
+
|
|
103
|
+
// Add triggers
|
|
104
|
+
_database_schemas.TRIGGERS.forEach(trigger => {
|
|
105
|
+
try {
|
|
106
|
+
this.conn.exec(trigger);
|
|
107
|
+
} catch (triggerError) {
|
|
108
|
+
console.warn(`Warning during trigger creation: ${triggerError.message}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
console.log('Database tables created successfully');
|
|
112
|
+
} else {
|
|
113
|
+
console.log('Database tables already exist, skipping creation');
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Database setup failed:', error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Close the database connection
|
|
123
|
+
*/
|
|
124
|
+
disconnect() {
|
|
125
|
+
try {
|
|
126
|
+
if (this.conn) {
|
|
127
|
+
this.conn.close();
|
|
128
|
+
this.conn = null;
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.warn(`Error during database disconnect: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Commit the current transaction
|
|
137
|
+
*/
|
|
138
|
+
commit() {
|
|
139
|
+
if (this.conn) {
|
|
140
|
+
this.conn.prepare('COMMIT').run();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Rollback the current transaction
|
|
146
|
+
*/
|
|
147
|
+
rollback() {
|
|
148
|
+
if (this.conn) {
|
|
149
|
+
this.conn.prepare('ROLLBACK').run();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Add a method to execute raw queries
|
|
155
|
+
* @param {string} query - Raw query to execute
|
|
156
|
+
* @param {array} params - Parameters for the query
|
|
157
|
+
* @returns {array} Results of the query
|
|
158
|
+
*/
|
|
159
|
+
executeQuery(query, params = []) {
|
|
160
|
+
if (!this.conn) {
|
|
161
|
+
this.connect();
|
|
162
|
+
}
|
|
163
|
+
const stmt = this.conn.prepare(query);
|
|
164
|
+
return stmt.all(...params);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.SQLiteConnection = SQLiteConnection;
|
|
168
|
+
class SQLiteEngine {
|
|
169
|
+
/**
|
|
170
|
+
* Create a new SQLite storage engine
|
|
171
|
+
* @param {SQLiteConnection} connection - Database connection
|
|
172
|
+
*/
|
|
173
|
+
constructor(connection = null) {
|
|
174
|
+
this.connection = connection || SQLiteConnection.getInstance();
|
|
175
|
+
this.connection.connect();
|
|
176
|
+
this.connection.setup_database();
|
|
177
|
+
this.clearStmt = this.connection.conn.prepare('DELETE FROM card');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Destructor to ensure database connection is closed
|
|
182
|
+
*/
|
|
183
|
+
destructor() {
|
|
184
|
+
this.connection.disconnect();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Symbol.dispose method for resource cleanup
|
|
189
|
+
*/
|
|
190
|
+
[Symbol.dispose]() {
|
|
191
|
+
this.destructor();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Add a card to the database
|
|
196
|
+
* @param {MCard} card - Card to add
|
|
197
|
+
* @returns {string} Hash of the added card
|
|
198
|
+
*/
|
|
199
|
+
add(card) {
|
|
200
|
+
try {
|
|
201
|
+
console.log('SQLiteEngine.add called with card hash:', card.hash);
|
|
202
|
+
|
|
203
|
+
// Check if the card already exists
|
|
204
|
+
const existingCard = this.get(card.hash);
|
|
205
|
+
if (existingCard) {
|
|
206
|
+
console.log('Card already exists with hash:', card.hash);
|
|
207
|
+
return card.hash;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Ensure content is properly serialized for SQLite storage
|
|
211
|
+
let finalContent;
|
|
212
|
+
// Always store as string (JSON) or Buffer
|
|
213
|
+
if (_bufferPolyfill.SafeBuffer.isBuffer(card.content)) {
|
|
214
|
+
// Buffers can be stored directly
|
|
215
|
+
finalContent = card.content;
|
|
216
|
+
console.log('Using Buffer content directly');
|
|
217
|
+
} else if (typeof card.content === 'object' && card.content !== null) {
|
|
218
|
+
// Explicitly serialize objects
|
|
219
|
+
finalContent = JSON.stringify(card.content);
|
|
220
|
+
console.log('Serialized object content to JSON string');
|
|
221
|
+
} else if (typeof card.content === 'string') {
|
|
222
|
+
// Strings can be stored directly
|
|
223
|
+
finalContent = card.content;
|
|
224
|
+
console.log('Using string content directly');
|
|
225
|
+
} else {
|
|
226
|
+
// Convert other types to string
|
|
227
|
+
finalContent = String(card.content);
|
|
228
|
+
console.log('Converted content to string');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Insert the card into the database
|
|
232
|
+
try {
|
|
233
|
+
const stmt = this.connection.conn.prepare('INSERT INTO card (hash, content, g_time) VALUES (?, ?, ?)');
|
|
234
|
+
stmt.run(card.hash, finalContent, card.g_time);
|
|
235
|
+
console.log('Card inserted successfully with hash:', card.hash);
|
|
236
|
+
return card.hash;
|
|
237
|
+
} catch (sqlError) {
|
|
238
|
+
console.error('SQL error inserting card:', sqlError);
|
|
239
|
+
throw sqlError;
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('Error in SQLiteEngine.add:', error);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Retrieve a card by its hash
|
|
249
|
+
* @param {string} hashValue - Hash of the card to retrieve
|
|
250
|
+
* @returns {MCard|null} Retrieved card or null
|
|
251
|
+
*/
|
|
252
|
+
get(hash) {
|
|
253
|
+
try {
|
|
254
|
+
console.log('SQLiteEngine.get called with hash:', hash);
|
|
255
|
+
|
|
256
|
+
// Query the database for the card
|
|
257
|
+
const stmt = this.connection.conn.prepare('SELECT hash, content, g_time, typeof(content) as content_type FROM card WHERE hash = ?');
|
|
258
|
+
const row = stmt.get(String(hash));
|
|
259
|
+
if (!row) {
|
|
260
|
+
console.log('No card found with hash:', hash);
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
console.log(`SQLiteEngine.get - Raw content typeof:`, row.content_type);
|
|
264
|
+
console.log(`SQLiteEngine.get - JS typeof:`, typeof row.content);
|
|
265
|
+
console.log(`SQLiteEngine.get - Is Buffer:`, _bufferPolyfill.SafeBuffer.isBuffer(row.content));
|
|
266
|
+
console.log(`SQLiteEngine.get - Content length:`, row.content ? row.content.length : 0);
|
|
267
|
+
if (_bufferPolyfill.SafeBuffer.isBuffer(row.content)) {
|
|
268
|
+
console.log(`SQLiteEngine.get - Buffer content first 20 bytes:`, row.content.slice(0, 20).toString('hex'));
|
|
269
|
+
} else if (typeof row.content === 'string') {
|
|
270
|
+
console.log(`SQLiteEngine.get - String content first 50 chars:`, row.content.substring(0, 50));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Detect content type BEFORE any transformations
|
|
274
|
+
const isBlob = _bufferPolyfill.SafeBuffer.isBuffer(row.content);
|
|
275
|
+
const contentForDetection = row.content;
|
|
276
|
+
|
|
277
|
+
// Detect content type from the raw content
|
|
278
|
+
const contentType = _content_type_detector.default.detectContentType(contentForDetection);
|
|
279
|
+
console.log(`SQLiteEngine.get - Detected contentType:`, contentType);
|
|
280
|
+
|
|
281
|
+
// Add isBlob flag to contentType object
|
|
282
|
+
contentType.isBlob = isBlob;
|
|
283
|
+
|
|
284
|
+
// Parse content if it's a JSON string - but only if not detected as another type
|
|
285
|
+
let content = row.content;
|
|
286
|
+
// If stored as Buffer (Node.js), convert to Uint8Array for cross-env compatibility
|
|
287
|
+
if (_bufferPolyfill.SafeBuffer.isBuffer(content)) {
|
|
288
|
+
// Optionally decode to string if contentType is text or JSON
|
|
289
|
+
if (contentType.mimeType === 'application/json' || contentType.mimeType.startsWith('text/')) {
|
|
290
|
+
try {
|
|
291
|
+
const str = _bufferPolyfill.SafeBuffer.toString(content);
|
|
292
|
+
if (contentType.mimeType === 'application/json' && (str.startsWith('{') || str.startsWith('['))) {
|
|
293
|
+
content = JSON.parse(str);
|
|
294
|
+
console.log('Parsed JSON content successfully from Buffer');
|
|
295
|
+
} else {
|
|
296
|
+
content = str;
|
|
297
|
+
console.log('Converted Buffer to string');
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
console.warn('Failed to decode Buffer content:', e);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// else: leave as Uint8Array/Buffer for binary data
|
|
304
|
+
} else if (typeof content === 'string' && contentType.mimeType === 'application/json') {
|
|
305
|
+
try {
|
|
306
|
+
// Check if the string is a JSON object
|
|
307
|
+
if (content.startsWith('{') || content.startsWith('[')) {
|
|
308
|
+
const parsed = JSON.parse(content);
|
|
309
|
+
content = parsed;
|
|
310
|
+
console.log('Parsed JSON content successfully');
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
// If parsing fails, keep the original string
|
|
314
|
+
console.log('Content is not a valid JSON string, keeping as-is');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
console.log('Card retrieved successfully with hash:', hash);
|
|
318
|
+
return {
|
|
319
|
+
hash: row.hash,
|
|
320
|
+
content: content,
|
|
321
|
+
g_time: row.g_time,
|
|
322
|
+
contentType: contentType // Add content type information to the response
|
|
323
|
+
};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('Error retrieving card:', error);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Delete a card by its hash
|
|
332
|
+
* @param {string} hashValue - Hash of the card to delete
|
|
333
|
+
* @returns {boolean} Whether deletion was successful
|
|
334
|
+
*/
|
|
335
|
+
delete(hashValue) {
|
|
336
|
+
try {
|
|
337
|
+
const stmt = this.connection.conn.prepare('DELETE FROM card WHERE hash = ?');
|
|
338
|
+
const result = stmt.run(String(hashValue));
|
|
339
|
+
return result.changes > 0;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error(`Error deleting card: ${error.message}`);
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get a page of cards
|
|
348
|
+
* @param {number} page_number - Page number to retrieve
|
|
349
|
+
* @param {number} page_size - Number of items per page
|
|
350
|
+
* @returns {Page} Page of cards
|
|
351
|
+
*/
|
|
352
|
+
get_page(page_number = 1, page_size = _config_constants.DEFAULT_PAGE_SIZE) {
|
|
353
|
+
if (page_number < 1 || page_size < 1) {
|
|
354
|
+
throw new Error('Page number and size must be >= 1');
|
|
355
|
+
}
|
|
356
|
+
const offset = (page_number - 1) * page_size;
|
|
357
|
+
|
|
358
|
+
// Get total count of items
|
|
359
|
+
const countStmt = this.connection.conn.prepare('SELECT COUNT(*) as total FROM card');
|
|
360
|
+
const {
|
|
361
|
+
total
|
|
362
|
+
} = countStmt.get();
|
|
363
|
+
|
|
364
|
+
// Get page of items
|
|
365
|
+
const stmt = this.connection.conn.prepare(`
|
|
366
|
+
SELECT content, g_time, hash
|
|
367
|
+
FROM card
|
|
368
|
+
ORDER BY g_time DESC
|
|
369
|
+
LIMIT ? OFFSET ?
|
|
370
|
+
`);
|
|
371
|
+
const rows = stmt.all(page_size, offset);
|
|
372
|
+
|
|
373
|
+
// Convert rows to cards
|
|
374
|
+
const items = [];
|
|
375
|
+
for (const row of rows) {
|
|
376
|
+
const [content, g_time, hash] = [row.content, row.g_time, row.hash];
|
|
377
|
+
|
|
378
|
+
// Parse content if it's a JSON string
|
|
379
|
+
let parsedContent = content;
|
|
380
|
+
if (typeof content === 'string' && (content.startsWith('{') || content.startsWith('['))) {
|
|
381
|
+
try {
|
|
382
|
+
parsedContent = JSON.parse(content);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.warn('Failed to parse JSON content for hash:', hash);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Create card with properly parsed content
|
|
389
|
+
const card = new _mcard.MCardFromData(parsedContent, hash, g_time);
|
|
390
|
+
items.push(card);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Calculate total pages
|
|
394
|
+
const total_pages = Math.ceil(total / page_size);
|
|
395
|
+
return new _cardCollection.Page({
|
|
396
|
+
items,
|
|
397
|
+
total_items: total,
|
|
398
|
+
page_number,
|
|
399
|
+
page_size,
|
|
400
|
+
has_next: offset + page_size < total,
|
|
401
|
+
has_previous: page_number > 1,
|
|
402
|
+
total_pages
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Search cards by string
|
|
408
|
+
* @param {string} search_string - String to search for
|
|
409
|
+
* @param {number} page_number - Page number to retrieve
|
|
410
|
+
* @param {number} page_size - Number of items per page
|
|
411
|
+
* @returns {Page} Page of matching cards
|
|
412
|
+
*/
|
|
413
|
+
search_by_string(searchString, pageNumber = 1, pageSize = _config_constants.DEFAULT_PAGE_SIZE) {
|
|
414
|
+
try {
|
|
415
|
+
if (pageNumber < 1) {
|
|
416
|
+
throw new Error('Page number must be >= 1');
|
|
417
|
+
}
|
|
418
|
+
if (pageSize < 1) {
|
|
419
|
+
throw new Error('Page size must be >= 1');
|
|
420
|
+
}
|
|
421
|
+
const offset = (pageNumber - 1) * pageSize;
|
|
422
|
+
const cursor = this.connection.conn;
|
|
423
|
+
|
|
424
|
+
// First, get total count of matching items
|
|
425
|
+
const countStmt = cursor.prepare(`
|
|
426
|
+
SELECT COUNT(*) as total FROM card
|
|
427
|
+
WHERE
|
|
428
|
+
CAST(content AS TEXT) LIKE ? OR
|
|
429
|
+
hash LIKE ? OR
|
|
430
|
+
g_time LIKE ?
|
|
431
|
+
`);
|
|
432
|
+
const {
|
|
433
|
+
total
|
|
434
|
+
} = countStmt.get(`%${searchString}%`, `%${searchString}%`, `%${searchString}%`);
|
|
435
|
+
|
|
436
|
+
// Then, get the actual items for the current page
|
|
437
|
+
const stmt = cursor.prepare(`
|
|
438
|
+
SELECT content, g_time, hash FROM card
|
|
439
|
+
WHERE
|
|
440
|
+
CAST(content AS TEXT) LIKE ? OR
|
|
441
|
+
hash LIKE ? OR
|
|
442
|
+
g_time LIKE ?
|
|
443
|
+
ORDER BY g_time DESC LIMIT ? OFFSET ?
|
|
444
|
+
`);
|
|
445
|
+
const rows = stmt.all(`%${searchString}%`, `%${searchString}%`, `%${searchString}%`, pageSize, offset);
|
|
446
|
+
|
|
447
|
+
// Convert rows to cards
|
|
448
|
+
const items = [];
|
|
449
|
+
for (const row of rows) {
|
|
450
|
+
const {
|
|
451
|
+
content,
|
|
452
|
+
g_time,
|
|
453
|
+
hash
|
|
454
|
+
} = row;
|
|
455
|
+
|
|
456
|
+
// Parse content if it's a JSON string
|
|
457
|
+
let parsedContent = content;
|
|
458
|
+
if (typeof content === 'string' && (content.startsWith('{') || content.startsWith('['))) {
|
|
459
|
+
try {
|
|
460
|
+
parsedContent = JSON.parse(content);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
console.warn('Failed to parse JSON content for hash:', hash);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Create card with properly parsed content
|
|
467
|
+
const card = new _mcard.MCardFromData(parsedContent, hash, g_time);
|
|
468
|
+
items.push(card);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Calculate pagination flags
|
|
472
|
+
const has_next = total > pageNumber * pageSize;
|
|
473
|
+
const has_previous = pageNumber > 1;
|
|
474
|
+
return new _cardCollection.Page({
|
|
475
|
+
items,
|
|
476
|
+
total_items: total,
|
|
477
|
+
page_number: pageNumber,
|
|
478
|
+
page_size: pageSize,
|
|
479
|
+
has_next,
|
|
480
|
+
has_previous,
|
|
481
|
+
total_pages: Math.ceil(total / pageSize)
|
|
482
|
+
});
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error(`Error searching cards: ${error.message}`);
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Search for cards by content, hash, or g_time
|
|
491
|
+
* @param {string} searchString - String to search for
|
|
492
|
+
* @param {number} pageNumber - Page number for pagination
|
|
493
|
+
* @param {number} pageSize - Number of items per page
|
|
494
|
+
* @returns {Page} Paginated search results
|
|
495
|
+
*/
|
|
496
|
+
search_by_content(searchString, pageNumber = 1, pageSize = _config_constants.DEFAULT_PAGE_SIZE) {
|
|
497
|
+
try {
|
|
498
|
+
if (pageNumber < 1) {
|
|
499
|
+
throw new Error('Page number must be >= 1');
|
|
500
|
+
}
|
|
501
|
+
if (pageSize < 1) {
|
|
502
|
+
throw new Error('Page size must be >= 1');
|
|
503
|
+
}
|
|
504
|
+
const offset = (pageNumber - 1) * pageSize;
|
|
505
|
+
const cursor = this.connection.conn;
|
|
506
|
+
|
|
507
|
+
// First, get total count of matching items
|
|
508
|
+
const countStmt = cursor.prepare('SELECT COUNT(*) as total FROM card WHERE CAST(content AS TEXT) LIKE ?');
|
|
509
|
+
const {
|
|
510
|
+
total
|
|
511
|
+
} = countStmt.get(`%${searchString}%`);
|
|
512
|
+
|
|
513
|
+
// Then, get the actual items for the current page
|
|
514
|
+
const stmt = cursor.prepare(`
|
|
515
|
+
SELECT content, g_time, hash FROM card
|
|
516
|
+
WHERE CAST(content AS TEXT) LIKE ?
|
|
517
|
+
ORDER BY g_time DESC LIMIT ? OFFSET ?
|
|
518
|
+
`);
|
|
519
|
+
const rows = stmt.all(`%${searchString}%`, pageSize, offset);
|
|
520
|
+
|
|
521
|
+
// Convert rows to cards
|
|
522
|
+
const items = [];
|
|
523
|
+
for (const row of rows) {
|
|
524
|
+
const {
|
|
525
|
+
content,
|
|
526
|
+
g_time,
|
|
527
|
+
hash
|
|
528
|
+
} = row;
|
|
529
|
+
|
|
530
|
+
// Parse content if it's a JSON string
|
|
531
|
+
let parsedContent = content;
|
|
532
|
+
if (typeof content === 'string' && (content.startsWith('{') || content.startsWith('['))) {
|
|
533
|
+
try {
|
|
534
|
+
parsedContent = JSON.parse(content);
|
|
535
|
+
} catch (e) {
|
|
536
|
+
console.warn('Failed to parse JSON content for hash:', hash);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Create card with properly parsed content
|
|
541
|
+
const card = new _mcard.MCardFromData(parsedContent, hash, g_time);
|
|
542
|
+
items.push(card);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Calculate pagination flags
|
|
546
|
+
const has_next = total > pageNumber * pageSize;
|
|
547
|
+
const has_previous = pageNumber > 1;
|
|
548
|
+
return new _cardCollection.Page({
|
|
549
|
+
items,
|
|
550
|
+
total_items: total,
|
|
551
|
+
page_number: pageNumber,
|
|
552
|
+
page_size: pageSize,
|
|
553
|
+
has_next,
|
|
554
|
+
has_previous,
|
|
555
|
+
total_pages: Math.ceil(total / pageSize)
|
|
556
|
+
});
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.error(`Error searching cards: ${error.message}`);
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
begin() {
|
|
563
|
+
if (this.connection.conn) {
|
|
564
|
+
this.connection.conn.prepare('BEGIN TRANSACTION').run();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
commit() {
|
|
568
|
+
if (this.connection.conn) {
|
|
569
|
+
this.connection.conn.prepare('COMMIT').run();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
rollback() {
|
|
573
|
+
if (this.connection.conn) {
|
|
574
|
+
this.connection.conn.prepare('ROLLBACK').run();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
clear() {
|
|
578
|
+
try {
|
|
579
|
+
this.begin();
|
|
580
|
+
this.clearStmt.run();
|
|
581
|
+
this.commit();
|
|
582
|
+
} catch (error) {
|
|
583
|
+
this.rollback();
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Count the total number of cards
|
|
590
|
+
* @returns {number} Total number of cards
|
|
591
|
+
*/
|
|
592
|
+
count() {
|
|
593
|
+
const stmt = this.connection.conn.prepare('SELECT COUNT(*) as total FROM card');
|
|
594
|
+
const {
|
|
595
|
+
total
|
|
596
|
+
} = stmt.get();
|
|
597
|
+
return total;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Update a card's content by hash
|
|
602
|
+
* @param {string} hash - Hash of the card to update
|
|
603
|
+
* @param {any} newContent - New content for the card
|
|
604
|
+
* @returns {boolean} Whether the update was successful
|
|
605
|
+
*/
|
|
606
|
+
update(hash, newContent) {
|
|
607
|
+
try {
|
|
608
|
+
console.log('SQLiteEngine.update called with hash:', hash);
|
|
609
|
+
|
|
610
|
+
// First verify the card exists
|
|
611
|
+
const existingCard = this.get(hash);
|
|
612
|
+
if (!existingCard) {
|
|
613
|
+
console.log('No card found with hash:', hash);
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Prepare content for storage
|
|
618
|
+
let finalContent;
|
|
619
|
+
if (typeof newContent === 'object' && newContent !== null && !_bufferPolyfill.SafeBuffer.isBuffer(newContent)) {
|
|
620
|
+
// For objects, stringify to ensure proper SQLite storage
|
|
621
|
+
finalContent = JSON.stringify(newContent);
|
|
622
|
+
console.log('Serialized object content to JSON string for update');
|
|
623
|
+
} else if (typeof newContent === 'string') {
|
|
624
|
+
// Strings can be stored directly
|
|
625
|
+
finalContent = newContent;
|
|
626
|
+
console.log('Using string content directly for update');
|
|
627
|
+
} else if (_bufferPolyfill.SafeBuffer.isBuffer(newContent)) {
|
|
628
|
+
// Buffers can be stored directly
|
|
629
|
+
finalContent = newContent;
|
|
630
|
+
console.log('Using Buffer content directly for update');
|
|
631
|
+
} else {
|
|
632
|
+
// Convert other types to string
|
|
633
|
+
finalContent = String(newContent);
|
|
634
|
+
console.log('Converted content to string for update');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Update the card in the database
|
|
638
|
+
const stmt = this.connection.conn.prepare('UPDATE card SET content = ? WHERE hash = ?');
|
|
639
|
+
const result = stmt.run(finalContent, String(hash));
|
|
640
|
+
if (result.changes > 0) {
|
|
641
|
+
console.log('Card updated successfully with hash:', hash);
|
|
642
|
+
return true;
|
|
643
|
+
} else {
|
|
644
|
+
console.log('Card update had no effect for hash:', hash);
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
console.error('Error updating card:', error);
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Get all cards
|
|
655
|
+
* @param {number} page_number - Page number to retrieve
|
|
656
|
+
* @param {number} page_size - Number of items per page
|
|
657
|
+
* @returns {Page} Page of all cards
|
|
658
|
+
*/
|
|
659
|
+
get_all(page_number = 1, page_size = _config_constants.DEFAULT_PAGE_SIZE) {
|
|
660
|
+
if (page_number < 1) {
|
|
661
|
+
throw new Error("Page number must be >= 1");
|
|
662
|
+
}
|
|
663
|
+
if (page_size < 1) {
|
|
664
|
+
throw new Error("Page size must be >= 1");
|
|
665
|
+
}
|
|
666
|
+
const offset = (page_number - 1) * page_size;
|
|
667
|
+
|
|
668
|
+
// Get total count of items
|
|
669
|
+
const countStmt = this.connection.conn.prepare('SELECT COUNT(*) as total FROM card');
|
|
670
|
+
const {
|
|
671
|
+
total
|
|
672
|
+
} = countStmt.get();
|
|
673
|
+
console.log('Total cards:', total);
|
|
674
|
+
|
|
675
|
+
// Get page of items
|
|
676
|
+
const stmt = this.connection.conn.prepare(`
|
|
677
|
+
SELECT content, g_time, hash
|
|
678
|
+
FROM card
|
|
679
|
+
ORDER BY g_time DESC
|
|
680
|
+
LIMIT ? OFFSET ?
|
|
681
|
+
`);
|
|
682
|
+
const rows = stmt.all(page_size, offset);
|
|
683
|
+
console.log('Rows found:', rows.length);
|
|
684
|
+
|
|
685
|
+
// Convert rows to cards
|
|
686
|
+
const items = [];
|
|
687
|
+
for (const row of rows) {
|
|
688
|
+
const {
|
|
689
|
+
content,
|
|
690
|
+
g_time,
|
|
691
|
+
hash
|
|
692
|
+
} = row;
|
|
693
|
+
|
|
694
|
+
// Parse content if it's a JSON string
|
|
695
|
+
let parsedContent = content;
|
|
696
|
+
if (typeof content === 'string' && (content.startsWith('{') || content.startsWith('['))) {
|
|
697
|
+
try {
|
|
698
|
+
parsedContent = JSON.parse(content);
|
|
699
|
+
} catch (e) {
|
|
700
|
+
console.warn('Failed to parse JSON content for hash:', hash);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Create card with properly parsed content
|
|
705
|
+
const card = new _mcard.MCardFromData(parsedContent, hash, g_time);
|
|
706
|
+
items.push(card);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Calculate total pages
|
|
710
|
+
const total_pages = Math.ceil(total / page_size);
|
|
711
|
+
return new _cardCollection.Page({
|
|
712
|
+
items,
|
|
713
|
+
total_items: total,
|
|
714
|
+
page_number,
|
|
715
|
+
page_size,
|
|
716
|
+
has_next: offset + page_size < total,
|
|
717
|
+
has_previous: page_number > 1,
|
|
718
|
+
total_pages
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
exports.SQLiteEngine = SQLiteEngine;
|
|
723
|
+
//# sourceMappingURL=sqlite_engine.js.map
|