Skip to content

Commit 13cea2b

Browse files
committed
Query result (and related objects) is now generic on the row type
1 parent a64516e commit 13cea2b

13 files changed

+161
-109
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
"@typescript-eslint"
1616
],
1717
"rules": {
18+
"@typescript-eslint/no-explicit-any": 0
1819
}
1920
}

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
In next release ...
22

3+
- The `Value` type has been replaced with `any`, motivated by the new
4+
generic result type as well as the possibility to implement custom
5+
value readers which could return objects of any type.
6+
7+
- Query results are now generic with typing support for the `get`
8+
method on each row.
9+
10+
In addition, a new `map` method now produces records which conform
11+
to the specified type. This method is available on all of the result
12+
objects.
13+
314
- Use lookup table to optimize `get` method.
415

516
- The `connect` method now returns a boolean status of whether the

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ The client uses an async/await-based programming model.
3535
```typescript
3636
import { Client } from 'ts-postgres';
3737

38+
interface Greeting {
39+
message: string;
40+
}
41+
3842
async function main() {
3943
const client = new Client();
4044
await client.connect();
4145

4246
try {
43-
// Querying the client returns a query result promise
44-
// which is also an asynchronous result iterator.
45-
const result = client.query(
47+
// The query method is generic on the result row.
48+
const result = client.query<Greeting>(
4649
"SELECT 'Hello ' || $1 || '!' AS message",
4750
['world']
4851
);
@@ -112,9 +115,14 @@ const query = new Query(
112115
"SELECT 'Hello ' || $1 || '!' AS message",
113116
['world']
114117
);
115-
const result = await client.execute(query);
118+
const result = await client.execute<Greeting>(query);
116119
```
117120

121+
If the row type is omitted, it defaults to `Record<string, any>`, but
122+
providing a type ensures that the row values are typed, both when
123+
accessed via the `get` method or if the row is mapped to a record
124+
using the `map` method.
125+
118126
### Passing query parameters
119127

120128
Query parameters use the format `$1`, `$2` etc.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ import { ConnectionOptions, TLSSocket, connect as tls, createSecureContext } fro
1515

1616
import {
1717
DataHandler,
18-
Result as _Result,
19-
ResultIterator as _ResultIterator,
20-
ResultRow as _ResultRow,
18+
ResultIterator,
19+
ResultRecord,
2120
makeResult
2221
} from './result';
2322

@@ -37,19 +36,11 @@ import {
3736
import {
3837
DataFormat,
3938
DataType,
40-
Row,
41-
Value,
4239
ValueTypeReader
4340
} from './types';
4441

4542
import { md5 } from './utils';
4643

47-
export type Result = _Result<Value>;
48-
49-
export type ResultIterator = _ResultIterator<Value>;
50-
51-
export type ResultRow = _ResultRow<Value>;
52-
5344
export type Connect = Error | null;
5445

5546
export type End = void;
@@ -67,7 +58,7 @@ export interface ClientNotice extends DatabaseError {
6758

6859
export interface DataTypeError {
6960
dataType: DataType,
70-
value: Value
61+
value: any
7162
}
7263

7364
export enum SSLMode {
@@ -100,14 +91,14 @@ export interface Notification {
10091
payload?: string
10192
}
10293

103-
export interface PreparedStatement {
94+
export interface PreparedStatement<T = ResultRecord> {
10495
close: (portal?: string) => Promise<void>;
10596
execute: (
106-
values?: Value[],
97+
values?: any[],
10798
portal?: string,
10899
format?: DataFormat | DataFormat[],
109100
streams?: Record<string, Writable>,
110-
) => ResultIterator
101+
) => ResultIterator<T>
111102
}
112103

113104
type Callback<T> = (data: T) => void;
@@ -127,7 +118,7 @@ type Event = (
127118
type CloseHandler = () => void;
128119

129120
interface RowDataHandler {
130-
callback: DataHandler<Row>,
121+
callback: DataHandler<any[]>,
131122
streams: Record<string, Writable>,
132123
}
133124

@@ -154,7 +145,7 @@ interface Bind {
154145
name: string;
155146
format: DataFormat | DataFormat[]
156147
portal: string;
157-
values: Value[],
148+
values: any[],
158149
close: boolean
159150
}
160151

@@ -188,7 +179,7 @@ export class Client {
188179
private expect = 5;
189180
private stream = new Socket();
190181
private mustDrain = false;
191-
private activeRow: Array<Value> | null = null;
182+
private activeRow: Array<any> | null = null;
192183

193184
private bindQueue = new Queue<RowDataHandlerInfo | null>();
194185
private closeHandlerQueue = new Queue<CloseHandler | null>();
@@ -513,18 +504,18 @@ export class Client {
513504
}
514505
}
515506

516-
prepare(
507+
prepare<T = ResultRecord>(
517508
text: string,
518509
name?: string,
519-
types?: DataType[]): Promise<PreparedStatement> {
510+
types?: DataType[]): Promise<PreparedStatement<T>> {
520511

521512
const providedNameOrGenerated = name || (
522513
(this.config.preparedStatementPrefix ||
523514
defaults.preparedStatementPrefix) + (
524515
this.nextPreparedStatementId++
525516
));
526517

527-
return new Promise<PreparedStatement>(
518+
return new Promise<PreparedStatement<T>>(
528519
(resolve, reject) => {
529520
const errorHandler: ErrorHandler = (error) => reject(error);
530521
this.errorHandlerQueue.push(errorHandler);
@@ -551,12 +542,12 @@ export class Client {
551542
);
552543
},
553544
execute: (
554-
values?: Value[],
545+
values?: any[],
555546
portal?: string,
556547
format?: DataFormat | DataFormat[],
557548
streams?: Record<string, Writable>,
558549
) => {
559-
const result = makeResult<Value>();
550+
const result = makeResult<T>();
560551
result.nameHandler(description.names);
561552
const info = {
562553
handler: {
@@ -587,13 +578,13 @@ export class Client {
587578
});
588579
}
589580

590-
query(
581+
query<T = ResultRecord>(
591582
text: string,
592-
values?: Value[],
583+
values?: any[],
593584
types?: DataType[],
594585
format?: DataFormat | DataFormat[],
595586
streams?: Record<string, Writable>):
596-
ResultIterator {
587+
ResultIterator<T> {
597588
const query =
598589
(typeof text === 'string') ?
599590
new Query(
@@ -604,7 +595,7 @@ export class Client {
604595
streams: streams,
605596
}) :
606597
text;
607-
return this.execute(query);
598+
return this.execute<T>(query);
608599
}
609600

610601
private bindAndExecute(
@@ -642,7 +633,7 @@ export class Client {
642633
this.send();
643634
}
644635

645-
execute(query: Query): ResultIterator {
636+
execute<T = ResultRecord>(query: Query): ResultIterator<T> {
646637
if (this.closed && !this.connecting) {
647638
throw new Error('Connection is closed.');
648639
}
@@ -654,7 +645,7 @@ export class Client {
654645
const types = options ? options.types : undefined;
655646
const streams = options ? options.streams : undefined;
656647
const portal = (options ? options.portal : undefined) || '';
657-
const result = makeResult<Value>();
648+
const result = makeResult<T>();
658649

659650
const descriptionHandler = (description: RowDescription) => {
660651
result.nameHandler(description.names);
@@ -877,7 +868,7 @@ export class Client {
877868

878869
if (row === null) {
879870
const count = buffer.readInt16BE(start);
880-
row = new Array<Value>(count);
871+
row = new Array<any>(count);
881872
}
882873

883874
const startRowData = start + 2;

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './client';
22
export * from './types';
33
export * from './query';
4+
export * from './result';
45
export { DatabaseError } from './protocol';

src/protocol.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@ import { sum } from './utils';
77
import {
88
arrayDataTypeMapping,
99
isPoint,
10-
ArrayValue,
1110
DataFormat,
1211
DataType,
13-
Primitive,
14-
Value,
1512
ValueTypeReader
1613
} from './types';
1714

@@ -278,7 +275,7 @@ export function readRowDescription(
278275

279276
export function readRowData(
280277
buffer: Buffer,
281-
row: Array<Value>,
278+
row: Array<any>,
282279
columnSpecification: Uint32Array,
283280
encoding: BufferEncoding,
284281
types: ReadonlyMap<DataType, ValueTypeReader> | null,
@@ -309,7 +306,7 @@ export function readRowData(
309306
const remaining = end - bufferLength;
310307
const partial = remaining > 0;
311308

312-
let value: Value = null;
309+
let value: any = null;
313310

314311
if (start < end) {
315312
const spec = columnSpecification[j];
@@ -449,7 +446,7 @@ export function readRowData(
449446
let offset = start;
450447

451448
const readArray = (size: number) => {
452-
const array: ArrayValue<Primitive> =
449+
const array: any[] =
453450
new Array(size);
454451

455452
for (let j = 0; j < size; j++) {
@@ -476,7 +473,7 @@ export function readRowData(
476473
offset += 8;
477474
value = readArray(size);
478475
} else {
479-
const arrays: ArrayValue<Primitive>[] =
476+
const arrays: any[][] =
480477
new Array(dimCount);
481478
const dims = new Uint32Array(dimCount);
482479

@@ -624,7 +621,7 @@ export class Reader {
624621
}
625622

626623
readRowData(
627-
row: Array<Value>,
624+
row: any[],
628625
columnSpecification: Uint32Array,
629626
encoding: BufferEncoding,
630627
types: ReadonlyMap<DataType, ValueTypeReader> | null,
@@ -654,7 +651,7 @@ export class Writer {
654651
name: string,
655652
portal: string,
656653
format: DataFormat | DataFormat[] = DataFormat.Binary,
657-
values: Value[] = [],
654+
values: any[] = [],
658655
types: DataType[] = []) {
659656
// We silently ignore any mismatch here, assuming that the
660657
// query will fail and make the error evident.
@@ -690,7 +687,7 @@ export class Writer {
690687
}
691688
};
692689

693-
const addBinaryValue = (value: Value, dataType: DataType): number => {
690+
const addBinaryValue = (value: any, dataType: DataType): number => {
694691
let size = -1;
695692
const setSize = reserve(SegmentType.Int32BE);
696693

@@ -836,7 +833,7 @@ export class Writer {
836833
};
837834

838835
const addBinaryArray = (
839-
value: Value[],
836+
value: any[],
840837
dataType: DataType): number => {
841838
const setDimCount = reserve(SegmentType.Int32BE);
842839
add(SegmentType.Int32BE, 1);
@@ -845,7 +842,7 @@ export class Writer {
845842
let bytes = 12;
846843
let dimCount = 0;
847844

848-
const go = (level: number, value: Value[]) => {
845+
const go = (level: number, value: any[]) => {
849846
const length = value.length;
850847
if (length === 0) return;
851848

@@ -874,7 +871,7 @@ export class Writer {
874871
}
875872

876873
const getTextFromValue = (
877-
value: Value,
874+
value: any,
878875
dataType: DataType): null | string | string[] => {
879876
if (value === null) return null;
880877

@@ -929,7 +926,7 @@ export class Writer {
929926
}
930927

931928
const getTextFromArray = (
932-
value: Value[],
929+
value: any[],
933930
dataType: DataType): string[] => {
934931
const strings: string[] = [];
935932
strings.push('{');

src/query.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Writable } from 'stream';
22
import {
33
DataFormat,
44
DataType,
5-
Value
65
} from './types';
76

87
export interface QueryOptions {
@@ -16,7 +15,7 @@ export interface QueryOptions {
1615
export class Query {
1716
constructor(
1817
public readonly text: string,
19-
public readonly values?: Value[],
18+
public readonly values?: any[],
2019
public readonly options?: Partial<QueryOptions>
2120
) { }
2221
}

0 commit comments

Comments
 (0)