diff --git a/.changeset/selfish-poets-grab.md b/.changeset/selfish-poets-grab.md new file mode 100644 index 00000000..591aa009 --- /dev/null +++ b/.changeset/selfish-poets-grab.md @@ -0,0 +1,15 @@ +--- +"@dojoengine/sdk": patch +"@dojoengine/core": patch +"@dojoengine/create-burner": patch +"@dojoengine/create-dojo": patch +"@dojoengine/predeployed-connector": patch +"@dojoengine/react": patch +"@dojoengine/state": patch +"@dojoengine/torii-client": patch +"@dojoengine/torii-wasm": patch +"@dojoengine/utils": patch +"@dojoengine/utils-wasm": patch +--- + +fix: Add nested query test to match book + syntactic sugar diff --git a/packages/sdk/src/__tests__/clauseBuilder.test.ts b/packages/sdk/src/__tests__/clauseBuilder.test.ts index 5c3afb16..c9e66cfa 100644 --- a/packages/sdk/src/__tests__/clauseBuilder.test.ts +++ b/packages/sdk/src/__tests__/clauseBuilder.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest"; -import { ClauseBuilder } from "../clauseBuilder"; +import { + AndComposeClause, + ClauseBuilder, + MemberClause, + OrComposeClause, +} from "../clauseBuilder"; import { ComparisonOperator, LogicalOperator, @@ -208,4 +213,148 @@ describe("ClauseBuilder", () => { }); }); }); + + it("should handle complex composition", () => { + const clause = new ClauseBuilder() + .compose() + .and([ + new ClauseBuilder() + .compose() + .and([ + new ClauseBuilder().where( + "world-player", + "score", + "Gt", + 100 + ), + new ClauseBuilder() + .compose() + .or([ + new ClauseBuilder().where( + "world-player", + "name", + "Eq", + "Bob" + ), + new ClauseBuilder().where( + "world-player", + "name", + "Eq", + "Alice" + ), + ]), + ]), + new ClauseBuilder().where("world-item", "durability", "Lt", 50), + ]) + .build(); + + expect(clause).toEqual({ + Composite: { + operator: "And", + clauses: [ + { + Composite: { + operator: "And", + clauses: [ + { + Member: { + model: "world-player", + member: "score", + operator: "Gt" as ComparisonOperator, + value: { Primitive: { U32: 100 } }, + }, + }, + + { + Composite: { + operator: "Or", + clauses: [ + { + Member: { + model: "world-player", + member: "name", + operator: + "Eq" as ComparisonOperator, + value: { + String: "Bob", + }, + }, + }, + + { + Member: { + model: "world-player", + member: "name", + operator: + "Eq" as ComparisonOperator, + value: { + String: "Alice", + }, + }, + }, + ], + }, + }, + ], + }, + }, + { + Member: { + model: "world-item", + member: "durability", + operator: "Lt" as ComparisonOperator, + value: { Primitive: { U32: 50 } }, + }, + }, + ], + }, + }); + }); + it("should be nice to use", () => { + const clause = new ClauseBuilder() + .compose() + .and([ + new ClauseBuilder() + .compose() + .and([ + new ClauseBuilder().where( + "world-player", + "score", + "Gt", + 100 + ), + new ClauseBuilder() + .compose() + .or([ + new ClauseBuilder().where( + "world-player", + "name", + "Eq", + "Bob" + ), + new ClauseBuilder().where( + "world-player", + "name", + "Eq", + "Alice" + ), + ]), + ]), + new ClauseBuilder().where("world-item", "durability", "Lt", 50), + ]) + .build(); + + const nicerClause = AndComposeClause([ + AndComposeClause([ + MemberClause("world-player", "score", "Gt", 100), + OrComposeClause([ + MemberClause("world-player", "name", "Eq", "Bob"), + MemberClause("world-player", "name", "Eq", "Alice"), + ]), + ]), + MemberClause("world-item", "durability", "Lt", 50), + ]).build(); + + expect(clause).toEqual(nicerClause); + }); }); diff --git a/packages/sdk/src/clauseBuilder.ts b/packages/sdk/src/clauseBuilder.ts index e882da78..614fd81a 100644 --- a/packages/sdk/src/clauseBuilder.ts +++ b/packages/sdk/src/clauseBuilder.ts @@ -8,6 +8,10 @@ import { import { convertToPrimitive } from "./convertToMemberValue"; import { SchemaType } from "./types"; +type ClauseBuilderInterface = { + build(): Clause; +}; + // Helper types for nested model structure type ModelPath = K extends string ? T[K] extends Record @@ -28,6 +32,70 @@ type GetModelType< : never : never; +/** + * Saves some keyboard strokes to get a KeysClause. + * + * @param models - the models you want to query, has to be in form of ns-Model + * @param keys - the keys that has the model. You can use `undefined` as a wildcard to match any key + * @param pattern - either VariableLen or FixedLen - to check exact match of key number + * @return ClauseBuilder + */ +export function KeysClause( + models: ModelPath[], + keys: (string | undefined)[], + pattern: PatternMatching = "VariableLen" +): ClauseBuilder { + return new ClauseBuilder().keys(models, keys, pattern); +} + +/** + * Saves some keyboard strokes to get a MemberClause. + * + * @template T - the schema type + * @param model - the model you want to query, has to be in form of ns-Model + * @param member - the member of the model on which you want to apply operator + * @param operator - the operator to apply + * @param value - the value to operate on. + * @return ClauseBuilder + */ +export function MemberClause< + T extends SchemaType, + Path extends ModelPath, + M extends keyof GetModelType>, +>( + model: Path, + member: M & string, + operator: ComparisonOperator, + value: GetModelType[M] | GetModelType[M][] +): ClauseBuilder { + return new ClauseBuilder().where(model, member, operator, value); +} + +/** + * Saves some keyboard strokes to get a Composite "Or" Clause + * + * @template T - the schema type + * @param clauses - the inner clauses that you want to compose + * @return CompositeBuilder + */ +export function AndComposeClause( + clauses: ClauseBuilderInterface[] +): CompositeBuilder { + return new ClauseBuilder().compose().and(clauses); +} + +/** + * Saves some keyboard strokes to get a Composite "And" Clause + * @template T - the schema type + * @param clauses - the inner clauses that you want to compose + * @return CompositeBuilder + */ +export function OrComposeClause( + clauses: ClauseBuilderInterface[] +): CompositeBuilder { + return new ClauseBuilder().compose().or(clauses); +} + export class ClauseBuilder { private clause: Clause; @@ -103,12 +171,12 @@ class CompositeBuilder>> { private orClauses: Clause[] = []; private andClauses: Clause[] = []; - or(clauses: ClauseBuilder[]): CompositeBuilder { + or(clauses: ClauseBuilderInterface[]): CompositeBuilder { this.orClauses = clauses.map((c) => c.build()); return this; } - and(clauses: ClauseBuilder[]): CompositeBuilder { + and(clauses: ClauseBuilderInterface[]): CompositeBuilder { this.andClauses = clauses.map((c) => c.build()); return this; }