Skip to content

Commit cc841f8

Browse files
committed
refactor(db): restructure QueryStrategyFactory with improved registry pattern and lazy loading
Refactored the QueryStrategyFactory to: - Rename QueryBuilder to QueryStrategyFactory - Implement a registry-based approach for strategy management - Enhance lazy loading and caching of query strategies - Improve error handling and type safety for strategy resolution - Moved some files around
1 parent 0ff0e0e commit cc841f8

6 files changed

Lines changed: 254 additions & 386 deletions

File tree

src/services/database/entities/EntityServiceFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
createStandardQueryModifier,
66
QueryModifier,
77
} from "../../../lib/db/queryModifiers/queryModifiers.js";
8-
import { QueryStrategyFactory } from "../../../services/database/strategies/QueryBuilder.js";
8+
import { QueryStrategyFactory } from "../strategies/QueryStrategyFactory.js";
99
import {
1010
QueryStrategy,
1111
SupportedDatabases,

src/services/database/strategies/QueryBuilder.ts

Lines changed: 0 additions & 125 deletions
This file was deleted.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { BaseQueryArgsType } from "../../../lib/graphql/BaseQueryArgs.js";
2+
import { AllowlistQueryStrategy } from "./AllowlistQueryStrategy.js";
3+
import { AttestationsQueryStrategy } from "./AttestationQueryStrategy.js";
4+
import { BlueprintsQueryStrategy } from "./BlueprintsQueryStrategy.js";
5+
import { ClaimsQueryStrategy } from "./ClaimsQueryStrategy.js";
6+
import { CollectionsQueryStrategy } from "./CollectionsQueryStrategy.js";
7+
import { ContractsQueryStrategy } from "./ContractsQueryStrategy.js";
8+
import { FractionsQueryStrategy } from "./FractionsQueryStrategy.js";
9+
import { HyperboardsQueryStrategy } from "./HyperboardsQueryStrategy.js";
10+
import { MarketplaceOrdersQueryStrategy } from "./MarketplaceOrdersQueryStrategy.js";
11+
import { MetadataQueryStrategy } from "./MetadataQueryStrategy.js";
12+
import { QueryStrategy, SupportedDatabases } from "./QueryStrategy.js";
13+
import { SalesQueryStrategy } from "./SalesQueryStrategy.js";
14+
import { SignatureRequestsQueryStrategy } from "./SignatureRequestsQueryStrategy.js";
15+
import { SupportedSchemasQueryStrategy } from "./SupportedSchemasQueryStrategy.js";
16+
import { UsersQueryStrategy } from "./UsersQueryStrategy.js";
17+
import { EntityFields } from "../../../lib/graphql/createEntityArgs.js";
18+
import { SortOptions } from "../../../lib/graphql/createEntitySortArgs.js";
19+
20+
/**
21+
* Base type for query arguments used across all strategies
22+
*/
23+
type QueryArgs = BaseQueryArgsType<
24+
Record<string, unknown>,
25+
SortOptions<EntityFields>
26+
>;
27+
28+
/**
29+
* Type for strategy constructors to ensure they match the QueryStrategy interface
30+
*/
31+
type QueryStrategyConstructor<
32+
DB extends SupportedDatabases = SupportedDatabases,
33+
T extends keyof DB & string = keyof DB & string,
34+
Args extends QueryArgs = QueryArgs,
35+
> = new () => QueryStrategy<DB, T, Args>;
36+
37+
/**
38+
* Type for the strategy registry mapping table names to their constructors
39+
*/
40+
type StrategyRegistry = {
41+
[K in keyof SupportedDatabases & string]: QueryStrategyConstructor<
42+
SupportedDatabases,
43+
K
44+
>;
45+
};
46+
47+
/**
48+
* Type for the strategy cache mapping table names to their instances
49+
*/
50+
type StrategyCache = {
51+
[K in keyof SupportedDatabases & string]?: QueryStrategy<
52+
SupportedDatabases,
53+
K
54+
>;
55+
};
56+
57+
/**
58+
* Factory class for creating query strategies for different tables
59+
* Uses a registry pattern for extensibility and a proxy for lazy loading
60+
*/
61+
export class QueryStrategyFactory {
62+
/**
63+
* Registry of strategy constructors
64+
* @private
65+
*/
66+
private static strategyRegistry: Partial<StrategyRegistry> = {
67+
attestations: AttestationsQueryStrategy,
68+
allowlist_records: AllowlistQueryStrategy,
69+
claimable_fractions_with_proofs: AllowlistQueryStrategy,
70+
blueprints_with_admins: BlueprintsQueryStrategy,
71+
blueprints: BlueprintsQueryStrategy,
72+
claims: ClaimsQueryStrategy,
73+
hypercerts: ClaimsQueryStrategy,
74+
collections: CollectionsQueryStrategy,
75+
contracts: ContractsQueryStrategy,
76+
fractions: FractionsQueryStrategy,
77+
fractions_view: FractionsQueryStrategy,
78+
hyperboards: HyperboardsQueryStrategy,
79+
metadata: MetadataQueryStrategy,
80+
orders: MarketplaceOrdersQueryStrategy,
81+
marketplace_orders: MarketplaceOrdersQueryStrategy,
82+
sales: SalesQueryStrategy,
83+
signature_requests: SignatureRequestsQueryStrategy,
84+
attestation_schema: SupportedSchemasQueryStrategy,
85+
eas_schema: SupportedSchemasQueryStrategy,
86+
supported_schemas: SupportedSchemasQueryStrategy,
87+
users: UsersQueryStrategy,
88+
};
89+
90+
/**
91+
* Cache of strategy instances
92+
* @private
93+
*/
94+
private static strategies: StrategyCache = new Proxy<StrategyCache>(
95+
{},
96+
{
97+
get<K extends keyof SupportedDatabases & string>(
98+
target: StrategyCache,
99+
prop: K | string | symbol,
100+
): QueryStrategy<SupportedDatabases, K> | undefined {
101+
if (typeof prop !== "string") {
102+
return undefined;
103+
}
104+
105+
const key = prop as K;
106+
107+
// Check if we already have a cached instance
108+
if (key in target && target[key]) {
109+
return target[key] as QueryStrategy<SupportedDatabases, K>;
110+
}
111+
112+
// Get the constructor from the registry
113+
const Constructor = QueryStrategyFactory.strategyRegistry[key];
114+
if (!Constructor) {
115+
throw new Error(
116+
`No strategy registered for table "${String(key)}". Available tables: ${Object.keys(
117+
QueryStrategyFactory.strategyRegistry,
118+
).join(", ")}`,
119+
);
120+
}
121+
122+
// Create and cache a new instance
123+
const strategy = new Constructor() as QueryStrategy<
124+
SupportedDatabases,
125+
K
126+
>;
127+
(target as Record<K, QueryStrategy<SupportedDatabases, K>>)[key] =
128+
strategy;
129+
return strategy;
130+
},
131+
},
132+
);
133+
134+
/**
135+
* Get a strategy instance for a given table
136+
* Creates and caches the instance if it doesn't exist
137+
*
138+
* @param tableName - The name of the table to get a strategy for
139+
* @returns A query strategy instance for the given table
140+
* @throws Error if no strategy is registered for the table
141+
*/
142+
static getStrategy<
143+
DB extends SupportedDatabases,
144+
T extends keyof DB & string,
145+
Args extends QueryArgs = QueryArgs,
146+
>(tableName: T): QueryStrategy<DB, T, Args> {
147+
const strategy = (this.strategies as Record<T, QueryStrategy<DB, T, Args>>)[
148+
tableName
149+
];
150+
if (!strategy) {
151+
throw new Error(
152+
`Failed to get strategy for table "${tableName}". This might be a type mismatch or the strategy is not properly registered.`,
153+
);
154+
}
155+
return strategy;
156+
}
157+
}

test/services/database/QueryBuilder.test.ts

Lines changed: 0 additions & 90 deletions
This file was deleted.

0 commit comments

Comments
 (0)