mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
community: add graphql benchmark
GITHUB_PR_NUMBER: 9401 GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/9401 PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7672 Co-authored-by: arjunyel <11153289+arjunyel@users.noreply.github.com> Co-authored-by: Praveen Durairaju <14110316+praveenweb@users.noreply.github.com> GitOrigin-RevId: 9b0e9a3dd4cee1ef0a76fb45dee3d5e76d404512
This commit is contained in:
parent
0b72259c1f
commit
aaf20b971f
15
community/sample-apps/graphql-benchmark/README.md
Normal file
15
community/sample-apps/graphql-benchmark/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# GraphQL Benchmarking
|
||||
|
||||
Uses the [Chinook sample database](https://github.com/lerocha/chinook-database). Tested on macOS Ventura 13.1.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Checkout [https://github.com/hasura/graphql-bench](https://github.com/hasura/graphql-bench) and build the docker container with `make build_local_docker_image`
|
||||
|
||||
1. Run `docker compose -f docker-compose.hasura.yml up -d` to bootstrap Postgres with Chinook.
|
||||
|
||||
1. After a few minutes check the Hasura docker logs to see if Hasura is running, which implies the database is now bootstrapped. Run `docker compose -f docker-compose.hasura.yml down`
|
||||
|
||||
1. Run the benchmarks `sh benchmark.sh`
|
||||
|
||||
1. Open the results on the GraphQL bench website [https://hasura.github.io/graphql-bench/app/web-app/](https://hasura.github.io/graphql-bench/app/web-app/)
|
31
community/sample-apps/graphql-benchmark/benchmark.sh
Normal file
31
community/sample-apps/graphql-benchmark/benchmark.sh
Normal file
@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
|
||||
docker compose -f docker-compose.hasura.yml up -d --build
|
||||
|
||||
echo "Running Hasura Benchmark"
|
||||
|
||||
sleep 5
|
||||
|
||||
docker run --net=host -v "$PWD":/app/tmp -it \
|
||||
graphql-bench-local query \
|
||||
--config="./tmp/config.query.hasura.yaml" \
|
||||
--outfile="./tmp/report.hasura.json"
|
||||
|
||||
docker compose -f docker-compose.hasura.yml down
|
||||
|
||||
echo "Hasura Benchmark done"
|
||||
|
||||
docker compose -f docker-compose.node.yml up -d --build
|
||||
|
||||
echo "Running Nodejs Benchmark"
|
||||
|
||||
sleep 5
|
||||
|
||||
docker run --net=host -v "$PWD":/app/tmp -it \
|
||||
graphql-bench-local query \
|
||||
--config="./tmp/config.query.node.yaml" \
|
||||
--outfile="./tmp/report.nodejs.json"
|
||||
|
||||
docker compose -f docker-compose.node.yml down
|
||||
|
||||
echo "Node.js Benchmark done"
|
@ -0,0 +1,64 @@
|
||||
url: "http://host.docker.internal:8080/v1/graphql"
|
||||
# url: https://benchmark-hasura-rso3d2ja7a-uc.a.run.app/v1/graphql
|
||||
headers:
|
||||
content-type: application/json
|
||||
# "Debug" mode enables request and response logging for Autocannon and K6
|
||||
# This lets you see what is happening and confirm proper behavior.
|
||||
# This should be disabled for genuine benchmarks, and only used for debugging/visibility.
|
||||
debug: false
|
||||
queries:
|
||||
# Name: Unique name for the query
|
||||
- name: GetAllArtistsAlbumsAndTracks
|
||||
# Tools: List of benchmarking tools to run: ['autocannon', 'k6', 'wrk2']
|
||||
tools: [k6]
|
||||
execution_strategy: REQUESTS_PER_SECOND
|
||||
rps: 2000
|
||||
duration: 10s
|
||||
connections: 50
|
||||
query: |
|
||||
query GetAllArtistsAlbumsTracks_Genres {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
Albums {
|
||||
AlbumId
|
||||
Title
|
||||
Tracks {
|
||||
TrackId
|
||||
Name
|
||||
Composer
|
||||
Genre {
|
||||
GenreId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- name: AlbumByPK
|
||||
tools: [k6]
|
||||
execution_strategy: FIXED_REQUEST_NUMBER
|
||||
requests: 10000
|
||||
query: |
|
||||
query AlbumByPK {
|
||||
Album_by_pk(AlbumId: 1) {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
- name: AlbumByPKMultiStage
|
||||
tools: [k6]
|
||||
execution_strategy: MULTI_STAGE
|
||||
initial_rps: 0
|
||||
stages:
|
||||
- duration: 5s
|
||||
target: 100
|
||||
- duration: 5s
|
||||
target: 1000
|
||||
query: |
|
||||
query AlbumByPK {
|
||||
Album_by_pk(AlbumId: 1) {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
url: "http://host.docker.internal:8080/v1/graphql"
|
||||
# url: https://benchmark-node-rso3d2ja7a-ul.a.run.app/v1/graphql
|
||||
headers:
|
||||
content-type: application/json
|
||||
# "Debug" mode enables request and response logging for Autocannon and K6
|
||||
# This lets you see what is happening and confirm proper behavior.
|
||||
# This should be disabled for genuine benchmarks, and only used for debugging/visibility.
|
||||
debug: false
|
||||
queries:
|
||||
# Name: Unique name for the query
|
||||
- name: GetAllArtistsAlbumsAndTracks
|
||||
# Tools: List of benchmarking tools to run: ['autocannon', 'k6', 'wrk2']
|
||||
tools: [k6]
|
||||
execution_strategy: REQUESTS_PER_SECOND
|
||||
rps: 2000
|
||||
duration: 10s
|
||||
connections: 10
|
||||
query: |
|
||||
query GetAllArtistsAlbumsTracks_Genres {
|
||||
Artist {
|
||||
ArtistId
|
||||
Name
|
||||
Albums {
|
||||
AlbumId
|
||||
Title
|
||||
Tracks {
|
||||
TrackId
|
||||
Name
|
||||
Composer
|
||||
Genre {
|
||||
GenreId
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- name: AlbumByPK
|
||||
tools: [k6]
|
||||
execution_strategy: FIXED_REQUEST_NUMBER
|
||||
requests: 10000
|
||||
query: |
|
||||
query AlbumByPK {
|
||||
Album_by_pk(AlbumId: 1) {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
||||
- name: AlbumByPKMultiStage
|
||||
tools: [k6]
|
||||
execution_strategy: MULTI_STAGE
|
||||
initial_rps: 0
|
||||
stages:
|
||||
- duration: 5s
|
||||
target: 100
|
||||
- duration: 5s
|
||||
target: 1000
|
||||
query: |
|
||||
query AlbumByPK {
|
||||
Album_by_pk(AlbumId: 1) {
|
||||
AlbumId
|
||||
Title
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
restart: always
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgrespassword
|
||||
graphql-engine:
|
||||
image: hasura/graphql-engine:v2.18.0.cli-migrations-v3
|
||||
volumes:
|
||||
- ./hasura/migrations:/hasura-migrations
|
||||
- ./hasura/metadata:/hasura-metadata
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- "postgres"
|
||||
restart: always
|
||||
environment:
|
||||
## postgres database to store Hasura metadata
|
||||
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
|
||||
## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs
|
||||
PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
|
||||
## enable the console served by server
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
|
||||
## enable debugging mode. It is recommended to disable this in production
|
||||
HASURA_GRAPHQL_DEV_MODE: "true"
|
||||
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
|
||||
## uncomment next line to run console offline (i.e load console assets from server instead of CDN)
|
||||
# HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets
|
||||
## uncomment next line to set an admin secret
|
||||
# HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
|
||||
volumes:
|
||||
db_data:
|
@ -0,0 +1,18 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
restart: always
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgrespassword
|
||||
node:
|
||||
build: ./nodejs
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- "postgres"
|
||||
volumes:
|
||||
db_data:
|
@ -0,0 +1,6 @@
|
||||
version: 3
|
||||
endpoint: http://localhost:8080
|
||||
metadata_directory: metadata
|
||||
actions:
|
||||
kind: synchronous
|
||||
handler_webhook_baseurl: http://localhost:3000
|
@ -0,0 +1,6 @@
|
||||
actions: []
|
||||
custom_types:
|
||||
enums: []
|
||||
input_objects: []
|
||||
objects: []
|
||||
scalars: []
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1,9 @@
|
||||
- name: default
|
||||
kind: postgres
|
||||
configuration:
|
||||
connection_info:
|
||||
database_url:
|
||||
from_env: PG_DATABASE_URL
|
||||
isolation_level: read-committed
|
||||
use_prepared_statements: false
|
||||
tables: "!include default/tables/tables.yaml"
|
@ -0,0 +1,15 @@
|
||||
table:
|
||||
name: Album
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Artist
|
||||
using:
|
||||
foreign_key_constraint_on: ArtistId
|
||||
array_relationships:
|
||||
- name: Tracks
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: AlbumId
|
||||
table:
|
||||
name: Track
|
||||
schema: public
|
@ -0,0 +1,11 @@
|
||||
table:
|
||||
name: Artist
|
||||
schema: public
|
||||
array_relationships:
|
||||
- name: Albums
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: ArtistId
|
||||
table:
|
||||
name: Album
|
||||
schema: public
|
@ -0,0 +1,15 @@
|
||||
table:
|
||||
name: Customer
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Employee
|
||||
using:
|
||||
foreign_key_constraint_on: SupportRepId
|
||||
array_relationships:
|
||||
- name: Invoices
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: CustomerId
|
||||
table:
|
||||
name: Invoice
|
||||
schema: public
|
@ -0,0 +1,22 @@
|
||||
table:
|
||||
name: Employee
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Employee
|
||||
using:
|
||||
foreign_key_constraint_on: ReportsTo
|
||||
array_relationships:
|
||||
- name: Customers
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: SupportRepId
|
||||
table:
|
||||
name: Customer
|
||||
schema: public
|
||||
- name: Employees
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: ReportsTo
|
||||
table:
|
||||
name: Employee
|
||||
schema: public
|
@ -0,0 +1,11 @@
|
||||
table:
|
||||
name: Genre
|
||||
schema: public
|
||||
array_relationships:
|
||||
- name: Tracks
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: GenreId
|
||||
table:
|
||||
name: Track
|
||||
schema: public
|
@ -0,0 +1,15 @@
|
||||
table:
|
||||
name: Invoice
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Customer
|
||||
using:
|
||||
foreign_key_constraint_on: CustomerId
|
||||
array_relationships:
|
||||
- name: InvoiceLines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: InvoiceId
|
||||
table:
|
||||
name: InvoiceLine
|
||||
schema: public
|
@ -0,0 +1,10 @@
|
||||
table:
|
||||
name: InvoiceLine
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Invoice
|
||||
using:
|
||||
foreign_key_constraint_on: InvoiceId
|
||||
- name: Track
|
||||
using:
|
||||
foreign_key_constraint_on: TrackId
|
@ -0,0 +1,11 @@
|
||||
table:
|
||||
name: MediaType
|
||||
schema: public
|
||||
array_relationships:
|
||||
- name: Tracks
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: MediaTypeId
|
||||
table:
|
||||
name: Track
|
||||
schema: public
|
@ -0,0 +1,11 @@
|
||||
table:
|
||||
name: Playlist
|
||||
schema: public
|
||||
array_relationships:
|
||||
- name: PlaylistTracks
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: PlaylistId
|
||||
table:
|
||||
name: PlaylistTrack
|
||||
schema: public
|
@ -0,0 +1,10 @@
|
||||
table:
|
||||
name: PlaylistTrack
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Playlist
|
||||
using:
|
||||
foreign_key_constraint_on: PlaylistId
|
||||
- name: Track
|
||||
using:
|
||||
foreign_key_constraint_on: TrackId
|
@ -0,0 +1,28 @@
|
||||
table:
|
||||
name: Track
|
||||
schema: public
|
||||
object_relationships:
|
||||
- name: Album
|
||||
using:
|
||||
foreign_key_constraint_on: AlbumId
|
||||
- name: Genre
|
||||
using:
|
||||
foreign_key_constraint_on: GenreId
|
||||
- name: MediaType
|
||||
using:
|
||||
foreign_key_constraint_on: MediaTypeId
|
||||
array_relationships:
|
||||
- name: InvoiceLines
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: TrackId
|
||||
table:
|
||||
name: InvoiceLine
|
||||
schema: public
|
||||
- name: PlaylistTracks
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: TrackId
|
||||
table:
|
||||
name: PlaylistTrack
|
||||
schema: public
|
@ -0,0 +1,11 @@
|
||||
- "!include public_Album.yaml"
|
||||
- "!include public_Artist.yaml"
|
||||
- "!include public_Customer.yaml"
|
||||
- "!include public_Employee.yaml"
|
||||
- "!include public_Genre.yaml"
|
||||
- "!include public_Invoice.yaml"
|
||||
- "!include public_InvoiceLine.yaml"
|
||||
- "!include public_MediaType.yaml"
|
||||
- "!include public_Playlist.yaml"
|
||||
- "!include public_PlaylistTrack.yaml"
|
||||
- "!include public_Track.yaml"
|
@ -0,0 +1 @@
|
||||
disabled_for_roles: []
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
version: 3
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,193 @@
|
||||
/*******************************************************************************
|
||||
Chinook Database - Version 1.4
|
||||
Script: Chinook_PostgreSql.sql
|
||||
Description: Creates and populates the Chinook database.
|
||||
DB Server: PostgreSql
|
||||
Author: Luis Rocha
|
||||
License: http://www.codeplex.com/ChinookDatabase/license
|
||||
********************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Create Tables
|
||||
********************************************************************************/
|
||||
CREATE TABLE "Album"
|
||||
(
|
||||
"AlbumId" INT NOT NULL,
|
||||
"Title" VARCHAR(160) NOT NULL,
|
||||
"ArtistId" INT NOT NULL,
|
||||
CONSTRAINT "PK_Album" PRIMARY KEY ("AlbumId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Artist"
|
||||
(
|
||||
"ArtistId" INT NOT NULL,
|
||||
"Name" VARCHAR(120),
|
||||
CONSTRAINT "PK_Artist" PRIMARY KEY ("ArtistId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Customer"
|
||||
(
|
||||
"CustomerId" INT NOT NULL,
|
||||
"FirstName" VARCHAR(40) NOT NULL,
|
||||
"LastName" VARCHAR(20) NOT NULL,
|
||||
"Company" VARCHAR(80),
|
||||
"Address" VARCHAR(70),
|
||||
"City" VARCHAR(40),
|
||||
"State" VARCHAR(40),
|
||||
"Country" VARCHAR(40),
|
||||
"PostalCode" VARCHAR(10),
|
||||
"Phone" VARCHAR(24),
|
||||
"Fax" VARCHAR(24),
|
||||
"Email" VARCHAR(60) NOT NULL,
|
||||
"SupportRepId" INT,
|
||||
CONSTRAINT "PK_Customer" PRIMARY KEY ("CustomerId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Employee"
|
||||
(
|
||||
"EmployeeId" INT NOT NULL,
|
||||
"LastName" VARCHAR(20) NOT NULL,
|
||||
"FirstName" VARCHAR(20) NOT NULL,
|
||||
"Title" VARCHAR(30),
|
||||
"ReportsTo" INT,
|
||||
"BirthDate" TIMESTAMP,
|
||||
"HireDate" TIMESTAMP,
|
||||
"Address" VARCHAR(70),
|
||||
"City" VARCHAR(40),
|
||||
"State" VARCHAR(40),
|
||||
"Country" VARCHAR(40),
|
||||
"PostalCode" VARCHAR(10),
|
||||
"Phone" VARCHAR(24),
|
||||
"Fax" VARCHAR(24),
|
||||
"Email" VARCHAR(60),
|
||||
CONSTRAINT "PK_Employee" PRIMARY KEY ("EmployeeId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Genre"
|
||||
(
|
||||
"GenreId" INT NOT NULL,
|
||||
"Name" VARCHAR(120),
|
||||
CONSTRAINT "PK_Genre" PRIMARY KEY ("GenreId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Invoice"
|
||||
(
|
||||
"InvoiceId" INT NOT NULL,
|
||||
"CustomerId" INT NOT NULL,
|
||||
"InvoiceDate" TIMESTAMP NOT NULL,
|
||||
"BillingAddress" VARCHAR(70),
|
||||
"BillingCity" VARCHAR(40),
|
||||
"BillingState" VARCHAR(40),
|
||||
"BillingCountry" VARCHAR(40),
|
||||
"BillingPostalCode" VARCHAR(10),
|
||||
"Total" NUMERIC(10,2) NOT NULL,
|
||||
CONSTRAINT "PK_Invoice" PRIMARY KEY ("InvoiceId")
|
||||
);
|
||||
|
||||
CREATE TABLE "InvoiceLine"
|
||||
(
|
||||
"InvoiceLineId" INT NOT NULL,
|
||||
"InvoiceId" INT NOT NULL,
|
||||
"TrackId" INT NOT NULL,
|
||||
"UnitPrice" NUMERIC(10,2) NOT NULL,
|
||||
"Quantity" INT NOT NULL,
|
||||
CONSTRAINT "PK_InvoiceLine" PRIMARY KEY ("InvoiceLineId")
|
||||
);
|
||||
|
||||
CREATE TABLE "MediaType"
|
||||
(
|
||||
"MediaTypeId" INT NOT NULL,
|
||||
"Name" VARCHAR(120),
|
||||
CONSTRAINT "PK_MediaType" PRIMARY KEY ("MediaTypeId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Playlist"
|
||||
(
|
||||
"PlaylistId" INT NOT NULL,
|
||||
"Name" VARCHAR(120),
|
||||
CONSTRAINT "PK_Playlist" PRIMARY KEY ("PlaylistId")
|
||||
);
|
||||
|
||||
CREATE TABLE "PlaylistTrack"
|
||||
(
|
||||
"PlaylistId" INT NOT NULL,
|
||||
"TrackId" INT NOT NULL,
|
||||
CONSTRAINT "PK_PlaylistTrack" PRIMARY KEY ("PlaylistId", "TrackId")
|
||||
);
|
||||
|
||||
CREATE TABLE "Track"
|
||||
(
|
||||
"TrackId" INT NOT NULL,
|
||||
"Name" VARCHAR(200) NOT NULL,
|
||||
"AlbumId" INT,
|
||||
"MediaTypeId" INT NOT NULL,
|
||||
"GenreId" INT,
|
||||
"Composer" VARCHAR(220),
|
||||
"Milliseconds" INT NOT NULL,
|
||||
"Bytes" INT,
|
||||
"UnitPrice" NUMERIC(10,2) NOT NULL,
|
||||
CONSTRAINT "PK_Track" PRIMARY KEY ("TrackId")
|
||||
);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
Create Primary Key Unique Indexes
|
||||
********************************************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
Create Foreign Keys
|
||||
********************************************************************************/
|
||||
ALTER TABLE "Album" ADD CONSTRAINT "FK_AlbumArtistId"
|
||||
FOREIGN KEY ("ArtistId") REFERENCES "Artist" ("ArtistId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_AlbumArtistId" ON "Album" ("ArtistId");
|
||||
|
||||
ALTER TABLE "Customer" ADD CONSTRAINT "FK_CustomerSupportRepId"
|
||||
FOREIGN KEY ("SupportRepId") REFERENCES "Employee" ("EmployeeId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_CustomerSupportRepId" ON "Customer" ("SupportRepId");
|
||||
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "FK_EmployeeReportsTo"
|
||||
FOREIGN KEY ("ReportsTo") REFERENCES "Employee" ("EmployeeId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_EmployeeReportsTo" ON "Employee" ("ReportsTo");
|
||||
|
||||
ALTER TABLE "Invoice" ADD CONSTRAINT "FK_InvoiceCustomerId"
|
||||
FOREIGN KEY ("CustomerId") REFERENCES "Customer" ("CustomerId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_InvoiceCustomerId" ON "Invoice" ("CustomerId");
|
||||
|
||||
ALTER TABLE "InvoiceLine" ADD CONSTRAINT "FK_InvoiceLineInvoiceId"
|
||||
FOREIGN KEY ("InvoiceId") REFERENCES "Invoice" ("InvoiceId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_InvoiceLineInvoiceId" ON "InvoiceLine" ("InvoiceId");
|
||||
|
||||
ALTER TABLE "InvoiceLine" ADD CONSTRAINT "FK_InvoiceLineTrackId"
|
||||
FOREIGN KEY ("TrackId") REFERENCES "Track" ("TrackId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_InvoiceLineTrackId" ON "InvoiceLine" ("TrackId");
|
||||
|
||||
ALTER TABLE "PlaylistTrack" ADD CONSTRAINT "FK_PlaylistTrackPlaylistId"
|
||||
FOREIGN KEY ("PlaylistId") REFERENCES "Playlist" ("PlaylistId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
ALTER TABLE "PlaylistTrack" ADD CONSTRAINT "FK_PlaylistTrackTrackId"
|
||||
FOREIGN KEY ("TrackId") REFERENCES "Track" ("TrackId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_PlaylistTrackTrackId" ON "PlaylistTrack" ("TrackId");
|
||||
|
||||
ALTER TABLE "Track" ADD CONSTRAINT "FK_TrackAlbumId"
|
||||
FOREIGN KEY ("AlbumId") REFERENCES "Album" ("AlbumId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_TrackAlbumId" ON "Track" ("AlbumId");
|
||||
|
||||
ALTER TABLE "Track" ADD CONSTRAINT "FK_TrackGenreId"
|
||||
FOREIGN KEY ("GenreId") REFERENCES "Genre" ("GenreId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_TrackGenreId" ON "Track" ("GenreId");
|
||||
|
||||
ALTER TABLE "Track" ADD CONSTRAINT "FK_TrackMediaTypeId"
|
||||
FOREIGN KEY ("MediaTypeId") REFERENCES "MediaType" ("MediaTypeId") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||
|
||||
CREATE INDEX "IFK_TrackMediaTypeId" ON "Track" ("MediaTypeId");
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
node_modules/
|
4
community/sample-apps/graphql-benchmark/nodejs/.gitignore
vendored
Normal file
4
community/sample-apps/graphql-benchmark/nodejs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
report.hasura.json
|
||||
report.node.json
|
19
community/sample-apps/graphql-benchmark/nodejs/Dockerfile
Normal file
19
community/sample-apps/graphql-benchmark/nodejs/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# https://www.andreadiotallevi.com/blog/how-to-create-a-production-image-for-a-node-typescript-app-using-docker-multi-stage-builds
|
||||
|
||||
FROM node:18-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
RUN npx tsc
|
||||
|
||||
FROM node:18-alpine
|
||||
ENV NODE_ENV=production
|
||||
RUN apk add --no-cache tini
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm ci --production
|
||||
COPY --from=builder ./app/dist ./dist
|
||||
COPY --from=builder ./app/src/schema.graphql ./dist
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT [ "/sbin/tini", "--", "node", "dist/main.js" ]
|
14
community/sample-apps/graphql-benchmark/nodejs/codegen.ts
Normal file
14
community/sample-apps/graphql-benchmark/nodejs/codegen.ts
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
import type { CodegenConfig } from '@graphql-codegen/cli';
|
||||
|
||||
const config: CodegenConfig = {
|
||||
overwrite: true,
|
||||
schema: "./src/schema.graphql",
|
||||
generates: {
|
||||
"src/generated/graphql.ts": {
|
||||
plugins: ["typescript", "typescript-resolvers"]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
10762
community/sample-apps/graphql-benchmark/nodejs/package-lock.json
generated
Normal file
10762
community/sample-apps/graphql-benchmark/nodejs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
community/sample-apps/graphql-benchmark/nodejs/package.json
Normal file
22
community/sample-apps/graphql-benchmark/nodejs/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^3.0.0",
|
||||
"@graphql-codegen/typescript": "^3.0.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^3.0.0",
|
||||
"@tsconfig/node18-strictest": "^1.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@envelop/dataloader": "^4.0.4",
|
||||
"@escape.tech/graphql-armor": "^1.7.0",
|
||||
"dataloader": "^2.2.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-yoga": "^3.5.1",
|
||||
"postgres": "^3.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen --config codegen.ts",
|
||||
"start": "ts-node ./src/main.ts"
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import sql from "./db";
|
||||
import type { Album, Artist, Track } from "./generated/graphql";
|
||||
import { keyByArray } from "./utils";
|
||||
|
||||
export async function genericBatchFunction<T extends Album | Artist | Track>(
|
||||
keys: Readonly<number[]> | Readonly<string[]>,
|
||||
{ name, id }:
|
||||
| { name: "Album"; id: "AlbumId" }
|
||||
| { name: "Album"; id: "ArtistId" }
|
||||
| { name: "Artist"; id: "ArtistId" }
|
||||
| { name: "Track"; id: "TrackId" }
|
||||
| { name: "Track"; id: "AlbumId" }
|
||||
| { name: "Genre"; id: "GenreId" },
|
||||
resultsAreArray = false,
|
||||
) {
|
||||
// console.log(name, id, keys)
|
||||
const results = await sql<T[]>`SELECT * FROM ${sql(
|
||||
name
|
||||
)} WHERE ${sql(id)} in ${sql(keys)}`;
|
||||
|
||||
const resultsMap = keyByArray<T>(results, id as any);
|
||||
|
||||
return keys.map((key: typeof keys[number]) => resultsAreArray ? resultsMap[key] : resultsMap[key]![0]);
|
||||
}
|
5
community/sample-apps/graphql-benchmark/nodejs/src/db.ts
Normal file
5
community/sample-apps/graphql-benchmark/nodejs/src/db.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import postgres from 'postgres'
|
||||
|
||||
const sql = postgres(process.env["PG_DATABASE_URL"] || 'postgres://postgres:postgrespassword@postgres:5432/postgres', { ssl: 'require' });
|
||||
|
||||
export default sql;
|
@ -0,0 +1,230 @@
|
||||
import type { GraphQLResolveInfo } from 'graphql';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
export type RequireFields<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: NonNullable<T[P]> };
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: string;
|
||||
String: string;
|
||||
Boolean: boolean;
|
||||
Int: number;
|
||||
Float: number;
|
||||
};
|
||||
|
||||
export type Album = {
|
||||
__typename?: 'Album';
|
||||
AlbumId: Scalars['Int'];
|
||||
Artist?: Maybe<Artist>;
|
||||
ArtistId: Scalars['Int'];
|
||||
Title: Scalars['String'];
|
||||
Tracks?: Maybe<Array<Track>>;
|
||||
};
|
||||
|
||||
export type Artist = {
|
||||
__typename?: 'Artist';
|
||||
Albums?: Maybe<Array<Album>>;
|
||||
ArtistId: Scalars['Int'];
|
||||
Name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type Genre = {
|
||||
__typename?: 'Genre';
|
||||
GenreId: Scalars['Int'];
|
||||
Name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
Album: Array<Album>;
|
||||
Album_by_pk?: Maybe<Album>;
|
||||
Artist: Array<Artist>;
|
||||
Artist_by_pk?: Maybe<Artist>;
|
||||
Genre: Array<Genre>;
|
||||
Genre_by_pk?: Maybe<Genre>;
|
||||
Track: Array<Track>;
|
||||
Track_by_pk?: Maybe<Track>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryAlbum_By_PkArgs = {
|
||||
AlbumId: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryArtist_By_PkArgs = {
|
||||
ArtistId: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGenre_By_PkArgs = {
|
||||
GenreId: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryTrack_By_PkArgs = {
|
||||
TrackId: Scalars['Int'];
|
||||
};
|
||||
|
||||
export type Track = {
|
||||
__typename?: 'Track';
|
||||
Album?: Maybe<Album>;
|
||||
AlbumId?: Maybe<Scalars['Int']>;
|
||||
Bytes?: Maybe<Scalars['Int']>;
|
||||
Composer?: Maybe<Scalars['String']>;
|
||||
Genre?: Maybe<Genre>;
|
||||
GenreId: Scalars['Int'];
|
||||
MediaTypeId: Scalars['Int'];
|
||||
Milliseconds: Scalars['Int'];
|
||||
Name: Scalars['String'];
|
||||
TrackId: Scalars['Int'];
|
||||
};
|
||||
|
||||
|
||||
|
||||
export type ResolverTypeWrapper<T> = Promise<T> | T;
|
||||
|
||||
|
||||
export type ResolverWithResolve<TResult, TParent, TContext, TArgs> = {
|
||||
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
|
||||
};
|
||||
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs> | ResolverWithResolve<TResult, TParent, TContext, TArgs>;
|
||||
|
||||
export type ResolverFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo
|
||||
) => Promise<TResult> | TResult;
|
||||
|
||||
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo
|
||||
) => AsyncIterable<TResult> | Promise<AsyncIterable<TResult>>;
|
||||
|
||||
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo
|
||||
) => TResult | Promise<TResult>;
|
||||
|
||||
export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
|
||||
subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
|
||||
resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
|
||||
}
|
||||
|
||||
export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
|
||||
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
|
||||
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
|
||||
}
|
||||
|
||||
export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
|
||||
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
|
||||
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
|
||||
|
||||
export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
|
||||
| ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
|
||||
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
|
||||
|
||||
export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
|
||||
parent: TParent,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo
|
||||
) => Maybe<TTypes> | Promise<Maybe<TTypes>>;
|
||||
|
||||
export type IsTypeOfResolverFn<T = {}, TContext = {}> = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise<boolean>;
|
||||
|
||||
export type NextResolverFn<T> = () => Promise<T>;
|
||||
|
||||
export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
|
||||
next: NextResolverFn<TResult>,
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo
|
||||
) => TResult | Promise<TResult>;
|
||||
|
||||
/** Mapping between all available schema types and the resolvers types */
|
||||
export type ResolversTypes = {
|
||||
Album: ResolverTypeWrapper<Album>;
|
||||
Artist: ResolverTypeWrapper<Artist>;
|
||||
Boolean: ResolverTypeWrapper<Scalars['Boolean']>;
|
||||
Genre: ResolverTypeWrapper<Genre>;
|
||||
Int: ResolverTypeWrapper<Scalars['Int']>;
|
||||
Query: ResolverTypeWrapper<{}>;
|
||||
String: ResolverTypeWrapper<Scalars['String']>;
|
||||
Track: ResolverTypeWrapper<Track>;
|
||||
};
|
||||
|
||||
/** Mapping between all available schema types and the resolvers parents */
|
||||
export type ResolversParentTypes = {
|
||||
Album: Album;
|
||||
Artist: Artist;
|
||||
Boolean: Scalars['Boolean'];
|
||||
Genre: Genre;
|
||||
Int: Scalars['Int'];
|
||||
Query: {};
|
||||
String: Scalars['String'];
|
||||
Track: Track;
|
||||
};
|
||||
|
||||
export type AlbumResolvers<ContextType = any, ParentType extends ResolversParentTypes['Album'] = ResolversParentTypes['Album']> = {
|
||||
AlbumId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Artist?: Resolver<Maybe<ResolversTypes['Artist']>, ParentType, ContextType>;
|
||||
ArtistId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
Tracks?: Resolver<Maybe<Array<ResolversTypes['Track']>>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ArtistResolvers<ContextType = any, ParentType extends ResolversParentTypes['Artist'] = ResolversParentTypes['Artist']> = {
|
||||
Albums?: Resolver<Maybe<Array<ResolversTypes['Album']>>, ParentType, ContextType>;
|
||||
ArtistId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type GenreResolvers<ContextType = any, ParentType extends ResolversParentTypes['Genre'] = ResolversParentTypes['Genre']> = {
|
||||
GenreId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type QueryResolvers<ContextType = any, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
|
||||
Album?: Resolver<Array<ResolversTypes['Album']>, ParentType, ContextType>;
|
||||
Album_by_pk?: Resolver<Maybe<ResolversTypes['Album']>, ParentType, ContextType, RequireFields<QueryAlbum_By_PkArgs, 'AlbumId'>>;
|
||||
Artist?: Resolver<Array<ResolversTypes['Artist']>, ParentType, ContextType>;
|
||||
Artist_by_pk?: Resolver<Maybe<ResolversTypes['Artist']>, ParentType, ContextType, RequireFields<QueryArtist_By_PkArgs, 'ArtistId'>>;
|
||||
Genre?: Resolver<Array<ResolversTypes['Genre']>, ParentType, ContextType>;
|
||||
Genre_by_pk?: Resolver<Maybe<ResolversTypes['Genre']>, ParentType, ContextType, RequireFields<QueryGenre_By_PkArgs, 'GenreId'>>;
|
||||
Track?: Resolver<Array<ResolversTypes['Track']>, ParentType, ContextType>;
|
||||
Track_by_pk?: Resolver<Maybe<ResolversTypes['Track']>, ParentType, ContextType, RequireFields<QueryTrack_By_PkArgs, 'TrackId'>>;
|
||||
};
|
||||
|
||||
export type TrackResolvers<ContextType = any, ParentType extends ResolversParentTypes['Track'] = ResolversParentTypes['Track']> = {
|
||||
Album?: Resolver<Maybe<ResolversTypes['Album']>, ParentType, ContextType>;
|
||||
AlbumId?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
Bytes?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
Composer?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
Genre?: Resolver<Maybe<ResolversTypes['Genre']>, ParentType, ContextType>;
|
||||
GenreId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
MediaTypeId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Milliseconds?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
Name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
TrackId?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type Resolvers<ContextType = any> = {
|
||||
Album?: AlbumResolvers<ContextType>;
|
||||
Artist?: ArtistResolvers<ContextType>;
|
||||
Genre?: GenreResolvers<ContextType>;
|
||||
Query?: QueryResolvers<ContextType>;
|
||||
Track?: TrackResolvers<ContextType>;
|
||||
};
|
||||
|
223
community/sample-apps/graphql-benchmark/nodejs/src/main.ts
Normal file
223
community/sample-apps/graphql-benchmark/nodejs/src/main.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createServer } from "node:http";
|
||||
import { join } from "node:path";
|
||||
import { createSchema, createYoga } from "graphql-yoga";
|
||||
import { GraphQLError } from "graphql";
|
||||
import DataLoader from "dataloader";
|
||||
import { useDataLoader } from "@envelop/dataloader";
|
||||
import { EnvelopArmorPlugin } from "@escape.tech/graphql-armor";
|
||||
import type {
|
||||
Album,
|
||||
Artist,
|
||||
Genre,
|
||||
Resolvers,
|
||||
Track,
|
||||
} from "./generated/graphql";
|
||||
import sql from "./db";
|
||||
import { genericBatchFunction } from "./dataloader";
|
||||
import { keyByArray } from "./utils";
|
||||
|
||||
type Context = {
|
||||
getAlbumsById: DataLoader<string, Album>;
|
||||
getAllAlbums: DataLoader<string, Album[]>;
|
||||
getAlbumsByArtistId: DataLoader<string, Album[]>;
|
||||
getArtistsById: DataLoader<string, Artist>;
|
||||
getAllArtists: DataLoader<string, Artist[]>;
|
||||
getTracksById: DataLoader<string, Track>;
|
||||
getTracksByAlbumId: DataLoader<string, Track[]>;
|
||||
getAllTracks: DataLoader<string, Track[]>;
|
||||
getGenreById: DataLoader<string, Genre>;
|
||||
getAllGenres: DataLoader<string, Genre[]>;
|
||||
loggedInArtistId?: number;
|
||||
};
|
||||
|
||||
const typeDefs = readFileSync(join(__dirname, "schema.graphql"), "utf8");
|
||||
|
||||
const resolvers: Resolvers = {
|
||||
Query: {
|
||||
Album_by_pk: async (_parent, args, context: Context, _info) => {
|
||||
return context.getAlbumsById.load(args.AlbumId.toString());
|
||||
},
|
||||
Album: async (_parent, _args, context: Context, _info) => {
|
||||
const albums = await context.getAllAlbums.load("1");
|
||||
for (const album of albums) {
|
||||
context.getAlbumsById.prime(album.AlbumId.toString(), album);
|
||||
}
|
||||
const albumsByArtistId = keyByArray(albums, "ArtistId");
|
||||
for (const [ArtistId, albums] of Object.entries(albumsByArtistId)) {
|
||||
context.getAlbumsByArtistId.prime(ArtistId, albums);
|
||||
}
|
||||
return albums;
|
||||
},
|
||||
Artist_by_pk: async (_parent, args, context: Context, _info) => {
|
||||
return context.getArtistsById.load(args.ArtistId.toString());
|
||||
},
|
||||
Artist: async (_parent, _args, context: Context, _info) => {
|
||||
if (context.loggedInArtistId) {
|
||||
}
|
||||
|
||||
const artists = await context.getAllArtists.load("1");
|
||||
if (!artists) {
|
||||
throw new GraphQLError(`Albums not found.`);
|
||||
}
|
||||
for (const artist of artists) {
|
||||
context.getArtistsById.prime(artist.ArtistId.toString(), artist);
|
||||
}
|
||||
return artists;
|
||||
},
|
||||
Track_by_pk: async (_parent, args, context: Context, _info) => {
|
||||
return context.getTracksById.load(args.TrackId.toString());
|
||||
},
|
||||
Track: async (_parent, _args, context: Context, _info) => {
|
||||
const tracks = await context.getAllTracks.load("1");
|
||||
for (const track of tracks) {
|
||||
context.getTracksById.prime(track.TrackId.toString(), track);
|
||||
}
|
||||
const tracksByAlbumId = keyByArray(tracks, "AlbumId");
|
||||
for (const [AlbumId, tracks] of Object.entries(tracksByAlbumId)) {
|
||||
context.getTracksByAlbumId.prime(AlbumId, tracks);
|
||||
}
|
||||
return tracks;
|
||||
},
|
||||
Genre_by_pk: async (_parent, args, context: Context, _info) => {
|
||||
return context.getGenreById.load(args.GenreId.toString());
|
||||
},
|
||||
Genre: async (_parent, _args, context: Context, _info) => {
|
||||
const genres = await context.getAllGenres.load("1");
|
||||
if (!genres) {
|
||||
throw new GraphQLError(`Albums not found.`);
|
||||
}
|
||||
for (const genre of genres) {
|
||||
context.getGenreById.prime(genre.GenreId.toString(), genre);
|
||||
}
|
||||
return genres;
|
||||
},
|
||||
},
|
||||
Album: {
|
||||
async Artist(parent, _args, context: Context, _info) {
|
||||
return context.getArtistsById.load(parent.ArtistId.toString());
|
||||
},
|
||||
async Tracks(parent, _args, context: Context, _info) {
|
||||
const tracks = await context.getTracksByAlbumId.load(
|
||||
parent.AlbumId.toString()
|
||||
);
|
||||
for (const track of tracks) {
|
||||
context.getTracksById.prime(track.TrackId.toString(), track);
|
||||
}
|
||||
return tracks;
|
||||
},
|
||||
},
|
||||
Artist: {
|
||||
async Albums(parent, _args, context: Context, _info) {
|
||||
const albums = await context.getAlbumsByArtistId.load(
|
||||
parent.ArtistId.toString()
|
||||
);
|
||||
if (Array.isArray(albums)) {
|
||||
for (const album of albums) {
|
||||
context.getAlbumsById.prime(album.AlbumId.toString(), album);
|
||||
}
|
||||
}
|
||||
return albums || [];
|
||||
},
|
||||
},
|
||||
Track: {
|
||||
async Album(parent, _args, context: Context, _info) {
|
||||
return context.getAlbumsById.load(parent.AlbumId!.toString());
|
||||
},
|
||||
async Genre(parent, _args, context: Context, _info) {
|
||||
return context.getGenreById.load(parent.GenreId!.toString());
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const schema = createSchema({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
});
|
||||
|
||||
const server = createServer(
|
||||
// @ts-ignore
|
||||
createYoga({
|
||||
graphqlEndpoint: "/v1/graphql",
|
||||
// @ts-ignore
|
||||
schema,
|
||||
plugins: [
|
||||
EnvelopArmorPlugin(),
|
||||
useDataLoader("getAlbumsById", (_context: Context) => {
|
||||
return new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Album", id: "AlbumId" })
|
||||
);
|
||||
}),
|
||||
useDataLoader("getAllAlbums", (_context: Context) => {
|
||||
return new DataLoader(async (keys: Readonly<string[]>) => {
|
||||
const albums = await sql`SELECT * FROM ${sql("Album")}`;
|
||||
return keys.map((_key) => albums);
|
||||
});
|
||||
}),
|
||||
useDataLoader(
|
||||
"getAlbumsByArtistId",
|
||||
(_context: Context) =>
|
||||
new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Album", id: "ArtistId" }, true)
|
||||
)
|
||||
),
|
||||
useDataLoader(
|
||||
"getArtistsById",
|
||||
(_context: Context) =>
|
||||
new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Artist", id: "ArtistId" })
|
||||
)
|
||||
),
|
||||
useDataLoader("getAllArtists", (_context: Context) => {
|
||||
return new DataLoader(async (keys: Readonly<string[]>) => {
|
||||
const artists = await sql`SELECT * FROM ${sql("Artist")}`;
|
||||
return keys.map((_key) => artists);
|
||||
});
|
||||
}),
|
||||
useDataLoader(
|
||||
"getTracksById",
|
||||
(_context: Context) =>
|
||||
new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Track", id: "TrackId" })
|
||||
)
|
||||
),
|
||||
useDataLoader(
|
||||
"getTracksByAlbumId",
|
||||
(_context: Context) =>
|
||||
new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Track", id: "AlbumId" }, true)
|
||||
)
|
||||
),
|
||||
useDataLoader(
|
||||
"getAllTracks",
|
||||
(_context: Context) =>
|
||||
new DataLoader(async (keys: Readonly<string[]>) => {
|
||||
const tracks = await sql`SELECT * FROM ${sql("Track")}`;
|
||||
return keys.map((_key) => tracks);
|
||||
})
|
||||
),
|
||||
useDataLoader(
|
||||
"getGenreById",
|
||||
(_context: Context) =>
|
||||
new DataLoader((keys: Readonly<string[]>) =>
|
||||
genericBatchFunction(keys, { name: "Genre", id: "GenreId" })
|
||||
)
|
||||
),
|
||||
useDataLoader(
|
||||
"getAllGenres",
|
||||
(_context: Context) =>
|
||||
new DataLoader(async (keys: Readonly<string[]>) => {
|
||||
const genres = await sql`SELECT * FROM ${sql("Genre")}`;
|
||||
return keys.map((_key) => genres);
|
||||
})
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
server.listen(process.env["PORT"] || 8080, () => {
|
||||
console.info(
|
||||
`Server is running on http://localhost:${process.env["PORT"] || 8080
|
||||
}/v1/graphql`
|
||||
);
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
|
||||
type Query {
|
||||
Album_by_pk(AlbumId: Int!): Album
|
||||
Album: [Album!]!
|
||||
Artist_by_pk(ArtistId: Int!): Artist
|
||||
Artist: [Artist!]!
|
||||
Track_by_pk(TrackId: Int!): Track
|
||||
Track: [Track!]!
|
||||
Genre_by_pk(GenreId: Int!): Genre
|
||||
Genre: [Genre!]!
|
||||
}
|
||||
|
||||
type Album {
|
||||
AlbumId: Int!
|
||||
Artist: Artist
|
||||
ArtistId: Int!
|
||||
Title: String!
|
||||
Tracks: [Track!]
|
||||
}
|
||||
|
||||
type Artist {
|
||||
ArtistId: Int!
|
||||
Name: String
|
||||
Albums: [Album!]
|
||||
}
|
||||
|
||||
type Track {
|
||||
Album: Album
|
||||
AlbumId: Int
|
||||
Bytes: Int
|
||||
Composer: String
|
||||
TrackId: Int!
|
||||
MediaTypeId: Int!
|
||||
Milliseconds: Int!
|
||||
Name: String!
|
||||
GenreId: Int!
|
||||
Genre: Genre
|
||||
}
|
||||
|
||||
type Genre {
|
||||
GenreId: Int!
|
||||
Name: String!
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
export function keyByArray<T>(input: T[], id: keyof T) {
|
||||
return input.reduce((acc, result) => {
|
||||
const existing = acc[result[id] as string | number];
|
||||
acc[result[id as keyof T] as string | number] = existing
|
||||
? [...existing, result]
|
||||
: [result];
|
||||
return acc;
|
||||
}, {} as Record<string | number, T[]>);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@tsconfig/node18-strictest/tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
2550
community/sample-apps/graphql-benchmark/report.hasura.json
Normal file
2550
community/sample-apps/graphql-benchmark/report.hasura.json
Normal file
File diff suppressed because it is too large
Load Diff
2263
community/sample-apps/graphql-benchmark/report.nodejs.json
Normal file
2263
community/sample-apps/graphql-benchmark/report.nodejs.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user