@travetto/model-sql 2.1.5 → 2.2.2

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.
@@ -52,36 +52,37 @@ export class MySQLDialect extends SQLDialect {
52
52
  /**
53
53
  * Compute hash
54
54
  */
55
- hash(value: string) {
55
+ hash(value: string): string {
56
56
  return `SHA2('${value}', ${this.KEY_LEN * 4})`;
57
57
  }
58
58
 
59
59
  /**
60
60
  * Build identifier
61
61
  */
62
- ident(field: FieldConfig | string) {
62
+ ident(field: FieldConfig | string): string {
63
63
  return `\`${typeof field === 'string' ? field : field.name}\``;
64
64
  }
65
65
 
66
66
  /**
67
67
  * Create table, adding in specific engine options
68
68
  */
69
- override getCreateTableSQL(stack: VisitStack[]) {
69
+ override getCreateTableSQL(stack: VisitStack[]): string {
70
70
  return super.getCreateTableSQL(stack).replace(/;$/, ` ${this.tablePostfix};`);
71
71
  }
72
72
 
73
73
  /**
74
74
  * Define column modification
75
75
  */
76
- getModifyColumnSQL(stack: VisitStack[]) {
77
- const field = stack[stack.length - 1];
78
- return `ALTER TABLE ${this.parentTable(stack)} MODIFY COLUMN ${this.getColumnDefinition(field as FieldConfig)};`;
76
+ getModifyColumnSQL(stack: VisitStack[]): string {
77
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
78
+ const field = stack[stack.length - 1] as FieldConfig;
79
+ return `ALTER TABLE ${this.parentTable(stack)} MODIFY COLUMN ${this.getColumnDefinition(field)};`;
79
80
  }
80
81
 
81
82
  /**
82
83
  * Add root alias to delete clause
83
84
  */
84
- override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>) {
85
+ override getDeleteSQL(stack: VisitStack[], where?: WhereClause<unknown>): string {
85
86
  const sql = super.getDeleteSQL(stack, where);
86
87
  return sql.replace(/\bDELETE\b/g, `DELETE ${this.rootAlias}`);
87
88
  }
@@ -28,7 +28,7 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
28
28
  * Initializes connection and establishes crypto extension for use with hashing
29
29
  */
30
30
  @WithAsyncContext()
31
- async init() {
31
+ async init(): Promise<void> {
32
32
  this.#pool = new pg.Pool({
33
33
  user: this.#config.user,
34
34
  password: this.#config.password,
@@ -53,9 +53,10 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
53
53
  console.debug('Executing query', { query });
54
54
  try {
55
55
  const out = await conn.query(query);
56
- return { count: out.rowCount, records: [...out.rows].map(v => ({ ...v })) as T[] };
56
+ const records: T[] = [...out.rows].map(v => ({ ...v }));
57
+ return { count: out.rowCount, records };
57
58
  } catch (err) {
58
- if (err.message.includes('duplicate key value')) {
59
+ if (err instanceof Error && err.message.includes('duplicate key value')) {
59
60
  throw new ExistsError('query', query);
60
61
  } else {
61
62
  throw err;
@@ -63,11 +64,11 @@ export class PostgreSQLConnection extends Connection<pg.PoolClient> {
63
64
  }
64
65
  }
65
66
 
66
- acquire() {
67
+ acquire(): Promise<pg.PoolClient> {
67
68
  return this.#pool.connect();
68
69
  }
69
70
 
70
- release(conn: pg.PoolClient) {
71
+ release(conn: pg.PoolClient): void {
71
72
  conn.release();
72
73
  }
73
74
  }
@@ -41,20 +41,21 @@ export class PostgreSQLDialect extends SQLDialect {
41
41
  /**
42
42
  * How to hash
43
43
  */
44
- hash(value: string) {
44
+ hash(value: string): string {
45
45
  return `encode(digest('${value}', 'sha1'), 'hex')`;
46
46
  }
47
47
 
48
- ident(field: FieldConfig | string) {
48
+ ident(field: FieldConfig | string): string {
49
49
  return `"${typeof field === 'string' ? field : field.name}"`;
50
50
  }
51
51
 
52
52
  /**
53
53
  * Define column modification
54
54
  */
55
- getModifyColumnSQL(stack: VisitStack[]) {
56
- const field = stack[stack.length - 1];
57
- const type = this.getColumnType(field as FieldConfig);
55
+ getModifyColumnSQL(stack: VisitStack[]): string {
56
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
+ const field = stack[stack.length - 1] as FieldConfig;
58
+ const type = this.getColumnType(field);
58
59
  const ident = this.ident(field.name);
59
60
  return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
60
61
  }
@@ -29,12 +29,12 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
29
29
  this.#config = config;
30
30
  }
31
31
 
32
- async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250) {
32
+ async #withRetries<T>(op: () => Promise<T>, retries = 10, delay = 250): Promise<T> {
33
33
  for (; ;) {
34
34
  try {
35
35
  return await op();
36
36
  } catch (err) {
37
- if (retries > 1 && err.message.includes('database is locked')) {
37
+ if (err instanceof Error && retries > 1 && err.message.includes('database is locked')) {
38
38
  console.error('Failed, and waiting', retries);
39
39
  await Util.wait(delay);
40
40
  retries -= 1;
@@ -49,11 +49,11 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
49
49
  * Initializes connection and establishes crypto extension for use with hashing
50
50
  */
51
51
  @WithAsyncContext()
52
- override async init() {
52
+ override async init(): Promise<void> {
53
53
  this.#pool = pool.createPool({
54
54
  create: () => this.#withRetries(async () => {
55
55
  const db = Db(AppCache.toEntryName('sqlite_db'),
56
- this.#config.options as sqlite3.Options
56
+ this.#config.options
57
57
  );
58
58
  await db.pragma('foreign_keys = ON');
59
59
  await db.pragma('journal_mode = WAL');
@@ -73,21 +73,26 @@ export class SqliteConnection extends Connection<sqlite3.Database> {
73
73
  try {
74
74
  const out = await conn.prepare(query)[query.trim().startsWith('SELECT') ? 'all' : 'run']();
75
75
  if (Array.isArray(out)) {
76
- return { count: out.length, records: [...out].map(v => ({ ...v })) as T[] };
76
+ const records: T[] = [...out].map(v => ({ ...v }));
77
+ return { count: out.length, records };
77
78
  } else {
78
79
  return { count: out.changes, records: [] };
79
80
  }
80
81
  } catch (err) {
81
- throw err.message.includes('UNIQUE constraint failed') ? new ExistsError('query', query) : err;
82
+ if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
83
+ throw new ExistsError('query', query);
84
+ } else {
85
+ throw err;
86
+ }
82
87
  }
83
88
  });
84
89
  }
85
90
 
86
- async acquire() {
91
+ async acquire(): Promise<Db.Database> {
87
92
  return this.#pool.acquire();
88
93
  }
89
94
 
90
- async release(db: sqlite3.Database) {
95
+ async release(db: sqlite3.Database): Promise<void> {
91
96
  return this.#pool.release(db);
92
97
  }
93
98
  }
@@ -34,30 +34,31 @@ export class SqliteDialect extends SQLDialect {
34
34
  });
35
35
  }
36
36
 
37
- override resolveDateValue(value: Date) {
37
+ override resolveDateValue(value: Date): string {
38
38
  return `${value.getTime()}`;
39
39
  }
40
40
 
41
41
  /**
42
42
  * How to hash
43
43
  */
44
- hash(value: string) {
44
+ hash(value: string): string {
45
45
  return `hex('${value}')`;
46
46
  }
47
47
 
48
48
  /**
49
49
  * Build identifier
50
50
  */
51
- ident(field: FieldConfig | string) {
51
+ ident(field: FieldConfig | string): string {
52
52
  return `\`${typeof field === 'string' ? field : field.name}\``;
53
53
  }
54
54
 
55
55
  /**
56
56
  * Define column modification
57
57
  */
58
- getModifyColumnSQL(stack: VisitStack[]) {
59
- const field = stack[stack.length - 1];
60
- const type = this.getColumnType(field as FieldConfig);
58
+ getModifyColumnSQL(stack: VisitStack[]): string {
59
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
60
+ const field = stack[stack.length - 1] as FieldConfig;
61
+ const type = this.getColumnType(field);
61
62
  const ident = this.ident(field.name);
62
63
  return `ALTER TABLE ${this.parentTable(stack)} ALTER COLUMN ${ident} TYPE ${type} USING (${ident}::${type});`;
63
64
  }
@@ -65,7 +66,7 @@ export class SqliteDialect extends SQLDialect {
65
66
  /**
66
67
  * Generate truncate SQL
67
68
  */
68
- override getTruncateTableSQL(stack: VisitStack[]) {
69
+ override getTruncateTableSQL(stack: VisitStack[]): string {
69
70
  return `DELETE FROM ${this.table(stack)};`;
70
71
  }
71
72
 
@@ -16,17 +16,19 @@ export type VisitStack = {
16
16
  index?: number;
17
17
  };
18
18
 
19
+ type FieldCacheEntry = {
20
+ local: FieldConfig[];
21
+ localMap: Record<string, FieldConfig>;
22
+ foreign: FieldConfig[];
23
+ foreignMap: Record<string, FieldConfig>;
24
+ };
25
+
19
26
  /**
20
27
  * Utilities for dealing with SQL operations
21
28
  */
22
29
  export class SQLUtil {
23
30
 
24
- static SCHEMA_FIELDS_CACHE = new Map<Class, {
25
- local: FieldConfig[];
26
- localMap: Record<string, FieldConfig>;
27
- foreign: FieldConfig[];
28
- foreignMap: Record<string, FieldConfig>;
29
- }>();
31
+ static SCHEMA_FIELDS_CACHE = new Map<Class, FieldCacheEntry>();
30
32
 
31
33
  /**
32
34
  * Creates a new visitation stack with the class as the root
@@ -44,15 +46,17 @@ export class SQLUtil {
44
46
  if (Array.isArray(o)) {
45
47
  return o.filter(x => x !== null && x !== undefined).map(x => this.cleanResults(dct, x));
46
48
  } else if (!Util.isSimple(o)) {
47
- for (const k of Object.keys(o) as (keyof T)[]) {
49
+ for (const k of Object.keys(o)) {
48
50
  if (o[k] === null || o[k] === undefined || k === dct.parentPathField.name || k === dct.pathField.name || k === dct.idxField.name) {
49
51
  delete o[k];
50
52
  } else {
51
53
  o[k] = this.cleanResults(dct, o[k]);
52
54
  }
53
55
  }
56
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
54
57
  return { ...o } as unknown as U[];
55
58
  } else {
59
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
56
60
  return o as unknown as U;
57
61
  }
58
62
  }
@@ -60,7 +64,7 @@ export class SQLUtil {
60
64
  /**
61
65
  * Get all available fields at current stack path
62
66
  */
63
- static getFieldsByLocation(stack: VisitStack[]) {
67
+ static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
64
68
  const top = stack[stack.length - 1];
65
69
  const cls = SchemaRegistry.get(top.type);
66
70
 
@@ -69,6 +73,7 @@ export class SQLUtil {
69
73
  }
70
74
 
71
75
  if (!cls) { // If a simple type, it is it's own field
76
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
72
77
  const field = { ...top } as FieldConfig;
73
78
  return {
74
79
  local: [field], localMap: { [field.name]: field },
@@ -94,9 +99,9 @@ export class SQLUtil {
94
99
  }
95
100
  }
96
101
 
97
- const ret = {
98
- localMap: {} as Record<string, FieldConfig>,
99
- foreignMap: {} as Record<string, FieldConfig>,
102
+ const ret: FieldCacheEntry = {
103
+ localMap: {},
104
+ foreignMap: {},
100
105
  local: fields.filter(x => !SchemaRegistry.has(x.type) && !x.array),
101
106
  foreign: fields.filter(x => SchemaRegistry.has(x.type) || x.array)
102
107
  };
@@ -112,11 +117,11 @@ export class SQLUtil {
112
117
  /**
113
118
  * Process a schema structure, synchronously
114
119
  */
115
- static visitSchemaSync(config: ClassConfig | FieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }) {
120
+ static visitSchemaSync(config: ClassConfig | FieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
116
121
  const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
117
122
  const { local: fields, foreign } = this.getFieldsByLocation(path);
118
123
 
119
- const descend = () => {
124
+ const descend = (): void => {
120
125
  for (const field of foreign) {
121
126
  if (SchemaRegistry.has(field.type)) {
122
127
  this.visitSchemaSync(field, handler, { path });
@@ -140,11 +145,11 @@ export class SQLUtil {
140
145
  /**
141
146
  * Visit a Schema structure
142
147
  */
143
- static async visitSchema(config: ClassConfig | FieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }) {
148
+ static async visitSchema(config: ClassConfig | FieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
144
149
  const path = 'class' in config ? this.classToStack(config.class) : [...state.path, config];
145
150
  const { local: fields, foreign } = this.getFieldsByLocation(path);
146
151
 
147
- const descend = async () => {
152
+ const descend = async (): Promise<void> => {
148
153
  for (const field of foreign) {
149
154
  if (SchemaRegistry.has(field.type)) {
150
155
  await this.visitSchema(field, handler, { path });
@@ -168,7 +173,7 @@ export class SQLUtil {
168
173
  /**
169
174
  * Process a schema instance by visiting it synchronously. This is synchronous to prevent concurrent calls from breaking
170
175
  */
171
- static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>) {
176
+ static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
172
177
  const pathObj: unknown[] = [instance];
173
178
  this.visitSchemaSync(SchemaRegistry.get(cls), {
174
179
  onRoot: (config) => {
@@ -179,15 +184,16 @@ export class SQLUtil {
179
184
  },
180
185
  onSub: (config) => {
181
186
  const { config: field } = config;
187
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
182
188
  const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
183
189
  const top = config.path[config.path.length - 1];
184
190
 
185
191
  if (field.name in topObj) {
186
192
  const value = topObj[field.name];
187
- const vals = Array.isArray(value) ? value : [value];
193
+ const values = Array.isArray(value) ? value : [value];
188
194
 
189
195
  let i = 0;
190
- for (const val of vals) {
196
+ for (const val of values) {
191
197
  try {
192
198
  pathObj.push(val);
193
199
  config.path[config.path.length - 1] = { ...top, index: i++ };
@@ -202,6 +208,7 @@ export class SQLUtil {
202
208
  },
203
209
  onSimple: (config) => {
204
210
  const { config: field } = config;
211
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
205
212
  const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
206
213
  const value = topObj[field.name];
207
214
  return handler.onSimple({ ...config, value });
@@ -214,7 +221,7 @@ export class SQLUtil {
214
221
  */
215
222
  static select<T>(cls: Class<T>, select?: SelectClause<T>): FieldConfig[] {
216
223
  if (!select || Object.keys(select).length === 0) {
217
- return [{ type: cls, name: '*' } as FieldConfig];
224
+ return [{ type: cls, name: '*', owner: cls, array: false }];
218
225
  }
219
226
 
220
227
  const { localMap } = this.getFieldsByLocation(this.classToStack(cls));
@@ -222,7 +229,9 @@ export class SQLUtil {
222
229
  let toGet = new Set<string>();
223
230
 
224
231
  for (const [k, v] of Object.entries(select)) {
225
- if (!Util.isPlainObject(select[k as keyof typeof select]) && localMap[k]) {
232
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
233
+ const typedKey = k as keyof typeof select;
234
+ if (!Util.isPlainObject(select[typedKey]) && localMap[k]) {
226
235
  if (!v) {
227
236
  if (toGet.size === 0) {
228
237
  toGet = new Set(SchemaRegistry.get(cls).views[AllViewⲐ].fields);
@@ -254,6 +263,7 @@ export class SQLUtil {
254
263
  } else {
255
264
  stack.push(field);
256
265
  schema = SchemaRegistry.get(field.type);
266
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
257
267
  cl = val as Record<string, unknown>;
258
268
  }
259
269
  }
@@ -264,18 +274,24 @@ export class SQLUtil {
264
274
  /**
265
275
  * Find all dependent fields via child tables
266
276
  */
267
- static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?: FieldConfig) {
277
+ static collectDependents<T>(dct: DialectState, parent: unknown, v: T[], field?: FieldConfig): Record<string, T> {
268
278
  if (field) {
269
279
  const isSimple = SchemaRegistry.has(field.type);
270
280
  for (const el of v) {
271
- const root = (parent as { [k: string]: unknown })[el[dct.parentPathField.name as keyof T] as unknown as string] as unknown as Record<string, unknown>;
281
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
282
+ const parentKey = el[dct.parentPathField.name as keyof T] as unknown as string;
283
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
284
+ const root = (parent as Record<string, Record<string, unknown>>)[parentKey];
272
285
  if (field.array) {
273
286
  if (!root[field.name]) {
287
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
274
288
  root[field.name] = [isSimple ? el : el[field.name as keyof T]];
275
289
  } else {
290
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
276
291
  (root[field.name] as unknown[]).push(isSimple ? el : el[field.name as keyof T]);
277
292
  }
278
293
  } else {
294
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
279
295
  root[field.name] = isSimple ? el : el[field.name as keyof T];
280
296
  }
281
297
  }
@@ -283,6 +299,7 @@ export class SQLUtil {
283
299
 
284
300
  const mapping: Record<string, T> = {};
285
301
  for (const el of v) {
302
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
286
303
  const key = el[dct.pathField.name as keyof T];
287
304
  if (typeof key === 'string') {
288
305
  mapping[key] = el;
@@ -294,7 +311,7 @@ export class SQLUtil {
294
311
  /**
295
312
  * Build table name via stack path
296
313
  */
297
- static buildTable(list: VisitStack[]) {
314
+ static buildTable(list: VisitStack[]): string {
298
315
  const top = list[list.length - 1];
299
316
  if (!top[TableⲐ]) {
300
317
  top[TableⲐ] = list.map((el, i) => i === 0 ? ModelRegistry.getStore(el.type) : el.name).join('_');
@@ -305,7 +322,7 @@ export class SQLUtil {
305
322
  /**
306
323
  * Build property path for a table/field given the current stack
307
324
  */
308
- static buildPath(list: VisitStack[]) {
325
+ static buildPath(list: VisitStack[]): string {
309
326
  return list.map((el, i) => `${el.name}${el.index ? `[${el.index}]` : ''}`).join('.');
310
327
  }
311
328
 
@@ -313,9 +330,9 @@ export class SQLUtil {
313
330
  * Get insert statements for a given class, and its child tables
314
331
  */
315
332
  static async getInserts<T extends ModelType>(cls: Class<T>, els: T[]): Promise<InsertWrapper[]> {
316
- const ins = {} as Record<string, InsertWrapper>;
333
+ const ins: Record<string, InsertWrapper> = {};
317
334
 
318
- const track = (stack: VisitStack[], value: unknown) => {
335
+ const track = (stack: VisitStack[], value: unknown): void => {
319
336
  const key = this.buildTable(stack);
320
337
  (ins[key] = ins[key] ?? { stack, records: [] }).records.push({ stack, value });
321
338
  };
package/src/service.ts CHANGED
@@ -26,6 +26,8 @@ import { Connected, ConnectedIterator, Transactional } from './connection/decora
26
26
  import { SQLUtil } from './internal/util';
27
27
  import { SQLDialect } from './dialect/base';
28
28
  import { TableManager } from './table-manager';
29
+ import { Connection } from './connection/base';
30
+ import { InsertWrapper } from './internal/types';
29
31
 
30
32
  /**
31
33
  * Core for SQL Model Source. Should not have any direct queries,
@@ -45,7 +47,7 @@ export class SQLModelService implements
45
47
 
46
48
  readonly config: SQLModelConfig;
47
49
 
48
- get client() {
50
+ get client(): SQLDialect {
49
51
  return this.#dialect;
50
52
  }
51
53
 
@@ -66,7 +68,7 @@ export class SQLModelService implements
66
68
  cls: Class<T>,
67
69
  addedIds: Map<number, string>,
68
70
  toCheck: Map<string, number>
69
- ) {
71
+ ): Promise<Map<number, string>> {
70
72
  // Get all upsert ids
71
73
  const all = toCheck.size ?
72
74
  (await this.#exec<ModelType>(
@@ -86,20 +88,21 @@ export class SQLModelService implements
86
88
  return addedIds;
87
89
  }
88
90
 
89
- #exec<T = unknown>(sql: string) {
91
+ #exec<T = unknown>(sql: string): Promise<{ records: T[], count: number }> {
90
92
  return this.#dialect.executeSQL<T>(sql);
91
93
  }
92
94
 
93
- async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true) {
95
+ async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, checkExpiry = true): Promise<void> {
96
+ const where: WhereClauseRaw<ModelType> = { id };
94
97
  const count = await this.#dialect.deleteAndGetCount<ModelType>(cls, {
95
- where: ModelQueryUtil.getWhereClause(cls, { id } as WhereClauseRaw<T>, checkExpiry)
98
+ where: ModelQueryUtil.getWhereClause(cls, where, checkExpiry)
96
99
  });
97
100
  if (count === 0) {
98
101
  throw new NotFoundError(cls, id);
99
102
  }
100
103
  }
101
104
 
102
- async postConstruct() {
105
+ async postConstruct(): Promise<void> {
103
106
  if (this.#dialect) {
104
107
  if (this.#dialect.conn.init) {
105
108
  await this.#dialect.conn.init();
@@ -110,32 +113,32 @@ export class SQLModelService implements
110
113
  }
111
114
  }
112
115
 
113
- get conn() {
116
+ get conn(): Connection {
114
117
  return this.#dialect.conn;
115
118
  }
116
119
 
117
- uuid() {
120
+ uuid(): string {
118
121
  return this.#dialect.generateId();
119
122
  }
120
123
 
121
- async changeSchema(cls: Class, change: SchemaChange) {
124
+ async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
122
125
  await this.#manager.changeSchema(cls, change);
123
126
  }
124
127
 
125
- async createModel(cls: Class) {
128
+ async createModel(cls: Class): Promise<void> {
126
129
  await this.#manager.createTables(cls);
127
130
  }
128
131
 
129
- async deleteModel(cls: Class) {
132
+ async deleteModel(cls: Class): Promise<void> {
130
133
  await this.#manager.dropTables(cls);
131
134
  }
132
135
 
133
- async truncateModel(cls: Class) {
136
+ async truncateModel(cls: Class): Promise<void> {
134
137
  await this.#manager.truncateTables(cls);
135
138
  }
136
139
 
137
- async createStorage() { }
138
- async deleteStorage() { }
140
+ async createStorage(): Promise<void> { }
141
+ async deleteStorage(): Promise<void> { }
139
142
 
140
143
  @Transactional()
141
144
  async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
@@ -183,7 +186,8 @@ export class SQLModelService implements
183
186
 
184
187
  @Connected()
185
188
  async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
186
- const res = await this.query(cls, { where: { id } as WhereClauseRaw<T> });
189
+ const where: WhereClauseRaw<ModelType> = { id };
190
+ const res = await this.query(cls, { where });
187
191
  if (res.length === 1) {
188
192
  return await ModelCrudUtil.load(cls, res[0]);
189
193
  }
@@ -191,14 +195,14 @@ export class SQLModelService implements
191
195
  }
192
196
 
193
197
  @ConnectedIterator()
194
- async * list<T extends ModelType>(cls: Class<T>) {
198
+ async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
195
199
  for (const item of await this.query(cls, {})) {
196
200
  yield await ModelCrudUtil.load(cls, item);
197
201
  }
198
202
  }
199
203
 
200
204
  @Transactional()
201
- async delete<T extends ModelType>(cls: Class<T>, id: string) {
205
+ async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
202
206
  await this.#deleteRaw(cls, id, false);
203
207
  }
204
208
 
@@ -214,8 +218,10 @@ export class SQLModelService implements
214
218
  new Map([...existingUpsertedIds.entries()].map(([k, v]) => [v, k]))
215
219
  );
216
220
 
217
- const get = (k: keyof BulkOp<T>) => operations.map(x => x[k]).filter(x => !!x) as T[];
218
- const getStatements = async (k: keyof BulkOp<T>) => (await SQLUtil.getInserts(cls, get(k))).filter(x => !!x.records.length);
221
+ const get = (k: keyof BulkOp<T>): T[] =>
222
+ operations.map(x => x[k]).filter((x): x is T => !!x);
223
+ const getStatements = async (k: keyof BulkOp<T>): Promise<InsertWrapper[]> =>
224
+ (await SQLUtil.getInserts(cls, get(k))).filter(x => !!x.records.length);
219
225
 
220
226
  const deletes = [{ stack: SQLUtil.classToStack(cls), ids: get('delete').map(x => x.id) }].filter(x => !!x.ids.length);
221
227
 
@@ -232,7 +238,7 @@ export class SQLModelService implements
232
238
 
233
239
  // Expiry
234
240
  @Transactional()
235
- async deleteExpired<T extends ModelType>(cls: Class<T>) {
241
+ async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
236
242
  return ModelQueryExpiryUtil.deleteExpired(this, cls);
237
243
  }
238
244
 
@@ -284,12 +290,15 @@ export class SQLModelService implements
284
290
  async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
285
291
  const q = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
286
292
  const results = await this.query(cls, q);
287
- return ModelQuerySuggestUtil.combineSuggestResults(cls, field as 'type', prefix, results, x => x, query?.limit);
293
+
294
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
295
+ const modelTypeField = field as ValidStringFields<ModelType>;
296
+ return ModelQuerySuggestUtil.combineSuggestResults(cls, modelTypeField, prefix, results, x => x, query?.limit);
288
297
  }
289
298
 
290
299
  @Connected()
291
300
  async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<{ key: string, count: number }[]> {
292
- const col = this.#dialect.ident(field as string);
301
+ const col = this.#dialect.ident(field);
293
302
  const ttl = this.#dialect.ident('count');
294
303
  const key = this.#dialect.ident('key');
295
304
  const q = [
@@ -7,6 +7,7 @@ import { SchemaChange } from '@travetto/schema';
7
7
  import { Connected, Transactional } from './connection/decorator';
8
8
  import { SQLDialect } from './dialect/base';
9
9
  import { SQLUtil, VisitStack } from './internal/util';
10
+ import { Connection } from './connection/base';
10
11
 
11
12
  /**
12
13
  * Manage creation/updating of all tables
@@ -22,7 +23,7 @@ export class TableManager {
22
23
  this.#dialect = dialect;
23
24
  }
24
25
 
25
- #exec<T = unknown>(sql: string) {
26
+ #exec<T = unknown>(sql: string): Promise<{ records: T[], count: number }> {
26
27
  return this.#dialect.executeSQL<T>(sql);
27
28
  }
28
29
 
@@ -41,9 +42,12 @@ export class TableManager {
41
42
  for (const op of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
42
43
  try {
43
44
  await this.#exec(op);
44
- } catch (e) {
45
- if (!/\bexists|duplicate\b/i.test(e.message)) {
46
- throw e;
45
+ } catch (err) {
46
+ if (!(err instanceof Error)) {
47
+ throw err;
48
+ }
49
+ if (!/\bexists|duplicate\b/i.test(err.message)) {
50
+ throw err;
47
51
  }
48
52
  }
49
53
  }
@@ -78,7 +82,7 @@ export class TableManager {
78
82
  /**
79
83
  * Get a valid connection
80
84
  */
81
- get conn() {
85
+ get conn(): Connection {
82
86
  return this.#dialect.conn;
83
87
  }
84
88
 
@@ -88,24 +92,24 @@ export class TableManager {
88
92
  @WithAsyncContext({})
89
93
  @Transactional()
90
94
  @Connected()
91
- async changeSchema(cls: Class, change: SchemaChange) {
95
+ async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
92
96
  try {
93
97
  const rootStack = SQLUtil.classToStack(cls);
94
98
 
95
- const changes = change.subs.reduce((acc, v) => {
99
+ const changes = change.subs.reduce<Record<ChangeEvent<unknown>['type'], VisitStack[][]>>((acc, v) => {
96
100
  const path = v.path.map(f => ({ ...f }));
97
101
  for (const ev of v.fields) {
98
102
  acc[ev.type].push([...rootStack, ...path, { ...(ev.type === 'removing' ? ev.prev : ev.curr)! }]);
99
103
  }
100
104
  return acc;
101
- }, { added: [], changed: [], removing: [] } as Record<ChangeEvent<unknown>['type'], VisitStack[][]>);
105
+ }, { added: [], changed: [], removing: [] });
102
106
 
103
107
  await Promise.all(changes.added.map(v => this.#dialect.executeSQL(this.#dialect.getAddColumnSQL(v))));
104
108
  await Promise.all(changes.changed.map(v => this.#dialect.executeSQL(this.#dialect.getModifyColumnSQL(v))));
105
109
  await Promise.all(changes.removing.map(v => this.#dialect.executeSQL(this.#dialect.getDropColumnSQL(v))));
106
- } catch (e) {
110
+ } catch (err) {
107
111
  // Failed to change
108
- console.error('Unable to change field', { error: e });
112
+ console.error('Unable to change field', { error: err });
109
113
  }
110
114
  }
111
115
  }