diff --git a/packages/sdk/src/__tests__/convertQueryToClause.test.ts b/packages/sdk/src/__tests__/convertQueryToClause.test.ts deleted file mode 100644 index 8b5e8ba2..00000000 --- a/packages/sdk/src/__tests__/convertQueryToClause.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -// packages/sdk/src/__tests__/convertQueryToClause.test.ts - -import { describe, expect, it } from "vitest"; - -import { MockSchemaType, schema } from "../__example__/index"; -import { convertQueryToClause } from "../convertQuerytoClause"; -import { QueryType } from "../types"; - -describe("convertQueryToClause", () => { - it("should convert a single model query with conditions", () => { - const query: QueryType = { - world: { - player: { - $: { where: { id: { $eq: "1" }, name: { $eq: "Alice" } } }, - }, - }, - universe: { - galaxy: { - $: { - where: { name: { $is: "Milky Way" } }, - }, - }, - }, - }; - - const result = convertQueryToClause(query, schema); - - expect(result).toEqual({ - Composite: { - operator: "Or", - clauses: [ - { - Composite: { - operator: "And", - clauses: [ - { - Member: { - model: "world-player", - member: "id", - operator: "Eq", - value: { String: "1" }, - }, - }, - { - Member: { - model: "world-player", - member: "name", - operator: "Eq", - value: { String: "Alice" }, - }, - }, - ], - }, - }, - { - Keys: { - keys: [undefined, "Milky Way"], // ['id', 'name'] - pattern_matching: "VariableLen", - models: ["universe-galaxy"], - }, - }, - ], - }, - }); - }); - - it("should return undefined, indicating fetch all", () => { - const query: QueryType = { - world: {}, - }; - - const result = convertQueryToClause(query, schema); - - expect(result).toEqual(undefined); - }); - - it("should convert multiple model queries", () => { - const query: QueryType = { - world: { - player: { $: { where: { id: { $eq: "1" } } } }, - game: { - $: { where: { status: { $eq: "active" } } }, - }, - }, - }; - - const result = convertQueryToClause(query, schema); - - expect(result).toEqual({ - Composite: { - operator: "Or", - clauses: [ - { - Member: { - model: "world-player", - member: "id", - operator: "Eq", - value: { String: "1" }, - }, - }, - { - Member: { - model: "world-game", - member: "status", - operator: "Eq", - value: { String: "active" }, - }, - }, - ], - }, - }); - }); - - it("should handle complex conditions with multiple operators", () => { - const query: QueryType = { - world: { - player: { - $: { - where: { - And: [ - { score: { $gt: 100 } }, - { - Or: [ - { name: { $eq: "Alice" } }, - { name: { $eq: "Bob" } }, - ], - }, - ], - }, - }, - }, - item: { - $: { - where: { - And: [{ durability: { $lt: 50 } }], - }, - }, - }, - }, - }; - - const result = convertQueryToClause(query, schema); - - // Updated expectation to match the actual output - expect(result).toEqual({ - Composite: { - operator: "Or", - clauses: [ - { - Composite: { - operator: "And", - clauses: [ - { - Member: { - model: "world-player", - member: "score", - operator: "Gt", - value: { Primitive: { U32: 100 } }, - }, - }, - { - Composite: { - operator: "Or", - clauses: [ - { - Member: { - model: "world-player", - member: "name", - operator: "Eq", - value: { String: "Alice" }, - }, - }, - { - Member: { - model: "world-player", - member: "name", - operator: "Eq", - value: { String: "Bob" }, - }, - }, - ], - }, - }, - ], - }, - }, - { - Member: { - model: "world-item", - member: "durability", - operator: "Lt", - value: { Primitive: { U32: 50 } }, - }, - }, - ], - }, - }); - }); -}); diff --git a/packages/sdk/src/__tests__/convertQueryToEntityKeyClauses.test.ts b/packages/sdk/src/__tests__/convertQueryToEntityKeyClauses.test.ts deleted file mode 100644 index 4d434dfc..00000000 --- a/packages/sdk/src/__tests__/convertQueryToEntityKeyClauses.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -import * as torii from "@dojoengine/torii-client"; -import { describe, expect, it } from "vitest"; - -import { MockSchemaType, schema } from "../__example__/index"; -import { convertQueryToEntityKeyClauses } from "../convertQueryToEntityKeyClauses"; -import { SubscriptionQueryType } from "../types"; - -describe("convertQueryToEntityKeyClauses", () => { - it("should handle multiple models within a single namespace with ordered keys", () => { - const query: SubscriptionQueryType = { - world: { - player: { - $: {}, - }, - item: { - $: { - where: { durability: { $is: 2 } }, - }, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: Array(3).fill(undefined), // ['id', 'name', 'score'] - pattern_matching: "VariableLen", - models: ["world-player"], - }, - }, - { - Keys: { - keys: [undefined, undefined, "2"], // ['id', 'type', 'durability'] - pattern_matching: "VariableLen", - models: ["world-item"], - }, - }, - ]; - expect(result).toEqual(expected); - }); - - it("should handle queries with multiple where conditions", () => { - const query: SubscriptionQueryType = { - world: { - player: { - $: { - where: { name: { $is: "Alice" }, score: { $is: 10 } }, - }, - }, - item: { - $: { - where: { - type: { $is: "sword" }, - durability: { $is: 5 }, - }, - }, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: [undefined, "Alice", "10"], // ['id', 'name', 'score'] - pattern_matching: "VariableLen", - models: ["world-player"], - }, - }, - { - Keys: { - keys: [undefined, "sword", "5"], // ['id', 'type', 'durability'] - pattern_matching: "VariableLen", - models: ["world-item"], - }, - }, - ]; - expect(result).toEqual(expected); - }); - - it("should handle queries without where conditions", () => { - const query: SubscriptionQueryType = { - world: { - player: { - $: {}, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: Array(3).fill(undefined), // ['id', 'name', 'score'] - pattern_matching: "VariableLen", - models: ["world-player"], - }, - }, - ]; - expect(result).toEqual(expected); - }); - - it("should handle queries with entityIds", () => { - const query: SubscriptionQueryType = { - world: { - item: { - $: { - where: { - type: { $is: "sword" }, - }, - }, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: [undefined, "sword", undefined], // ['id', 'type'] - pattern_matching: "VariableLen", - models: ["world-item"], - }, - }, - ]; - expect(result).toEqual(expected); - }); - - it("should handle queries with multiple namespaces", () => { - const query: SubscriptionQueryType = { - world: { - player: { - $: { - where: { name: { $is: "Bob" }, score: { $is: 20 } }, - }, - }, - }, - universe: { - galaxy: { - $: { - where: { name: { $is: "Milky Way" } }, - }, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: [undefined, "Bob", "20"], // ['id', 'name', 'score'] - pattern_matching: "VariableLen", - models: ["world-player"], - }, - }, - { - Keys: { - keys: [undefined, "Milky Way"], // ['id', 'name'] - pattern_matching: "VariableLen", - models: ["universe-galaxy"], - }, - }, - ]; - expect(result).toEqual(expected); - }); - - it("should handle queries with mixed conditions and entityIds", () => { - const query: SubscriptionQueryType = { - world: { - player: { - $: { - where: { name: { $is: "Charlie" } }, - }, - }, - item: { - $: { - where: { durability: { $is: 10 } }, - }, - }, - }, - universe: { - galaxy: { - $: { - where: { name: { $is: "Andromeda" } }, - }, - }, - }, - }; - - const result = convertQueryToEntityKeyClauses(query, schema); - const expected: torii.EntityKeysClause[] = [ - { - Keys: { - keys: [undefined, "Charlie", undefined], // ['id', 'name', 'score'] - pattern_matching: "VariableLen", - models: ["world-player"], - }, - }, - { - Keys: { - keys: [undefined, undefined, "10"], // ['id', 'type', 'durability'] - pattern_matching: "VariableLen", - models: ["world-item"], - }, - }, - { - Keys: { - keys: [undefined, "Andromeda"], // ['id', 'name'] - pattern_matching: "VariableLen", - models: ["universe-galaxy"], - }, - }, - ]; - expect(result).toEqual(expected); - }); -}); diff --git a/packages/sdk/src/clauseBuilder.ts b/packages/sdk/src/clauseBuilder.ts index 3b5aa1bc..3abf0473 100644 --- a/packages/sdk/src/clauseBuilder.ts +++ b/packages/sdk/src/clauseBuilder.ts @@ -1,12 +1,14 @@ -import { +import type { Clause, ComparisonOperator, MemberValue, PatternMatching, } from "@dojoengine/torii-client"; - -import { convertToPrimitive, MemberValueParam } from "./convertToMemberValue"; -import { SchemaType } from "./types"; +import { + convertToPrimitive, + type MemberValueParam, +} from "./convertToMemberValue"; +import type { SchemaType } from "./types"; type ClauseBuilderInterface = { build(): Clause; diff --git a/packages/sdk/src/convertQueryToEntityKeyClauses.ts b/packages/sdk/src/convertQueryToEntityKeyClauses.ts deleted file mode 100644 index 4bc36a15..00000000 --- a/packages/sdk/src/convertQueryToEntityKeyClauses.ts +++ /dev/null @@ -1,193 +0,0 @@ -import * as torii from "@dojoengine/torii-client"; - -import { SchemaType, SubscriptionQueryType } from "./types"; - -/** - * Converts a subscription query to an array of EntityKeysClause. - * - * @template T - The schema type. - * @param {SubscriptionQueryType} query - The subscription query to convert. - * @param {T} schema - The schema providing field order information. - * @returns {torii.EntityKeysClause[]} An array of EntityKeysClause. - */ -export function convertQueryToEntityKeyClauses( - query: SubscriptionQueryType, - schema: T -): torii.EntityKeysClause[] { - if (!query) { - return []; - } - - const clauses: torii.EntityKeysClause[] = []; - - const { entityIds, ...namespaces } = query; - - if (entityIds && entityIds.length && (entityIds.length as number) > 0) { - clauses.push({ HashedKeys: entityIds as string[] }); - return clauses; - } - - clauses.push(...convertQueryToKeysClause(namespaces, schema)); - - return clauses; -} - -/** - * Converts namespaces to an array of EntityKeysClause. - * - * @template T - The schema type. - * @param {Omit, "entityIds">} namespaces - The namespaces to convert. - * @param {T} schema - The schema providing field order information. - * @returns {torii.EntityKeysClause[]} An array of EntityKeysClause. - */ -export function convertQueryToKeysClause( - namespaces: Omit, "entityIds">, - schema: T -): torii.EntityKeysClause[] { - const clauses: torii.EntityKeysClause[] = []; - - Object.entries(namespaces).forEach(([namespace, models]) => { - if (models && typeof models === "object") { - Object.entries(models).forEach(([model, value]) => { - const namespaceModel = `${namespace}-${model}`; - if (Array.isArray(value)) { - const clause = createClause(namespaceModel, value); - if (clause) { - clauses.push(clause); - } - } else if ( - typeof value === "object" && - value !== null && - "$" in value - ) { - const whereOptions = (value as { $: { where: any } }).$ - .where; - const modelSchema = schema[namespace]?.[model]; - if (modelSchema) { - const clause = createClauseFromWhere( - namespaceModel, - whereOptions, - modelSchema.fieldOrder - ); - if (clause) { - clauses.push(clause); - } - } - } - }); - } - }); - - return clauses; -} - -/** - * Creates an EntityKeysClause based on the provided model and value. - * - * @param {string} namespaceModel - The combined namespace and model string. - * @param {string[]} value - The value associated with the model. - * @returns {torii.EntityKeysClause | undefined} An EntityKeysClause or undefined. - */ -function createClause( - namespaceModel: string, - value: string[] -): torii.EntityKeysClause | undefined { - if (Array.isArray(value) && value.length === 0) { - return { - Keys: { - keys: [undefined], - pattern_matching: "VariableLen", - models: [namespaceModel], - }, - }; - } else if (Array.isArray(value)) { - return { - Keys: { - keys: value, - pattern_matching: "FixedLen", - models: [namespaceModel], - }, - }; - } - return undefined; -} - -/** - * Creates an EntityKeysClause based on the provided where conditions. - * Orders the keys array based on the fieldOrder from the schema, - * inserting undefined placeholders where necessary. - * - * @param {string} namespaceModel - The combined namespace and model string. - * @param {Record} [whereOptions] - The where conditions from the query. - * @param {string[]} [fieldOrder=[]] - The defined order of fields for the model. - * @returns {torii.EntityKeysClause | undefined} An EntityKeysClause or undefined. - */ -function createClauseFromWhere( - namespaceModel: string, - whereOptions?: Record< - string, - { - $is?: any; - $eq?: any; - $neq?: any; - $gt?: any; - $gte?: any; - $lt?: any; - $lte?: any; - } - >, - fieldOrder: string[] = [] -): torii.EntityKeysClause | undefined { - if (!whereOptions || Object.keys(whereOptions).length === 0) { - return { - Keys: { - keys: Array(fieldOrder.length).fill(undefined), - pattern_matching: "VariableLen", - models: [namespaceModel], - }, - }; - } - - // Initialize keys array with undefined placeholders - const keys: (string | undefined)[] = Array(fieldOrder.length).fill( - undefined - ); - - Object.entries(whereOptions).forEach(([field, condition]) => { - // Find the index of the field in the fieldOrder - const index = fieldOrder.indexOf(field); - if (index !== -1) { - // Assign value without operator prefixes - if (condition.$is !== undefined) { - keys[index] = condition.$is.toString(); - } - if (condition.$eq !== undefined) { - keys[index] = condition.$eq.toString(); - } - if (condition.$neq !== undefined) { - keys[index] = condition.$neq.toString(); - } - if (condition.$gt !== undefined) { - keys[index] = condition.$gt.toString(); - } - if (condition.$gte !== undefined) { - keys[index] = condition.$gte.toString(); - } - if (condition.$lt !== undefined) { - keys[index] = condition.$lt.toString(); - } - if (condition.$lte !== undefined) { - keys[index] = condition.$lte.toString(); - } - // Add more operators as needed - } - }); - - return { - Keys: { - keys: keys, - pattern_matching: "VariableLen", - models: [namespaceModel], - }, - }; -} diff --git a/packages/sdk/src/convertQuerytoClause.ts b/packages/sdk/src/convertQuerytoClause.ts deleted file mode 100644 index 211e7492..00000000 --- a/packages/sdk/src/convertQuerytoClause.ts +++ /dev/null @@ -1,307 +0,0 @@ -// packages/sdk/src/convertQuerytoClause.ts - -import * as torii from "@dojoengine/torii-client"; -import { QueryType, SchemaType, SubscriptionQueryType } from "./types"; -import { convertQueryToEntityKeyClauses } from "./convertQueryToEntityKeyClauses"; -import { convertToPrimitive } from "./convertToMemberValue"; - -/** - * Converts a query object into a Torii clause. - * - * @template T - The schema type. - * @param {QueryType} query - The query object to convert. - * @param {T} schema - The schema providing field order information. - * @returns {torii.Clause | undefined} - The resulting Torii clause or undefined. - */ -export function convertQueryToClause( - query: QueryType, - schema: T -): torii.Clause | undefined { - const clauses: torii.Clause[] = []; - - for (const [namespace, models] of Object.entries(query)) { - if (namespace === "entityIds") { - return { - // match every models that has at least input keys as key - Keys: { - keys: [...models], - pattern_matching: "VariableLen", - models: [], - }, - }; - } - - if (models && typeof models === "object") { - const modelClauses = processModels(namespace, models, schema); - if (modelClauses.length > 0) { - clauses.push(...modelClauses); - } - } - } - - // If there are clauses, combine them under a single Composite clause - if (clauses.length > 1) { - return { - Composite: { - operator: "Or", - clauses: clauses, - }, - }; - } else if (clauses.length === 1) { - return clauses[0]; - } - - // If there are no clauses, return undefined - return undefined; -} - -/** - * Processes all models within a namespace and generates corresponding clauses. - * - * @template T - The schema type. - * @param {string} namespace - The namespace of the models. - * @param {any} models - The models object to process. - * @param {T} schema - The schema providing field order information. - * @returns {torii.Clause[]} - An array of generated clauses. - */ -function processModels( - namespace: string, - models: any, - schema: T -): torii.Clause[] { - const clauses: torii.Clause[] = []; - - for (const [model, modelData] of Object.entries(models)) { - const namespaceModel = `${namespace}-${model}`; - - if (modelData && typeof modelData === "object" && "$" in modelData) { - const conditions = modelData.$ as Record; - if ( - conditions && - typeof conditions === "object" && - "where" in conditions - ) { - const whereClause = conditions.where; - if (whereClause && typeof whereClause === "object") { - // Iterate over each member in the whereClause to handle $is - for (const [member, memberConditions] of Object.entries( - whereClause - )) { - if ( - typeof memberConditions === "object" && - memberConditions !== null && - "$is" in memberConditions - ) { - // Convert $is to EntityKeysClause - const isClauses = convertQueryToEntityKeyClauses( - { - [namespace]: { - [model]: { - $: { - where: { - [member]: { - $is: memberConditions[ - "$is" - ], - }, - }, - }, - }, - }, - } as SubscriptionQueryType, - schema - ); - clauses.push(...(isClauses as any)); - - // Remove $is from memberConditions to prevent further processing - const { $is, ...remainingConditions } = - memberConditions; - (whereClause as Record)[member] = - remainingConditions; - } - } - - // After handling all $is, build the remaining whereClause - const clause = buildWhereClause( - namespaceModel, - whereClause - ); - if (clause) { - if ( - "Composite" in clause && - clause.Composite.operator === "Or" - ) { - // If the composite operator is "Or", flatten the clauses - clauses.push(...clause.Composite.clauses); - } else { - // Otherwise, keep the composite as is to preserve logical structure - clauses.push(clause); - } - } - } - } - } else { - // Handle the case where there are no conditions - clauses.push({ - Keys: { - keys: [undefined], - pattern_matching: "FixedLen", - models: [namespaceModel], - }, - }); - } - } - - return clauses; -} - -/** - * Builds a Torii clause from a where clause object. - * - * @param {string} namespaceModel - The namespaced model identifier. - * @param {Record} where - The where clause conditions. - * @returns {torii.Clause | undefined} - The constructed Torii clause or undefined. - */ -function buildWhereClause( - namespaceModel: string, - where: Record -): torii.Clause | undefined { - // Define logical operator mapping - const logicalOperators: Record = { - And: "And", - Or: "Or", - }; - - // Check for logical operators first - const keys = Object.keys(where); - const logicalKey = keys.find((key) => key in logicalOperators); - - if (logicalKey) { - const operator = logicalOperators[logicalKey]; - const conditions = where[logicalKey] as Array>; - - const subClauses: torii.Clause[] = []; - - for (const condition of conditions) { - const clause = buildWhereClause(namespaceModel, condition); - if (clause) { - subClauses.push(clause); - } - } - - if (subClauses.length === 1) { - return subClauses[0]; - } - - return { - Composite: { - operator: operator, - clauses: subClauses, - }, - }; - } - - // If no logical operator, build Member clauses - const memberClauses: torii.Clause[] = []; - - for (const [member, memberValue] of Object.entries(where)) { - if (typeof memberValue === "object" && memberValue !== null) { - const memberKeys = Object.keys(memberValue); - // Check if memberValue contains logical operators - const memberLogicalKey = memberKeys.find( - (key) => key in logicalOperators - ); - if (memberLogicalKey) { - const operator = logicalOperators[memberLogicalKey]; - const conditions = memberValue[memberLogicalKey] as Array< - Record - >; - - const nestedClauses: torii.Clause[] = []; - for (const condition of conditions) { - const clause = buildWhereClause(namespaceModel, condition); - if (clause) { - nestedClauses.push(clause); - } - } - - if (nestedClauses.length === 1) { - memberClauses.push(nestedClauses[0]); - } else { - memberClauses.push({ - Composite: { - operator: operator, - clauses: nestedClauses, - }, - }); - } - } else { - // Process operators like $eq, $gt, etc - for (const [op, val] of Object.entries(memberValue)) { - memberClauses.push({ - Member: { - model: namespaceModel, - member, - operator: convertOperator(op), - value: convertToPrimitive(val), - }, - }); - } - } - } else { - // Assume equality condition - memberClauses.push({ - Member: { - model: namespaceModel, - member, - operator: "Eq", - value: convertToPrimitive(memberValue), - }, - }); - } - } - - if (memberClauses.length === 1) { - return memberClauses[0]; - } else if (memberClauses.length > 1) { - return { - // conditions in member clause should be treated as "And" Conditions by default - Composite: { - operator: "And", - clauses: memberClauses, - }, - }; - } - - return undefined; -} - -/** - * Converts a query operator to a Torii comparison operator. - * - * @param {string} operator - The query operator to convert. - * @returns {torii.ComparisonOperator} - The corresponding Torii comparison operator. - * @throws {Error} - If the operator is unsupported. - */ -function convertOperator(operator: string): torii.ComparisonOperator { - switch (operator) { - case "$eq": - return "Eq"; - case "$neq": - return "Neq"; - case "$gt": - return "Gt"; - case "$gte": - return "Gte"; - case "$lt": - return "Lt"; - case "$lte": - return "Lte"; - case "$in": - return "In"; - case "$nin": - return "NotIn"; - default: - throw new Error(`Unsupported operator: ${operator}`); - } -} diff --git a/packages/sdk/src/convertToMemberValue.ts b/packages/sdk/src/convertToMemberValue.ts index 28dcac07..37a498ab 100644 --- a/packages/sdk/src/convertToMemberValue.ts +++ b/packages/sdk/src/convertToMemberValue.ts @@ -21,17 +21,25 @@ export function convertToPrimitive(value: MemberValueParam): torii.MemberValue { if (typeof value === "number") { return { Primitive: { U32: value } }; - } else if (typeof value === "boolean") { + } + + if (typeof value === "boolean") { return { Primitive: { Bool: value } }; - } else if (typeof value === "bigint") { + } + + if (typeof value === "bigint") { return { Primitive: { Felt252: torii.cairoShortStringToFelt(value.toString()), }, }; - } else if (typeof value === "string") { + } + + if (typeof value === "string") { return { String: value }; - } else if (Array.isArray(value)) { + } + + if (Array.isArray(value)) { return { List: value.map((item) => convertToPrimitive(item)) }; } diff --git a/packages/sdk/src/queryBuilder.ts b/packages/sdk/src/queryBuilder.ts index 9fd03507..5b30c374 100644 --- a/packages/sdk/src/queryBuilder.ts +++ b/packages/sdk/src/queryBuilder.ts @@ -1,4 +1,4 @@ -import { QueryType, SchemaType, SubscriptionQueryType } from "./types"; +import type { QueryType, SchemaType, SubscriptionQueryType } from "./types"; type FirstLevelKeys = ObjectType extends object ? keyof ObjectType & (string | number) diff --git a/packages/sdk/src/toriiQueryBuilder.ts b/packages/sdk/src/toriiQueryBuilder.ts index 45356edd..3fd60fd2 100644 --- a/packages/sdk/src/toriiQueryBuilder.ts +++ b/packages/sdk/src/toriiQueryBuilder.ts @@ -1,5 +1,5 @@ -import { Clause, OrderBy, Query } from "@dojoengine/torii-client"; -import { SchemaType } from "./types"; +import type { Clause, OrderBy, Query } from "@dojoengine/torii-client"; +import type { SchemaType } from "./types"; const defaultToriiOptions = () => ({ limit: 100, // default limit