@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.
Files changed (108) hide show
  1. package/.dynamo/logs/cicd-pipeline/output.log +1746 -1819
  2. package/.dynamo/logs/cicd-pipeline/status.json +37 -37
  3. package/.github/workflows/main.yml +432 -426
  4. package/build/_collections/global-settings.const.d.ts.map +1 -1
  5. package/build/_collections/global-settings.const.js +6 -0
  6. package/build/_collections/global-settings.const.js.map +1 -1
  7. package/build/_collections/mongo-reconnect-guard.util.d.ts +74 -0
  8. package/build/_collections/mongo-reconnect-guard.util.d.ts.map +1 -0
  9. package/build/_collections/mongo-reconnect-guard.util.js +111 -0
  10. package/build/_collections/mongo-reconnect-guard.util.js.map +1 -0
  11. package/build/_models/interfaces/global-settings.interface.d.ts +21 -0
  12. package/build/_models/interfaces/global-settings.interface.d.ts.map +1 -1
  13. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.d.ts.map +1 -1
  14. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js +2 -2
  15. package/build/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.js.map +1 -1
  16. package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts +1 -1
  17. package/build/_modules/ai/_services/ai-embedding-mock.service.d.ts.map +1 -1
  18. package/build/_modules/ai/_services/ai-embedding-mock.service.js.map +1 -1
  19. package/build/_modules/ai/_services/ai-embedding-provider.registry.d.ts.map +1 -1
  20. package/build/_modules/ai/_services/ai-embedding-provider.registry.js.map +1 -1
  21. package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts +1 -1
  22. package/build/_modules/ai/_services/lmstudio-embedding.control-service.d.ts.map +1 -1
  23. package/build/_modules/ai/_services/lmstudio-embedding.control-service.js +3 -3
  24. package/build/_modules/ai/_services/lmstudio-embedding.control-service.js.map +1 -1
  25. package/build/_modules/ai/index.d.ts +2 -0
  26. package/build/_modules/ai/index.d.ts.map +1 -1
  27. package/build/_modules/ai/index.js +4 -0
  28. package/build/_modules/ai/index.js.map +1 -1
  29. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts +17 -17
  30. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.d.ts.map +1 -1
  31. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js +21 -21
  32. package/build/_modules/data-readers/_collections/dynts-sqlite-reader.util.js.map +1 -1
  33. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts +4 -4
  34. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.d.ts.map +1 -1
  35. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js +5 -5
  36. package/build/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.js.map +1 -1
  37. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts +4 -4
  38. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.d.ts.map +1 -1
  39. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js +4 -4
  40. package/build/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.js.map +1 -1
  41. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts +6 -6
  42. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.d.ts.map +1 -1
  43. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js +5 -5
  44. package/build/_modules/local-vector-search/_services/lvs-vector-persist.data-service.js.map +1 -1
  45. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.d.ts +1 -1
  46. package/build/_modules/mcp/_models/interfaces/dynts-mcp.interface.js +1 -1
  47. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts +4 -4
  48. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.d.ts.map +1 -1
  49. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js +6 -6
  50. package/build/_modules/mcp/_services/dynts-mcp-server.service-base.js.map +1 -1
  51. package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts +11 -11
  52. package/build/_modules/mcp/_services/dynts-mcp.adapter.d.ts.map +1 -1
  53. package/build/_modules/mcp/_services/dynts-mcp.adapter.js +16 -11
  54. package/build/_modules/mcp/_services/dynts-mcp.adapter.js.map +1 -1
  55. package/build/_modules/mcp/index.js +1 -1
  56. package/build/_modules/mcp/index.js.map +1 -1
  57. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts +3 -3
  58. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.d.ts.map +1 -1
  59. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js +4 -4
  60. package/build/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.js.map +1 -1
  61. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.d.ts.map +1 -1
  62. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js +9 -0
  63. package/build/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.js.map +1 -1
  64. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts +3 -3
  65. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.d.ts.map +1 -1
  66. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js +1 -1
  67. package/build/_modules/scoped-config/_services/dynts-scoped-config.control-service.js.map +1 -1
  68. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts +7 -7
  69. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.d.ts.map +1 -1
  70. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js +2 -2
  71. package/build/_modules/scoped-config/_services/dynts-scoped-config.data-service.js.map +1 -1
  72. package/build/_services/core/global.service.d.ts.map +1 -1
  73. package/build/_services/core/global.service.js +15 -2
  74. package/build/_services/core/global.service.js.map +1 -1
  75. package/build/_services/server/app.server.d.ts.map +1 -1
  76. package/build/_services/server/app.server.js +21 -0
  77. package/build/_services/server/app.server.js.map +1 -1
  78. package/package.json +1 -1
  79. package/src/_collections/global-settings.const.ts +7 -0
  80. package/src/_collections/mongo-reconnect-guard.util.spec.ts +52 -0
  81. package/src/_collections/mongo-reconnect-guard.util.ts +172 -0
  82. package/src/_models/interfaces/global-settings.interface.ts +22 -0
  83. package/src/_modules/ai/_modules/document-ai/_collections/dai-code-chunking.util.ts +39 -7
  84. package/src/_modules/ai/_services/ai-embedding-mock.service.ts +18 -4
  85. package/src/_modules/ai/_services/ai-embedding-provider.registry.ts +4 -0
  86. package/src/_modules/ai/_services/lmstudio-embedding.control-service.ts +26 -5
  87. package/src/_modules/ai/index.ts +5 -0
  88. package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.spec.ts +145 -130
  89. package/src/_modules/data-readers/_collections/dynts-sqlite-reader.util.ts +131 -120
  90. package/src/_modules/local-vector-search/_models/data-models/lvs-vector-persist.data-model.ts +6 -5
  91. package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.spec.ts +35 -35
  92. package/src/_modules/local-vector-search/_services/lvs-persistent-vector-pool.control-service.ts +9 -5
  93. package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.spec.ts +11 -11
  94. package/src/_modules/local-vector-search/_services/lvs-vector-persist.data-service.ts +19 -17
  95. package/src/_modules/mcp/_models/interfaces/dynts-mcp.interface.ts +1 -1
  96. package/src/_modules/mcp/_services/dynts-mcp-server.service-base.spec.ts +123 -114
  97. package/src/_modules/mcp/_services/dynts-mcp-server.service-base.ts +44 -39
  98. package/src/_modules/mcp/_services/dynts-mcp.adapter.ts +114 -103
  99. package/src/_modules/mcp/index.ts +1 -1
  100. package/src/_modules/scoped-config/_models/data-models/dynts-scoped-config.data-model.ts +5 -4
  101. package/src/_modules/scoped-config/_models/interfaces/dynts-scoped-config.interface.ts +0 -2
  102. package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.spec.ts +19 -13
  103. package/src/_modules/scoped-config/_services/dynts-scoped-config.control-service.ts +37 -21
  104. package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.spec.ts +11 -6
  105. package/src/_modules/scoped-config/_services/dynts-scoped-config.data-service.ts +17 -14
  106. package/src/_services/core/global.service.spec.ts +17 -0
  107. package/src/_services/core/global.service.ts +19 -5
  108. 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
- /** A temp DB-fájl útvonala (minden teszt-futáshoz egyedi). */
26
- let dbPath: string;
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
- beforeAll(() => {
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
- db.prepare('INSERT INTO users (id, name, age) VALUES (?, ?, ?)').run(1, 'Ada', 36);
36
- db.prepare('INSERT INTO users (id, name, age) VALUES (?, ?, ?)').run(2, 'Grace', 40);
37
- db.prepare('INSERT INTO posts (id, title) VALUES (?, ?)').run(1, 'Hello');
38
- db.close();
39
- });
40
-
41
- afterAll(() => {
42
- if (dbPath && fs.existsSync(dbPath)) {
43
- fs.unlinkSync(dbPath);
44
- }
45
- });
46
-
47
- /** Egy hívás, ami egy adott errorCode-dal kell hogy dobjon (strukturált, NEM néma). */
48
- const expectErrorCode = (fn: () => unknown, expectedCode: string): void => {
49
- let thrown: unknown;
50
- try {
51
- fn();
52
- } catch (error) {
53
- thrown = error;
54
- }
55
- expect(thrown).withContext(`dobnia kellett (${expectedCode})`).toBeDefined();
56
- const code: string | undefined =
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
- expect(code).withContext('a hibakód').toBe(expectedCode);
59
- };
60
-
61
- describe('listTables', () => {
62
- it('a user-táblák ABC-sorrendben (a belső sqlite_% táblák kiszűrve)', () => {
63
- expect(DyNTS_Sqlite_Reader_Util.listTables(dbPath)).toEqual(['posts', 'users']);
64
- });
65
- });
66
-
67
- describe('getSchema', () => {
68
- it('a users tábla oszlop-info-ja (name/type/notNull/primaryKey)', () => {
69
- const schema: DyNTS_SqliteColumnInfo[] = DyNTS_Sqlite_Reader_Util.getSchema(dbPath, 'users');
70
- expect(schema.map((column) => column.name)).toEqual(['id', 'name', 'age']);
71
- const idColumn: DyNTS_SqliteColumnInfo = schema.find((column) => column.name === 'id')!;
72
- expect(idColumn.primaryKey).toBe(true);
73
- const nameColumn: DyNTS_SqliteColumnInfo = schema.find((column) => column.name === 'name')!;
74
- expect(nameColumn.notNull).toBe(true);
75
- expect(nameColumn.type).toBe('TEXT');
76
- });
77
-
78
- it('nem-létező tábla → DyNTS-SQLITE-READ-002 (NEM néma)', () => {
79
- expectErrorCode(() => DyNTS_Sqlite_Reader_Util.getSchema(dbPath, 'no_such_table'), 'DyNTS-SQLITE-READ-002');
80
- });
81
- });
82
-
83
- describe('readTable', () => {
84
- it('a users tábla minden sora', () => {
85
- const rows: { id: number; name: string }[] =
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
- expect(rows.length).toBe(2);
88
- expect(rows.map((row) => row.name).sort()).toEqual(['Ada', 'Grace']);
89
- });
90
-
91
- it('nem-létező tábla → DyNTS-SQLITE-READ-002 (NEM néma-[])', () => {
92
- expectErrorCode(() => DyNTS_Sqlite_Reader_Util.readTable(dbPath, 'ghost'), 'DyNTS-SQLITE-READ-002');
93
- });
94
- });
95
-
96
- describe('query (SELECT-only)', () => {
97
- it('egy SELECT lekérdezés sorai', () => {
98
- const rows: { name: string }[] =
99
- DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT name FROM users ORDER BY age DESC') as { name: string }[];
100
- expect(rows.map((row) => row.name)).toEqual(['Grace', 'Ada']);
101
- });
102
-
103
- it('paraméteres SELECT (prepared-statement param)', () => {
104
- const rows: { name: string }[] =
105
- DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT * FROM users WHERE age > ?', [37]) as { name: string }[];
106
- expect(rows.map((row) => row.name)).toEqual(['Grace']);
107
- });
108
-
109
- it('WITH (CTE) is engedett (read-only)', () => {
110
- const rows: { n: number }[] = DyNTS_Sqlite_Reader_Util.query(dbPath,
111
- 'WITH adults AS (SELECT * FROM users WHERE age >= 36) SELECT COUNT(*) AS n FROM adults') as { n: number }[];
112
- expect(rows[0].n).toBe(2);
113
- });
114
-
115
- it('INSERT → DyNTS-SQLITE-READONLY-001 (elutasít a forrás-rétegben)', () => {
116
- expectErrorCode(
117
- () => DyNTS_Sqlite_Reader_Util.query(dbPath, "INSERT INTO users (id, name) VALUES (9, 'x')"),
118
- 'DyNTS-SQLITE-READONLY-001');
119
- });
120
-
121
- it('UPDATE DyNTS-SQLITE-READONLY-001', () => {
122
- expectErrorCode(
123
- () => DyNTS_Sqlite_Reader_Util.query(dbPath, "UPDATE users SET name='x' WHERE id=1"),
124
- 'DyNTS-SQLITE-READONLY-001');
125
- });
126
-
127
- it('DELETE DyNTS-SQLITE-READONLY-001', () => {
128
- expectErrorCode(
129
- () => DyNTS_Sqlite_Reader_Util.query(dbPath, 'DELETE FROM users'),
130
- 'DyNTS-SQLITE-READONLY-001');
131
- });
132
-
133
- it('DROP DyNTS-SQLITE-READONLY-001', () => {
134
- expectErrorCode(
135
- () => DyNTS_Sqlite_Reader_Util.query(dbPath, 'DROP TABLE users'),
136
- 'DyNTS-SQLITE-READONLY-001');
137
- });
138
-
139
- it('multi-statement (SELECT; DELETE) DyNTS-SQLITE-READONLY-001 (nem rejthető írás SELECT mögé)', () => {
140
- expectErrorCode(
141
- () => DyNTS_Sqlite_Reader_Util.query(dbPath, 'SELECT 1; DELETE FROM users'),
142
- 'DyNTS-SQLITE-READONLY-001');
143
- });
144
-
145
- it('üres SQL DyNTS-SQLITE-READONLY-001', () => {
146
- expectErrorCode(() => DyNTS_Sqlite_Reader_Util.query(dbPath, ' '), 'DyNTS-SQLITE-READONLY-001');
147
- });
148
- });
149
-
150
- describe('path-validáció (fail-soft, NEM crash)', () => {
151
- it('üres path → DyNTS-SQLITE-READ-001', () => {
152
- expectErrorCode(() => DyNTS_Sqlite_Reader_Util.listTables(''), 'DyNTS-SQLITE-READ-001');
153
- });
154
-
155
- it('nem-létező fájl → DyNTS-SQLITE-READ-001', () => {
156
- expectErrorCode(
157
- () => DyNTS_Sqlite_Reader_Util.listTables(path.join(os.tmpdir(), 'no-such-db-file-xyz.db')),
158
- 'DyNTS-SQLITE-READ-001');
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
- DyNTS_SqliteColumnInfo,
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
- /** A hibák issuer-e (a strukturált DyFM_Error-okhoz). */
33
- private static readonly issuer: string = 'DyNTS_Sqlite_Reader_Util';
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
- /** A `query()` SELECT-only guard regex-e: a (trimmelt, lowercase) SQL `select`/`with`-tel kezdődik. */
36
- private static readonly SELECT_ONLY: RegExp = /^\s*(select|with)\b/i;
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
- public static readTable(dbPath: string, table: string): unknown[] {
44
- return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
45
- if (!DyNTS_Sqlite_Reader_Util.tableExists(db, table)) {
46
- throw new DyFM_Error({
47
- errorCode: 'DyNTS-SQLITE-READ-002',
48
- message: `Table '${table}' does not exist in the SQLite DB: '${dbPath}'.`,
49
- issuer: DyNTS_Sqlite_Reader_Util.issuer,
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
- public static query(dbPath: string, sql: string, params?: unknown[] | { [key: string]: unknown }): unknown[] {
63
- DyNTS_Sqlite_Reader_Util.assertSelectOnly(sql);
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
- /** A DB ÖSSZES (nem-belső) táblanevének read-only listája (`sqlite_master`, ABC-sorrendben). */
73
- public static listTables(dbPath: string): string[] {
74
- return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
75
- const rows: { name: string }[] = db.prepare(
76
- "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
77
- ).all() as { name: string }[];
78
- return rows.map((row) => row.name);
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
- public static getSchema(dbPath: string, table: string): DyNTS_SqliteColumnInfo[] {
88
- return DyNTS_Sqlite_Reader_Util.withDb(dbPath, (db) => {
89
- if (!DyNTS_Sqlite_Reader_Util.tableExists(db, table)) {
90
- throw new DyFM_Error({
91
- errorCode: 'DyNTS-SQLITE-READ-002',
92
- message: `Table '${table}' does not exist in the SQLite DB: '${dbPath}'.`,
93
- issuer: DyNTS_Sqlite_Reader_Util.issuer,
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
- // Belső segédek (read-only megnyitás + guard-ok)
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
- private static withDb<T>(dbPath: string, work: (db: Database.Database) => T, options?: DyNTS_SqliteReadOptions): T {
119
- if (!dbPath || !dbPath.trim().length) {
120
- throw new DyFM_Error({
121
- errorCode: 'DyNTS-SQLITE-READ-001',
122
- message: 'A SQLite read requires a non-empty `dbPath` (path to the `.db` file).',
123
- issuer: DyNTS_Sqlite_Reader_Util.issuer,
124
- });
125
- }
126
- if (!fs.existsSync(dbPath)) {
127
- throw new DyFM_Error({
128
- errorCode: 'DyNTS-SQLITE-READ-001',
129
- message: `The SQLite DB file was not found: '${dbPath}'.`,
130
- issuer: DyNTS_Sqlite_Reader_Util.issuer,
131
- });
132
- }
133
- let db: Database.Database | undefined = undefined;
134
- try {
135
- db = new Database(dbPath, { readonly: true, fileMustExist: true });
136
- return work(db);
137
- } catch (error) {
138
- if (error instanceof DyFM_Error) {
139
- throw error;
140
- }
141
- throw new DyFM_Error({
142
- errorCode: 'DyNTS-SQLITE-READ-003',
143
- message: `Opening/reading the SQLite DB failed: '${dbPath}'.\n error: `
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
- issuer: DyNTS_Sqlite_Reader_Util.issuer,
146
- error: error,
147
- });
148
- } finally {
149
- if (db) {
150
- db.close();
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
- /** A SELECT-only guard: nem-SELECT/WITH kezdetű VAGY multi-statement SQL → strukturált hiba. */
156
- private static assertSelectOnly(sql: string): void {
157
- const trimmed: string = (sql ?? '').trim();
158
- if (!trimmed.length || !DyNTS_Sqlite_Reader_Util.SELECT_ONLY.test(trimmed)) {
159
- throw new DyFM_Error({
160
- errorCode: 'DyNTS-SQLITE-READONLY-001',
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
- /** Egy tábla létezésének read-only ellenőrzése (`sqlite_master`). */
177
- private static tableExists(db: Database.Database, table: string): boolean {
178
- const found: unknown =
179
- db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table);
180
- return Boolean(found);
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). */
@@ -2,7 +2,7 @@
2
2
  import { DyFM_DataModel_Params, DyFM_Metadata, DyFM_Object } from '@futdevpro/fsm-dynamo';
3
3
 
4
4
  /**
5
- * `DyNTS_LVS_VectorPersist_DataModel` (BFR-AM-001) — egy MongoDB-ben perzistált vektor-rekord, ami a
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 DyNTS_LVS_VectorPersist_DataModel extends DyFM_Metadata {
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<DyNTS_LVS_VectorPersist_DataModel>) {
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 dyNTS_lvsVectorPersist_dataParams: DyFM_DataModel_Params<DyNTS_LVS_VectorPersist_DataModel> =
50
- new DyFM_DataModel_Params<DyNTS_LVS_VectorPersist_DataModel>({
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 },