@futdevpro/nts-dynamo 1.15.58 → 1.15.64
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/.dynamo/logs/cicd-pipeline/output.log +1746 -1819
- package/.dynamo/logs/cicd-pipeline/status.json +37 -37
- package/.github/workflows/main.yml +432 -426
- package/build/_collections/global-settings.const.d.ts.map +1 -1
- package/build/_collections/global-settings.const.js +6 -0
- package/build/_collections/global-settings.const.js.map +1 -1
- package/build/_collections/mongo-reconnect-guard.util.d.ts +74 -0
- package/build/_collections/mongo-reconnect-guard.util.d.ts.map +1 -0
- package/build/_collections/mongo-reconnect-guard.util.js +111 -0
- package/build/_collections/mongo-reconnect-guard.util.js.map +1 -0
- package/build/_models/interfaces/global-settings.interface.d.ts +21 -0
- package/build/_models/interfaces/global-settings.interface.d.ts.map +1 -1
- package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.d.ts.map +1 -1
- package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js +2 -2
- package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js.map +1 -1
- package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts +1 -1
- package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts.map +1 -1
- package/build/_modules/ai/_services/ai-embedding-mock.service.js.map +1 -1
- package/build/_modules/ai/_services/ai-embedding-provider.registry.d.ts.map +1 -1
- package/build/_modules/ai/_services/ai-embedding-provider.registry.js.map +1 -1
- package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts +1 -1
- package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts.map +1 -1
- package/build/_modules/ai/_services/lmstudio-embedding.control-service.js +3 -3
- package/build/_modules/ai/_services/lmstudio-embedding.control-service.js.map +1 -1
- package/build/_modules/ai/index.d.ts +2 -0
- package/build/_modules/ai/index.d.ts.map +1 -1
- package/build/_modules/ai/index.js +4 -0
- package/build/_modules/ai/index.js.map +1 -1
- package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts +17 -17
- package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts.map +1 -1
- package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js +21 -21
- package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js.map +1 -1
- package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts +4 -4
- package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js +5 -5
- package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts +4 -4
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js +4 -4
- package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts +6 -6
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts.map +1 -1
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js +5 -5
- package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js.map +1 -1
- package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.d.ts +1 -1
- package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.js +1 -1
- package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts +4 -4
- package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts.map +1 -1
- package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js +6 -6
- package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js.map +1 -1
- package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts +11 -11
- package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts.map +1 -1
- package/build/_modules/mcp/_services/dynts-mcp.adapter.js +16 -11
- package/build/_modules/mcp/_services/dynts-mcp.adapter.js.map +1 -1
- package/build/_modules/mcp/index.js +1 -1
- package/build/_modules/mcp/index.js.map +1 -1
- package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts +3 -3
- package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts.map +1 -1
- package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js +4 -4
- package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js.map +1 -1
- package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.d.ts.map +1 -1
- package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js +9 -0
- package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js.map +1 -1
- package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts +3 -3
- package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts.map +1 -1
- package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js +1 -1
- package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js.map +1 -1
- package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts +7 -7
- package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts.map +1 -1
- package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js +2 -2
- package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js.map +1 -1
- package/build/_services/core/global.service.d.ts.map +1 -1
- package/build/_services/core/global.service.js +15 -2
- package/build/_services/core/global.service.js.map +1 -1
- package/build/_services/server/app.server.d.ts.map +1 -1
- package/build/_services/server/app.server.js +21 -0
- package/build/_services/server/app.server.js.map +1 -1
- package/package.json +1 -1
- package/src/_collections/global-settings.const.ts +7 -0
- package/src/_collections/mongo-reconnect-guard.util.spec.ts +52 -0
- package/src/_collections/mongo-reconnect-guard.util.ts +172 -0
- package/src/_models/interfaces/global-settings.interface.ts +22 -0
- package/src/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.ts +39 -7
- package/src/_modules/ai/_services/ai-embedding-mock.service.ts +18 -4
- package/src/_modules/ai/_services/ai-embedding-provider.registry.ts +4 -0
- package/src/_modules/ai/_services/lmstudio-embedding.control-service.ts +26 -5
- package/src/_modules/ai/index.ts +5 -0
- package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.spec.ts +145 -130
- package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.ts +131 -120
- package/src/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.ts +6 -5
- package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.spec.ts +35 -35
- package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.ts +9 -5
- package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.spec.ts +11 -11
- package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.ts +19 -17
- package/src/_modules/mcp/_models/interfaces/dynts-mcp.interface.ts +1 -1
- package/src/_modules/mcp/_services/dynts-mcp-server.service-base.spec.ts +123 -114
- package/src/_modules/mcp/_services/dynts-mcp-server.service-base.ts +44 -39
- package/src/_modules/mcp/_services/dynts-mcp.adapter.ts +114 -103
- package/src/_modules/mcp/index.ts +1 -1
- package/src/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.ts +5 -4
- package/src/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.ts +0 -2
- package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.spec.ts +19 -13
- package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.ts +37 -21
- package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.spec.ts +11 -6
- package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.ts +17 -14
- package/src/_services/core/global.service.spec.ts +17 -0
- package/src/_services/core/global.service.ts +19 -5
- package/src/_services/server/app.server.ts +22 -1
|
@@ -9,6 +9,11 @@ import { DyFM_Error } from '@futdevpro/fsm-dynamo';
|
|
|
9
9
|
import { DyNTS_Sqlite_Reader_Util } from './dynts-sqlite-reader.util';
|
|
10
10
|
import { DyNTS_SqliteColumnInfo } from '../_models/interfaces/dynts-sqlite-reader.interface';
|
|
11
11
|
|
|
12
|
+
interface NameInterface {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
18
|
* `DyNTS_Sqlite_Reader_Util` spec (BFR-AM-009). Egy temp `.db` fájlt hozunk létre (a reader path-szal
|
|
14
19
|
* + `fileMustExist:true` nyit, ezért valódi fájl kell — a `:memory:` path-ön nem ad fájlt), feltöltjük,
|
|
@@ -20,142 +25,152 @@ import { DyNTS_SqliteColumnInfo } from '../_models/interfaces/dynts-sqlite-reade
|
|
|
20
25
|
* - `query` NEM-SELECT (INSERT/UPDATE/DELETE/DROP/multi-statement) → READONLY-hiba (elutasít),
|
|
21
26
|
* - hiányzó path / nem-létező fájl → strukturált hiba.
|
|
22
27
|
*/
|
|
23
|
-
describe('DyNTS_Sqlite_Reader_Util (read-only SQLite reader, BFR-AM-009)', () => {
|
|
28
|
+
describe('| DyNTS_Sqlite_Reader_Util (read-only SQLite reader, BFR-AM-009)', () => {
|
|
29
|
+
|
|
30
|
+
/** A temp DB-fájl útvonala (minden teszt-futáshoz egyedi). */
|
|
31
|
+
let dbPath: string;
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
dbPath = path.join(os.tmpdir(), `dynts-sqlite-spec-${process.pid}-${Date.now()}.db`);
|
|
35
|
+
const db: Database.Database = new Database(dbPath);
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
dbPath = path.join(os.tmpdir(), `dynts-sqlite-spec-${process.pid}-${Date.now()}.db`);
|
|
30
|
-
const db: Database.Database = new Database(dbPath);
|
|
31
|
-
db.exec(`
|
|
37
|
+
db.exec(`
|
|
32
38
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER);
|
|
33
39
|
CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT);
|
|
34
40
|
`);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
db.prepare('INSERT INTO users (id, name, age) VALUES (?, ?, ?)').run(1, 'Ada', 36);
|
|
42
|
+
db.prepare('INSERT INTO users (id, name, age) VALUES (?, ?, ?)').run(2, 'Grace', 40);
|
|
43
|
+
db.prepare('INSERT INTO posts (id, title) VALUES (?, ?)').run(1, 'Hello');
|
|
44
|
+
db.close();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
if (dbPath && fs.existsSync(dbPath)) {
|
|
49
|
+
fs.unlinkSync(dbPath);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Egy hívás, ami egy adott errorCode-dal kell hogy dobjon (strukturált, NEM néma). */
|
|
54
|
+
const expectErrorCode = (fn: () => unknown, expectedCode: string): void => {
|
|
55
|
+
let thrown: unknown;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
fn();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
thrown = error;
|
|
61
|
+
}
|
|
62
|
+
expect(thrown).withContext(`dobnia kellett (${expectedCode})`).toBeDefined();
|
|
63
|
+
const code: string | undefined =
|
|
57
64
|
thrown instanceof DyFM_Error ? DyFM_Error.getErrorCode(thrown) : undefined;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
65
|
+
|
|
66
|
+
expect(code).withContext('a hibakód').toBe(expectedCode);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
describe('| listTables', () => {
|
|
70
|
+
it('| a user-táblák ABC-sorrendben (a belső sqlite_% táblák kiszűrve)', () => {
|
|
71
|
+
expect(DyNTS_Sqlite_Reader_Util.listTables(dbPath)).toEqual([ 'posts', 'users' ]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('| getSchema', () => {
|
|
76
|
+
it('| a users tábla oszlop-info-ja (name/type/notNull/primaryKey)', () => {
|
|
77
|
+
const schema: DyNTS_SqliteColumnInfo[] = DyNTS_Sqlite_Reader_Util.getSchema(dbPath, 'users');
|
|
78
|
+
|
|
79
|
+
expect(schema.map((column) => column.name)).toEqual([ 'id', 'name', 'age' ]);
|
|
80
|
+
const idColumn: DyNTS_SqliteColumnInfo = schema.find((column) => column.name === 'id');
|
|
81
|
+
|
|
82
|
+
expect(idColumn.primaryKey).toBe(true);
|
|
83
|
+
const nameColumn: DyNTS_SqliteColumnInfo = schema.find((column) => column.name === 'name');
|
|
84
|
+
|
|
85
|
+
expect(nameColumn.notNull).toBe(true);
|
|
86
|
+
expect(nameColumn.type).toBe('TEXT');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('| nem-létező tábla → DyNTS-SQLITE-READ-002 (NEM néma)', () => {
|
|
90
|
+
expectErrorCode(() => DyNTS_Sqlite_Reader_Util.getSchema(dbPath, 'no_such_table'), 'DyNTS-SQLITE-READ-002');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('| readTable', () => {
|
|
95
|
+
it('| a users tábla minden sora', () => {
|
|
96
|
+
const rows: { id: number; name: string }[] =
|
|
86
97
|
DyNTS_Sqlite_Reader_Util.readTable(dbPath, 'users') as { id: number; name: string }[];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
98
|
+
|
|
99
|
+
expect(rows.length).toBe(2);
|
|
100
|
+
expect(rows.map((row) => row.name).sort()).toEqual([ 'Ada', 'Grace' ]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('| nem-létező tábla → DyNTS-SQLITE-READ-002 (NEM néma-[])', () => {
|
|
104
|
+
expectErrorCode(() => DyNTS_Sqlite_Reader_Util.readTable(dbPath, 'ghost'), 'DyNTS-SQLITE-READ-002');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('| query (SELECT-only)', () => {
|
|
109
|
+
it('| egy SELECT lekérdezés sorai', () => {
|
|
110
|
+
const rows: NameInterface[] =
|
|
111
|
+
DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT name FROM users ORDER BY age DESC') as NameInterface[];
|
|
112
|
+
|
|
113
|
+
expect(rows.map((row) => row.name)).toEqual([ 'Grace', 'Ada' ]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('| paraméteres SELECT (prepared-statement param)', () => {
|
|
117
|
+
const rows: NameInterface[] =
|
|
118
|
+
DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT * FROM users WHERE age > ?', [ 37 ]) as NameInterface[];
|
|
119
|
+
|
|
120
|
+
expect(rows.map((row) => row.name)).toEqual([ 'Grace' ]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('| WITH (CTE) is engedett (read-only)', () => {
|
|
124
|
+
const rows: { n: number }[] = DyNTS_Sqlite_Reader_Util.query(dbPath,
|
|
125
|
+
'WITH adults AS (SELECT * FROM users WHERE age >= 36) SELECT COUNT(*) AS n FROM adults') as { n: number }[];
|
|
126
|
+
|
|
127
|
+
expect(rows[0].n).toBe(2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('| INSERT → DyNTS-SQLITE-READONLY-001 (elutasít a forrás-rétegben)', () => {
|
|
131
|
+
expectErrorCode(
|
|
132
|
+
() => DyNTS_Sqlite_Reader_Util.query(dbPath, 'INSERT INTO users (id, name) VALUES (9, \'x\')'),
|
|
133
|
+
'DyNTS-SQLITE-READONLY-001');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('| UPDATE → DyNTS-SQLITE-READONLY-001', () => {
|
|
137
|
+
expectErrorCode(
|
|
138
|
+
() => DyNTS_Sqlite_Reader_Util.query(dbPath, 'UPDATE users SET name=\'x\' WHERE id=1'),
|
|
139
|
+
'DyNTS-SQLITE-READONLY-001');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('| DELETE → DyNTS-SQLITE-READONLY-001', () => {
|
|
143
|
+
expectErrorCode(
|
|
144
|
+
() => DyNTS_Sqlite_Reader_Util.query(dbPath, 'DELETE FROM users'),
|
|
145
|
+
'DyNTS-SQLITE-READONLY-001');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('| DROP → DyNTS-SQLITE-READONLY-001', () => {
|
|
149
|
+
expectErrorCode(
|
|
150
|
+
() => DyNTS_Sqlite_Reader_Util.query(dbPath, 'DROP TABLE users'),
|
|
151
|
+
'DyNTS-SQLITE-READONLY-001');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('| multi-statement (SELECT; DELETE) → DyNTS-SQLITE-READONLY-001 (nem rejthető írás SELECT mögé)', () => {
|
|
155
|
+
expectErrorCode(
|
|
156
|
+
() => DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT 1; DELETE FROM users'),
|
|
157
|
+
'DyNTS-SQLITE-READONLY-001');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('| üres SQL → DyNTS-SQLITE-READONLY-001', () => {
|
|
161
|
+
expectErrorCode(() => DyNTS_Sqlite_Reader_Util.query(dbPath, ' '), 'DyNTS-SQLITE-READONLY-001');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('| path-validáció (fail-soft, NEM crash)', () => {
|
|
166
|
+
it('| üres path → DyNTS-SQLITE-READ-001', () => {
|
|
167
|
+
expectErrorCode(() => DyNTS_Sqlite_Reader_Util.listTables(''), 'DyNTS-SQLITE-READ-001');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('| nem-létező fájl → DyNTS-SQLITE-READ-001', () => {
|
|
171
|
+
expectErrorCode(
|
|
172
|
+
() => DyNTS_Sqlite_Reader_Util.listTables(path.join(os.tmpdir(), 'no-such-db-file-xyz.db')),
|
|
173
|
+
'DyNTS-SQLITE-READ-001');
|
|
160
174
|
});
|
|
175
|
+
});
|
|
161
176
|
});
|
|
@@ -5,8 +5,7 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { DyFM_Error } from '@futdevpro/fsm-dynamo';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
DyNTS_SqliteReadOptions,
|
|
8
|
+
DyNTS_SqliteColumnInfo
|
|
10
9
|
} from '../_models/interfaces/dynts-sqlite-reader.interface';
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -29,156 +28,168 @@ import {
|
|
|
29
28
|
*/
|
|
30
29
|
export class DyNTS_Sqlite_Reader_Util {
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
/** A hibák issuer-e (a strukturált DyFM_Error-okhoz). */
|
|
32
|
+
private static readonly issuer: string = 'DyNTS_Sqlite_Reader_Util';
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
/** A `query()` SELECT-only guard regex-e: a (trimmelt, lowercase) SQL `select`/`with`-tel kezdődik. */
|
|
35
|
+
private static readonly SELECT_ONLY: RegExp = /^\s*(select|with)\b/i;
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
/**
|
|
39
38
|
* Egy tábla ÖSSZES sorának read-only olvasása (`SELECT * FROM <table>`). A `table` nevet
|
|
40
39
|
* `sqlite_master` ellen validáljuk (csak létező táblanév fut, így nincs SQL-injekciós felület a
|
|
41
40
|
* tábla-néven). Nem-létező tábla → strukturált hiba (NEM néma-[]).
|
|
42
41
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
// A `table` itt már egy igazolt, létező táblanév (sqlite_master), nem nyers user-input.
|
|
53
|
-
return db.prepare(`SELECT * FROM "${table}"`).all();
|
|
42
|
+
public static readTable(dbPath: string, table: string): unknown[] {
|
|
43
|
+
return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
|
|
44
|
+
if (!DyNTS_Sqlite_Reader_Util.tableExists(db, table)) {
|
|
45
|
+
throw new DyFM_Error({
|
|
46
|
+
errorCode: 'DyNTS-SQLITE-READ-002',
|
|
47
|
+
message: `Table '${table}' does not exist in the SQLite DB: '${dbPath}'.`,
|
|
48
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
54
49
|
});
|
|
55
|
-
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A `table` itt már egy igazolt, létező táblanév (sqlite_master), nem nyers user-input.
|
|
53
|
+
return db.prepare(`SELECT * FROM "${table}"`).all();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
58
|
* Egy tetszőleges **SELECT-only** lekérdezés read-only futtatása. A guard elutasít minden nem-SELECT
|
|
59
59
|
* (insert/update/delete/drop/attach/pragma-write/…) SQL-t → `DyNTS-SQLITE-READONLY-001`. A
|
|
60
60
|
* `params` opcionális prepared-statement paraméterek (pozícionális `?` vagy named `@x`).
|
|
61
61
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
|
|
65
|
-
const statement: Database.Statement = db.prepare(sql);
|
|
66
|
-
// A `better-sqlite3` `.all()` csak read-statement-en ad sort; write-statement-en dobna —
|
|
67
|
-
// a SELECT-only guard ezt már a forrás-rétegben kizárja (defenzív kettősség).
|
|
68
|
-
return params === undefined ? statement.all() : statement.all(params as never);
|
|
69
|
-
});
|
|
70
|
-
}
|
|
62
|
+
public static query(dbPath: string, sql: string, params?: unknown[] | { [key: string]: unknown }): unknown[] {
|
|
63
|
+
DyNTS_Sqlite_Reader_Util.assertSelectOnly(sql);
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
|
|
66
|
+
const statement: Database.Statement = db.prepare(sql);
|
|
67
|
+
|
|
68
|
+
// A `better-sqlite3` `.all()` csak read-statement-en ad sort; write-statement-en dobna —
|
|
69
|
+
// a SELECT-only guard ezt már a forrás-rétegben kizárja (defenzív kettősség).
|
|
70
|
+
return params === undefined ? statement.all() : statement.all(params);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A DB ÖSSZES (nem-belső) táblanevének read-only listája (`sqlite_master`, ABC-sorrendben). */
|
|
75
|
+
public static listTables(dbPath: string): string[] {
|
|
76
|
+
return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
|
|
77
|
+
const rows: { name: string }[] = db.prepare(
|
|
78
|
+
'SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\' ORDER BY name',
|
|
79
|
+
).all() as { name: string }[];
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
return rows.map((row) => row.name);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
83
86
|
* Egy tábla séma-leírása (`PRAGMA table_info(<table>)` — a SQLite read-only introspekciója). A
|
|
84
87
|
* `table` nevet `sqlite_master` ellen validáljuk (nem-létező → strukturált hiba). Az oszlop-info
|
|
85
88
|
* (`cid`/`name`/`type`/`notNull`/`defaultValue`/`primaryKey`) normalizált shape-ben tér vissza.
|
|
86
89
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
const rows: PragmaColumnRow[] = db.pragma(`table_info("${table}")`) as PragmaColumnRow[];
|
|
97
|
-
return rows.map((row) => ({
|
|
98
|
-
cid: row.cid,
|
|
99
|
-
name: row.name,
|
|
100
|
-
type: row.type,
|
|
101
|
-
notNull: row.notnull === 1,
|
|
102
|
-
defaultValue: row.dflt_value,
|
|
103
|
-
primaryKey: row.pk > 0,
|
|
104
|
-
}));
|
|
90
|
+
public static getSchema(dbPath: string, table: string): DyNTS_SqliteColumnInfo[] {
|
|
91
|
+
return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
|
|
92
|
+
if (!DyNTS_Sqlite_Reader_Util.tableExists(db, table)) {
|
|
93
|
+
throw new DyFM_Error({
|
|
94
|
+
errorCode: 'DyNTS-SQLITE-READ-002',
|
|
95
|
+
message: `Table '${table}' does not exist in the SQLite DB: '${dbPath}'.`,
|
|
96
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
105
97
|
});
|
|
106
|
-
|
|
98
|
+
}
|
|
99
|
+
const rows: PragmaColumnRow[] = db.pragma(`table_info("${table}")`) as PragmaColumnRow[];
|
|
107
100
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
return rows.map((row) => ({
|
|
102
|
+
cid: row.cid,
|
|
103
|
+
name: row.name,
|
|
104
|
+
type: row.type,
|
|
105
|
+
notNull: row.notnull === 1,
|
|
106
|
+
defaultValue: row.dflt_value,
|
|
107
|
+
primaryKey: row.pk > 0,
|
|
108
|
+
}));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
// =========================================================================
|
|
113
|
+
// Belső segédek (read-only megnyitás + guard-ok)
|
|
114
|
+
// =========================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
113
117
|
* A DB read-only megnyitása + a callback futtatása + GARANTÁLT zárás (try/finally). A `path`
|
|
114
118
|
* validálva (üres/nem-létező → strukturált hiba); a megnyitás-/olvasás-hibát strukturált
|
|
115
119
|
* `DyFM_Error`-rá fordítjuk (a már-DyFM_Error-t NEM csomagoljuk újra). A `readonly:true` az
|
|
116
120
|
* első védvonal (a SQLite-engine elutasít minden írást).
|
|
117
121
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
private static withDb<T>(dbPath: string, work: (db: Database.Database) => T): T {
|
|
123
|
+
if (!dbPath?.trim().length) {
|
|
124
|
+
throw new DyFM_Error({
|
|
125
|
+
errorCode: 'DyNTS-SQLITE-READ-001',
|
|
126
|
+
message: 'A SQLite read requires a non-empty `dbPath` (path to the `.db` file).',
|
|
127
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(dbPath)) {
|
|
132
|
+
throw new DyFM_Error({
|
|
133
|
+
errorCode: 'DyNTS-SQLITE-READ-001',
|
|
134
|
+
message: `The SQLite DB file was not found: '${dbPath}'.`,
|
|
135
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
let db: Database.Database | undefined = undefined;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
142
|
+
|
|
143
|
+
return work(db);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error instanceof DyFM_Error) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
throw new DyFM_Error({
|
|
150
|
+
errorCode: 'DyNTS-SQLITE-READ-003',
|
|
151
|
+
message: `Opening/reading the SQLite DB failed: '${dbPath}'.\n error: `
|
|
144
152
|
+ `${DyFM_Error.getErrorMessage(error)}`,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
154
|
+
error: error,
|
|
155
|
+
});
|
|
156
|
+
} finally {
|
|
157
|
+
if (db) {
|
|
158
|
+
db.close();
|
|
159
|
+
}
|
|
153
160
|
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A SELECT-only guard: nem-SELECT/WITH kezdetű VAGY multi-statement SQL → strukturált hiba. */
|
|
164
|
+
private static assertSelectOnly(sql: string): void {
|
|
165
|
+
const trimmed: string = (sql ?? '').trim();
|
|
154
166
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
message: 'Only read-only SELECT/WITH queries are allowed (the reader is read-only).',
|
|
162
|
-
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
// Multi-statement (`;` után további nem-üres tartalom) tiltott — egy SELECT mögé nem rejthető írás.
|
|
166
|
-
const withoutTrailingSemicolon: string = trimmed.replace(/;\s*$/, '');
|
|
167
|
-
if (withoutTrailingSemicolon.includes(';')) {
|
|
168
|
-
throw new DyFM_Error({
|
|
169
|
-
errorCode: 'DyNTS-SQLITE-READONLY-001',
|
|
170
|
-
message: 'Multi-statement SQL is not allowed (only a single read-only SELECT/WITH).',
|
|
171
|
-
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
167
|
+
if (!trimmed.length || !DyNTS_Sqlite_Reader_Util.SELECT_ONLY.test(trimmed)) {
|
|
168
|
+
throw new DyFM_Error({
|
|
169
|
+
errorCode: 'DyNTS-SQLITE-READONLY-001',
|
|
170
|
+
message: 'Only read-only SELECT/WITH queries are allowed (the reader is read-only).',
|
|
171
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
172
|
+
});
|
|
174
173
|
}
|
|
174
|
+
// Multi-statement (`;` után további nem-üres tartalom) tiltott — egy SELECT mögé nem rejthető írás.
|
|
175
|
+
const withoutTrailingSemicolon: string = trimmed.replace(/;\s*$/, '');
|
|
175
176
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
if (withoutTrailingSemicolon.includes(';')) {
|
|
178
|
+
throw new DyFM_Error({
|
|
179
|
+
errorCode: 'DyNTS-SQLITE-READONLY-001',
|
|
180
|
+
message: 'Multi-statement SQL is not allowed (only a single read-only SELECT/WITH).',
|
|
181
|
+
issuer: DyNTS_Sqlite_Reader_Util.issuer,
|
|
182
|
+
});
|
|
181
183
|
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Egy tábla létezésének read-only ellenőrzése (`sqlite_master`). */
|
|
187
|
+
private static tableExists(db: Database.Database, table: string): boolean {
|
|
188
|
+
const found: unknown =
|
|
189
|
+
db.prepare('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=?').get(table);
|
|
190
|
+
|
|
191
|
+
return Boolean(found);
|
|
192
|
+
}
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
/** A `PRAGMA table_info` nyers sor-shape-je (a `better-sqlite3` ezt adja vissza). */
|
package/src/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { DyFM_DataModel_Params, DyFM_Metadata, DyFM_Object } from '@futdevpro/fsm-dynamo';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* `
|
|
5
|
+
* `DyNTS_LVS_VectorPersist` (BFR-AM-001) — egy MongoDB-ben perzistált vektor-rekord, ami a
|
|
6
6
|
* `LVS_VectorPool_ControlService` **in-memory** pool-jának tartós háttértára. A FAM (fdp-agent-memory)
|
|
7
7
|
* workaround-ját generalizálja: a vektorok a SAJÁT Mongo-ban élnek, boot-kor a memória-pool-ba
|
|
8
8
|
* hidratálódnak — **NEM** MongoDB Atlas Vector Search.
|
|
@@ -15,7 +15,7 @@ import { DyFM_DataModel_Params, DyFM_Metadata, DyFM_Object } from '@futdevpro/fs
|
|
|
15
15
|
* ősből jön. A `metadata` `unknown` (Mongoose `Mixed`) — a fogyasztó tetszőleges kísérő-adatot tárolhat
|
|
16
16
|
* (a séma NEM validál); az `embedding` `number[]` a perzistált nyers vektor.
|
|
17
17
|
*/
|
|
18
|
-
export class
|
|
18
|
+
export class DyNTS_LVS_VectorPersist extends DyFM_Metadata {
|
|
19
19
|
|
|
20
20
|
/** A pool-on belüli vektor-kulcs (a `pool.addVector(vectorId, ...)` ezzel illeszt). */
|
|
21
21
|
vectorId?: string;
|
|
@@ -32,8 +32,9 @@ export class DyNTS_LVS_VectorPersist_DataModel extends DyFM_Metadata {
|
|
|
32
32
|
/** Opcionális fogyasztó-specifikus kísérő-adat (Mongoose `Mixed`; a séma NEM validál). */
|
|
33
33
|
metadata?: unknown;
|
|
34
34
|
|
|
35
|
-
constructor(set?: Partial<
|
|
35
|
+
constructor(set?: Partial<DyNTS_LVS_VectorPersist>) {
|
|
36
36
|
super(set);
|
|
37
|
+
|
|
37
38
|
if (set) {
|
|
38
39
|
DyFM_Object.cleanAssign(this, set);
|
|
39
40
|
}
|
|
@@ -46,8 +47,8 @@ export class DyNTS_LVS_VectorPersist_DataModel extends DyFM_Metadata {
|
|
|
46
47
|
* `vectorId` + `collectionKey` index-elt (a hidratálás `collectionKey`-re, az upsert/remove a
|
|
47
48
|
* `vectorId`+`collectionKey` párra szűr).
|
|
48
49
|
*/
|
|
49
|
-
export const
|
|
50
|
-
new DyFM_DataModel_Params<
|
|
50
|
+
export const DyNTS_lvsVectorPersist_dataParams: DyFM_DataModel_Params<DyNTS_LVS_VectorPersist> =
|
|
51
|
+
new DyFM_DataModel_Params<DyNTS_LVS_VectorPersist>({
|
|
51
52
|
dataName: 'dynts_lvs_vector',
|
|
52
53
|
properties: {
|
|
53
54
|
vectorId: { type: 'string', index: true, required: true },
|