From 69dad946ce7451205bbfb7228b4a3e45bdbd7b59 Mon Sep 17 00:00:00 2001 From: Eric Crooks Date: Tue, 25 Jun 2024 08:31:03 -0400 Subject: [PATCH 1/3] fix: ao.link URLs --- README.md | 2 +- src/modules/ao/v0/types/UnitInfo.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f653c11..a505fae 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ const client = new Client(aoconnect); // Create Slick Transacti .data("some data") // Optionally, send data with this message .post(); // Send the request (calls HTTP POST under the hood) - console.log({ newProcess: `https://www.ao.link/entity/${newProcessId}` }); + console.log({ newProcess: `https://www.ao.link/#/entity/${newProcessId}` }); })(); ``` diff --git a/src/modules/ao/v0/types/UnitInfo.ts b/src/modules/ao/v0/types/UnitInfo.ts index 1f3ab68..910d91c 100644 --- a/src/modules/ao/v0/types/UnitInfo.ts +++ b/src/modules/ao/v0/types/UnitInfo.ts @@ -4,7 +4,7 @@ export type UnitInfo = { * query block explorers. For example, the following link queries the * aolink block explorer for the address: * - * https://www.ao.link/entity/fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY + * https://www.ao.link/#/entity/fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY */ address: string; From 5f680d12d4ed67cc27e21d1c36534a687c6fc02a Mon Sep 17 00:00:00 2001 From: Eric Crooks Date: Tue, 25 Jun 2024 08:31:17 -0400 Subject: [PATCH 2/3] fix: ao.link URLs --- examples/aoconnect/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aoconnect/app.js b/examples/aoconnect/app.js index 3df340a..b5f7a72 100644 --- a/examples/aoconnect/app.js +++ b/examples/aoconnect/app.js @@ -79,5 +79,5 @@ const client = new Client(aoconnect); // Create Slick Transacti .data("some data") // Optionally, send data with this message .post(); // Send the request (calls HTTP POST under the hood) - console.log({ newProcess: `https://www.ao.link/entity/${newProcessId}` }); + console.log({ newProcess: `https://www.ao.link/#/entity/${newProcessId}` }); })(); From 24bbe4fb125a350757312b23c9c44876c7ceea73 Mon Sep 17 00:00:00 2001 From: Eric Crooks Date: Tue, 25 Jun 2024 08:58:44 -0400 Subject: [PATCH 3/3] refactor(modules/graphql): move arweave.net code into arweave.net directory --- .../graphql/{ => arweave.net}/Query.ts | 0 .../builders/AbstractQueryBuilder.ts | 57 +++--- .../builders/TransactionQueryBuilder.ts | 48 +++++ .../builders/TransactionsQueryBuilder.ts | 121 +++++++++++++ .../types/QueryBuilderOptions.ts | 2 - .../graphql/{ => arweave.net}/types/Schema.ts | 0 .../builders/TransactionQueryBuilder.ts | 51 ------ .../builders/TransactionsQueryBuilder.ts | 168 ------------------ .../integration/modules/graphql/Query.test.ts | 78 +++++--- 9 files changed, 248 insertions(+), 277 deletions(-) rename src/modules/graphql/{ => arweave.net}/Query.ts (100%) rename src/modules/graphql/{ => arweave.net}/builders/AbstractQueryBuilder.ts (75%) create mode 100644 src/modules/graphql/arweave.net/builders/TransactionQueryBuilder.ts create mode 100644 src/modules/graphql/arweave.net/builders/TransactionsQueryBuilder.ts rename src/modules/graphql/{ => arweave.net}/types/QueryBuilderOptions.ts (54%) rename src/modules/graphql/{ => arweave.net}/types/Schema.ts (100%) delete mode 100644 src/modules/graphql/builders/TransactionQueryBuilder.ts delete mode 100644 src/modules/graphql/builders/TransactionsQueryBuilder.ts diff --git a/src/modules/graphql/Query.ts b/src/modules/graphql/arweave.net/Query.ts similarity index 100% rename from src/modules/graphql/Query.ts rename to src/modules/graphql/arweave.net/Query.ts diff --git a/src/modules/graphql/builders/AbstractQueryBuilder.ts b/src/modules/graphql/arweave.net/builders/AbstractQueryBuilder.ts similarity index 75% rename from src/modules/graphql/builders/AbstractQueryBuilder.ts rename to src/modules/graphql/arweave.net/builders/AbstractQueryBuilder.ts index 6e03e63..04e556f 100644 --- a/src/modules/graphql/builders/AbstractQueryBuilder.ts +++ b/src/modules/graphql/arweave.net/builders/AbstractQueryBuilder.ts @@ -1,20 +1,34 @@ import { QueryBuilderOptions } from "../types/QueryBuilderOptions.ts"; -export abstract class AbstractQueryBuilder { - protected return_schema = "{}"; +export abstract class AbstractQueryBuilder { + protected abstract query: string; + protected server_url: string; - protected variables: string; + protected operation_variables: Partial = {}; + protected return_schema: string; constructor(options: QueryBuilderOptions) { this.server_url = options?.server_url || "https://arweave.net/graphql"; - this.variables = options?.variables || ""; + } + + variables(variables: Variables) { + this.operation_variables = { + ...(this.operation_variables || {}), + ...(variables || {}), + }; + + return this; } /** * Build this query. * @return The query as a string to send to the GraphQL server. */ - abstract build(): string; + abstract build(): { + operationName: string; + query: string; + variables: Partial; + }; /** * Make a `fetch` request to the GraphQL server. @@ -26,7 +40,7 @@ export abstract class AbstractQueryBuilder { */ post( options: RequestInit & { url?: string } = {}, - ): Promise(): Promise }> { + ): Promise { const query = this.build(); if (!options.url) { @@ -40,11 +54,7 @@ export abstract class AbstractQueryBuilder { "accpet": "application/json, text/plain, */*", }, ...(options || {}), - body: JSON.stringify({ - query, - operationName: null, - variables: {}, - }), + body: JSON.stringify(query), method: "POST", }); @@ -102,27 +112,14 @@ export abstract class AbstractQueryBuilder { * ``` */ returnSchema(schema?: string) { - this.return_schema = schema || "{}"; + this.return_schema = schema; return this; } - protected _concat(values: string[]) { - let ret = null; - - if (values && Array.isArray(values)) { - const concatted = values - .map((value) => { - return `"${value}"`; - }) - .join("\n,"); - - ret = `[${concatted}]`; - } - - return ret; - } - - protected stringExists(str: unknown) { - return str && (typeof str === "string") && str.trim() !== ""; + protected buildQuery() { + return this.query.replace( + /\{\{ return_schema \}\}/g, + this.return_schema || "{}", + ); } } diff --git a/src/modules/graphql/arweave.net/builders/TransactionQueryBuilder.ts b/src/modules/graphql/arweave.net/builders/TransactionQueryBuilder.ts new file mode 100644 index 0000000..a4a673f --- /dev/null +++ b/src/modules/graphql/arweave.net/builders/TransactionQueryBuilder.ts @@ -0,0 +1,48 @@ +import { QueryBuilderOptions } from "../types/QueryBuilderOptions.ts"; +import { QueryTransactionArgs } from "../types/Schema.ts"; +import { AbstractQueryBuilder } from "./AbstractQueryBuilder.ts"; + +const GetTransactionOperation = `query GetTransaction( + $id: ID! +) { + transaction(id: $id) { + {{ return_schema }} + } +} +`; + +export class TransactionQueryBuilder + extends AbstractQueryBuilder { + protected query = GetTransactionOperation; + + constructor(options?: QueryBuilderOptions) { + super(options); + this.returnSchema(` + id + owner { + address + } + block { + height + timestamp + } + tags { + name + value + } +`); + } + + build() { + return { + operationName: `GetTransaction`, + query: this.buildQuery(), + variables: this.operation_variables, + }; + } + + id(id: string) { + this.operation_variables.id = id; + return this; + } +} diff --git a/src/modules/graphql/arweave.net/builders/TransactionsQueryBuilder.ts b/src/modules/graphql/arweave.net/builders/TransactionsQueryBuilder.ts new file mode 100644 index 0000000..ecd7e96 --- /dev/null +++ b/src/modules/graphql/arweave.net/builders/TransactionsQueryBuilder.ts @@ -0,0 +1,121 @@ +import { QueryBuilderOptions } from "../types/QueryBuilderOptions.ts"; +import { QueryTransactionsArgs, TagFilter } from "../types/Schema.ts"; +import { AbstractQueryBuilder } from "./AbstractQueryBuilder.ts"; + +const GetTransactionsOperations = `query GetTransactions( + $ids: [ID!] + $owners: [String!] + $recipients: [String!] + $tags: [TagFilter!] + $bundledIn: [ID!] + $block: BlockFilter + $first: Int = 10 + $after: String + $sort: SortOrder = HEIGHT_DESC +) { + transactions( + ids: $ids + owners: $owners + recipients: $recipients + tags: $tags + bundledIn: $bundledIn + block: $block + first: $first + after: $after + sort: $sort + ) { + {{ return_schema }} + } +}`; + +export class TransactionsQueryBuilder + extends AbstractQueryBuilder { + query = GetTransactionsOperations; + + constructor(options?: QueryBuilderOptions) { + super(options); + + this.returnSchema(` + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + owner { + address + } + block { + height + timestamp + } + tags { + name + value + } + } + } +`); + } + + /** + * @returns `this` instance for further method chaining. + */ + first(first: number) { + this.operation_variables.first = first; + return this; + } + + /** + * @returns `this` instance for further method chaining. + */ + after(after: string) { + this.operation_variables.after = after; + return this; + } + + build() { + return { + operationName: `GetTransactions`, + query: this.buildQuery(), + variables: this.operation_variables, + }; + } + + /** + * @returns `this` instance for further method chaining. + */ + ids(ids: string[]) { + this.operation_variables.ids = ids; + + return this; + } + + /** + * @returns `this` instance for further method chaining. + */ + owners(owners: string[]) { + this.operation_variables.owners = owners; + + return this; + } + + /** + * @returns `this` instance for further method chaining. + */ + recipients(recipients: string[]) { + this.operation_variables.recipients = recipients; + return this; + } + + /** + * Set the tags to send with the query. + * @param tags The tags in question. + * @returns `this` instance for further method chaining. + */ + tags(tags: TagFilter[]) { + this.operation_variables.tags = tags; + return this; + } +} diff --git a/src/modules/graphql/types/QueryBuilderOptions.ts b/src/modules/graphql/arweave.net/types/QueryBuilderOptions.ts similarity index 54% rename from src/modules/graphql/types/QueryBuilderOptions.ts rename to src/modules/graphql/arweave.net/types/QueryBuilderOptions.ts index 3f1f842..2a0b01c 100644 --- a/src/modules/graphql/types/QueryBuilderOptions.ts +++ b/src/modules/graphql/arweave.net/types/QueryBuilderOptions.ts @@ -1,5 +1,3 @@ export type QueryBuilderOptions = { server_url?: string; - /** GraphQL query variables */ - variables?: any; }; diff --git a/src/modules/graphql/types/Schema.ts b/src/modules/graphql/arweave.net/types/Schema.ts similarity index 100% rename from src/modules/graphql/types/Schema.ts rename to src/modules/graphql/arweave.net/types/Schema.ts diff --git a/src/modules/graphql/builders/TransactionQueryBuilder.ts b/src/modules/graphql/builders/TransactionQueryBuilder.ts deleted file mode 100644 index 77cf465..0000000 --- a/src/modules/graphql/builders/TransactionQueryBuilder.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { QueryBuilderOptions } from "../types/QueryBuilderOptions.ts"; -import { AbstractQueryBuilder } from "./AbstractQueryBuilder.ts"; - -export const defaultTransactionQueryProjection = ` - id - owner { - address - } - block { - height - timestamp - } - tags { - name - value - } -`; - -export class TransactionQueryBuilder extends AbstractQueryBuilder { - #id?: string; - - constructor(options?: QueryBuilderOptions) { - super(options); - this.return_schema = defaultTransactionQueryProjection; - } - - build() { - if (!this.#id) { - throw new Error(`Cannot create Transaction query without transaction ID`); - } - - const ret: string[] = [ - `{`, - ` transaction(`, - ` id: "${this.#id}"`, - ]; - - ret.push(`) {`); - ret.push(`${this.return_schema}`); - ret.push(` }`); - ret.push(`}`); - - return ret.join("\n"); - } - - id(id: string) { - this.#id = id; - - return this; - } -} diff --git a/src/modules/graphql/builders/TransactionsQueryBuilder.ts b/src/modules/graphql/builders/TransactionsQueryBuilder.ts deleted file mode 100644 index 82fd3e5..0000000 --- a/src/modules/graphql/builders/TransactionsQueryBuilder.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { QueryBuilderOptions } from "../types/QueryBuilderOptions.ts"; -import { AbstractQueryBuilder } from "./AbstractQueryBuilder.ts"; - -export const defaultTransactionsQueryProjection = `edges { - node { - id - owner { - address - } - block { - height - timestamp - } - tags { - name - value - } - } - cursor -}`; - -export class TransactionsQueryBuilder extends AbstractQueryBuilder { - #after?: string; - #first?: string; - #ids?: string; - #owners?: string; - #recipients?: string; - #sort = "sort: HEIGHT_DESC"; - #tags: { name: string; values: string[] }[] = []; - - constructor(options?: QueryBuilderOptions) { - super(options); - this.return_schema = defaultTransactionsQueryProjection; - } - - /** - * @returns `this` instance for further method chaining. - */ - after(after: string) { - if (typeof after === "string") { - this.#after = `after: "${after}"`; - } - return this; - } - - build() { - const ret: string[] = [ - `{`, - ` transactions(`, - ` ${this.#sort}`, - ]; - - if (this.#getQueryVariables() !== "") { - ret.push(` ${this.#getQueryVariables()}`); - } - - if (this.#getQueryTags() !== "") { - ret.push(` ${this.#getQueryTags()}`); - } - - ret.push(`) {`); - ret.push(`${this.return_schema}`); - ret.push(` }`); - ret.push(`}`); - - return ret.join("\n"); - } - - /** - * @returns `this` instance for further method chaining. - */ - ids(ids: string[]) { - const concatted = this._concat(ids); - - if (concatted) { - this.#ids = `ids: ${concatted}`; - } - - return this; - } - - /** - * @returns `this` instance for further method chaining. - */ - owners(owners: string[]) { - const concatted = this._concat(owners); - - if (concatted) { - this.#owners = `owners: ${concatted}`; - } - - return this; - } - - /** - * @returns `this` instance for further method chaining. - */ - recipients(recipients: string[]) { - const concatted = this._concat(recipients); - - if (concatted) { - this.#recipients = `recipients: ${concatted}`; - } - - return this; - } - - /** - * Set the tags to send with the query. - * @param tags The tags in question. - * @returns `this` instance for further method chaining. - */ - tags(tags: { name: string; values: string[] }[]) { - this.#tags = this.#tags.concat(tags); - return this; - } - - #getQueryTags() { - const tags: string[] = []; - - const tagsToBuild = this.#tags; - - if (tagsToBuild && tagsToBuild.length > 0) { - const concattedTags = tagsToBuild.map((tag) => { - const concattedValues = this._concat(tag.values); - - const ret: string[] = [ - ` {`, - ` name: "${tag.name}"`, - ` values: ${concattedValues}`, - ` }`, - ]; - - return ret.join("\n"); - }); - - tags.push(`tags: [`); - tags.push(` ${concattedTags}`); - tags.push(` ]`); - } - - return tags.join("\n"); - } - - #getQueryVariables() { - const vars = [ - this.#after, - this.#ids, - this.#owners, - this.#recipients, - this.#first, - ]; - - const ret: string[] = []; - - for (const value of vars) { - if (this.stringExists(value)) { - ret.push(` ${value}`); - } - } - - if (this.stringExists(this.variables)) { - ret.push(` ${this.variables}`); - } - - return ret.join("\n"); - } -} diff --git a/tests/integration/modules/graphql/Query.test.ts b/tests/integration/modules/graphql/Query.test.ts index 6844a04..1d343d4 100644 --- a/tests/integration/modules/graphql/Query.test.ts +++ b/tests/integration/modules/graphql/Query.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { query } from "../../../../src/modules/graphql/Query.ts"; +import { query } from "../../../../src/modules/graphql/arweave.net/Query.ts"; describe("query()", () => { describe("tags()", () => { @@ -14,37 +14,63 @@ describe("query()", () => { ]) .build(); - const expected = `{ - transactions( - sort: HEIGHT_DESC - tags: [ - { - name: \"Pushed-For\" - values: [\"bgdRvkb_eSrbd3PbrZZ0HhdLgcdu7TYHxIvgNU3E2Ec\"] - } - ] + const expected = `query GetTransactions( + $ids: [ID!] + $owners: [String!] + $recipients: [String!] + $tags: [TagFilter!] + $bundledIn: [ID!] + $block: BlockFilter + $first: Int = 10 + $after: String + $sort: SortOrder = HEIGHT_DESC ) { -edges { - node { - id - owner { - address - } - block { - height - timestamp + transactions( + ids: $ids + owners: $owners + recipients: $recipients + tags: $tags + bundledIn: $bundledIn + block: $block + first: $first + after: $after + sort: $sort + ) { + + pageInfo { + hasNextPage } - tags { - name - value + edges { + cursor + node { + id + owner { + address + } + block { + height + timestamp + } + tags { + name + value + } + } } - } - cursor -} + } }`; - expect(res).toStrictEqual(expected); + expect(res.operationName).toStrictEqual(`GetTransactions`); + expect(res.query).toStrictEqual(expected); + expect(res.variables).toStrictEqual({ + tags: [ + { + name: "Pushed-For", + values: ["bgdRvkb_eSrbd3PbrZZ0HhdLgcdu7TYHxIvgNU3E2Ec"], + }, + ], + }); }); }); });