metal-orm 1.1.2 → 1.1.4
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/README.md +728 -707
- package/dist/index.cjs +813 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +277 -8
- package/dist/index.d.ts +277 -8
- package/dist/index.js +812 -75
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/scripts/naming-strategy.mjs +16 -1
- package/src/cache/adapters/keyv-cache-adapter.ts +5 -0
- package/src/cache/adapters/memory-cache-adapter.ts +5 -0
- package/src/cache/adapters/redis-cache-adapter.ts +233 -0
- package/src/cache/cache-interfaces.ts +11 -0
- package/src/cache/index.ts +2 -0
- package/src/core/ast/procedure.ts +21 -0
- package/src/core/ast/query.ts +47 -19
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/dialect/abstract.ts +560 -547
- package/src/core/dialect/base/sql-dialect.ts +43 -29
- package/src/core/dialect/mssql/index.ts +369 -232
- package/src/core/dialect/mysql/index.ts +99 -7
- package/src/core/dialect/postgres/index.ts +121 -60
- package/src/core/dialect/sqlite/index.ts +97 -64
- package/src/core/execution/db-executor.ts +108 -90
- package/src/core/execution/executors/mssql-executor.ts +28 -24
- package/src/core/execution/executors/mysql-executor.ts +62 -27
- package/src/core/execution/executors/sqlite-executor.ts +10 -9
- package/src/index.ts +9 -6
- package/src/orm/execute-procedure.ts +77 -0
- package/src/orm/execute.ts +74 -73
- package/src/orm/interceptor-pipeline.ts +21 -17
- package/src/orm/pooled-executor-factory.ts +41 -20
- package/src/orm/unit-of-work.ts +6 -4
- package/src/query/index.ts +8 -5
- package/src/query-builder/delete.ts +3 -2
- package/src/query-builder/insert-query-state.ts +47 -19
- package/src/query-builder/insert.ts +142 -28
- package/src/query-builder/procedure-call.ts +122 -0
- package/src/query-builder/select/select-operations.ts +5 -2
- package/src/query-builder/select.ts +1146 -1105
- package/src/query-builder/update.ts +3 -2
- package/src/tree/tree-manager.ts +754 -754
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metal-orm",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"engines": {
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"pg": "^8.0.0",
|
|
45
45
|
"sqlite3": "^5.1.6",
|
|
46
46
|
"tedious": "^19.0.0",
|
|
47
|
-
"keyv": "^5.6.0"
|
|
47
|
+
"keyv": "^5.6.0",
|
|
48
|
+
"ioredis": "^5.0.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependenciesMeta": {
|
|
50
51
|
"mysql2": {
|
|
@@ -61,6 +62,9 @@
|
|
|
61
62
|
},
|
|
62
63
|
"keyv": {
|
|
63
64
|
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"ioredis": {
|
|
67
|
+
"optional": true
|
|
64
68
|
}
|
|
65
69
|
},
|
|
66
70
|
"devDependencies": {
|
|
@@ -70,6 +74,8 @@
|
|
|
70
74
|
"@vitest/ui": "^4.0.18",
|
|
71
75
|
"eslint": "^9.39.2",
|
|
72
76
|
"express": "^5.2.1",
|
|
77
|
+
"ioredis": "^5.6.1",
|
|
78
|
+
"ioredis-mock": "^8.9.0",
|
|
73
79
|
"keyv": "^5.6.0",
|
|
74
80
|
"mysql-memory-server": "^1.14.0",
|
|
75
81
|
"mysql2": "^3.16.2",
|
|
@@ -95,10 +95,25 @@ export class BaseNamingStrategy {
|
|
|
95
95
|
|
|
96
96
|
belongsToProperty(foreignKeyName, targetTable) {
|
|
97
97
|
const trimmed = foreignKeyName.replace(/_?id$/i, '');
|
|
98
|
-
const
|
|
98
|
+
const targetBase = this.singularize(this.normalizeTableName(targetTable));
|
|
99
|
+
// If FK name ends with _id, use the trimmed version
|
|
100
|
+
// If FK name doesn't end with _id but is different from target table name, use the FK name (e.g., "criador", "responsavel_judicial")
|
|
101
|
+
// Otherwise fallback to target table name
|
|
102
|
+
const base = trimmed && trimmed !== foreignKeyName
|
|
103
|
+
? trimmed
|
|
104
|
+
: trimmed && this.toCamelCase(trimmed) !== this.toCamelCase(targetBase)
|
|
105
|
+
? trimmed
|
|
106
|
+
: targetBase;
|
|
99
107
|
return this.toCamelCase(base);
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
normalizeTableName(tableName) {
|
|
111
|
+
// Strip schema prefix if present (e.g., "dbo.usuario" -> "usuario")
|
|
112
|
+
return typeof tableName === 'string' && tableName.includes('.')
|
|
113
|
+
? tableName.split('.').pop()
|
|
114
|
+
: tableName;
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
hasManyProperty(targetTable) {
|
|
103
118
|
const base = this.singularize(targetTable);
|
|
104
119
|
const plural = this.inflector.pluralizeRelationProperty
|
|
@@ -11,6 +11,11 @@ interface CacheEntry<T> {
|
|
|
11
11
|
*/
|
|
12
12
|
export class MemoryCacheAdapter implements CacheProvider {
|
|
13
13
|
readonly name = 'memory';
|
|
14
|
+
readonly capabilities = {
|
|
15
|
+
tags: true,
|
|
16
|
+
prefix: true,
|
|
17
|
+
ttl: true,
|
|
18
|
+
};
|
|
14
19
|
private storage: Map<string, CacheEntry<unknown>> = new Map();
|
|
15
20
|
private tagIndex: Map<string, Set<string>> = new Map();
|
|
16
21
|
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { CacheProvider } from '../cache-interfaces.js';
|
|
2
|
+
|
|
3
|
+
// Tipos mínimos para ioredis (para evitar dependência obrigatória)
|
|
4
|
+
interface RedisLike {
|
|
5
|
+
get(key: string): Promise<string | null>;
|
|
6
|
+
set(key: string, value: string, ...args: (string | number)[]): Promise<string | null>;
|
|
7
|
+
del(...keys: string[]): Promise<number>;
|
|
8
|
+
sadd(key: string, ...members: string[]): Promise<number>;
|
|
9
|
+
smembers(key: string): Promise<string[]>;
|
|
10
|
+
srem(key: string, ...members: string[]): Promise<number>;
|
|
11
|
+
scan(cursor: string | number, ...args: (string | number)[]): Promise<[string, string[]]>;
|
|
12
|
+
quit(): Promise<string>;
|
|
13
|
+
disconnect(): void;
|
|
14
|
+
isReady?: boolean;
|
|
15
|
+
status?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface RedisOptions {
|
|
19
|
+
host?: string;
|
|
20
|
+
port?: number;
|
|
21
|
+
password?: string;
|
|
22
|
+
db?: number;
|
|
23
|
+
keyPrefix?: string;
|
|
24
|
+
lazyConnect?: boolean;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adapter para Redis usando ioredis
|
|
30
|
+
*
|
|
31
|
+
* Suporta:
|
|
32
|
+
* - Tags via Redis Sets (SADD, SMEMBERS, SREM)
|
|
33
|
+
* - Prefix invalidation via SCAN
|
|
34
|
+
* - TTL nativo do Redis
|
|
35
|
+
*
|
|
36
|
+
* Instalação:
|
|
37
|
+
* npm install ioredis
|
|
38
|
+
*
|
|
39
|
+
* Para testes (dev):
|
|
40
|
+
* npm install --save-dev ioredis-mock
|
|
41
|
+
*/
|
|
42
|
+
export class RedisCacheAdapter implements CacheProvider {
|
|
43
|
+
readonly name = 'redis';
|
|
44
|
+
readonly capabilities = {
|
|
45
|
+
tags: true,
|
|
46
|
+
prefix: true,
|
|
47
|
+
ttl: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
private redis: RedisLike;
|
|
51
|
+
private ownsConnection: boolean;
|
|
52
|
+
private tagPrefix: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Cria um adapter Redis
|
|
56
|
+
*
|
|
57
|
+
* @param redis - Instância do ioredis OU opções de conexão
|
|
58
|
+
* @param options - Opções adicionais
|
|
59
|
+
* @param options.tagPrefix - Prefixo para chaves de tag (default: 'tag:')
|
|
60
|
+
*
|
|
61
|
+
* Exemplos:
|
|
62
|
+
*
|
|
63
|
+
* // Com instância existente (recomendado para connection pooling):
|
|
64
|
+
* const redis = new Redis({ host: 'localhost', port: 6379 });
|
|
65
|
+
* const adapter = new RedisCacheAdapter(redis);
|
|
66
|
+
*
|
|
67
|
+
* // Com opções (adapter gerencia conexão):
|
|
68
|
+
* const adapter = new RedisCacheAdapter({ host: 'localhost', port: 6379 });
|
|
69
|
+
*
|
|
70
|
+
* // Para testes com ioredis-mock:
|
|
71
|
+
* import Redis from 'ioredis-mock';
|
|
72
|
+
* const adapter = new RedisCacheAdapter(new Redis());
|
|
73
|
+
*/
|
|
74
|
+
constructor(
|
|
75
|
+
redis: RedisLike | RedisOptions,
|
|
76
|
+
options?: { tagPrefix?: string }
|
|
77
|
+
) {
|
|
78
|
+
this.tagPrefix = options?.tagPrefix ?? 'tag:';
|
|
79
|
+
|
|
80
|
+
if (this.isRedisInstance(redis)) {
|
|
81
|
+
// Recebeu uma instância existente
|
|
82
|
+
this.redis = redis;
|
|
83
|
+
this.ownsConnection = false;
|
|
84
|
+
} else {
|
|
85
|
+
// Recebeu opções, precisa criar a conexão
|
|
86
|
+
this.redis = this.createRedis(redis);
|
|
87
|
+
this.ownsConnection = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private isRedisInstance(obj: unknown): obj is RedisLike {
|
|
92
|
+
return (
|
|
93
|
+
typeof obj === 'object' &&
|
|
94
|
+
obj !== null &&
|
|
95
|
+
'get' in obj &&
|
|
96
|
+
'set' in obj &&
|
|
97
|
+
'del' in obj &&
|
|
98
|
+
typeof (obj as RedisLike).get === 'function'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private createRedis(options: RedisOptions): RedisLike {
|
|
103
|
+
// Dynamic import para evitar dependência obrigatória
|
|
104
|
+
try {
|
|
105
|
+
const Redis = require('ioredis');
|
|
106
|
+
return new Redis(options) as RedisLike;
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'ioredis is required for RedisCacheAdapter. ' +
|
|
110
|
+
'Install it with: npm install ioredis'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async get<T>(key: string): Promise<T | undefined> {
|
|
116
|
+
const value = await this.redis.get(key);
|
|
117
|
+
if (value === null) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(value) as T;
|
|
122
|
+
} catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async has(key: string): Promise<boolean> {
|
|
128
|
+
const value = await this.redis.get(key);
|
|
129
|
+
return value !== null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async set<T>(
|
|
133
|
+
key: string,
|
|
134
|
+
value: T,
|
|
135
|
+
ttlMs?: number,
|
|
136
|
+
tags?: string[]
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
const serialized = JSON.stringify(value);
|
|
139
|
+
|
|
140
|
+
if (ttlMs) {
|
|
141
|
+
// EX = seconds, PX = milliseconds
|
|
142
|
+
await this.redis.set(key, serialized, 'PX', ttlMs);
|
|
143
|
+
} else {
|
|
144
|
+
await this.redis.set(key, serialized);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Registra tags se fornecidas
|
|
148
|
+
if (tags && tags.length > 0) {
|
|
149
|
+
await this.registerTags(key, tags);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async delete(key: string): Promise<void> {
|
|
154
|
+
await this.redis.del(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async invalidate(key: string): Promise<void> {
|
|
158
|
+
await this.delete(key);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async invalidateTags(tags: string[]): Promise<void> {
|
|
162
|
+
const keysToDelete = new Set<string>();
|
|
163
|
+
|
|
164
|
+
for (const tag of tags) {
|
|
165
|
+
const tagKey = `${this.tagPrefix}${tag}`;
|
|
166
|
+
const keys = await this.redis.smembers(tagKey);
|
|
167
|
+
|
|
168
|
+
for (const key of keys) {
|
|
169
|
+
keysToDelete.add(key);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Deleta o set da tag
|
|
173
|
+
await this.redis.del(tagKey);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Deleta todas as chaves associadas
|
|
177
|
+
if (keysToDelete.size > 0) {
|
|
178
|
+
await this.redis.del(...Array.from(keysToDelete));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async invalidatePrefix(prefix: string): Promise<void> {
|
|
183
|
+
const keysToDelete: string[] = [];
|
|
184
|
+
let cursor = '0';
|
|
185
|
+
|
|
186
|
+
do {
|
|
187
|
+
const [nextCursor, keys] = await this.redis.scan(
|
|
188
|
+
cursor,
|
|
189
|
+
'MATCH',
|
|
190
|
+
`${prefix}*`,
|
|
191
|
+
'COUNT',
|
|
192
|
+
100
|
|
193
|
+
);
|
|
194
|
+
cursor = nextCursor;
|
|
195
|
+
keysToDelete.push(...keys);
|
|
196
|
+
} while (cursor !== '0');
|
|
197
|
+
|
|
198
|
+
if (keysToDelete.length > 0) {
|
|
199
|
+
// Deleta em batches de 1000 para evitar bloqueio
|
|
200
|
+
const batchSize = 1000;
|
|
201
|
+
for (let i = 0; i < keysToDelete.length; i += batchSize) {
|
|
202
|
+
const batch = keysToDelete.slice(i, i + batchSize);
|
|
203
|
+
await this.redis.del(...batch);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async registerTags(key: string, tags: string[]): Promise<void> {
|
|
209
|
+
for (const tag of tags) {
|
|
210
|
+
const tagKey = `${this.tagPrefix}${tag}`;
|
|
211
|
+
await this.redis.sadd(tagKey, key);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async dispose(): Promise<void> {
|
|
216
|
+
if (this.ownsConnection) {
|
|
217
|
+
try {
|
|
218
|
+
await this.redis.quit();
|
|
219
|
+
} catch {
|
|
220
|
+
// Se quit falhar, tenta disconnect
|
|
221
|
+
this.redis.disconnect?.();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Retorna a instância Redis subjacente
|
|
228
|
+
* Útil para operações avançadas ou health checks
|
|
229
|
+
*/
|
|
230
|
+
getRedis(): RedisLike {
|
|
231
|
+
return this.redis;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -27,11 +27,22 @@ export interface CacheInvalidator {
|
|
|
27
27
|
invalidatePrefix(prefix: string): Promise<void>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Capabilities de um cache provider
|
|
32
|
+
* Permite detectar funcionalidades suportadas em runtime
|
|
33
|
+
*/
|
|
34
|
+
export interface CacheCapabilities {
|
|
35
|
+
tags: boolean;
|
|
36
|
+
prefix: boolean;
|
|
37
|
+
ttl: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
/**
|
|
31
41
|
* Interface completa para implementações full-featured
|
|
32
42
|
*/
|
|
33
43
|
export interface CacheProvider extends CacheReader, CacheWriter, CacheInvalidator {
|
|
34
44
|
readonly name: string;
|
|
45
|
+
readonly capabilities: CacheCapabilities;
|
|
35
46
|
dispose?(): Promise<void>;
|
|
36
47
|
}
|
|
37
48
|
|
package/src/cache/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type {
|
|
|
4
4
|
CacheWriter,
|
|
5
5
|
CacheInvalidator,
|
|
6
6
|
CacheProvider,
|
|
7
|
+
CacheCapabilities,
|
|
7
8
|
Duration,
|
|
8
9
|
CacheOptions,
|
|
9
10
|
InvalidationStrategy,
|
|
@@ -20,6 +21,7 @@ export { DefaultCacheStrategy } from './strategies/default-cache-strategy.js';
|
|
|
20
21
|
// Adapters
|
|
21
22
|
export { MemoryCacheAdapter } from './adapters/memory-cache-adapter.js';
|
|
22
23
|
export { KeyvCacheAdapter } from './adapters/keyv-cache-adapter.js';
|
|
24
|
+
export { RedisCacheAdapter } from './adapters/redis-cache-adapter.js';
|
|
23
25
|
|
|
24
26
|
// Manager
|
|
25
27
|
export { QueryCacheManager } from './query-cache-manager.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OperandNode } from './expression.js';
|
|
2
|
+
|
|
3
|
+
export type ProcedureDirection = 'in' | 'out' | 'inout';
|
|
4
|
+
|
|
5
|
+
export interface ProcedureRefNode {
|
|
6
|
+
name: string;
|
|
7
|
+
schema?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProcedureParamNode {
|
|
11
|
+
name: string;
|
|
12
|
+
direction: ProcedureDirection;
|
|
13
|
+
value?: OperandNode;
|
|
14
|
+
dbType?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProcedureCallNode {
|
|
18
|
+
type: 'ProcedureCall';
|
|
19
|
+
ref: ProcedureRefNode;
|
|
20
|
+
params: ProcedureParamNode[];
|
|
21
|
+
}
|
package/src/core/ast/query.ts
CHANGED
|
@@ -161,25 +161,53 @@ export interface InsertValuesSourceNode {
|
|
|
161
161
|
rows: OperandNode[][];
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export interface InsertSelectSourceNode {
|
|
165
|
-
type: 'InsertSelect';
|
|
166
|
-
/** SELECT query providing rows */
|
|
167
|
-
query: SelectQueryNode;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
|
|
171
|
-
|
|
172
|
-
export interface
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
export interface InsertSelectSourceNode {
|
|
165
|
+
type: 'InsertSelect';
|
|
166
|
+
/** SELECT query providing rows */
|
|
167
|
+
query: SelectQueryNode;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
|
|
171
|
+
|
|
172
|
+
export interface UpsertConflictTarget {
|
|
173
|
+
/** Conflict columns (primary key or unique columns) */
|
|
174
|
+
columns: ColumnNode[];
|
|
175
|
+
/** Named constraint (PostgreSQL only) */
|
|
176
|
+
constraint?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface UpsertUpdateAction {
|
|
180
|
+
type: 'DoUpdate';
|
|
181
|
+
/** Assignments to apply on conflict */
|
|
182
|
+
set: UpdateAssignmentNode[];
|
|
183
|
+
/** Optional condition for the update branch */
|
|
184
|
+
where?: ExpressionNode;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface UpsertDoNothingAction {
|
|
188
|
+
type: 'DoNothing';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type UpsertAction = UpsertUpdateAction | UpsertDoNothingAction;
|
|
192
|
+
|
|
193
|
+
export interface UpsertClause {
|
|
194
|
+
target: UpsertConflictTarget;
|
|
195
|
+
action: UpsertAction;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface InsertQueryNode {
|
|
199
|
+
type: 'InsertQuery';
|
|
200
|
+
/** Target table */
|
|
201
|
+
into: TableNode;
|
|
202
|
+
/** Column order for inserted values */
|
|
203
|
+
columns: ColumnNode[];
|
|
204
|
+
/** Source of inserted rows (either literal values or a SELECT query) */
|
|
205
|
+
source: InsertSourceNode;
|
|
206
|
+
/** Optional dialect-specific UPSERT clause */
|
|
207
|
+
onConflict?: UpsertClause;
|
|
208
|
+
/** Optional RETURNING clause */
|
|
209
|
+
returning?: ColumnNode[];
|
|
210
|
+
}
|
|
183
211
|
|
|
184
212
|
export interface UpdateAssignmentNode {
|
|
185
213
|
/** Column to update */
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
|
-
import { IntrospectOptions } from './types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Converts a query result to an array of row objects.
|
|
6
|
-
* @param result - The query result.
|
|
7
|
-
* @returns The array of rows.
|
|
8
|
-
*/
|
|
9
|
-
export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
|
|
10
|
-
if (!result) return [];
|
|
11
|
-
return result.values.map(row =>
|
|
12
|
-
result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
|
|
13
|
-
acc[col] = row[idx];
|
|
14
|
-
return acc;
|
|
15
|
-
}, {})
|
|
16
|
-
);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Executes a SQL query and returns the rows.
|
|
21
|
-
* @param executor - The database executor.
|
|
22
|
-
* @param sql - The SQL query.
|
|
23
|
-
* @param params - The query parameters.
|
|
24
|
-
* @returns The array of rows.
|
|
25
|
-
*/
|
|
26
|
-
export const queryRows = async (
|
|
27
|
-
executor: DbExecutor,
|
|
28
|
-
sql: string,
|
|
29
|
-
params: unknown[] = []
|
|
30
|
-
): Promise<Record<string, unknown>[]> => {
|
|
31
|
-
const [first] = await executor.executeSql(sql, params);
|
|
32
|
-
return toRows(first);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if a table should be included in introspection based on options.
|
|
37
|
-
* @param name - The table name.
|
|
38
|
-
* @param options - The introspection options.
|
|
39
|
-
* @returns True if the table should be included.
|
|
40
|
-
*/
|
|
41
|
-
export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
|
|
42
|
-
if (options.includeTables && !options.includeTables.includes(name)) return false;
|
|
43
|
-
if (options.excludeTables && options.excludeTables.includes(name)) return false;
|
|
44
|
-
return true;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Checks if a view should be included in introspection based on options.
|
|
49
|
-
* @param name - The view name.
|
|
50
|
-
* @param options - The introspection options.
|
|
51
|
-
* @returns True if the view should be included.
|
|
52
|
-
*/
|
|
53
|
-
export const shouldIncludeView = (name: string, options: IntrospectOptions): boolean => {
|
|
54
|
-
if (options.excludeViews && options.excludeViews.includes(name)) return false;
|
|
55
|
-
return true;
|
|
56
|
-
};
|
|
1
|
+
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
|
+
import { IntrospectOptions } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a query result to an array of row objects.
|
|
6
|
+
* @param result - The query result.
|
|
7
|
+
* @returns The array of rows.
|
|
8
|
+
*/
|
|
9
|
+
export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
|
|
10
|
+
if (!result) return [];
|
|
11
|
+
return result.values.map(row =>
|
|
12
|
+
result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
|
|
13
|
+
acc[col] = row[idx];
|
|
14
|
+
return acc;
|
|
15
|
+
}, {})
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Executes a SQL query and returns the rows.
|
|
21
|
+
* @param executor - The database executor.
|
|
22
|
+
* @param sql - The SQL query.
|
|
23
|
+
* @param params - The query parameters.
|
|
24
|
+
* @returns The array of rows.
|
|
25
|
+
*/
|
|
26
|
+
export const queryRows = async (
|
|
27
|
+
executor: DbExecutor,
|
|
28
|
+
sql: string,
|
|
29
|
+
params: unknown[] = []
|
|
30
|
+
): Promise<Record<string, unknown>[]> => {
|
|
31
|
+
const [first] = await executor.executeSql(sql, params);
|
|
32
|
+
return toRows(first);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a table should be included in introspection based on options.
|
|
37
|
+
* @param name - The table name.
|
|
38
|
+
* @param options - The introspection options.
|
|
39
|
+
* @returns True if the table should be included.
|
|
40
|
+
*/
|
|
41
|
+
export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
|
|
42
|
+
if (options.includeTables && !options.includeTables.includes(name)) return false;
|
|
43
|
+
if (options.excludeTables && options.excludeTables.includes(name)) return false;
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a view should be included in introspection based on options.
|
|
49
|
+
* @param name - The view name.
|
|
50
|
+
* @param options - The introspection options.
|
|
51
|
+
* @returns True if the view should be included.
|
|
52
|
+
*/
|
|
53
|
+
export const shouldIncludeView = (name: string, options: IntrospectOptions): boolean => {
|
|
54
|
+
if (options.excludeViews && options.excludeViews.includes(name)) return false;
|
|
55
|
+
return true;
|
|
56
|
+
};
|