@onurege3467/zerohelper 9.2.0 → 10.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.
@@ -7,49 +7,74 @@ class RedisDatabase extends IDatabase_1.IDatabase {
7
7
  constructor(config) {
8
8
  super();
9
9
  this.client = null;
10
- this.isConnecting = false;
10
+ this._queue = [];
11
+ this._isReady = false;
12
+ this._connectionPromise = null;
11
13
  this.config = config;
12
14
  this.keyPrefix = config.keyPrefix || 'app:';
15
+ this._ensureConnection();
13
16
  }
14
- async _execute(op, table, fn) {
15
- const start = Date.now();
16
- const res = await fn();
17
- this.recordMetric(op, table, Date.now() - start);
18
- return res;
17
+ async _ensureConnection() {
18
+ if (this._connectionPromise)
19
+ return this._connectionPromise;
20
+ this._connectionPromise = (async () => {
21
+ try {
22
+ this.client = (0, redis_1.createClient)({
23
+ url: this.config.url,
24
+ socket: {
25
+ host: this.config.host || '127.0.0.1',
26
+ port: this.config.port || 6379,
27
+ connectTimeout: 5000,
28
+ reconnectStrategy: false
29
+ },
30
+ password: this.config.password,
31
+ database: Number(this.config.database) || 0,
32
+ });
33
+ await this.client.connect();
34
+ this._isReady = true;
35
+ this._flushQueue();
36
+ }
37
+ catch (error) {
38
+ this._flushQueueWithError(error);
39
+ }
40
+ })();
41
+ return this._connectionPromise;
19
42
  }
20
- ;
21
- async connect() {
22
- if (this.client && this.client.isReady)
23
- return this.client;
24
- if (this.isConnecting) {
25
- while (this.isConnecting)
26
- await new Promise(r => setTimeout(r, 100));
27
- return this.client;
43
+ _flushQueue() {
44
+ while (this._queue.length > 0) {
45
+ const item = this._queue.shift();
46
+ if (item)
47
+ item.operation().then(item.resolve).catch(item.reject);
28
48
  }
29
- this.isConnecting = true;
30
- try {
31
- this.client = (0, redis_1.createClient)({
32
- url: this.config.url,
33
- socket: { host: this.config.host || '127.0.0.1', port: this.config.port || 6379 },
34
- password: this.config.password,
35
- database: Number(this.config.database) || 0,
36
- });
37
- await this.client.connect();
38
- return this.client;
39
- }
40
- finally {
41
- this.isConnecting = false;
49
+ }
50
+ _flushQueueWithError(error) {
51
+ while (this._queue.length > 0) {
52
+ const item = this._queue.shift();
53
+ if (item)
54
+ item.reject(error);
42
55
  }
43
56
  }
57
+ async _execute(op, table, fn) {
58
+ const operation = async () => {
59
+ const start = Date.now();
60
+ const res = await fn();
61
+ this.recordMetric(op, table, Date.now() - start);
62
+ return res;
63
+ };
64
+ if (this._isReady)
65
+ return operation();
66
+ return new Promise((resolve, reject) => {
67
+ this._queue.push({ operation, resolve, reject });
68
+ });
69
+ }
44
70
  _getKey(table, id) { return `${this.keyPrefix}${table}:${id}`; }
45
71
  _getTableKey(table) { return `${this.keyPrefix}${table}:*`; }
46
72
  async select(table, where = {}) {
47
73
  return this._execute('select', table, async () => {
48
- const client = await this.connect();
49
- const keys = await client.keys(this._getTableKey(table));
74
+ const keys = await this.client.keys(this._getTableKey(table));
50
75
  if (!keys.length)
51
76
  return [];
52
- const vals = await client.mGet(keys);
77
+ const vals = await this.client.mGet(keys);
53
78
  return vals.map(v => v ? JSON.parse(v) : null).filter(Boolean)
54
79
  .filter(item => Object.entries(where).every(([k, v]) => String(item[k]) === String(v)));
55
80
  });
@@ -61,12 +86,11 @@ class RedisDatabase extends IDatabase_1.IDatabase {
61
86
  async insert(table, data) {
62
87
  await this.runHooks('beforeInsert', table, data);
63
88
  return this._execute('insert', table, async () => {
64
- const client = await this.connect();
65
89
  const d = { ...data };
66
90
  if (!d._id && !d.id)
67
91
  d._id = Date.now().toString() + Math.random().toString(36).slice(2, 9);
68
92
  const id = String(d._id || d.id);
69
- await client.set(this._getKey(table, id), JSON.stringify(d));
93
+ await this.client.set(this._getKey(table, id), JSON.stringify(d));
70
94
  await this.runHooks('afterInsert', table, d);
71
95
  return d._id || d.id;
72
96
  });
@@ -75,10 +99,9 @@ class RedisDatabase extends IDatabase_1.IDatabase {
75
99
  await this.runHooks('beforeUpdate', table, { data, where });
76
100
  return this._execute('update', table, async () => {
77
101
  const existing = await this.select(table, where);
78
- const client = await this.connect();
79
102
  for (const item of existing) {
80
103
  const merged = { ...item, ...data };
81
- await client.set(this._getKey(table, item._id || item.id), JSON.stringify(merged));
104
+ await this.client.set(this._getKey(table, item._id || item.id), JSON.stringify(merged));
82
105
  }
83
106
  return existing.length;
84
107
  });
@@ -87,10 +110,9 @@ class RedisDatabase extends IDatabase_1.IDatabase {
87
110
  await this.runHooks('beforeDelete', table, where);
88
111
  return this._execute('delete', table, async () => {
89
112
  const existing = await this.select(table, where);
90
- const client = await this.connect();
91
113
  if (existing.length) {
92
114
  const keys = existing.map(i => this._getKey(table, String(i._id || i.id)));
93
- await client.del(keys);
115
+ await this.client.del(keys);
94
116
  }
95
117
  return existing.length;
96
118
  });
@@ -104,14 +126,33 @@ class RedisDatabase extends IDatabase_1.IDatabase {
104
126
  await this.insert(table, d);
105
127
  return dataArray.length;
106
128
  }
129
+ /**
130
+ * Atomic Increment using Lua for Redis
131
+ */
107
132
  async increment(table, incs, where = {}) {
108
133
  return this._execute('increment', table, async () => {
109
134
  const recs = await this.select(table, where);
110
- const client = await this.connect();
111
135
  for (const r of recs) {
112
- for (const [f, v] of Object.entries(incs))
113
- r[f] = (Number(r[f]) || 0) + v;
114
- await client.set(this._getKey(table, r._id || r.id), JSON.stringify(r));
136
+ const id = String(r._id || r.id);
137
+ const key = this._getKey(table, id);
138
+ // Redis'te JSON sakladığımız için her alanı ayrı artırmak yerine
139
+ // objeyi okuyup, güncelleyip tekrar yazmalıyız.
140
+ // Bunu Lua script ile Redis tarafında atomik yapalım.
141
+ const lua = `
142
+ local val = redis.call('get', KEYS[1])
143
+ if not val then return 0 end
144
+ local data = cjson.decode(val)
145
+ local incs = cjson.decode(ARGV[1])
146
+ for k, v in pairs(incs) do
147
+ data[k] = (tonumber(data[k]) or 0) + v
148
+ end
149
+ redis.call('set', KEYS[1], cjson.encode(data))
150
+ return 1
151
+ `;
152
+ await this.client.eval(lua, {
153
+ keys: [key],
154
+ arguments: [JSON.stringify(incs)]
155
+ });
115
156
  }
116
157
  return recs.length;
117
158
  });
@@ -122,10 +163,14 @@ class RedisDatabase extends IDatabase_1.IDatabase {
122
163
  incs[k] = -decs[k];
123
164
  return this.increment(table, incs, where);
124
165
  }
125
- async close() { if (this.client) {
126
- await this.client.quit();
127
- this.client = null;
128
- } }
166
+ async close() {
167
+ if (this.client) {
168
+ await this.client.quit();
169
+ this.client = null;
170
+ this._isReady = false;
171
+ this._connectionPromise = null;
172
+ }
173
+ }
129
174
  }
130
175
  exports.RedisDatabase = RedisDatabase;
131
176
  exports.default = RedisDatabase;
@@ -2,7 +2,11 @@ import { IDatabase } from './IDatabase';
2
2
  import { SQLiteConfig } from './types';
3
3
  export declare class SQLiteDatabase extends IDatabase {
4
4
  private db;
5
+ private _queue;
6
+ private _isOpen;
5
7
  constructor(config: SQLiteConfig);
8
+ private _flushQueue;
9
+ private _flushQueueWithError;
6
10
  private _execute;
7
11
  query(sql: string, params?: any[]): Promise<any>;
8
12
  ensureTable(table: string, data?: any): Promise<void>;
@@ -11,20 +11,52 @@ const path_1 = __importDefault(require("path"));
11
11
  class SQLiteDatabase extends IDatabase_1.IDatabase {
12
12
  constructor(config) {
13
13
  super();
14
+ this._queue = [];
15
+ this._isOpen = false;
14
16
  if (!config || !config.path)
15
17
  throw new Error('SQLite "path" gereklidir.');
16
18
  const dir = path_1.default.dirname(config.path);
17
19
  if (!fs_1.default.existsSync(dir))
18
20
  fs_1.default.mkdirSync(dir, { recursive: true });
19
- this.db = new sqlite3_1.default.Database(config.path);
21
+ // SQLite open is technically async but the object is returned immediately.
22
+ // However, we simulate a queue to serialize operations strictly.
23
+ this.db = new sqlite3_1.default.Database(config.path, (err) => {
24
+ if (err) {
25
+ this._flushQueueWithError(err);
26
+ }
27
+ else {
28
+ this._isOpen = true;
29
+ this._flushQueue();
30
+ }
31
+ });
32
+ }
33
+ _flushQueue() {
34
+ while (this._queue.length > 0) {
35
+ const item = this._queue.shift();
36
+ if (item)
37
+ item.operation().then(item.resolve).catch(item.reject);
38
+ }
39
+ }
40
+ _flushQueueWithError(error) {
41
+ while (this._queue.length > 0) {
42
+ const item = this._queue.shift();
43
+ if (item)
44
+ item.reject(error);
45
+ }
20
46
  }
21
47
  async _execute(op, table, fn) {
22
- const start = Date.now();
23
- const res = await fn();
24
- this.recordMetric(op, table, Date.now() - start);
25
- return res;
48
+ const operation = async () => {
49
+ const start = Date.now();
50
+ const res = await fn();
51
+ this.recordMetric(op, table, Date.now() - start);
52
+ return res;
53
+ };
54
+ if (this._isOpen)
55
+ return operation();
56
+ return new Promise((resolve, reject) => {
57
+ this._queue.push({ operation, resolve, reject });
58
+ });
26
59
  }
27
- ;
28
60
  async query(sql, params = []) {
29
61
  return new Promise((resolve, reject) => {
30
62
  const s = sql.trim().toUpperCase();
@@ -115,9 +147,23 @@ class SQLiteDatabase extends IDatabase_1.IDatabase {
115
147
  async bulkInsert(table, dataArray) {
116
148
  if (!dataArray.length)
117
149
  return 0;
118
- for (const d of dataArray)
119
- await this.insert(table, d);
120
- return dataArray.length;
150
+ return this._execute('bulkInsert', table, async () => {
151
+ await this.ensureTable(table, dataArray[0]);
152
+ await this.query('BEGIN TRANSACTION');
153
+ try {
154
+ const keys = Object.keys(dataArray[0]);
155
+ const sql = `INSERT INTO "${table}" (${keys.map(k => `"${k}"`).join(',')}) VALUES (${keys.map(() => '?').join(',')})`;
156
+ for (const d of dataArray) {
157
+ await this.query(sql, Object.values(d).map(v => typeof v === 'object' ? JSON.stringify(v) : v));
158
+ }
159
+ await this.query('COMMIT');
160
+ return dataArray.length;
161
+ }
162
+ catch (error) {
163
+ await this.query('ROLLBACK');
164
+ throw error;
165
+ }
166
+ });
121
167
  }
122
168
  async increment(table, incs, where) {
123
169
  return this._execute('increment', table, async () => {
@@ -135,7 +181,14 @@ class SQLiteDatabase extends IDatabase_1.IDatabase {
135
181
  incs[k] = -decs[k];
136
182
  return this.increment(table, incs, where);
137
183
  }
138
- async close() { return new Promise((resolve, reject) => this.db.close(err => err ? reject(err) : resolve())); }
184
+ async close() {
185
+ return new Promise((resolve, reject) => {
186
+ this.db.close(err => {
187
+ this._isOpen = false;
188
+ err ? reject(err) : resolve();
189
+ });
190
+ });
191
+ }
139
192
  _serializeValue(v) {
140
193
  if (v instanceof Date)
141
194
  return v.toISOString().slice(0, 19).replace('T', ' ');
@@ -12,6 +12,7 @@ export declare class ToonDatabase extends IDatabase {
12
12
  constructor(config: ToonConfig);
13
13
  private _execute;
14
14
  private _load;
15
+ private _getTable;
15
16
  private _queueRequest;
16
17
  private _processQueue;
17
18
  private _scheduleSave;
@@ -41,12 +41,29 @@ class ToonDatabase extends IDatabase_1.IDatabase {
41
41
  return;
42
42
  }
43
43
  const content = await promises_1.default.readFile(this.filePath, 'utf-8');
44
- this.db = (0, toon_1.parse)(content);
44
+ const parsed = (0, toon_1.parse)(content);
45
+ // ✅ Parse sonucunu doğrula
46
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
47
+ this.db = {};
48
+ }
49
+ else {
50
+ this.db = {};
51
+ // Her tablonun array olduğundan emin ol
52
+ for (const [key, value] of Object.entries(parsed)) {
53
+ this.db[key] = Array.isArray(value) ? value : [];
54
+ }
55
+ }
45
56
  }
46
57
  catch (error) {
58
+ console.error("ToonDB load error:", error);
47
59
  this.db = {};
48
60
  }
49
61
  }
62
+ _getTable(table) {
63
+ // ✅ Her zaman array döndüren yardımcı fonksiyon
64
+ const data = this.db[table];
65
+ return Array.isArray(data) ? data : [];
66
+ }
50
67
  _queueRequest(operation) {
51
68
  return new Promise((resolve, reject) => {
52
69
  this.writeQueue.push({ operation, resolve, reject });
@@ -104,8 +121,11 @@ class ToonDatabase extends IDatabase_1.IDatabase {
104
121
  }
105
122
  async ensureTable(table) {
106
123
  await this.initPromise;
107
- if (!this.db[table]) {
108
- return this._queueRequest(() => { this.db[table] = []; });
124
+ // Tablonun array olduğundan emin ol
125
+ if (!Array.isArray(this.db[table])) {
126
+ return this._queueRequest(() => {
127
+ this.db[table] = [];
128
+ });
109
129
  }
110
130
  }
111
131
  async insert(table, data) {
@@ -113,7 +133,8 @@ class ToonDatabase extends IDatabase_1.IDatabase {
113
133
  return this._execute('insert', table, async () => {
114
134
  await this.ensureTable(table);
115
135
  return this._queueRequest(() => {
116
- const maxId = this.db[table].reduce((max, row) => (row._id > max ? row._id : max), 0);
136
+ const tableData = this._getTable(table);
137
+ const maxId = tableData.reduce((max, row) => (row._id > max ? row._id : max), 0);
117
138
  const newId = maxId + 1;
118
139
  const newRow = { _id: newId, ...data };
119
140
  this.db[table].push(newRow);
@@ -128,7 +149,8 @@ class ToonDatabase extends IDatabase_1.IDatabase {
128
149
  await this.ensureTable(table);
129
150
  return this._queueRequest(() => {
130
151
  let affected = 0;
131
- this.db[table].forEach(row => {
152
+ const tableData = this._getTable(table);
153
+ tableData.forEach(row => {
132
154
  if (Object.keys(where).every(k => String(row[k]) === String(where[k]))) {
133
155
  Object.assign(row, data);
134
156
  affected++;
@@ -144,8 +166,9 @@ class ToonDatabase extends IDatabase_1.IDatabase {
144
166
  return this._execute('delete', table, async () => {
145
167
  await this.ensureTable(table);
146
168
  return this._queueRequest(() => {
147
- const initial = this.db[table].length;
148
- this.db[table] = this.db[table].filter(row => !Object.keys(where).every(k => String(row[k]) === String(where[k])));
169
+ const tableData = this._getTable(table);
170
+ const initial = tableData.length;
171
+ this.db[table] = tableData.filter(row => !Object.keys(where).every(k => String(row[k]) === String(where[k])));
149
172
  const affected = initial - this.db[table].length;
150
173
  this.runHooks('afterDelete', table, { affected });
151
174
  return affected;
@@ -155,9 +178,11 @@ class ToonDatabase extends IDatabase_1.IDatabase {
155
178
  async select(table, where = null) {
156
179
  return this._execute('select', table, async () => {
157
180
  await this.initPromise;
181
+ // ✅ _getTable kullanarak array garantisi
182
+ const tableData = this._getTable(table);
158
183
  const results = where && Object.keys(where).length > 0
159
- ? (this.db[table] || []).filter(row => Object.keys(where).every(k => String(row[k]) === String(where[k])))
160
- : (this.db[table] || []);
184
+ ? tableData.filter(row => Object.keys(where).every(k => String(row[k]) === String(where[k])))
185
+ : tableData;
161
186
  return JSON.parse(JSON.stringify(results));
162
187
  });
163
188
  }
@@ -166,6 +191,7 @@ class ToonDatabase extends IDatabase_1.IDatabase {
166
191
  return res[0] || null;
167
192
  }
168
193
  async set(table, data, where) {
194
+ await this.ensureTable(table); // ✅ Önce tabloyu garantile
169
195
  const ex = await this.selectOne(table, where);
170
196
  return ex ? this.update(table, data, where) : this.insert(table, { ...where, ...data });
171
197
  }
@@ -175,8 +201,12 @@ class ToonDatabase extends IDatabase_1.IDatabase {
175
201
  return 0;
176
202
  await this.ensureTable(table);
177
203
  return this._queueRequest(() => {
178
- let maxId = this.db[table].reduce((max, row) => (row._id > max ? row._id : max), 0);
179
- dataArray.forEach(data => { maxId++; this.db[table].push({ _id: maxId, ...data }); });
204
+ const tableData = this._getTable(table);
205
+ let maxId = tableData.reduce((max, row) => (row._id > max ? row._id : max), 0);
206
+ dataArray.forEach(data => {
207
+ maxId++;
208
+ this.db[table].push({ _id: maxId, ...data });
209
+ });
180
210
  return dataArray.length;
181
211
  });
182
212
  });
@@ -186,7 +216,8 @@ class ToonDatabase extends IDatabase_1.IDatabase {
186
216
  await this.ensureTable(table);
187
217
  return this._queueRequest(() => {
188
218
  let affected = 0;
189
- this.db[table].forEach(row => {
219
+ const tableData = this._getTable(table);
220
+ tableData.forEach(row => {
190
221
  if (Object.keys(where).every(k => String(row[k]) === String(where[k]))) {
191
222
  for (const [f, v] of Object.entries(incs))
192
223
  row[f] = (Number(row[f]) || 0) + v;
@@ -8,6 +8,7 @@ export interface NetworkConfig extends BaseConfig {
8
8
  password?: string;
9
9
  database?: string;
10
10
  url?: string;
11
+ uri?: string;
11
12
  poolSize?: number;
12
13
  }
13
14
  export interface FileConfig extends BaseConfig {
@@ -1,5 +1,8 @@
1
1
  import { IDatabase } from './IDatabase';
2
2
  import { ZPackConfig } from './types';
3
+ /**
4
+ * ZPackDatabase: Low-level Binary Storage
5
+ */
3
6
  export declare class ZPackDatabase {
4
7
  filePath: string;
5
8
  private fd;
@@ -36,6 +39,9 @@ export declare class ZPackDatabase {
36
39
  private _tryLoadIndexFromFooter;
37
40
  private _scanAndRebuildIndex;
38
41
  }
42
+ /**
43
+ * ZPackAdapter: IDatabase Implementation
44
+ */
39
45
  export declare class ZPackAdapter extends IDatabase {
40
46
  private db;
41
47
  private initPromise;
@@ -44,8 +50,12 @@ export declare class ZPackAdapter extends IDatabase {
44
50
  private rowCache;
45
51
  private secondary;
46
52
  private indexedFields;
53
+ private _isClosing;
54
+ private _executing;
47
55
  constructor(config: ZPackConfig);
48
56
  private _init;
57
+ private _execute;
58
+ private _rawSelect;
49
59
  ensureTable(table: string): Promise<void>;
50
60
  private _updateSecondaryIndex;
51
61
  private _coerce;