1 | import { Pool, ClientBase, QueryResult, QueryConfig } from "pg";
|
2 | import DataLoader from "dataloader";
|
3 | import { NotFoundError } from "./errors";
|
4 | import { StrategyCollection } from "./StrategyCollection";
|
5 | import createHash from "object-hash";
|
6 |
|
7 | export class DataLoaderExecutor<
|
8 | T extends ClientBase | Pool = ClientBase | Pool
|
9 | > {
|
10 | public connection: T;
|
11 | public readonly strategies: StrategyCollection;
|
12 |
|
13 | constructor(connection: T, strategies: StrategyCollection) {
|
14 | this.connection = connection;
|
15 | this.strategies = strategies;
|
16 | }
|
17 | }
|
18 |
|
19 | export type ReadonlyDataLoaderExecutor<
|
20 | T extends ClientBase | Pool = ClientBase | Pool
|
21 | > = DataLoaderExecutor<T> & {
|
22 | readonly connection: T;
|
23 | };
|
24 |
|
25 | export type Reader<M> = (
|
26 | executor: DataLoaderExecutor,
|
27 | ids: readonly string[]
|
28 | ) => Promise<M[]>;
|
29 |
|
30 | export class DataLoaderCache<M extends { id: string }> {
|
31 | private _map: WeakMap<DataLoaderExecutor, DataLoader<string, M>>;
|
32 | private readonly _read: Reader<M>;
|
33 |
|
34 | constructor(read: Reader<M>) {
|
35 | this._map = new WeakMap();
|
36 | this._read = read;
|
37 | }
|
38 |
|
39 | get(executor: DataLoaderExecutor): DataLoader<string, M> {
|
40 | let loader = this._map.get(executor);
|
41 | if (loader) {
|
42 | return loader;
|
43 | }
|
44 |
|
45 | const read = this._read;
|
46 | loader = new DataLoader<string, M>(async function (
|
47 | ids: readonly string[]
|
48 | ): Promise<(M | Error)[]> {
|
49 |
|
50 |
|
51 | const results = await read(executor, ids);
|
52 |
|
53 |
|
54 | const resultsById = new Map<string, M>();
|
55 | for (let i = 0; i < results.length; i++) {
|
56 | const result = results[i];
|
57 | resultsById.set(result.id, result);
|
58 | }
|
59 |
|
60 |
|
61 | const returnValue = new Array(ids.length);
|
62 | for (let i = 0; i < ids.length; i++) {
|
63 | const id = ids[i];
|
64 | returnValue[i] = resultsById.get(id) ?? new NotFoundError();
|
65 | }
|
66 |
|
67 | return returnValue;
|
68 | });
|
69 | this._map.set(executor, loader);
|
70 | return loader;
|
71 | }
|
72 | }
|
73 |
|
74 | export class QueryCache<T> {
|
75 | private _map = new WeakMap<
|
76 | DataLoaderExecutor,
|
77 | Map<string, Promise<QueryResult<T>>>
|
78 | >();
|
79 |
|
80 | query(
|
81 | tx: Pool | ClientBase | DataLoaderExecutor,
|
82 | query: string | QueryConfig<unknown[]>,
|
83 | parameters: unknown[]
|
84 | ): Promise<QueryResult<T>> {
|
85 |
|
86 | if (!(tx instanceof DataLoaderExecutor)) {
|
87 | return tx.query(query, parameters);
|
88 | }
|
89 |
|
90 |
|
91 | let cache = this._map.get(tx);
|
92 | if (!cache) {
|
93 | cache = new Map();
|
94 | this._map.set(tx, cache);
|
95 | }
|
96 |
|
97 |
|
98 | const hash = createHash({ query, parameters });
|
99 |
|
100 |
|
101 | let result = cache.get(hash);
|
102 | if (!result) {
|
103 | result = tx.connection.query(query, parameters);
|
104 | cache.set(hash, result);
|
105 | }
|
106 |
|
107 | return result;
|
108 | }
|
109 | }
|