Skip to content

Commit d030ace

Browse files
committed
feat: add nested queries to match book + syntactic sugar
1 parent 4ebdb4c commit d030ace

File tree

3 files changed

+235
-3
lines changed

3 files changed

+235
-3
lines changed

.changeset/selfish-poets-grab.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@dojoengine/sdk": patch
3+
"@dojoengine/core": patch
4+
"@dojoengine/create-burner": patch
5+
"@dojoengine/create-dojo": patch
6+
"@dojoengine/predeployed-connector": patch
7+
"@dojoengine/react": patch
8+
"@dojoengine/state": patch
9+
"@dojoengine/torii-client": patch
10+
"@dojoengine/torii-wasm": patch
11+
"@dojoengine/utils": patch
12+
"@dojoengine/utils-wasm": patch
13+
---
14+
15+
fix: Add nested query test to match book + syntactic sugar

packages/sdk/src/__tests__/clauseBuilder.test.ts

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, expect, it } from "vitest";
2-
import { ClauseBuilder } from "../clauseBuilder";
2+
import {
3+
AndComposeClause,
4+
ClauseBuilder,
5+
MemberClause,
6+
OrComposeClause,
7+
} from "../clauseBuilder";
38
import {
49
ComparisonOperator,
510
LogicalOperator,
@@ -208,4 +213,148 @@ describe("ClauseBuilder", () => {
208213
});
209214
});
210215
});
216+
217+
it("should handle complex composition", () => {
218+
const clause = new ClauseBuilder()
219+
.compose()
220+
.and([
221+
new ClauseBuilder()
222+
.compose()
223+
.and([
224+
new ClauseBuilder().where(
225+
"world-player",
226+
"score",
227+
"Gt",
228+
100
229+
),
230+
new ClauseBuilder()
231+
.compose()
232+
.or([
233+
new ClauseBuilder().where(
234+
"world-player",
235+
"name",
236+
"Eq",
237+
"Bob"
238+
),
239+
new ClauseBuilder().where(
240+
"world-player",
241+
"name",
242+
"Eq",
243+
"Alice"
244+
),
245+
]),
246+
]),
247+
new ClauseBuilder().where("world-item", "durability", "Lt", 50),
248+
])
249+
.build();
250+
251+
expect(clause).toEqual({
252+
Composite: {
253+
operator: "And",
254+
clauses: [
255+
{
256+
Composite: {
257+
operator: "And",
258+
clauses: [
259+
{
260+
Member: {
261+
model: "world-player",
262+
member: "score",
263+
operator: "Gt" as ComparisonOperator,
264+
value: { Primitive: { U32: 100 } },
265+
},
266+
},
267+
268+
{
269+
Composite: {
270+
operator: "Or",
271+
clauses: [
272+
{
273+
Member: {
274+
model: "world-player",
275+
member: "name",
276+
operator:
277+
"Eq" as ComparisonOperator,
278+
value: {
279+
String: "Bob",
280+
},
281+
},
282+
},
283+
284+
{
285+
Member: {
286+
model: "world-player",
287+
member: "name",
288+
operator:
289+
"Eq" as ComparisonOperator,
290+
value: {
291+
String: "Alice",
292+
},
293+
},
294+
},
295+
],
296+
},
297+
},
298+
],
299+
},
300+
},
301+
{
302+
Member: {
303+
model: "world-item",
304+
member: "durability",
305+
operator: "Lt" as ComparisonOperator,
306+
value: { Primitive: { U32: 50 } },
307+
},
308+
},
309+
],
310+
},
311+
});
312+
});
313+
it("should be nice to use", () => {
314+
const clause = new ClauseBuilder()
315+
.compose()
316+
.and([
317+
new ClauseBuilder()
318+
.compose()
319+
.and([
320+
new ClauseBuilder().where(
321+
"world-player",
322+
"score",
323+
"Gt",
324+
100
325+
),
326+
new ClauseBuilder()
327+
.compose()
328+
.or([
329+
new ClauseBuilder().where(
330+
"world-player",
331+
"name",
332+
"Eq",
333+
"Bob"
334+
),
335+
new ClauseBuilder().where(
336+
"world-player",
337+
"name",
338+
"Eq",
339+
"Alice"
340+
),
341+
]),
342+
]),
343+
new ClauseBuilder().where("world-item", "durability", "Lt", 50),
344+
])
345+
.build();
346+
347+
const nicerClause = AndComposeClause([
348+
AndComposeClause([
349+
MemberClause("world-player", "score", "Gt", 100),
350+
OrComposeClause([
351+
MemberClause("world-player", "name", "Eq", "Bob"),
352+
MemberClause("world-player", "name", "Eq", "Alice"),
353+
]),
354+
]),
355+
MemberClause("world-item", "durability", "Lt", 50),
356+
]).build();
357+
358+
expect(clause).toEqual(nicerClause);
359+
});
211360
});

packages/sdk/src/clauseBuilder.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
import { convertToPrimitive } from "./convertToMemberValue";
99
import { SchemaType } from "./types";
1010

11+
type ClauseBuilderInterface = {
12+
build(): Clause;
13+
};
14+
1115
// Helper types for nested model structure
1216
type ModelPath<T, K extends keyof T> = K extends string
1317
? T[K] extends Record<string, any>
@@ -28,6 +32,70 @@ type GetModelType<
2832
: never
2933
: never;
3034

35+
/**
36+
* Saves some keyboard strokes to get a KeysClause.
37+
*
38+
* @param models - the models you want to query, has to be in form of ns-Model
39+
* @param keys - the keys that has the model. You can use `undefined` as a wildcard to match any key
40+
* @param pattern - either VariableLen or FixedLen - to check exact match of key number
41+
* @return ClauseBuilder<T>
42+
*/
43+
export function KeysClause<T extends SchemaType>(
44+
models: ModelPath<T, keyof T>[],
45+
keys: (string | undefined)[],
46+
pattern: PatternMatching = "VariableLen"
47+
): ClauseBuilder<T> {
48+
return new ClauseBuilder<T>().keys(models, keys, pattern);
49+
}
50+
51+
/**
52+
* Saves some keyboard strokes to get a MemberClause.
53+
*
54+
* @template T - the schema type
55+
* @param model - the model you want to query, has to be in form of ns-Model
56+
* @param member - the member of the model on which you want to apply operator
57+
* @param operator - the operator to apply
58+
* @param value - the value to operate on.
59+
* @return ClauseBuilder<T>
60+
*/
61+
export function MemberClause<
62+
T extends SchemaType,
63+
Path extends ModelPath<T, keyof T>,
64+
M extends keyof GetModelType<T, ModelPath<T, keyof T>>,
65+
>(
66+
model: Path,
67+
member: M & string,
68+
operator: ComparisonOperator,
69+
value: GetModelType<T, Path>[M] | GetModelType<T, Path>[M][]
70+
): ClauseBuilder<T> {
71+
return new ClauseBuilder<T>().where(model, member, operator, value);
72+
}
73+
74+
/**
75+
* Saves some keyboard strokes to get a Composite "Or" Clause
76+
*
77+
* @template T - the schema type
78+
* @param clauses - the inner clauses that you want to compose
79+
* @return CompositeBuilder<T>
80+
*/
81+
export function AndComposeClause<T extends SchemaType>(
82+
clauses: ClauseBuilderInterface[]
83+
): CompositeBuilder<T> {
84+
return new ClauseBuilder<T>().compose().and(clauses);
85+
}
86+
87+
/**
88+
* Saves some keyboard strokes to get a Composite "And" Clause
89+
* @template T - the schema type
90+
* @param clauses - the inner clauses that you want to compose
91+
* @return CompositeBuilder<T>
92+
*/
93+
export function OrComposeClause<T extends SchemaType>(
94+
clauses: ClauseBuilderInterface[]
95+
): CompositeBuilder<T> {
96+
return new ClauseBuilder<T>().compose().or(clauses);
97+
}
98+
3199
export class ClauseBuilder<T extends SchemaType> {
32100
private clause: Clause;
33101

@@ -103,12 +171,12 @@ class CompositeBuilder<T extends Record<string, Record<string, any>>> {
103171
private orClauses: Clause[] = [];
104172
private andClauses: Clause[] = [];
105173

106-
or(clauses: ClauseBuilder<T>[]): CompositeBuilder<T> {
174+
or(clauses: ClauseBuilderInterface[]): CompositeBuilder<T> {
107175
this.orClauses = clauses.map((c) => c.build());
108176
return this;
109177
}
110178

111-
and(clauses: ClauseBuilder<T>[]): CompositeBuilder<T> {
179+
and(clauses: ClauseBuilderInterface[]): CompositeBuilder<T> {
112180
this.andClauses = clauses.map((c) => c.build());
113181
return this;
114182
}

0 commit comments

Comments
 (0)