├── .gitignore ├── .graphqlconfig ├── .wundergraph ├── .env.dev ├── .env.prod ├── operations │ ├── AddMessage.graphql │ ├── ChangeUserName.graphql │ ├── DeleteAllMessagesByUserEmail.graphql │ ├── Hello.graphql │ ├── Messages.graphql │ ├── MockQuery.graphql │ ├── SetLastLogin.graphql │ └── UserInfo.graphql ├── tsconfig.json ├── wundergraph.config.ts ├── wundergraph.operations.ts ├── wundergraph.postman.json └── wundergraph.server.ts ├── README.md ├── components └── generated │ ├── forms.tsx │ ├── hooks.ts │ ├── jsonschema.ts │ ├── models.ts │ ├── provider.tsx │ ├── wundergraph.client.ts │ ├── wundergraph.hooks.ts │ ├── wundergraph.internal.client.ts │ ├── wundergraph.operations.ts │ └── wundergraph.server.ts ├── database.env ├── docker-compose.yml ├── index.html ├── init.sql ├── package.json ├── pnpm-lock.yaml ├── schema.prisma ├── src ├── App.tsx ├── assets │ └── favicon.ico ├── env.d.ts ├── index.tsx ├── lib │ ├── hooks.ts │ └── provider.tsx ├── logo.svg ├── pages │ ├── [...all].tsx │ ├── admin │ │ └── index.tsx │ ├── index.tsx │ ├── lazyload.tsx │ ├── mock.tsx │ ├── testquery.tsx │ └── updateuser.tsx └── solidjs-material-spinner.d.ts ├── styles └── App.module.css ├── tsconfig.json ├── vercel.json ├── vite.config.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .wundergraph/generated 3 | .wundergraph/.env.mysql 4 | .wundergraph/.env.prod.secrets 5 | .wundergraph/.env.prod.localdb 6 | .env 7 | schema.prisma.secrets 8 | migrations 9 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "api": { 4 | "name": "api", 5 | "schemaPath": ".wundergraph/generated/wundergraph.api.schema.graphql", 6 | "extensions": { 7 | "endpoints": { 8 | "api": { 9 | "introspect": false, 10 | "url": "http://localhost:9991/api/main/graphql", 11 | "headers": { 12 | "user-agent": "WunderGraph Client" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.wundergraph/.env.dev: -------------------------------------------------------------------------------- 1 | NODE_ENV=developement 2 | 3 | TYPEDB=postgresql 4 | HOST=localhost 5 | PORT=54322 6 | USER=admin 7 | PASSWORD=admin 8 | DB=example 9 | DATABASE_URL=$TYPEDB://$USER:$PASSWORD@$HOST:$PORT/$DB?schema=public 10 | -------------------------------------------------------------------------------- /.wundergraph/.env.prod: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | 3 | TYPEDB=xxx 4 | HOST=xxx 5 | PORT=xxx 6 | USER=xxx 7 | PASSWORD=xxx 8 | DB=xxx 9 | DATABASE_URL=$TYPEDB://$USER:$PASSWORD@$HOST:$PORT/$DB?schema=public 10 | 11 | github_clientId=xxx 12 | github_clientSecret=xxx -------------------------------------------------------------------------------- /.wundergraph/operations/AddMessage.graphql: -------------------------------------------------------------------------------- 1 | mutation ( 2 | $email: String! @fromClaim(name: EMAIL) 3 | $name: String! @fromClaim(name: NAME) 4 | $message: String! 5 | ) { 6 | createOnemessages: db_createOnemessages( 7 | data: { 8 | message: $message 9 | users: { 10 | connectOrCreate: { 11 | create: { name: $name, email: $email } 12 | where: { email: $email } 13 | } 14 | } 15 | } 16 | ) { 17 | id 18 | message 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.wundergraph/operations/ChangeUserName.graphql: -------------------------------------------------------------------------------- 1 | mutation ( 2 | $newName: String! 3 | $email: String! @fromClaim(name: EMAIL) 4 | $updatedAt: DateTime! @injectCurrentDateTime 5 | ) { 6 | updateOneusers: db_updateOneusers( 7 | data: { name: { set: $newName }, updatedat: { set: $updatedAt } } 8 | where: { email: $email } 9 | ) { 10 | id 11 | email 12 | name 13 | updatedat 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.wundergraph/operations/DeleteAllMessagesByUserEmail.graphql: -------------------------------------------------------------------------------- 1 | # check out ".wundergraph/wundergraph.hooks.ts" to make yourself a superadmin 2 | mutation ($email: String!) @rbac(requireMatchAll: [superadmin]) { 3 | deleteManymessages: db_deleteManymessages( 4 | where: { users: { is: { email: { equals: $email } } } } 5 | ) { 6 | count 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.wundergraph/operations/Hello.graphql: -------------------------------------------------------------------------------- 1 | { 2 | gql_hello 3 | } 4 | -------------------------------------------------------------------------------- /.wundergraph/operations/Messages.graphql: -------------------------------------------------------------------------------- 1 | { 2 | findManymessages: db_findManymessages(take: 20, orderBy: [{ id: desc }]) { 3 | id 4 | message 5 | users { 6 | id 7 | name 8 | email 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.wundergraph/operations/MockQuery.graphql: -------------------------------------------------------------------------------- 1 | { 2 | findFirstusers: db_findFirstusers { 3 | id 4 | email 5 | name 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.wundergraph/operations/SetLastLogin.graphql: -------------------------------------------------------------------------------- 1 | mutation ($email: String!, $now: DateTime! @injectCurrentDateTime) 2 | @internalOperation { 3 | updateOneusers: db_updateOneusers( 4 | where: { email: $email } 5 | data: { lastlogin: { set: $now } } 6 | ) { 7 | id 8 | lastlogin 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.wundergraph/operations/UserInfo.graphql: -------------------------------------------------------------------------------- 1 | query ($email: String! @fromClaim(name: EMAIL)) { 2 | findFirstusers: db_findFirstusers(where: { email: { equals: $email } }) { 3 | id 4 | email 5 | name 6 | lastlogin 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.wundergraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.wundergraph/wundergraph.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Application, 3 | authProviders, 4 | configureWunderGraphApplication, 5 | cors, 6 | EnvironmentVariable, 7 | introspect, 8 | templates, 9 | } from '@wundergraph/sdk' 10 | import operations from './wundergraph.operations' 11 | import server from './wundergraph.server' 12 | 13 | const dbParams = { 14 | apiNamespace: "db", 15 | databaseURL: new EnvironmentVariable("DATABASE_URL"), 16 | } 17 | 18 | const db = process.env.TYPEDB == "postgresql" ? introspect.postgresql(dbParams): introspect.mysql(dbParams); 19 | const countries = introspect.graphql({ 20 | apiNamespace: "countries", 21 | url: "https://countries.trevorblades.com/", 22 | }); 23 | 24 | const myApplication = new Application({ 25 | name: 'api', 26 | apis: [countries, db], 27 | }) 28 | 29 | // configureWunderGraph emits the configuration 30 | configureWunderGraphApplication({ 31 | application: myApplication, 32 | server, 33 | codeGenerators: [ 34 | { 35 | templates: [ 36 | templates.typescript.client, 37 | templates.typescript.inputModels, 38 | templates.typescript.jsonSchema, 39 | templates.typescript.linkBuilder, 40 | templates.typescript.operations, 41 | templates.typescript.responseModels, 42 | ], 43 | }, 44 | { 45 | templates: [ 46 | ...templates.typescript.react 47 | ], 48 | path: "../components/generated/", 49 | } 50 | ], 51 | cors: { 52 | ...cors.allowAll, 53 | allowedOrigins: 54 | process.env.NODE_ENV === 'production' 55 | ? ['https://solidgraph.ovh'] 56 | : ['http://localhost:3000'], 57 | }, 58 | authorization: { 59 | roles: ['user', 'superadmin'], 60 | }, 61 | authentication: { 62 | cookieBased: { 63 | providers: [ 64 | process.env.NODE_ENV !== "production" ? 65 | authProviders.demo(): 66 | authProviders.github({ 67 | id: "github", 68 | clientId: new EnvironmentVariable("github_clientId"), 69 | clientSecret: new EnvironmentVariable("github_clientSecret") 70 | }), 71 | ], 72 | authorizedRedirectUris: ['http://localhost:3000','https://solidgraph.ovh'], 73 | }, 74 | }, 75 | operations, 76 | security: { 77 | enableGraphQLEndpoint: process.env.NODE_ENV !== "production", 78 | allowedHosts: ["api.solidgraph.ovh"] 79 | }, 80 | }) 81 | -------------------------------------------------------------------------------- /.wundergraph/wundergraph.operations.ts: -------------------------------------------------------------------------------- 1 | import {configureWunderGraphOperations} from "@wundergraph/sdk"; 2 | import type { OperationsConfiguration } from "./generated/wundergraph.operations"; 3 | 4 | const disableAuth = (config: Configs): Configs => { 5 | return { 6 | ...config, 7 | authentication: { 8 | required: false, 9 | }, 10 | } 11 | } 12 | 13 | const enableAuth = (config: Configs): Configs => { 14 | return { 15 | ...config, 16 | authentication: { 17 | required: true, 18 | }, 19 | } 20 | } 21 | 22 | export default configureWunderGraphOperations({ 23 | operations: { 24 | defaultConfig: { 25 | authentication: { 26 | required: false 27 | } 28 | }, 29 | queries: config => ({ 30 | ...config, 31 | caching: { 32 | enable: false, 33 | staleWhileRevalidate: 60, 34 | maxAge: 60, 35 | public: true 36 | }, 37 | liveQuery: { 38 | enable: true, 39 | pollingIntervalSeconds: 1, 40 | } 41 | }), 42 | mutations: config => ({ 43 | ...config, 44 | }), 45 | subscriptions: config => ({ 46 | ...config, 47 | }), 48 | custom: { 49 | //Requires to be logged to load messages 50 | //Messages: enableAuth 51 | } 52 | } 53 | }); -------------------------------------------------------------------------------- /.wundergraph/wundergraph.postman.json: -------------------------------------------------------------------------------- 1 | { 2 | "item": [ 3 | { 4 | "_": { 5 | "postman_id": "463a21d6-cbad-4f36-b819-99dc97de3ceb" 6 | }, 7 | "id": "463a21d6-cbad-4f36-b819-99dc97de3ceb", 8 | "name": "Queries", 9 | "description": { 10 | "content": "All your query operations", 11 | "type": "text/plain" 12 | }, 13 | "item": [ 14 | { 15 | "id": "ab4a67d2-0393-4ab1-9e21-71369677d06c", 16 | "name": "AllUsers", 17 | "request": { 18 | "url": { 19 | "path": ["api", "main", "operations", "AllUsers"], 20 | "host": ["{{apiBaseUrl}}"], 21 | "query": [], 22 | "variable": [] 23 | }, 24 | "header": [ 25 | { 26 | "key": "Content-Type", 27 | "value": "application/json" 28 | } 29 | ], 30 | "method": "GET" 31 | }, 32 | "response": [], 33 | "event": [] 34 | }, 35 | { 36 | "id": "8c9d9920-10f3-4c08-aaba-94a8489f7677", 37 | "name": "Messages", 38 | "request": { 39 | "url": { 40 | "path": ["api", "main", "operations", "Messages"], 41 | "host": ["{{apiBaseUrl}}"], 42 | "query": [], 43 | "variable": [] 44 | }, 45 | "header": [ 46 | { 47 | "key": "Content-Type", 48 | "value": "application/json" 49 | } 50 | ], 51 | "method": "GET" 52 | }, 53 | "response": [], 54 | "event": [] 55 | }, 56 | { 57 | "id": "14d485f9-4414-45c5-9399-5b343e558369", 58 | "name": "MockQuery", 59 | "request": { 60 | "url": { 61 | "path": ["api", "main", "operations", "MockQuery"], 62 | "host": ["{{apiBaseUrl}}"], 63 | "query": [], 64 | "variable": [] 65 | }, 66 | "header": [ 67 | { 68 | "key": "Content-Type", 69 | "value": "application/json" 70 | } 71 | ], 72 | "method": "GET" 73 | }, 74 | "response": [], 75 | "event": [] 76 | } 77 | ], 78 | "event": [] 79 | }, 80 | { 81 | "_": { 82 | "postman_id": "98099773-dcd8-4498-b0e9-557750f798af" 83 | }, 84 | "id": "98099773-dcd8-4498-b0e9-557750f798af", 85 | "name": "Mutations", 86 | "description": { 87 | "content": "All your mutation operations", 88 | "type": "text/plain" 89 | }, 90 | "item": [ 91 | { 92 | "id": "87c8bf93-c456-43de-b13f-b764f186ead4", 93 | "name": "AddMessage", 94 | "request": { 95 | "url": { 96 | "path": ["api", "main", "operations", "AddMessage"], 97 | "host": ["{{apiBaseUrl}}"], 98 | "query": [], 99 | "variable": [] 100 | }, 101 | "method": "POST", 102 | "body": { 103 | "mode": "urlencoded", 104 | "urlencoded": [ 105 | { 106 | "disabled": true, 107 | "description": { 108 | "content": "Type string, Optional", 109 | "type": "text/plain" 110 | }, 111 | "key": "message", 112 | "value": "" 113 | } 114 | ] 115 | } 116 | }, 117 | "response": [], 118 | "event": [] 119 | }, 120 | { 121 | "id": "a027d878-e408-4f66-a316-5fcfb43b0093", 122 | "name": "DeleteAllMessagesByUserEmail", 123 | "request": { 124 | "url": { 125 | "path": [ 126 | "api", 127 | "main", 128 | "operations", 129 | "DeleteAllMessagesByUserEmail" 130 | ], 131 | "host": ["{{apiBaseUrl}}"], 132 | "query": [], 133 | "variable": [] 134 | }, 135 | "method": "POST", 136 | "body": { 137 | "mode": "urlencoded", 138 | "urlencoded": [ 139 | { 140 | "disabled": true, 141 | "description": { 142 | "content": "Type string, Optional", 143 | "type": "text/plain" 144 | }, 145 | "key": "email", 146 | "value": "" 147 | } 148 | ] 149 | } 150 | }, 151 | "response": [], 152 | "event": [] 153 | } 154 | ], 155 | "event": [] 156 | } 157 | ], 158 | "event": [], 159 | "variable": [ 160 | { 161 | "type": "string", 162 | "value": "http://localhost:9991", 163 | "key": "apiBaseUrl" 164 | } 165 | ], 166 | "info": { 167 | "_postman_id": "3b66134b-9aa1-4095-a569-e0567fb2f21e", 168 | "name": "Wundergraph", 169 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 170 | "description": { 171 | "content": "Your Wundergraph collection", 172 | "type": "text/plain" 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /.wundergraph/wundergraph.server.ts: -------------------------------------------------------------------------------- 1 | import {configureWunderGraphServer} from "@wundergraph/sdk"; 2 | import type {HooksConfig} from "../components/generated/wundergraph.hooks"; 3 | import type {InternalClient} from "../components/generated/wundergraph.internal.client"; 4 | import {GraphQLObjectType, GraphQLSchema, GraphQLString} from "graphql"; 5 | import type {GraphQLExecutionContext} from "../components/generated/wundergraph.server"; 6 | 7 | const superAdmins = [ 8 | 'paul75011@gmail.com', 9 | "jens@wundergraph.com", 10 | // replace or add your own github email address 11 | // to make yourself a super admin as well 12 | ] 13 | 14 | export default configureWunderGraphServer((serverContext) => ({ 15 | hooks: { 16 | authentication: { 17 | postAuthentication: async (user) => { 18 | if (user.email) { 19 | try { 20 | await serverContext.internalClient.mutations.SetLastLogin({ email: user.email }) 21 | } catch (e) { 22 | console.log(e) 23 | } 24 | } 25 | }, 26 | mutatingPostAuthentication: async (user) => { 27 | if (!user.email) { 28 | return { 29 | status: 'deny', 30 | message: "No email address provided" 31 | } 32 | } 33 | 34 | if (superAdmins.find((s) => s === user.email) !== undefined) { 35 | return { 36 | status: 'ok', 37 | user: { 38 | ...user, 39 | roles: ['user', 'superadmin'], 40 | }, 41 | } 42 | } 43 | 44 | return { 45 | status: 'ok', 46 | user: { 47 | ...user, 48 | roles: ['user'], 49 | }, 50 | } 51 | }, 52 | }, 53 | queries: { 54 | MockQuery: { 55 | mockResolve: async () => { 56 | return { 57 | data: { 58 | findFirstusers: { 59 | id: 1, 60 | email: 'webmaster@solidgraph.ovh', 61 | name: 'Hervé', 62 | }, 63 | }, 64 | } 65 | }, 66 | }, 67 | }, 68 | mutations: {}, 69 | }, 70 | graphqlServers: [ 71 | { 72 | apiNamespace: "gql", 73 | serverName: "gql", 74 | enableGraphQLEndpoint: true, 75 | schema: new GraphQLSchema({ 76 | query: new GraphQLObjectType({ 77 | name: 'RootQueryType', 78 | fields: { 79 | hello: { 80 | type: GraphQLString, 81 | resolve(root, args, ctx) { 82 | ctx.log.info(`headers: ${JSON.stringify(ctx.requestContext.clientRequest.headers)}`); 83 | return ctx.requestContext.clientRequest.headers["User-Agent"] || "world"; 84 | } 85 | }, 86 | }, 87 | }), 88 | }) 89 | } 90 | ], 91 | })); 92 | 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolidGraph 2 | 3 | SolidGraph lets you build applications with SolidJs and [WunderGraph](https://wundergraph.com/). 4 | 5 | ## What is Wundergraph ? 6 | 7 | WunderGraph is the best Developer Experience to build Serverless APIs : 8 | 9 | - Server-Side-Only Compile-Time GraphQL API Layer 10 | - Auto-Generated TypeSafe Clients 11 | - OpenID Connect-based Authentication with RBAC 12 | - S3 integration for File Uploads 13 | 14 | ## SolidGraph compostion 15 | 16 | The SolidGraph library is composed of two files: 17 | [hooks.ts](https://github.com/verdavaine/solidgraph/blob/master/src/lib/hooks.ts) and [provider.tsx](https://github.com/verdavaine/solidgraph/blob/master/src/lib/provider.tsx) 18 | 19 | ## License 20 | 21 | The SoliGraph library is under [MIT](https://choosealicense.com/licenses/mit/) license. 22 | 23 | # Realtime chat example 24 | 25 | This example demonstrates how SolidGraph lets you easily build a SolidJs application with Realtime subscription. 26 | 27 | The code that might interest you the most can be found in [index.tsx](https://github.com/verdavaine/solidgraph/blob/master/src/pages/index.tsx) 28 | 29 | ## Features 30 | 31 | Features: 32 | 33 | - Authentication 34 | - Authorization 35 | - Realtime Updates 36 | - Cross Tab Login/Logout 37 | - typesafe generated Typescript Client 38 | 39 | ## Prerequisites 40 | 41 | Make sure you have docker compose installed. Alternatively, you can use any PostgreSQL database available on localhost. 42 | 43 | ## Getting Started 44 | 45 | Install the dependencies and run the example: 46 | 47 | ```bash 48 | yarn global add @wundergraph/wunderctl@latest 49 | yarn 50 | yarn dev 51 | ``` 52 | 53 | ## Cleanup 54 | 55 | ```bash 56 | docker-compose rm -v -f 57 | ``` 58 | 59 | # Questions ? 60 | 61 | Read the [WunderGraph Docs](https://wundergraph.com/docs). 62 | -------------------------------------------------------------------------------- /components/generated/forms.tsx: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import React, { useEffect, useState } from "react"; 4 | import type { Response } from "@wundergraph/sdk"; 5 | import { 6 | AddMessageInput, 7 | AddMessageResponse, 8 | ChangeUserNameInput, 9 | ChangeUserNameResponse, 10 | DeleteAllMessagesByUserEmailInput, 11 | DeleteAllMessagesByUserEmailResponse, 12 | } from "./models"; 13 | import { useMutation } from "./hooks"; 14 | import jsonSchema from "./jsonschema"; 15 | import Form from "@rjsf/core"; 16 | 17 | export interface FormProps { 18 | onResult?: (result: T) => void; 19 | liveValidate?: boolean; 20 | } 21 | 22 | export interface MutationFormProps extends FormProps { 23 | refetchMountedQueriesOnSuccess?: boolean; 24 | } 25 | 26 | export const AddMessageForm: React.FC>> = ({ 27 | onResult, 28 | refetchMountedQueriesOnSuccess, 29 | liveValidate, 30 | }) => { 31 | const [formData, setFormData] = useState(); 32 | const { mutate, response } = useMutation.AddMessage({ refetchMountedQueriesOnSuccess }); 33 | useEffect(() => { 34 | if (onResult) { 35 | onResult(response); 36 | } 37 | }, [response]); 38 | return ( 39 |
40 |
{ 45 | setFormData(e.formData); 46 | }} 47 | onSubmit={async (e) => { 48 | await mutate({ input: e.formData, refetchMountedQueriesOnSuccess }); 49 | setFormData(undefined); 50 | }} 51 | /> 52 |
53 | ); 54 | }; 55 | export const ChangeUserNameForm: React.FC>> = ({ 56 | onResult, 57 | refetchMountedQueriesOnSuccess, 58 | liveValidate, 59 | }) => { 60 | const [formData, setFormData] = useState(); 61 | const { mutate, response } = useMutation.ChangeUserName({ refetchMountedQueriesOnSuccess }); 62 | useEffect(() => { 63 | if (onResult) { 64 | onResult(response); 65 | } 66 | }, [response]); 67 | return ( 68 |
69 | { 74 | setFormData(e.formData); 75 | }} 76 | onSubmit={async (e) => { 77 | await mutate({ input: e.formData, refetchMountedQueriesOnSuccess }); 78 | setFormData(undefined); 79 | }} 80 | /> 81 |
82 | ); 83 | }; 84 | export const DeleteAllMessagesByUserEmailForm: React.FC< 85 | MutationFormProps> 86 | > = ({ onResult, refetchMountedQueriesOnSuccess, liveValidate }) => { 87 | const [formData, setFormData] = useState(); 88 | const { mutate, response } = useMutation.DeleteAllMessagesByUserEmail({ refetchMountedQueriesOnSuccess }); 89 | useEffect(() => { 90 | if (onResult) { 91 | onResult(response); 92 | } 93 | }, [response]); 94 | return ( 95 |
96 | { 101 | setFormData(e.formData); 102 | }} 103 | onSubmit={async (e) => { 104 | await mutate({ input: e.formData, refetchMountedQueriesOnSuccess }); 105 | setFormData(undefined); 106 | }} 107 | /> 108 |
109 | ); 110 | }; 111 | -------------------------------------------------------------------------------- /components/generated/hooks.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import { useCallback, useContext, useEffect, useMemo, useState } from "react"; 4 | import { WunderGraphContext } from "./provider"; 5 | import { RequestOptions, MutateRequestOptions, SubscriptionRequestOptions, Response } from "@wundergraph/sdk"; 6 | import { 7 | AddMessageInput, 8 | ChangeUserNameInput, 9 | DeleteAllMessagesByUserEmailInput, 10 | HelloResponse, 11 | MessagesResponse, 12 | MockQueryResponse, 13 | UserInfoResponse, 14 | } from "./models"; 15 | 16 | export const useWunderGraph = () => { 17 | const ctx = useContext(WunderGraphContext); 18 | if (ctx === undefined) { 19 | throw new Error("WunderGraphContext missing, make sure to put WunderGraphProvider at the root of your app"); 20 | } 21 | return { 22 | client: ctx.client, 23 | onWindowFocus: ctx.onWindowFocus, 24 | onWindowBlur: ctx.onWindowBlur, 25 | user: ctx.user, 26 | initialized: ctx.initialized, 27 | initializing: ctx.initializing, 28 | refetchMountedQueries: ctx.refetchMountedQueries, 29 | setRefetchMountedQueries: ctx.setRefetchMountedQueries, 30 | queryCache: ctx.queryCache, 31 | }; 32 | }; 33 | 34 | interface InternalOptions { 35 | requiresAuthentication: boolean; 36 | } 37 | 38 | const Query = ( 39 | promiseFactory: (options: RequestOptions) => Promise>, 40 | internalOptions: InternalOptions, 41 | options?: RequestOptions 42 | ) => { 43 | const { initialized, user, onWindowFocus, refetchMountedQueries, queryCache } = useWunderGraph(); 44 | const [_options, _setOptions] = useState | undefined>(options); 45 | const [shouldFetch, setShouldFetch] = useState(options === undefined || options.initialState === undefined); 46 | const refetch = useCallback((options?: RequestOptions) => { 47 | if (options !== undefined) { 48 | _setOptions({ 49 | ...options, 50 | lazy: false, 51 | }); 52 | } else if (_options && _options.lazy === true) { 53 | _setOptions({ 54 | ..._options, 55 | lazy: false, 56 | }); 57 | } 58 | setResponse({ status: "loading" }); 59 | setShouldFetch(true); 60 | }, []); 61 | useEffect(() => { 62 | if (options && options.refetchOnWindowFocus === true) { 63 | setShouldFetch(true); 64 | } 65 | }, [onWindowFocus]); 66 | const [response, setResponse] = useState>( 67 | options !== undefined && options.initialState !== undefined 68 | ? { 69 | status: "ok", 70 | body: options.initialState, 71 | } 72 | : _options && _options.lazy === true 73 | ? { status: "lazy" } 74 | : { status: "loading" } 75 | ); 76 | useEffect(() => { 77 | if (!initialized) { 78 | return; 79 | } 80 | if (internalOptions.requiresAuthentication && !user) { 81 | setResponse({ status: "requiresAuthentication" }); 82 | return; 83 | } 84 | if (!shouldFetch) { 85 | return; 86 | } 87 | if (_options && _options.lazy === true) { 88 | return; 89 | } 90 | const abortController = new AbortController(); 91 | if (response.status === "ok") { 92 | setResponse({ status: "ok", refetching: true, body: response.body }); 93 | } 94 | const cacheKey = JSON.stringify(_options); 95 | const cached = queryCache[cacheKey]; 96 | if (response.status !== "ok" && cached) { 97 | setResponse({ 98 | status: "cached", 99 | body: cached as R, 100 | }); 101 | } 102 | (async () => { 103 | const result = await promiseFactory({ 104 | ..._options, 105 | abortSignal: abortController.signal, 106 | }); 107 | if (abortController.signal.aborted) { 108 | setResponse({ status: "aborted" }); 109 | return; 110 | } 111 | if (result.status === "ok") { 112 | queryCache[cacheKey] = result.body; 113 | } 114 | setResponse(result); 115 | setShouldFetch(false); 116 | })(); 117 | return () => { 118 | abortController.abort(); 119 | }; 120 | }, [user, initialized, shouldFetch, _options, promiseFactory]); 121 | useEffect(() => setShouldFetch(true), [user, refetchMountedQueries]); 122 | return { 123 | response, 124 | refetch, 125 | }; 126 | }; 127 | 128 | const Mutation = ( 129 | promiseFactory: (options: RequestOptions) => Promise>, 130 | internalOptions: InternalOptions, 131 | options?: MutateRequestOptions 132 | ) => { 133 | const { user, setRefetchMountedQueries } = useWunderGraph(); 134 | const [_options] = useState | undefined>(options); 135 | const [response, setResponse] = useState>({ status: "none" }); 136 | const mutate = useCallback( 137 | async (options?: MutateRequestOptions) => { 138 | if (internalOptions.requiresAuthentication && !user) { 139 | setResponse({ status: "requiresAuthentication" }); 140 | return; 141 | } 142 | const combinedOptions: MutateRequestOptions = { 143 | refetchMountedQueriesOnSuccess: 144 | options !== undefined && options.refetchMountedQueriesOnSuccess !== undefined 145 | ? options.refetchMountedQueriesOnSuccess 146 | : _options?.refetchMountedQueriesOnSuccess, 147 | input: options !== undefined && options.input !== undefined ? options.input : _options?.input, 148 | abortSignal: 149 | options !== undefined && options.abortSignal !== undefined ? options.abortSignal : _options?.abortSignal, 150 | }; 151 | setResponse({ status: "loading" }); 152 | const result = await promiseFactory(combinedOptions); 153 | setResponse(result); 154 | if (result.status === "ok" && combinedOptions.refetchMountedQueriesOnSuccess === true) { 155 | setRefetchMountedQueries(new Date()); 156 | } 157 | }, 158 | [user] 159 | ); 160 | return { 161 | response, 162 | mutate, 163 | }; 164 | }; 165 | 166 | const Subscription = ( 167 | subscriptionFactory: (options: RequestOptions, cb: (response: Response) => void) => void, 168 | internalOptions: InternalOptions, 169 | options?: SubscriptionRequestOptions 170 | ) => { 171 | const optionsJSON = JSON.stringify(options); 172 | const { user, initialized, refetchMountedQueries } = useWunderGraph(); 173 | const [_options, _setOptions] = useState | undefined>(options); 174 | const [response, setResponse] = useState>({ status: "loading" }); 175 | const [lastInit, setLastInit] = useState(); 176 | const computedInit = useMemo(() => { 177 | if (lastInit === undefined) { 178 | setLastInit(initialized); 179 | return initialized; 180 | } 181 | if (options?.stopOnWindowBlur) { 182 | return initialized; 183 | } 184 | if (initialized) { 185 | setLastInit(true); 186 | return true; 187 | } 188 | return lastInit; 189 | }, [initialized, lastInit, optionsJSON]); 190 | useEffect(() => { 191 | _setOptions(options); 192 | }, [optionsJSON]); 193 | useEffect(() => { 194 | if (!computedInit) { 195 | return; 196 | } 197 | if (internalOptions.requiresAuthentication && !user) { 198 | setResponse({ status: "requiresAuthentication" }); 199 | return; 200 | } 201 | const controller = new AbortController(); 202 | subscriptionFactory( 203 | { 204 | ..._options, 205 | abortSignal: controller.signal, 206 | }, 207 | (res) => { 208 | if (!controller.signal.aborted) setResponse(res); 209 | } 210 | ); 211 | return () => { 212 | controller.abort(); 213 | }; 214 | }, [user, computedInit, _options, refetchMountedQueries]); 215 | return { 216 | response, 217 | }; 218 | }; 219 | 220 | export const useLoadingComplete = (...responses: Response[]) => { 221 | const [loading, setLoading] = useState(true); 222 | useEffect(() => { 223 | const isLoading = responses.some((r) => r.status === "loading"); 224 | if (isLoading !== loading) setLoading(isLoading); 225 | }, responses); 226 | return loading; 227 | }; 228 | 229 | export const useQuery = { 230 | Hello: (options?: RequestOptions) => { 231 | const { client } = useWunderGraph(); 232 | return Query(client.query.Hello, { requiresAuthentication: false }, options); 233 | }, 234 | Messages: (options?: RequestOptions) => { 235 | const { client } = useWunderGraph(); 236 | return Query(client.query.Messages, { requiresAuthentication: false }, options); 237 | }, 238 | MockQuery: (options?: RequestOptions) => { 239 | const { client } = useWunderGraph(); 240 | return Query(client.query.MockQuery, { requiresAuthentication: false }, options); 241 | }, 242 | UserInfo: (options?: RequestOptions) => { 243 | const { client } = useWunderGraph(); 244 | return Query(client.query.UserInfo, { requiresAuthentication: true }, options); 245 | }, 246 | }; 247 | 248 | export const useMutation = { 249 | AddMessage: (options: MutateRequestOptions) => { 250 | const { client } = useWunderGraph(); 251 | return Mutation(client.mutation.AddMessage, { requiresAuthentication: true }, options); 252 | }, 253 | ChangeUserName: (options: MutateRequestOptions) => { 254 | const { client } = useWunderGraph(); 255 | return Mutation(client.mutation.ChangeUserName, { requiresAuthentication: true }, options); 256 | }, 257 | DeleteAllMessagesByUserEmail: (options: MutateRequestOptions) => { 258 | const { client } = useWunderGraph(); 259 | return Mutation(client.mutation.DeleteAllMessagesByUserEmail, { requiresAuthentication: true }, options); 260 | }, 261 | }; 262 | 263 | export const useLiveQuery = { 264 | Hello: (options?: SubscriptionRequestOptions) => { 265 | const { client } = useWunderGraph(); 266 | return Subscription(client.liveQuery.Hello, { requiresAuthentication: false }, options); 267 | }, 268 | Messages: (options?: SubscriptionRequestOptions) => { 269 | const { client } = useWunderGraph(); 270 | return Subscription(client.liveQuery.Messages, { requiresAuthentication: false }, options); 271 | }, 272 | MockQuery: (options?: SubscriptionRequestOptions) => { 273 | const { client } = useWunderGraph(); 274 | return Subscription(client.liveQuery.MockQuery, { requiresAuthentication: false }, options); 275 | }, 276 | UserInfo: (options?: SubscriptionRequestOptions) => { 277 | const { client } = useWunderGraph(); 278 | return Subscription(client.liveQuery.UserInfo, { requiresAuthentication: true }, options); 279 | }, 280 | }; 281 | -------------------------------------------------------------------------------- /components/generated/jsonschema.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import { JSONSchema7 } from "json-schema"; 4 | 5 | interface Schema { 6 | AddMessage: { 7 | input: JSONSchema7; 8 | response: JSONSchema7; 9 | }; 10 | ChangeUserName: { 11 | input: JSONSchema7; 12 | response: JSONSchema7; 13 | }; 14 | DeleteAllMessagesByUserEmail: { 15 | input: JSONSchema7; 16 | response: JSONSchema7; 17 | }; 18 | Hello: { 19 | input: JSONSchema7; 20 | response: JSONSchema7; 21 | }; 22 | Messages: { 23 | input: JSONSchema7; 24 | response: JSONSchema7; 25 | }; 26 | MockQuery: { 27 | input: JSONSchema7; 28 | response: JSONSchema7; 29 | }; 30 | SetLastLogin: { 31 | input: JSONSchema7; 32 | response: JSONSchema7; 33 | }; 34 | UserInfo: { 35 | input: JSONSchema7; 36 | response: JSONSchema7; 37 | }; 38 | } 39 | 40 | const jsonSchema: Schema = { 41 | AddMessage: { 42 | input: { 43 | type: "object", 44 | properties: { message: { type: "string" } }, 45 | additionalProperties: false, 46 | definitions: {}, 47 | required: ["message"], 48 | }, 49 | response: { 50 | type: "object", 51 | properties: { 52 | data: { 53 | type: "object", 54 | properties: { 55 | createOnemessages: { 56 | type: "object", 57 | properties: { id: { type: "integer" }, message: { type: "string" } }, 58 | additionalProperties: false, 59 | required: ["id", "message"], 60 | }, 61 | }, 62 | additionalProperties: false, 63 | }, 64 | }, 65 | additionalProperties: false, 66 | }, 67 | }, 68 | ChangeUserName: { 69 | input: { 70 | type: "object", 71 | properties: { newName: { type: "string" } }, 72 | additionalProperties: false, 73 | definitions: {}, 74 | required: ["newName"], 75 | }, 76 | response: { 77 | type: "object", 78 | properties: { 79 | data: { 80 | type: "object", 81 | properties: { 82 | updateOneusers: { 83 | type: "object", 84 | properties: { 85 | id: { type: "integer" }, 86 | email: { type: "string" }, 87 | name: { type: "string" }, 88 | updatedat: { type: "string" }, 89 | }, 90 | additionalProperties: false, 91 | required: ["id", "email", "name", "updatedat"], 92 | }, 93 | }, 94 | additionalProperties: false, 95 | }, 96 | }, 97 | additionalProperties: false, 98 | }, 99 | }, 100 | DeleteAllMessagesByUserEmail: { 101 | input: { 102 | type: "object", 103 | properties: { email: { type: "string" } }, 104 | additionalProperties: false, 105 | definitions: {}, 106 | required: ["email"], 107 | }, 108 | response: { 109 | type: "object", 110 | properties: { 111 | data: { 112 | type: "object", 113 | properties: { 114 | deleteManymessages: { 115 | type: "object", 116 | properties: { count: { type: "integer" } }, 117 | additionalProperties: false, 118 | required: ["count"], 119 | }, 120 | }, 121 | additionalProperties: false, 122 | }, 123 | }, 124 | additionalProperties: false, 125 | }, 126 | }, 127 | Hello: { 128 | input: { type: "object", properties: {}, additionalProperties: false, definitions: {} }, 129 | response: { 130 | type: "object", 131 | properties: { 132 | data: { type: "object", properties: { gql_hello: { type: "string" } }, additionalProperties: false }, 133 | }, 134 | additionalProperties: false, 135 | }, 136 | }, 137 | Messages: { 138 | input: { type: "object", properties: {}, additionalProperties: false, definitions: {} }, 139 | response: { 140 | type: "object", 141 | properties: { 142 | data: { 143 | type: "object", 144 | properties: { 145 | findManymessages: { 146 | type: "array", 147 | items: { 148 | type: "object", 149 | properties: { 150 | id: { type: "integer" }, 151 | message: { type: "string" }, 152 | users: { 153 | type: "object", 154 | properties: { id: { type: "integer" }, name: { type: "string" }, email: { type: "string" } }, 155 | additionalProperties: false, 156 | required: ["id", "name", "email"], 157 | }, 158 | }, 159 | additionalProperties: false, 160 | required: ["id", "message", "users"], 161 | }, 162 | }, 163 | }, 164 | additionalProperties: false, 165 | required: ["findManymessages"], 166 | }, 167 | }, 168 | additionalProperties: false, 169 | }, 170 | }, 171 | MockQuery: { 172 | input: { type: "object", properties: {}, additionalProperties: false, definitions: {} }, 173 | response: { 174 | type: "object", 175 | properties: { 176 | data: { 177 | type: "object", 178 | properties: { 179 | findFirstusers: { 180 | type: "object", 181 | properties: { id: { type: "integer" }, email: { type: "string" }, name: { type: "string" } }, 182 | additionalProperties: false, 183 | required: ["id", "email", "name"], 184 | }, 185 | }, 186 | additionalProperties: false, 187 | }, 188 | }, 189 | additionalProperties: false, 190 | }, 191 | }, 192 | SetLastLogin: { 193 | input: { 194 | type: "object", 195 | properties: { email: { type: "string" } }, 196 | additionalProperties: false, 197 | definitions: {}, 198 | required: ["email"], 199 | }, 200 | response: { 201 | type: "object", 202 | properties: { 203 | data: { 204 | type: "object", 205 | properties: { 206 | updateOneusers: { 207 | type: "object", 208 | properties: { id: { type: "integer" }, lastlogin: { type: "string" } }, 209 | additionalProperties: false, 210 | required: ["id", "lastlogin"], 211 | }, 212 | }, 213 | additionalProperties: false, 214 | }, 215 | }, 216 | additionalProperties: false, 217 | }, 218 | }, 219 | UserInfo: { 220 | input: { type: "object", properties: {}, additionalProperties: false, definitions: {} }, 221 | response: { 222 | type: "object", 223 | properties: { 224 | data: { 225 | type: "object", 226 | properties: { 227 | findFirstusers: { 228 | type: "object", 229 | properties: { 230 | id: { type: "integer" }, 231 | email: { type: "string" }, 232 | name: { type: "string" }, 233 | lastlogin: { type: "string" }, 234 | }, 235 | additionalProperties: false, 236 | required: ["id", "email", "name", "lastlogin"], 237 | }, 238 | }, 239 | additionalProperties: false, 240 | }, 241 | }, 242 | additionalProperties: false, 243 | }, 244 | }, 245 | }; 246 | export default jsonSchema; 247 | -------------------------------------------------------------------------------- /components/generated/models.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | export interface AddMessageInput { 4 | message: string; 5 | } 6 | 7 | export interface ChangeUserNameInput { 8 | newName: string; 9 | } 10 | 11 | export interface DeleteAllMessagesByUserEmailInput { 12 | email: string; 13 | } 14 | 15 | export interface SetLastLoginInput { 16 | email: string; 17 | } 18 | 19 | export interface InternalAddMessageInput { 20 | email: string; 21 | name: string; 22 | message: string; 23 | } 24 | 25 | export interface InternalChangeUserNameInput { 26 | newName: string; 27 | email: string; 28 | } 29 | 30 | export interface InternalDeleteAllMessagesByUserEmailInput { 31 | email: string; 32 | } 33 | 34 | export interface InternalSetLastLoginInput { 35 | email: string; 36 | } 37 | 38 | export interface InternalUserInfoInput { 39 | email: string; 40 | } 41 | 42 | export interface InjectedAddMessageInput { 43 | email: string; 44 | name: string; 45 | message: string; 46 | } 47 | 48 | export interface InjectedChangeUserNameInput { 49 | newName: string; 50 | email: string; 51 | updatedAt: string; 52 | } 53 | 54 | export interface InjectedDeleteAllMessagesByUserEmailInput { 55 | email: string; 56 | } 57 | 58 | export interface InjectedSetLastLoginInput { 59 | email: string; 60 | now: string; 61 | } 62 | 63 | export interface InjectedUserInfoInput { 64 | email: string; 65 | } 66 | 67 | export interface AddMessageResponse { 68 | data?: AddMessageResponseData; 69 | errors?: ReadonlyArray; 70 | } 71 | 72 | export interface ChangeUserNameResponse { 73 | data?: ChangeUserNameResponseData; 74 | errors?: ReadonlyArray; 75 | } 76 | 77 | export interface DeleteAllMessagesByUserEmailResponse { 78 | data?: DeleteAllMessagesByUserEmailResponseData; 79 | errors?: ReadonlyArray; 80 | } 81 | 82 | export interface HelloResponse { 83 | data?: HelloResponseData; 84 | errors?: ReadonlyArray; 85 | } 86 | 87 | export interface MessagesResponse { 88 | data?: MessagesResponseData; 89 | errors?: ReadonlyArray; 90 | } 91 | 92 | export interface MockQueryResponse { 93 | data?: MockQueryResponseData; 94 | errors?: ReadonlyArray; 95 | } 96 | 97 | export interface SetLastLoginResponse { 98 | data?: SetLastLoginResponseData; 99 | errors?: ReadonlyArray; 100 | } 101 | 102 | export interface UserInfoResponse { 103 | data?: UserInfoResponseData; 104 | errors?: ReadonlyArray; 105 | } 106 | 107 | export interface AddMessageResponseData { 108 | createOnemessages?: { 109 | id: number; 110 | message: string; 111 | }; 112 | } 113 | 114 | export interface ChangeUserNameResponseData { 115 | updateOneusers?: { 116 | id: number; 117 | email: string; 118 | name: string; 119 | updatedat: string; 120 | }; 121 | } 122 | 123 | export interface DeleteAllMessagesByUserEmailResponseData { 124 | deleteManymessages?: { 125 | count: number; 126 | }; 127 | } 128 | 129 | export interface HelloResponseData { 130 | gql_hello?: string; 131 | } 132 | 133 | export interface MessagesResponseData { 134 | findManymessages: { 135 | id: number; 136 | message: string; 137 | users: { 138 | id: number; 139 | name: string; 140 | email: string; 141 | }; 142 | }[]; 143 | } 144 | 145 | export interface MockQueryResponseData { 146 | findFirstusers?: { 147 | id: number; 148 | email: string; 149 | name: string; 150 | }; 151 | } 152 | 153 | export interface SetLastLoginResponseData { 154 | updateOneusers?: { 155 | id: number; 156 | lastlogin: string; 157 | }; 158 | } 159 | 160 | export interface UserInfoResponseData { 161 | findFirstusers?: { 162 | id: number; 163 | email: string; 164 | name: string; 165 | lastlogin: string; 166 | }; 167 | } 168 | 169 | export type JSONValue = string | number | boolean | JSONObject | Array; 170 | 171 | export type JSONObject = { [key: string]: JSONValue }; 172 | 173 | export interface GraphQLError { 174 | message: string; 175 | path?: ReadonlyArray; 176 | } 177 | -------------------------------------------------------------------------------- /components/generated/provider.tsx: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import { Client } from "./wundergraph.client"; 4 | import type { UserListener } from "@wundergraph/sdk"; 5 | import type { User } from "./wundergraph.server"; 6 | import React, { createContext, FunctionComponent, useMemo, useEffect, useState, Dispatch, SetStateAction } from "react"; 7 | 8 | export interface Config { 9 | client: Client; 10 | initialized: boolean; 11 | initializing: boolean; 12 | onWindowFocus: Date; 13 | onWindowBlur: Date; 14 | user?: User; 15 | refetchMountedQueries: Date; 16 | setRefetchMountedQueries: Dispatch>; 17 | queryCache: { 18 | [key: string]: Object; 19 | }; 20 | } 21 | 22 | export const WunderGraphContext = createContext(undefined); 23 | 24 | export interface Props { 25 | endpoint?: string; 26 | extraHeaders?: { [key: string]: string }; 27 | customFetch?: (input: RequestInfo, init?: RequestInit) => Promise; 28 | } 29 | 30 | export const WunderGraphProvider: FunctionComponent = ({ endpoint, extraHeaders, customFetch, children }) => { 31 | const [initialized, setInitialized] = useState(false); 32 | const [initializing, setInitializing] = useState(false); 33 | const [refetchMountedQueries, setRefetchMountedQueries] = useState(new Date()); 34 | const queryCache: { [key: string]: Object } = {}; 35 | const client = useMemo(() => { 36 | const client = new Client({ baseURL: endpoint, extraHeaders, customFetch }); 37 | client.setLogoutCallback(() => { 38 | Object.keys(queryCache).forEach((key) => { 39 | delete queryCache[key]; 40 | }); 41 | }); 42 | return client; 43 | }, [endpoint, extraHeaders]); 44 | const [onWindowBlur, setOnWindowBlur] = useState(new Date()); 45 | const [onWindowFocus, setOnWindowFocus] = useState(new Date()); 46 | const [user, setUser] = useState(); 47 | const userListener = useMemo>(() => { 48 | return (userOrNull) => { 49 | if (userOrNull === null) { 50 | setUser(undefined); 51 | } else { 52 | setUser(userOrNull); 53 | } 54 | }; 55 | }, []); 56 | useEffect(() => { 57 | client.setUserListener(userListener); 58 | (async () => { 59 | await client.fetchUser(); 60 | setInitialized(true); 61 | })(); 62 | const onFocus = async () => { 63 | setInitializing(true); 64 | await client.fetchUser(); 65 | setOnWindowFocus(new Date()); 66 | setInitialized(true); 67 | setInitializing(false); 68 | }; 69 | const onBlur = () => { 70 | setInitialized(false); 71 | setOnWindowBlur(new Date()); 72 | }; 73 | window.addEventListener("focus", onFocus); 74 | window.addEventListener("blur", onBlur); 75 | return () => { 76 | window.removeEventListener("focus", onFocus); 77 | window.removeEventListener("blur", onBlur); 78 | }; 79 | }, [client]); 80 | const providerData = { 81 | user, 82 | onWindowBlur, 83 | onWindowFocus, 84 | client, 85 | initialized, 86 | initializing, 87 | refetchMountedQueries, 88 | setRefetchMountedQueries, 89 | queryCache, 90 | }; 91 | return {children}; 92 | }; 93 | -------------------------------------------------------------------------------- /components/generated/wundergraph.client.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import type { 4 | AddMessageResponse, 5 | AddMessageInput, 6 | ChangeUserNameResponse, 7 | ChangeUserNameInput, 8 | DeleteAllMessagesByUserEmailResponse, 9 | DeleteAllMessagesByUserEmailInput, 10 | HelloResponse, 11 | MessagesResponse, 12 | MockQueryResponse, 13 | UserInfoResponse, 14 | } from "./models"; 15 | import type { RequestOptions, ClientConfig, UserListener, Response, FetchConfig, Headers } from "@wundergraph/sdk"; 16 | import type { User } from "./wundergraph.server"; 17 | 18 | export const WUNDERGRAPH_S3_ENABLED = false; 19 | export const WUNDERGRAPH_AUTH_ENABLED = true; 20 | 21 | export enum AuthProviderId { 22 | "github" = "github", 23 | } 24 | 25 | export interface AuthProvider { 26 | id: AuthProviderId; 27 | login: (redirectURI?: string) => void; 28 | } 29 | 30 | export interface LogoutOptions { 31 | logout_openid_connect_provider?: boolean; 32 | } 33 | 34 | export class Client { 35 | constructor(config?: ClientConfig) { 36 | this.baseURL = config?.baseURL || this.baseURL; 37 | this.extraHeaders = config?.extraHeaders; 38 | this.user = null; 39 | this.customFetch = config?.customFetch; 40 | } 41 | private logoutCallback: undefined | (() => void); 42 | public setLogoutCallback(cb: () => void) { 43 | this.logoutCallback = cb; 44 | } 45 | public setExtraHeaders = (headers: Headers) => { 46 | this.extraHeaders = headers; 47 | }; 48 | private customFetch?: (input: RequestInfo, init?: RequestInit) => Promise; 49 | private extraHeaders?: Headers; 50 | private readonly baseURL: string = "http://localhost:9991"; 51 | private readonly applicationHash: string = "560e7ed5"; 52 | private readonly applicationPath: string = "api/main"; 53 | private readonly sdkVersion: string = "1.0.0-next.17"; 54 | private csrfToken: string | undefined; 55 | private user: User | null; 56 | private userListener: UserListener | undefined; 57 | public setUserListener = (listener: UserListener) => { 58 | this.userListener = listener; 59 | }; 60 | private setUser = (user: User | null) => { 61 | if ( 62 | (user === null && this.user !== null) || 63 | (user !== null && this.user === null) || 64 | JSON.stringify(user) !== JSON.stringify(this.user) 65 | ) { 66 | this.user = user; 67 | if (this.userListener !== undefined) { 68 | this.userListener(this.user); 69 | } 70 | } 71 | }; 72 | public query = { 73 | Hello: async (options: RequestOptions) => { 74 | return await this.doFetch({ 75 | method: "GET", 76 | path: "Hello", 77 | input: options.input, 78 | abortSignal: options.abortSignal, 79 | }); 80 | }, 81 | Messages: async (options: RequestOptions) => { 82 | return await this.doFetch({ 83 | method: "GET", 84 | path: "Messages", 85 | input: options.input, 86 | abortSignal: options.abortSignal, 87 | }); 88 | }, 89 | MockQuery: async (options: RequestOptions) => { 90 | return await this.doFetch({ 91 | method: "GET", 92 | path: "MockQuery", 93 | input: options.input, 94 | abortSignal: options.abortSignal, 95 | }); 96 | }, 97 | UserInfo: async (options: RequestOptions) => { 98 | return await this.doFetch({ 99 | method: "GET", 100 | path: "UserInfo", 101 | input: options.input, 102 | abortSignal: options.abortSignal, 103 | }); 104 | }, 105 | }; 106 | public mutation = { 107 | AddMessage: async (options: RequestOptions) => { 108 | return await this.doFetch({ 109 | method: "POST", 110 | path: "AddMessage", 111 | input: options.input, 112 | abortSignal: options.abortSignal, 113 | }); 114 | }, 115 | ChangeUserName: async (options: RequestOptions) => { 116 | return await this.doFetch({ 117 | method: "POST", 118 | path: "ChangeUserName", 119 | input: options.input, 120 | abortSignal: options.abortSignal, 121 | }); 122 | }, 123 | DeleteAllMessagesByUserEmail: async ( 124 | options: RequestOptions 125 | ) => { 126 | return await this.doFetch({ 127 | method: "POST", 128 | path: "DeleteAllMessagesByUserEmail", 129 | input: options.input, 130 | abortSignal: options.abortSignal, 131 | }); 132 | }, 133 | }; 134 | 135 | public liveQuery = { 136 | Hello: (options: RequestOptions, cb: (response: Response) => void) => { 137 | return this.startSubscription( 138 | { 139 | method: "GET", 140 | path: "Hello", 141 | input: options.input, 142 | abortSignal: options.abortSignal, 143 | liveQuery: true, 144 | }, 145 | cb 146 | ); 147 | }, 148 | Messages: ( 149 | options: RequestOptions, 150 | cb: (response: Response) => void 151 | ) => { 152 | return this.startSubscription( 153 | { 154 | method: "GET", 155 | path: "Messages", 156 | input: options.input, 157 | abortSignal: options.abortSignal, 158 | liveQuery: true, 159 | }, 160 | cb 161 | ); 162 | }, 163 | MockQuery: ( 164 | options: RequestOptions, 165 | cb: (response: Response) => void 166 | ) => { 167 | return this.startSubscription( 168 | { 169 | method: "GET", 170 | path: "MockQuery", 171 | input: options.input, 172 | abortSignal: options.abortSignal, 173 | liveQuery: true, 174 | }, 175 | cb 176 | ); 177 | }, 178 | UserInfo: ( 179 | options: RequestOptions, 180 | cb: (response: Response) => void 181 | ) => { 182 | return this.startSubscription( 183 | { 184 | method: "GET", 185 | path: "UserInfo", 186 | input: options.input, 187 | abortSignal: options.abortSignal, 188 | liveQuery: true, 189 | }, 190 | cb 191 | ); 192 | }, 193 | }; 194 | 195 | private doFetch = async (fetchConfig: FetchConfig): Promise> => { 196 | try { 197 | const params = 198 | fetchConfig.method !== "POST" 199 | ? this.queryString({ 200 | wg_variables: fetchConfig.input, 201 | wg_api_hash: this.applicationHash, 202 | }) 203 | : ""; 204 | if (fetchConfig.method === "POST" && this.csrfToken === undefined) { 205 | const f = this.customFetch || fetch; 206 | const res = await f(this.baseURL + "/" + this.applicationPath + "/auth/cookie/csrf", { 207 | credentials: "include", 208 | mode: "cors", 209 | }); 210 | this.csrfToken = await res.text(); 211 | } 212 | const headers: Headers = { 213 | ...this.extraHeaders, 214 | Accept: "application/json", 215 | "WG-SDK-Version": this.sdkVersion, 216 | }; 217 | if (fetchConfig.method === "POST") { 218 | if (this.csrfToken) { 219 | headers["X-CSRF-Token"] = this.csrfToken; 220 | } 221 | } 222 | const body = fetchConfig.method === "POST" ? JSON.stringify(fetchConfig.input) : undefined; 223 | const data = await this.fetch( 224 | this.baseURL + "/" + this.applicationPath + "/operations/" + fetchConfig.path + params, 225 | { 226 | headers, 227 | body, 228 | method: fetchConfig.method, 229 | signal: fetchConfig.abortSignal, 230 | credentials: "include", 231 | mode: "cors", 232 | } 233 | ); 234 | return { 235 | status: "ok", 236 | body: data, 237 | }; 238 | } catch (e: any) { 239 | return { 240 | status: "error", 241 | message: e, 242 | }; 243 | } 244 | }; 245 | private inflight: { 246 | [key: string]: { 247 | reject: (reason?: any) => void; 248 | resolve: (value: globalThis.Response | PromiseLike) => void; 249 | }[]; 250 | } = {}; 251 | private fetch = (input: globalThis.RequestInfo, init?: RequestInit): Promise => { 252 | const key = input.toString(); 253 | return new Promise(async (resolve, reject) => { 254 | if (this.inflight[key]) { 255 | this.inflight[key].push({ resolve, reject }); 256 | return; 257 | } 258 | this.inflight[key] = [{ resolve, reject }]; 259 | try { 260 | const f = this.customFetch || fetch; 261 | const res = await f(input, init); 262 | const inflight = this.inflight[key]; 263 | if (res.status === 200) { 264 | const json = await res.json(); 265 | delete this.inflight[key]; 266 | setTimeout(() => { 267 | inflight.forEach((cb) => cb.resolve(json)); 268 | }, 0); 269 | } 270 | if (res.status >= 401 && res.status <= 499) { 271 | this.csrfToken = undefined; 272 | delete this.inflight[key]; 273 | inflight.forEach((cb) => cb.reject("unauthorized")); 274 | this.fetchUser(); 275 | } 276 | } catch (e: any) { 277 | const inflight = this.inflight[key]; 278 | delete this.inflight[key]; 279 | inflight.forEach((cb) => cb.reject(e)); 280 | } 281 | }); 282 | }; 283 | 284 | private startSubscription = (fetchConfig: FetchConfig, cb: (response: Response) => void) => { 285 | (async () => { 286 | try { 287 | const params = this.queryString({ 288 | wg_variables: fetchConfig.input, 289 | wg_live: fetchConfig.liveQuery === true ? true : undefined, 290 | }); 291 | const f = this.customFetch || fetch; 292 | const response = await f( 293 | this.baseURL + "/" + this.applicationPath + "/operations/" + fetchConfig.path + params, 294 | { 295 | headers: { 296 | ...this.extraHeaders, 297 | "Content-Type": "application/json", 298 | "WG-SDK-Version": this.sdkVersion, 299 | }, 300 | method: fetchConfig.method, 301 | signal: fetchConfig.abortSignal, 302 | credentials: "include", 303 | mode: "cors", 304 | } 305 | ); 306 | if (response.status === 401) { 307 | this.csrfToken = undefined; 308 | return; 309 | } 310 | if (response.status !== 200 || response.body == null) { 311 | return; 312 | } 313 | const reader = response.body.getReader(); 314 | const decoder = new TextDecoder(); 315 | let message: string = ""; 316 | while (true) { 317 | const { value, done } = await reader.read(); 318 | if (done) break; 319 | if (!value) continue; 320 | message += decoder.decode(value); 321 | if (message.endsWith("\n\n")) { 322 | cb({ 323 | status: "ok", 324 | body: JSON.parse(message.substring(0, message.length - 2)), 325 | }); 326 | message = ""; 327 | } 328 | } 329 | } catch (e: any) { 330 | cb({ 331 | status: "error", 332 | message: e, 333 | }); 334 | } 335 | })(); 336 | }; 337 | 338 | private queryString = (input?: Object): string => { 339 | if (!input) { 340 | return ""; 341 | } 342 | const query = (Object.keys(input) as Array) 343 | // @ts-ignore 344 | .filter((key) => input[key] !== undefined && input[key] !== "") 345 | .map((key) => { 346 | const value = typeof input[key] === "object" ? JSON.stringify(input[key]) : input[key]; 347 | const encodedKey = encodeURIComponent(key); 348 | // @ts-ignore 349 | const encodedValue = encodeURIComponent(value); 350 | return `${encodedKey}=${encodedValue}`; 351 | }) 352 | .join("&"); 353 | return query === "" ? query : "?" + query; 354 | }; 355 | public fetchUser = async (revalidate?: boolean): Promise => { 356 | try { 357 | const revalidateTrailer = revalidate === undefined ? "" : "?revalidate=true"; 358 | const f = this.customFetch || fetch; 359 | const response = await f(this.baseURL + "/" + this.applicationPath + "/auth/cookie/user" + revalidateTrailer, { 360 | headers: { 361 | ...this.extraHeaders, 362 | "Content-Type": "application/json", 363 | "WG-SDK-Version": this.sdkVersion, 364 | }, 365 | method: "GET", 366 | credentials: "include", 367 | mode: "cors", 368 | }); 369 | if (response.status === 200) { 370 | const user = await response.json(); 371 | this.setUser(user); 372 | return this.user; 373 | } 374 | } catch {} 375 | this.setUser(null); 376 | return null; 377 | }; 378 | public login: Record = { 379 | github: (redirectURI?: string): void => { 380 | this.startLogin(AuthProviderId.github, redirectURI); 381 | }, 382 | }; 383 | public authProviders: Array = [ 384 | { 385 | id: AuthProviderId.github, 386 | login: this.login[AuthProviderId.github], 387 | }, 388 | ]; 389 | public logout = async (options?: LogoutOptions): Promise => { 390 | const f = this.customFetch || fetch; 391 | const response = await f( 392 | this.baseURL + "/" + this.applicationPath + "/auth/cookie/user/logout" + this.queryString(options), 393 | { 394 | headers: { 395 | ...this.extraHeaders, 396 | "Content-Type": "application/json", 397 | "WG-SDK-Version": this.sdkVersion, 398 | }, 399 | method: "GET", 400 | credentials: "include", 401 | mode: "cors", 402 | } 403 | ); 404 | this.setUser(null); 405 | if (this.logoutCallback) { 406 | this.logoutCallback(); 407 | } 408 | return response.status === 200; 409 | }; 410 | private startLogin = (providerID: AuthProviderId, redirectURI?: string) => { 411 | const query = this.queryString({ 412 | redirect_uri: redirectURI || window.location.toString(), 413 | }); 414 | window.location.replace(`${this.baseURL}/${this.applicationPath}/auth/cookie/authorize/${providerID}${query}`); 415 | }; 416 | } 417 | -------------------------------------------------------------------------------- /components/generated/wundergraph.hooks.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import { 4 | AddMessageResponse, 5 | AddMessageInput, 6 | InternalAddMessageInput, 7 | InjectedAddMessageInput, 8 | ChangeUserNameResponse, 9 | ChangeUserNameInput, 10 | InternalChangeUserNameInput, 11 | InjectedChangeUserNameInput, 12 | DeleteAllMessagesByUserEmailResponse, 13 | DeleteAllMessagesByUserEmailInput, 14 | InternalDeleteAllMessagesByUserEmailInput, 15 | InjectedDeleteAllMessagesByUserEmailInput, 16 | HelloResponse, 17 | MessagesResponse, 18 | MockQueryResponse, 19 | SetLastLoginResponse, 20 | SetLastLoginInput, 21 | InternalSetLastLoginInput, 22 | InjectedSetLastLoginInput, 23 | UserInfoResponse, 24 | InternalUserInfoInput, 25 | InjectedUserInfoInput, 26 | } from "./models"; 27 | import type { WunderGraphRequestContext, Context, WunderGraphRequest, WunderGraphResponse } from "@wundergraph/sdk"; 28 | import type { InternalClient } from "./wundergraph.internal.client"; 29 | import type { User } from "./wundergraph.server"; 30 | 31 | export type AuthenticationResponse = AuthenticationOK | AuthenticationDeny; 32 | 33 | export interface AuthenticationOK { 34 | status: "ok"; 35 | user: User; 36 | } 37 | 38 | export interface AuthenticationDeny { 39 | status: "deny"; 40 | message: string; 41 | } 42 | 43 | // use SKIP to skip the hook and continue the request / response chain without modifying the request / response 44 | export type SKIP = "skip"; 45 | 46 | // use CANCEL to skip the hook and cancel the request / response chain 47 | // this is semantically equal to throwing an error (500) 48 | export type CANCEL = "cancel"; 49 | 50 | export type WUNDERGRAPH_OPERATION = 51 | | "AddMessage" 52 | | "ChangeUserName" 53 | | "DeleteAllMessagesByUserEmail" 54 | | "Hello" 55 | | "Messages" 56 | | "MockQuery" 57 | | "SetLastLogin" 58 | | "UserInfo"; 59 | 60 | export interface GlobalHooksConfig { 61 | httpTransport?: { 62 | // onRequest is called right before the request is sent 63 | // it can be used to modify the request 64 | // you can return SKIP to skip the hook and continue the request chain without modifying the request 65 | // you can return CANCEL to cancel the request chain and return a 500 error 66 | // not returning anything or undefined has the same effect as returning SKIP 67 | onRequest?: { 68 | hook: ( 69 | ctx: WunderGraphRequestContext, 70 | request: WunderGraphRequest 71 | ) => Promise; 72 | // calling the httpTransport hooks has a case, because the custom httpTransport hooks have to be called for each request 73 | // for this reason, you have to explicitly enable the hook for each Operation 74 | enableForOperations?: WUNDERGRAPH_OPERATION[]; 75 | // enableForAllOperations will disregard the enableForOperations property and enable the hook for all operations 76 | enableForAllOperations?: boolean; 77 | }; 78 | // onResponse is called right after the response is received 79 | // it can be used to modify the response 80 | // you can return SKIP to skip the hook and continue the response chain without modifying the response 81 | // you can return CANCEL to cancel the response chain and return a 500 error 82 | // not returning anything or undefined has the same effect as returning SKIP 83 | onResponse?: { 84 | hook: ( 85 | ctx: WunderGraphRequestContext, 86 | response: WunderGraphResponse 87 | ) => Promise; 88 | // calling the httpTransport hooks has a case, because the custom httpTransport hooks have to be called for each request 89 | // for this reason, you have to explicitly enable the hook for each Operation 90 | enableForOperations?: WUNDERGRAPH_OPERATION[]; 91 | // enableForAllOperations will disregard the enableForOperations property and enable the hook for all operations 92 | enableForAllOperations?: boolean; 93 | }; 94 | }; 95 | } 96 | 97 | export type JSONValue = string | number | boolean | JSONObject | Array; 98 | 99 | export type JSONObject = { [key: string]: JSONValue }; 100 | 101 | export interface HooksConfig { 102 | global?: GlobalHooksConfig; 103 | authentication?: { 104 | postAuthentication?: (user: User) => Promise; 105 | mutatingPostAuthentication?: (user: User) => Promise; 106 | revalidate?: (user: User) => Promise; 107 | }; 108 | queries?: { 109 | Hello?: { 110 | mockResolve?: (ctx: Context) => Promise; 111 | preResolve?: (ctx: Context) => Promise; 112 | postResolve?: (ctx: Context, response: HelloResponse) => Promise; 113 | customResolve?: (ctx: Context) => Promise; 114 | mutatingPostResolve?: (ctx: Context, response: HelloResponse) => Promise; 115 | }; 116 | Messages?: { 117 | mockResolve?: (ctx: Context) => Promise; 118 | preResolve?: (ctx: Context) => Promise; 119 | postResolve?: (ctx: Context, response: MessagesResponse) => Promise; 120 | customResolve?: (ctx: Context) => Promise; 121 | mutatingPostResolve?: ( 122 | ctx: Context, 123 | response: MessagesResponse 124 | ) => Promise; 125 | }; 126 | MockQuery?: { 127 | mockResolve?: (ctx: Context) => Promise; 128 | preResolve?: (ctx: Context) => Promise; 129 | postResolve?: (ctx: Context, response: MockQueryResponse) => Promise; 130 | customResolve?: (ctx: Context) => Promise; 131 | mutatingPostResolve?: ( 132 | ctx: Context, 133 | response: MockQueryResponse 134 | ) => Promise; 135 | }; 136 | UserInfo?: { 137 | mockResolve?: (ctx: Context, input: InjectedUserInfoInput) => Promise; 138 | preResolve?: (ctx: Context, input: InjectedUserInfoInput) => Promise; 139 | mutatingPreResolve?: ( 140 | ctx: Context, 141 | input: InjectedUserInfoInput 142 | ) => Promise; 143 | postResolve?: ( 144 | ctx: Context, 145 | input: InjectedUserInfoInput, 146 | response: UserInfoResponse 147 | ) => Promise; 148 | customResolve?: ( 149 | ctx: Context, 150 | input: InjectedUserInfoInput 151 | ) => Promise; 152 | mutatingPostResolve?: ( 153 | ctx: Context, 154 | input: InjectedUserInfoInput, 155 | response: UserInfoResponse 156 | ) => Promise; 157 | }; 158 | }; 159 | mutations?: { 160 | AddMessage?: { 161 | mockResolve?: (ctx: Context, input: InjectedAddMessageInput) => Promise; 162 | preResolve?: (ctx: Context, input: InjectedAddMessageInput) => Promise; 163 | mutatingPreResolve?: ( 164 | ctx: Context, 165 | input: InjectedAddMessageInput 166 | ) => Promise; 167 | postResolve?: ( 168 | ctx: Context, 169 | input: InjectedAddMessageInput, 170 | response: AddMessageResponse 171 | ) => Promise; 172 | mutatingPostResolve?: ( 173 | ctx: Context, 174 | input: InjectedAddMessageInput, 175 | response: AddMessageResponse 176 | ) => Promise; 177 | }; 178 | ChangeUserName?: { 179 | mockResolve?: ( 180 | ctx: Context, 181 | input: InjectedChangeUserNameInput 182 | ) => Promise; 183 | preResolve?: (ctx: Context, input: InjectedChangeUserNameInput) => Promise; 184 | mutatingPreResolve?: ( 185 | ctx: Context, 186 | input: InjectedChangeUserNameInput 187 | ) => Promise; 188 | postResolve?: ( 189 | ctx: Context, 190 | input: InjectedChangeUserNameInput, 191 | response: ChangeUserNameResponse 192 | ) => Promise; 193 | mutatingPostResolve?: ( 194 | ctx: Context, 195 | input: InjectedChangeUserNameInput, 196 | response: ChangeUserNameResponse 197 | ) => Promise; 198 | }; 199 | DeleteAllMessagesByUserEmail?: { 200 | mockResolve?: ( 201 | ctx: Context, 202 | input: InjectedDeleteAllMessagesByUserEmailInput 203 | ) => Promise; 204 | preResolve?: ( 205 | ctx: Context, 206 | input: InjectedDeleteAllMessagesByUserEmailInput 207 | ) => Promise; 208 | mutatingPreResolve?: ( 209 | ctx: Context, 210 | input: InjectedDeleteAllMessagesByUserEmailInput 211 | ) => Promise; 212 | postResolve?: ( 213 | ctx: Context, 214 | input: InjectedDeleteAllMessagesByUserEmailInput, 215 | response: DeleteAllMessagesByUserEmailResponse 216 | ) => Promise; 217 | mutatingPostResolve?: ( 218 | ctx: Context, 219 | input: InjectedDeleteAllMessagesByUserEmailInput, 220 | response: DeleteAllMessagesByUserEmailResponse 221 | ) => Promise; 222 | }; 223 | }; 224 | } 225 | -------------------------------------------------------------------------------- /components/generated/wundergraph.internal.client.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import { 4 | AddMessageResponse, 5 | AddMessageInput, 6 | InternalAddMessageInput, 7 | InjectedAddMessageInput, 8 | ChangeUserNameResponse, 9 | ChangeUserNameInput, 10 | InternalChangeUserNameInput, 11 | InjectedChangeUserNameInput, 12 | DeleteAllMessagesByUserEmailResponse, 13 | DeleteAllMessagesByUserEmailInput, 14 | InternalDeleteAllMessagesByUserEmailInput, 15 | InjectedDeleteAllMessagesByUserEmailInput, 16 | HelloResponse, 17 | MessagesResponse, 18 | MockQueryResponse, 19 | SetLastLoginResponse, 20 | SetLastLoginInput, 21 | InternalSetLastLoginInput, 22 | InjectedSetLastLoginInput, 23 | UserInfoResponse, 24 | InternalUserInfoInput, 25 | InjectedUserInfoInput, 26 | } from "./models"; 27 | 28 | export interface InternalClient { 29 | queries: { 30 | Hello: () => Promise; 31 | Messages: () => Promise; 32 | MockQuery: () => Promise; 33 | UserInfo: (input: InternalUserInfoInput) => Promise; 34 | }; 35 | mutations: { 36 | AddMessage: (input: InternalAddMessageInput) => Promise; 37 | ChangeUserName: (input: InternalChangeUserNameInput) => Promise; 38 | DeleteAllMessagesByUserEmail: ( 39 | input: InternalDeleteAllMessagesByUserEmailInput 40 | ) => Promise; 41 | SetLastLogin: (input: InternalSetLastLoginInput) => Promise; 42 | }; 43 | withHeaders: (headers: { [key: string]: string }) => InternalClient; 44 | } 45 | -------------------------------------------------------------------------------- /components/generated/wundergraph.operations.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import type { 4 | BaseOperationConfiguration, 5 | ConfigureQuery, 6 | ConfigureSubscription, 7 | ConfigureMutation, 8 | CustomizeQuery, 9 | CustomizeMutation, 10 | CustomizeSubscription, 11 | } from "@wundergraph/sdk"; 12 | 13 | export interface OperationsConfiguration { 14 | // defaultConfig is the base for all configurations 15 | // all configuration shared across queries, mutations and subscriptions can be done in the default config 16 | defaultConfig: BaseOperationConfiguration; 17 | 18 | // queries lets you define the base config for all Queries 19 | // the input config is the defaultConfig object 20 | queries: ConfigureQuery; 21 | 22 | mutations: ConfigureMutation; 23 | subscriptions: ConfigureSubscription; 24 | 25 | // custom allows you to override settings for each individual operation 26 | // the input config is the default config + the query/mutation/subscription extra config 27 | custom?: { 28 | AddMessage?: CustomizeMutation; 29 | ChangeUserName?: CustomizeMutation; 30 | DeleteAllMessagesByUserEmail?: CustomizeMutation; 31 | Hello?: CustomizeQuery; 32 | Messages?: CustomizeQuery; 33 | MockQuery?: CustomizeQuery; 34 | SetLastLogin?: CustomizeMutation; 35 | UserInfo?: CustomizeQuery; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /components/generated/wundergraph.server.ts: -------------------------------------------------------------------------------- 1 | // Code generated by wunderctl. DO NOT EDIT. 2 | 3 | import type { HooksConfig, JSONObject } from "./wundergraph.hooks"; 4 | import type { InternalClient } from "./wundergraph.internal.client"; 5 | import type { GraphQLServerConfig, Context } from "@wundergraph/sdk"; 6 | import { FastifyLoggerInstance } from "fastify"; 7 | 8 | export type Role = "user" | "superadmin"; 9 | 10 | export interface User { 11 | provider?: string; 12 | provider_id?: string; 13 | email?: string; 14 | email_verified?: boolean; 15 | name?: string; 16 | first_name?: string; 17 | last_name?: string; 18 | nick_name?: string; 19 | description?: string; 20 | user_id?: string; 21 | avatar_url?: string; 22 | location?: string; 23 | roles?: Role[]; 24 | custom_attributes?: string[]; 25 | custom_claims?: { 26 | [key: string]: any; 27 | }; 28 | access_token?: JSONObject; 29 | id_token?: JSONObject; 30 | raw_id_token?: string; 31 | } 32 | 33 | export interface Config { 34 | hooks: HooksConfig; 35 | graphqlServers?: Omit[]; 36 | } 37 | 38 | export interface OutputConfig { 39 | hooks: HooksConfig; 40 | graphqlServers?: (GraphQLServerConfig & { url: string })[]; 41 | } 42 | 43 | export interface GraphQLExecutionContext { 44 | internalClient: InternalClient; 45 | requestContext: Context; 46 | log: FastifyLoggerInstance; 47 | } 48 | -------------------------------------------------------------------------------- /database.env: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=admin 2 | POSTGRES_PASSWORD=admin 3 | POSTGRES_DB=example -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | database: 4 | image: "postgres" 5 | ports: 6 | - 54322:5432 7 | env_file: 8 | - database.env 9 | volumes: 10 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /init.sql: -------------------------------------------------------------------------------- 1 | create table if not exists Users ( 2 | id serial primary key not null, 3 | email text not null, 4 | name text not null, 5 | unique (email) 6 | ); 7 | 8 | create table if not exists Messages ( 9 | id serial primary key not null, 10 | user_id int not null references Users(id), 11 | message text not null 12 | ); 13 | 14 | insert into Users (email, name) VALUES ('herve.verdavaine@gmail.com','hervé'); 15 | insert into Messages (user_id, message) VALUES ((select id from Users where email = 'herve.verdavaine@gmail.com'),'Hey, welcome to the WunderChat! =)'); 16 | 17 | alter table Users add column updatedAt timestamptz not null default now(); 18 | 19 | alter table Users add column lastLogin timestamptz not null default now(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidgraph", 3 | "version": "1.0.0", 4 | "description": "SolidGraph lets you build SolidJs applications with WunderGraph.", 5 | "info": "WunderGraph was intially designed for React. Thanks to the SolidGraph library, you can now use WunderGraph when builing SolidJs applications. Apart from providing you with a GraphQL API from all your services (both GraphQL and REST!) and databases, it also comes with features like edge caching, authentication and Subscriptions.", 6 | "homepage": "https://solidgraph.ovh/", 7 | "contributors": [ 8 | { 9 | "name": "Hervé Verdavaine", 10 | "email": "paul75011@gmail.com" 11 | } 12 | ], 13 | "keywords": [ 14 | "solid", 15 | "solid-js", 16 | "solidjs", 17 | "wundergraph", 18 | "graphql", 19 | "solidhack", 20 | "best_ecosystem" 21 | ], 22 | "license": "MIT", 23 | "scripts": { 24 | "dev": "concurrently \"$npm_execpath run start\" \"$npm_execpath run wundergraph\" \"$npm_execpath run database\" \"$npm_execpath run browser\"", 25 | "start": "vite", 26 | "build": "vite build", 27 | "serve": "vite preview", 28 | "wundergraph": "wait-on \"tcp:54322\" && cd .wundergraph && wunderctl up --env .env.dev", 29 | "browser": "wait-on \"http-get://localhost:9991\" && open-cli http://localhost:3000", 30 | "database": "docker-compose up", 31 | "migrate": "prisma format && prisma migrate dev --name init" 32 | }, 33 | "devDependencies": { 34 | "concurrently": "^7.0.0", 35 | "open-cli": "^7.0.1", 36 | "prettier": "^2.6.0", 37 | "prisma": "2.28.0", 38 | "typescript": "^4.6.2", 39 | "vite": "^2.8.6", 40 | "vite-plugin-solid": "^2.2.6" 41 | }, 42 | "dependencies": { 43 | "@wundergraph/sdk": "^1.0.0-next.16", 44 | "graphql": "^15.8.0", 45 | "solid-app-router": "^0.3.1", 46 | "solid-js": "^1.3.12", 47 | "solidjs-material-spinner": "^1.0.4", 48 | "wait-on": "^6.0.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | solid-js: ^1.3.12 5 | typescript: ^4.6.2 6 | vite: ^2.8.6 7 | vite-plugin-solid: ^2.2.6 8 | 9 | dependencies: 10 | solid-js: 1.3.12 11 | 12 | devDependencies: 13 | typescript: 4.6.2 14 | vite: 2.8.6 15 | vite-plugin-solid: 2.2.6 16 | 17 | packages: 18 | 19 | /@ampproject/remapping/2.1.2: 20 | resolution: {integrity: sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==} 21 | engines: {node: '>=6.0.0'} 22 | dependencies: 23 | '@jridgewell/trace-mapping': 0.3.4 24 | dev: true 25 | 26 | /@babel/code-frame/7.16.7: 27 | resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==} 28 | engines: {node: '>=6.9.0'} 29 | dependencies: 30 | '@babel/highlight': 7.16.10 31 | dev: true 32 | 33 | /@babel/compat-data/7.17.0: 34 | resolution: {integrity: sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==} 35 | engines: {node: '>=6.9.0'} 36 | dev: true 37 | 38 | /@babel/core/7.17.5: 39 | resolution: {integrity: sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==} 40 | engines: {node: '>=6.9.0'} 41 | dependencies: 42 | '@ampproject/remapping': 2.1.2 43 | '@babel/code-frame': 7.16.7 44 | '@babel/generator': 7.17.3 45 | '@babel/helper-compilation-targets': 7.16.7_@babel+core@7.17.5 46 | '@babel/helper-module-transforms': 7.17.6 47 | '@babel/helpers': 7.17.2 48 | '@babel/parser': 7.17.3 49 | '@babel/template': 7.16.7 50 | '@babel/traverse': 7.17.3 51 | '@babel/types': 7.17.0 52 | convert-source-map: 1.8.0 53 | debug: 4.3.3 54 | gensync: 1.0.0-beta.2 55 | json5: 2.2.0 56 | semver: 6.3.0 57 | transitivePeerDependencies: 58 | - supports-color 59 | dev: true 60 | 61 | /@babel/generator/7.17.3: 62 | resolution: {integrity: sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==} 63 | engines: {node: '>=6.9.0'} 64 | dependencies: 65 | '@babel/types': 7.17.0 66 | jsesc: 2.5.2 67 | source-map: 0.5.7 68 | dev: true 69 | 70 | /@babel/helper-annotate-as-pure/7.16.7: 71 | resolution: {integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==} 72 | engines: {node: '>=6.9.0'} 73 | dependencies: 74 | '@babel/types': 7.17.0 75 | dev: true 76 | 77 | /@babel/helper-compilation-targets/7.16.7_@babel+core@7.17.5: 78 | resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==} 79 | engines: {node: '>=6.9.0'} 80 | peerDependencies: 81 | '@babel/core': ^7.0.0 82 | dependencies: 83 | '@babel/compat-data': 7.17.0 84 | '@babel/core': 7.17.5 85 | '@babel/helper-validator-option': 7.16.7 86 | browserslist: 4.20.0 87 | semver: 6.3.0 88 | dev: true 89 | 90 | /@babel/helper-create-class-features-plugin/7.17.6_@babel+core@7.17.5: 91 | resolution: {integrity: sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==} 92 | engines: {node: '>=6.9.0'} 93 | peerDependencies: 94 | '@babel/core': ^7.0.0 95 | dependencies: 96 | '@babel/core': 7.17.5 97 | '@babel/helper-annotate-as-pure': 7.16.7 98 | '@babel/helper-environment-visitor': 7.16.7 99 | '@babel/helper-function-name': 7.16.7 100 | '@babel/helper-member-expression-to-functions': 7.16.7 101 | '@babel/helper-optimise-call-expression': 7.16.7 102 | '@babel/helper-replace-supers': 7.16.7 103 | '@babel/helper-split-export-declaration': 7.16.7 104 | transitivePeerDependencies: 105 | - supports-color 106 | dev: true 107 | 108 | /@babel/helper-environment-visitor/7.16.7: 109 | resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==} 110 | engines: {node: '>=6.9.0'} 111 | dependencies: 112 | '@babel/types': 7.17.0 113 | dev: true 114 | 115 | /@babel/helper-function-name/7.16.7: 116 | resolution: {integrity: sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==} 117 | engines: {node: '>=6.9.0'} 118 | dependencies: 119 | '@babel/helper-get-function-arity': 7.16.7 120 | '@babel/template': 7.16.7 121 | '@babel/types': 7.17.0 122 | dev: true 123 | 124 | /@babel/helper-get-function-arity/7.16.7: 125 | resolution: {integrity: sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==} 126 | engines: {node: '>=6.9.0'} 127 | dependencies: 128 | '@babel/types': 7.17.0 129 | dev: true 130 | 131 | /@babel/helper-hoist-variables/7.16.7: 132 | resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==} 133 | engines: {node: '>=6.9.0'} 134 | dependencies: 135 | '@babel/types': 7.17.0 136 | dev: true 137 | 138 | /@babel/helper-member-expression-to-functions/7.16.7: 139 | resolution: {integrity: sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==} 140 | engines: {node: '>=6.9.0'} 141 | dependencies: 142 | '@babel/types': 7.17.0 143 | dev: true 144 | 145 | /@babel/helper-module-imports/7.16.0: 146 | resolution: {integrity: sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==} 147 | engines: {node: '>=6.9.0'} 148 | dependencies: 149 | '@babel/types': 7.17.0 150 | dev: true 151 | 152 | /@babel/helper-module-imports/7.16.7: 153 | resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==} 154 | engines: {node: '>=6.9.0'} 155 | dependencies: 156 | '@babel/types': 7.17.0 157 | dev: true 158 | 159 | /@babel/helper-module-transforms/7.17.6: 160 | resolution: {integrity: sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==} 161 | engines: {node: '>=6.9.0'} 162 | dependencies: 163 | '@babel/helper-environment-visitor': 7.16.7 164 | '@babel/helper-module-imports': 7.16.7 165 | '@babel/helper-simple-access': 7.16.7 166 | '@babel/helper-split-export-declaration': 7.16.7 167 | '@babel/helper-validator-identifier': 7.16.7 168 | '@babel/template': 7.16.7 169 | '@babel/traverse': 7.17.3 170 | '@babel/types': 7.17.0 171 | transitivePeerDependencies: 172 | - supports-color 173 | dev: true 174 | 175 | /@babel/helper-optimise-call-expression/7.16.7: 176 | resolution: {integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==} 177 | engines: {node: '>=6.9.0'} 178 | dependencies: 179 | '@babel/types': 7.17.0 180 | dev: true 181 | 182 | /@babel/helper-plugin-utils/7.16.7: 183 | resolution: {integrity: sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==} 184 | engines: {node: '>=6.9.0'} 185 | dev: true 186 | 187 | /@babel/helper-replace-supers/7.16.7: 188 | resolution: {integrity: sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==} 189 | engines: {node: '>=6.9.0'} 190 | dependencies: 191 | '@babel/helper-environment-visitor': 7.16.7 192 | '@babel/helper-member-expression-to-functions': 7.16.7 193 | '@babel/helper-optimise-call-expression': 7.16.7 194 | '@babel/traverse': 7.17.3 195 | '@babel/types': 7.17.0 196 | transitivePeerDependencies: 197 | - supports-color 198 | dev: true 199 | 200 | /@babel/helper-simple-access/7.16.7: 201 | resolution: {integrity: sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==} 202 | engines: {node: '>=6.9.0'} 203 | dependencies: 204 | '@babel/types': 7.17.0 205 | dev: true 206 | 207 | /@babel/helper-split-export-declaration/7.16.7: 208 | resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==} 209 | engines: {node: '>=6.9.0'} 210 | dependencies: 211 | '@babel/types': 7.17.0 212 | dev: true 213 | 214 | /@babel/helper-validator-identifier/7.16.7: 215 | resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} 216 | engines: {node: '>=6.9.0'} 217 | dev: true 218 | 219 | /@babel/helper-validator-option/7.16.7: 220 | resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==} 221 | engines: {node: '>=6.9.0'} 222 | dev: true 223 | 224 | /@babel/helpers/7.17.2: 225 | resolution: {integrity: sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==} 226 | engines: {node: '>=6.9.0'} 227 | dependencies: 228 | '@babel/template': 7.16.7 229 | '@babel/traverse': 7.17.3 230 | '@babel/types': 7.17.0 231 | transitivePeerDependencies: 232 | - supports-color 233 | dev: true 234 | 235 | /@babel/highlight/7.16.10: 236 | resolution: {integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==} 237 | engines: {node: '>=6.9.0'} 238 | dependencies: 239 | '@babel/helper-validator-identifier': 7.16.7 240 | chalk: 2.4.2 241 | js-tokens: 4.0.0 242 | dev: true 243 | 244 | /@babel/parser/7.17.3: 245 | resolution: {integrity: sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==} 246 | engines: {node: '>=6.0.0'} 247 | hasBin: true 248 | dev: true 249 | 250 | /@babel/plugin-syntax-jsx/7.16.7_@babel+core@7.17.5: 251 | resolution: {integrity: sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==} 252 | engines: {node: '>=6.9.0'} 253 | peerDependencies: 254 | '@babel/core': ^7.0.0-0 255 | dependencies: 256 | '@babel/core': 7.17.5 257 | '@babel/helper-plugin-utils': 7.16.7 258 | dev: true 259 | 260 | /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.17.5: 261 | resolution: {integrity: sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==} 262 | engines: {node: '>=6.9.0'} 263 | peerDependencies: 264 | '@babel/core': ^7.0.0-0 265 | dependencies: 266 | '@babel/core': 7.17.5 267 | '@babel/helper-plugin-utils': 7.16.7 268 | dev: true 269 | 270 | /@babel/plugin-transform-typescript/7.16.8_@babel+core@7.17.5: 271 | resolution: {integrity: sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==} 272 | engines: {node: '>=6.9.0'} 273 | peerDependencies: 274 | '@babel/core': ^7.0.0-0 275 | dependencies: 276 | '@babel/core': 7.17.5 277 | '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.17.5 278 | '@babel/helper-plugin-utils': 7.16.7 279 | '@babel/plugin-syntax-typescript': 7.16.7_@babel+core@7.17.5 280 | transitivePeerDependencies: 281 | - supports-color 282 | dev: true 283 | 284 | /@babel/preset-typescript/7.16.7_@babel+core@7.17.5: 285 | resolution: {integrity: sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==} 286 | engines: {node: '>=6.9.0'} 287 | peerDependencies: 288 | '@babel/core': ^7.0.0-0 289 | dependencies: 290 | '@babel/core': 7.17.5 291 | '@babel/helper-plugin-utils': 7.16.7 292 | '@babel/helper-validator-option': 7.16.7 293 | '@babel/plugin-transform-typescript': 7.16.8_@babel+core@7.17.5 294 | transitivePeerDependencies: 295 | - supports-color 296 | dev: true 297 | 298 | /@babel/template/7.16.7: 299 | resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==} 300 | engines: {node: '>=6.9.0'} 301 | dependencies: 302 | '@babel/code-frame': 7.16.7 303 | '@babel/parser': 7.17.3 304 | '@babel/types': 7.17.0 305 | dev: true 306 | 307 | /@babel/traverse/7.17.3: 308 | resolution: {integrity: sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==} 309 | engines: {node: '>=6.9.0'} 310 | dependencies: 311 | '@babel/code-frame': 7.16.7 312 | '@babel/generator': 7.17.3 313 | '@babel/helper-environment-visitor': 7.16.7 314 | '@babel/helper-function-name': 7.16.7 315 | '@babel/helper-hoist-variables': 7.16.7 316 | '@babel/helper-split-export-declaration': 7.16.7 317 | '@babel/parser': 7.17.3 318 | '@babel/types': 7.17.0 319 | debug: 4.3.3 320 | globals: 11.12.0 321 | transitivePeerDependencies: 322 | - supports-color 323 | dev: true 324 | 325 | /@babel/types/7.17.0: 326 | resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} 327 | engines: {node: '>=6.9.0'} 328 | dependencies: 329 | '@babel/helper-validator-identifier': 7.16.7 330 | to-fast-properties: 2.0.0 331 | dev: true 332 | 333 | /@jridgewell/resolve-uri/3.0.5: 334 | resolution: {integrity: sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==} 335 | engines: {node: '>=6.0.0'} 336 | dev: true 337 | 338 | /@jridgewell/sourcemap-codec/1.4.11: 339 | resolution: {integrity: sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==} 340 | dev: true 341 | 342 | /@jridgewell/trace-mapping/0.3.4: 343 | resolution: {integrity: sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==} 344 | dependencies: 345 | '@jridgewell/resolve-uri': 3.0.5 346 | '@jridgewell/sourcemap-codec': 1.4.11 347 | dev: true 348 | 349 | /ansi-styles/3.2.1: 350 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 351 | engines: {node: '>=4'} 352 | dependencies: 353 | color-convert: 1.9.3 354 | dev: true 355 | 356 | /babel-plugin-jsx-dom-expressions/0.32.8_@babel+core@7.17.5: 357 | resolution: {integrity: sha512-fhEWXD29PqvI60yMr4cJ57dKxkQzCNKmT2qT3mgRv5y8h2lPRY5Sc+Q6yDRDlEwCvOnIwhJ/32XMuYghZ0steQ==} 358 | dependencies: 359 | '@babel/helper-module-imports': 7.16.0 360 | '@babel/plugin-syntax-jsx': 7.16.7_@babel+core@7.17.5 361 | '@babel/types': 7.17.0 362 | html-entities: 2.3.2 363 | transitivePeerDependencies: 364 | - '@babel/core' 365 | dev: true 366 | 367 | /babel-preset-solid/1.3.12_@babel+core@7.17.5: 368 | resolution: {integrity: sha512-2/ggNVrYoZ/XMGB5ap9G5qoCXfYxm6GJ9NDhPz3+G4f4kSNQ1CMqZbQAfuKpUqEmtvcOM2riIgNsgeAdUiiGMw==} 369 | dependencies: 370 | babel-plugin-jsx-dom-expressions: 0.32.8_@babel+core@7.17.5 371 | transitivePeerDependencies: 372 | - '@babel/core' 373 | dev: true 374 | 375 | /browserslist/4.20.0: 376 | resolution: {integrity: sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==} 377 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 378 | hasBin: true 379 | dependencies: 380 | caniuse-lite: 1.0.30001314 381 | electron-to-chromium: 1.4.82 382 | escalade: 3.1.1 383 | node-releases: 2.0.2 384 | picocolors: 1.0.0 385 | dev: true 386 | 387 | /caniuse-lite/1.0.30001314: 388 | resolution: {integrity: sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==} 389 | dev: true 390 | 391 | /chalk/2.4.2: 392 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 393 | engines: {node: '>=4'} 394 | dependencies: 395 | ansi-styles: 3.2.1 396 | escape-string-regexp: 1.0.5 397 | supports-color: 5.5.0 398 | dev: true 399 | 400 | /color-convert/1.9.3: 401 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 402 | dependencies: 403 | color-name: 1.1.3 404 | dev: true 405 | 406 | /color-name/1.1.3: 407 | resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} 408 | dev: true 409 | 410 | /convert-source-map/1.8.0: 411 | resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} 412 | dependencies: 413 | safe-buffer: 5.1.2 414 | dev: true 415 | 416 | /debug/4.3.3: 417 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} 418 | engines: {node: '>=6.0'} 419 | peerDependencies: 420 | supports-color: '*' 421 | peerDependenciesMeta: 422 | supports-color: 423 | optional: true 424 | dependencies: 425 | ms: 2.1.2 426 | dev: true 427 | 428 | /electron-to-chromium/1.4.82: 429 | resolution: {integrity: sha512-Ks+ANzLoIrFDUOJdjxYMH6CMKB8UQo5modAwvSZTxgF+vEs/U7G5IbWFUp6dS4klPkTDVdxbORuk8xAXXhMsWw==} 430 | dev: true 431 | 432 | /esbuild-android-64/0.14.25: 433 | resolution: {integrity: sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==} 434 | engines: {node: '>=12'} 435 | cpu: [x64] 436 | os: [android] 437 | requiresBuild: true 438 | dev: true 439 | optional: true 440 | 441 | /esbuild-android-arm64/0.14.25: 442 | resolution: {integrity: sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==} 443 | engines: {node: '>=12'} 444 | cpu: [arm64] 445 | os: [android] 446 | requiresBuild: true 447 | dev: true 448 | optional: true 449 | 450 | /esbuild-darwin-64/0.14.25: 451 | resolution: {integrity: sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==} 452 | engines: {node: '>=12'} 453 | cpu: [x64] 454 | os: [darwin] 455 | requiresBuild: true 456 | dev: true 457 | optional: true 458 | 459 | /esbuild-darwin-arm64/0.14.25: 460 | resolution: {integrity: sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==} 461 | engines: {node: '>=12'} 462 | cpu: [arm64] 463 | os: [darwin] 464 | requiresBuild: true 465 | dev: true 466 | optional: true 467 | 468 | /esbuild-freebsd-64/0.14.25: 469 | resolution: {integrity: sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==} 470 | engines: {node: '>=12'} 471 | cpu: [x64] 472 | os: [freebsd] 473 | requiresBuild: true 474 | dev: true 475 | optional: true 476 | 477 | /esbuild-freebsd-arm64/0.14.25: 478 | resolution: {integrity: sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==} 479 | engines: {node: '>=12'} 480 | cpu: [arm64] 481 | os: [freebsd] 482 | requiresBuild: true 483 | dev: true 484 | optional: true 485 | 486 | /esbuild-linux-32/0.14.25: 487 | resolution: {integrity: sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==} 488 | engines: {node: '>=12'} 489 | cpu: [ia32] 490 | os: [linux] 491 | requiresBuild: true 492 | dev: true 493 | optional: true 494 | 495 | /esbuild-linux-64/0.14.25: 496 | resolution: {integrity: sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==} 497 | engines: {node: '>=12'} 498 | cpu: [x64] 499 | os: [linux] 500 | requiresBuild: true 501 | dev: true 502 | optional: true 503 | 504 | /esbuild-linux-arm/0.14.25: 505 | resolution: {integrity: sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==} 506 | engines: {node: '>=12'} 507 | cpu: [arm] 508 | os: [linux] 509 | requiresBuild: true 510 | dev: true 511 | optional: true 512 | 513 | /esbuild-linux-arm64/0.14.25: 514 | resolution: {integrity: sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==} 515 | engines: {node: '>=12'} 516 | cpu: [arm64] 517 | os: [linux] 518 | requiresBuild: true 519 | dev: true 520 | optional: true 521 | 522 | /esbuild-linux-mips64le/0.14.25: 523 | resolution: {integrity: sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==} 524 | engines: {node: '>=12'} 525 | cpu: [mips64el] 526 | os: [linux] 527 | requiresBuild: true 528 | dev: true 529 | optional: true 530 | 531 | /esbuild-linux-ppc64le/0.14.25: 532 | resolution: {integrity: sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==} 533 | engines: {node: '>=12'} 534 | cpu: [ppc64] 535 | os: [linux] 536 | requiresBuild: true 537 | dev: true 538 | optional: true 539 | 540 | /esbuild-linux-riscv64/0.14.25: 541 | resolution: {integrity: sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==} 542 | engines: {node: '>=12'} 543 | cpu: [riscv64] 544 | os: [linux] 545 | requiresBuild: true 546 | dev: true 547 | optional: true 548 | 549 | /esbuild-linux-s390x/0.14.25: 550 | resolution: {integrity: sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==} 551 | engines: {node: '>=12'} 552 | cpu: [s390x] 553 | os: [linux] 554 | requiresBuild: true 555 | dev: true 556 | optional: true 557 | 558 | /esbuild-netbsd-64/0.14.25: 559 | resolution: {integrity: sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==} 560 | engines: {node: '>=12'} 561 | cpu: [x64] 562 | os: [netbsd] 563 | requiresBuild: true 564 | dev: true 565 | optional: true 566 | 567 | /esbuild-openbsd-64/0.14.25: 568 | resolution: {integrity: sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==} 569 | engines: {node: '>=12'} 570 | cpu: [x64] 571 | os: [openbsd] 572 | requiresBuild: true 573 | dev: true 574 | optional: true 575 | 576 | /esbuild-sunos-64/0.14.25: 577 | resolution: {integrity: sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==} 578 | engines: {node: '>=12'} 579 | cpu: [x64] 580 | os: [sunos] 581 | requiresBuild: true 582 | dev: true 583 | optional: true 584 | 585 | /esbuild-windows-32/0.14.25: 586 | resolution: {integrity: sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==} 587 | engines: {node: '>=12'} 588 | cpu: [ia32] 589 | os: [win32] 590 | requiresBuild: true 591 | dev: true 592 | optional: true 593 | 594 | /esbuild-windows-64/0.14.25: 595 | resolution: {integrity: sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==} 596 | engines: {node: '>=12'} 597 | cpu: [x64] 598 | os: [win32] 599 | requiresBuild: true 600 | dev: true 601 | optional: true 602 | 603 | /esbuild-windows-arm64/0.14.25: 604 | resolution: {integrity: sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==} 605 | engines: {node: '>=12'} 606 | cpu: [arm64] 607 | os: [win32] 608 | requiresBuild: true 609 | dev: true 610 | optional: true 611 | 612 | /esbuild/0.14.25: 613 | resolution: {integrity: sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==} 614 | engines: {node: '>=12'} 615 | hasBin: true 616 | requiresBuild: true 617 | optionalDependencies: 618 | esbuild-android-64: 0.14.25 619 | esbuild-android-arm64: 0.14.25 620 | esbuild-darwin-64: 0.14.25 621 | esbuild-darwin-arm64: 0.14.25 622 | esbuild-freebsd-64: 0.14.25 623 | esbuild-freebsd-arm64: 0.14.25 624 | esbuild-linux-32: 0.14.25 625 | esbuild-linux-64: 0.14.25 626 | esbuild-linux-arm: 0.14.25 627 | esbuild-linux-arm64: 0.14.25 628 | esbuild-linux-mips64le: 0.14.25 629 | esbuild-linux-ppc64le: 0.14.25 630 | esbuild-linux-riscv64: 0.14.25 631 | esbuild-linux-s390x: 0.14.25 632 | esbuild-netbsd-64: 0.14.25 633 | esbuild-openbsd-64: 0.14.25 634 | esbuild-sunos-64: 0.14.25 635 | esbuild-windows-32: 0.14.25 636 | esbuild-windows-64: 0.14.25 637 | esbuild-windows-arm64: 0.14.25 638 | dev: true 639 | 640 | /escalade/3.1.1: 641 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 642 | engines: {node: '>=6'} 643 | dev: true 644 | 645 | /escape-string-regexp/1.0.5: 646 | resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} 647 | engines: {node: '>=0.8.0'} 648 | dev: true 649 | 650 | /fsevents/2.3.2: 651 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 652 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 653 | os: [darwin] 654 | requiresBuild: true 655 | dev: true 656 | optional: true 657 | 658 | /function-bind/1.1.1: 659 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 660 | dev: true 661 | 662 | /gensync/1.0.0-beta.2: 663 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 664 | engines: {node: '>=6.9.0'} 665 | dev: true 666 | 667 | /globals/11.12.0: 668 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 669 | engines: {node: '>=4'} 670 | dev: true 671 | 672 | /has-flag/3.0.0: 673 | resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} 674 | engines: {node: '>=4'} 675 | dev: true 676 | 677 | /has/1.0.3: 678 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 679 | engines: {node: '>= 0.4.0'} 680 | dependencies: 681 | function-bind: 1.1.1 682 | dev: true 683 | 684 | /html-entities/2.3.2: 685 | resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==} 686 | dev: true 687 | 688 | /is-core-module/2.8.1: 689 | resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} 690 | dependencies: 691 | has: 1.0.3 692 | dev: true 693 | 694 | /is-what/4.1.7: 695 | resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==} 696 | engines: {node: '>=12.13'} 697 | dev: true 698 | 699 | /js-tokens/4.0.0: 700 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 701 | dev: true 702 | 703 | /jsesc/2.5.2: 704 | resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} 705 | engines: {node: '>=4'} 706 | hasBin: true 707 | dev: true 708 | 709 | /json5/2.2.0: 710 | resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} 711 | engines: {node: '>=6'} 712 | hasBin: true 713 | dependencies: 714 | minimist: 1.2.5 715 | dev: true 716 | 717 | /merge-anything/5.0.2: 718 | resolution: {integrity: sha512-POPQBWkBC0vxdgzRJ2Mkj4+2NTKbvkHo93ih+jGDhNMLzIw+rYKjO7949hOQM2X7DxMHH1uoUkwWFLIzImw7gA==} 719 | engines: {node: '>=12.13'} 720 | dependencies: 721 | is-what: 4.1.7 722 | ts-toolbelt: 9.6.0 723 | dev: true 724 | 725 | /minimist/1.2.5: 726 | resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} 727 | dev: true 728 | 729 | /ms/2.1.2: 730 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 731 | dev: true 732 | 733 | /nanoid/3.3.1: 734 | resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} 735 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 736 | hasBin: true 737 | dev: true 738 | 739 | /node-releases/2.0.2: 740 | resolution: {integrity: sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==} 741 | dev: true 742 | 743 | /path-parse/1.0.7: 744 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 745 | dev: true 746 | 747 | /picocolors/1.0.0: 748 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 749 | dev: true 750 | 751 | /postcss/8.4.8: 752 | resolution: {integrity: sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==} 753 | engines: {node: ^10 || ^12 || >=14} 754 | dependencies: 755 | nanoid: 3.3.1 756 | picocolors: 1.0.0 757 | source-map-js: 1.0.2 758 | dev: true 759 | 760 | /resolve/1.22.0: 761 | resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} 762 | hasBin: true 763 | dependencies: 764 | is-core-module: 2.8.1 765 | path-parse: 1.0.7 766 | supports-preserve-symlinks-flag: 1.0.0 767 | dev: true 768 | 769 | /rollup/2.70.0: 770 | resolution: {integrity: sha512-iEzYw+syFxQ0X9RefVwhr8BA2TNJsTaX8L8dhyeyMECDbmiba+8UQzcu+xZdji0+JQ+s7kouQnw+9Oz5M19XKA==} 771 | engines: {node: '>=10.0.0'} 772 | hasBin: true 773 | optionalDependencies: 774 | fsevents: 2.3.2 775 | dev: true 776 | 777 | /safe-buffer/5.1.2: 778 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 779 | dev: true 780 | 781 | /semver/6.3.0: 782 | resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} 783 | hasBin: true 784 | dev: true 785 | 786 | /solid-js/1.3.12: 787 | resolution: {integrity: sha512-JnIoF5f40WR8LTEU1McFz/xVnXT3bGyBIme7gFbGj2ch6wXg+vcfbbhc2mz6YaLJIt8NEU+wAg8v+6SX3ZP9mA==} 788 | 789 | /solid-refresh/0.4.0_solid-js@1.3.12: 790 | resolution: {integrity: sha512-5XCUz845n/sHPzKK2i2G2EeV61tAmzv6SqzqhXcPaYhrgzVy7nKTQaBpKK8InKrriq9Z2JFF/mguIU00t/73xw==} 791 | peerDependencies: 792 | solid-js: ^1.3.0 793 | dependencies: 794 | '@babel/generator': 7.17.3 795 | '@babel/helper-module-imports': 7.16.7 796 | '@babel/types': 7.17.0 797 | solid-js: 1.3.12 798 | dev: true 799 | 800 | /source-map-js/1.0.2: 801 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 802 | engines: {node: '>=0.10.0'} 803 | dev: true 804 | 805 | /source-map/0.5.7: 806 | resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} 807 | engines: {node: '>=0.10.0'} 808 | dev: true 809 | 810 | /supports-color/5.5.0: 811 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 812 | engines: {node: '>=4'} 813 | dependencies: 814 | has-flag: 3.0.0 815 | dev: true 816 | 817 | /supports-preserve-symlinks-flag/1.0.0: 818 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 819 | engines: {node: '>= 0.4'} 820 | dev: true 821 | 822 | /to-fast-properties/2.0.0: 823 | resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} 824 | engines: {node: '>=4'} 825 | dev: true 826 | 827 | /ts-toolbelt/9.6.0: 828 | resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} 829 | dev: true 830 | 831 | /typescript/4.6.2: 832 | resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==} 833 | engines: {node: '>=4.2.0'} 834 | hasBin: true 835 | dev: true 836 | 837 | /vite-plugin-solid/2.2.6: 838 | resolution: {integrity: sha512-J1RnmqkZZJSNYDW7vZj0giKKHLWGr9tS/gxR70WDSTYfhyXrgukbZdIfSEFbtrsg8ZiQ2t2zXcvkWoeefenqKw==} 839 | dependencies: 840 | '@babel/core': 7.17.5 841 | '@babel/preset-typescript': 7.16.7_@babel+core@7.17.5 842 | babel-preset-solid: 1.3.12_@babel+core@7.17.5 843 | merge-anything: 5.0.2 844 | solid-js: 1.3.12 845 | solid-refresh: 0.4.0_solid-js@1.3.12 846 | vite: 2.8.6 847 | transitivePeerDependencies: 848 | - less 849 | - sass 850 | - stylus 851 | - supports-color 852 | dev: true 853 | 854 | /vite/2.8.6: 855 | resolution: {integrity: sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==} 856 | engines: {node: '>=12.2.0'} 857 | hasBin: true 858 | peerDependencies: 859 | less: '*' 860 | sass: '*' 861 | stylus: '*' 862 | peerDependenciesMeta: 863 | less: 864 | optional: true 865 | sass: 866 | optional: true 867 | stylus: 868 | optional: true 869 | dependencies: 870 | esbuild: 0.14.25 871 | postcss: 8.4.8 872 | resolve: 1.22.0 873 | rollup: 2.70.0 874 | optionalDependencies: 875 | fsevents: 2.3.2 876 | dev: true 877 | -------------------------------------------------------------------------------- /schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = "mysql://user:password@host:3306/database?schema=public" 4 | } 5 | 6 | model users { 7 | id Int @id @default(autoincrement()) 8 | email String @unique 9 | name String 10 | messages messages[] 11 | updatedat DateTime @default(now()) 12 | lastlogin DateTime @default(now()) 13 | } 14 | 15 | model messages { 16 | id Int @id @default(autoincrement()) 17 | user users @relation(fields: [user_id], references: [id]) 18 | message String 19 | user_id Int 20 | } 21 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "solid-js"; 2 | import { lazy } from "solid-js"; 3 | import { Routes, Route, Link } from "solid-app-router"; 4 | import styles from "../styles/App.module.css"; 5 | import Countries from "./pages/lazyload"; 6 | 7 | const Chat = lazy(() => import("./pages/index")); 8 | const UpdateUser = lazy(() => import("./pages/updateuser")); 9 | const AdminPage = lazy(() => import("./pages/admin/index")); 10 | const TestQuery = lazy(() => import("./pages/testquery")); 11 | const Mock = lazy(() => import("./pages/mock")); 12 | const NotFound = lazy(() => import("./pages/[...all]")); 13 | 14 | const App: Component = () => { 15 | return ( 16 | <> 17 |

18 | 19 | SolidGraph 20 | {" "} 21 | lets you build apps with{" "} 22 | 23 | SolidJS 24 | {" "} 25 | and{" "} 26 | 27 | WunderGraph 28 | 29 |

30 | This real-time chat has been built with SolidGraph 31 |

32 |

33 | 49 |
50 | 51 | } /> 52 | } /> 53 | } /> 54 | } /> 55 | } /> 56 | } /> 57 | 58 |
59 | 60 | ); 61 | }; 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verdavaine/solidgraph/cb3a070a0362f59a3e61a511c12859708a504a52/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMetaEnv { 2 | readonly VITE_BASEURL: string 3 | } 4 | 5 | interface ImportMeta { 6 | readonly env: ImportMetaEnv 7 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from "solid-app-router"; 2 | import { render } from "solid-js/web"; 3 | 4 | import App from "./App"; 5 | import { WunderGraphProvider } from "./lib/provider"; 6 | 7 | const baseURL = import.meta.env.VITE_BASEURL; 8 | 9 | render( 10 | () => ( 11 | 12 | 13 | 14 | 15 | 16 | ), 17 | document.getElementById("root") as HTMLElement 18 | ); 19 | -------------------------------------------------------------------------------- /src/lib/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useContext, createEffect, createSignal, onCleanup, untrack, on, createMemo, Accessor } from "solid-js"; 2 | import { WunderGraphContext } from "./provider"; 3 | import { RequestOptions, MutateRequestOptions, SubscriptionRequestOptions, Response } from "@wundergraph/sdk"; 4 | import { 5 | AddMessageInput, 6 | ChangeUserNameInput, 7 | DeleteAllMessagesByUserEmailInput, 8 | HelloResponse, 9 | MessagesResponse, 10 | MockQueryResponse, 11 | UserInfoResponse, 12 | } from "../../components/generated/models"; 13 | import { Config } from './provider'; 14 | 15 | export const useWunderGraph = () => { 16 | const ctx: Config|undefined = useContext(WunderGraphContext); 17 | if (ctx === undefined) { 18 | throw new Error("WunderGraphContext missing, make sure to put WunderGraphProvider at the root of your app"); 19 | } 20 | return { 21 | client: ctx.client, 22 | user: ctx.user, 23 | initialized: ctx.initialized, 24 | initializing: ctx.initializing, 25 | onWindowFocus: ctx.onWindowFocus, 26 | onWindowBlur: ctx.onWindowBlur, 27 | refetchMountedQueries: ctx.refetchMountedQueries, 28 | setRefetchMountedQueries: ctx.setRefetchMountedQueries, 29 | queryCache: ctx.queryCache, 30 | }; 31 | }; 32 | 33 | interface InternalOptions { 34 | requiresAuthentication: boolean; 35 | } 36 | 37 | const extractName = (promiseFactory: (options: RequestOptions) => Promise>) => { 38 | const query = promiseFactory.toString() 39 | const start = query.indexOf('path')+7 40 | const end = query.indexOf('"', start) 41 | const queryName = query.slice(query.indexOf('path')+7, query.indexOf('"', start)) 42 | return queryName 43 | } 44 | 45 | export const Query = ( 46 | promiseFactory: (options: RequestOptions) => Promise>, 47 | internalOptions: InternalOptions, 48 | _options?: RequestOptions 49 | ) => { 50 | const { user, initialized, onWindowFocus, refetchMountedQueries, queryCache } = useWunderGraph(); 51 | const [shouldFetch, setShouldFetch] = createSignal(_options === undefined ||(_options.initialState === undefined &&_options.lazy !== true)); 52 | const refetch = (options?: RequestOptions) => { 53 | if (options !== undefined) _options = {...options, lazy: false}; 54 | else if (_options && _options.lazy === true){ 55 | _options = {..._options, lazy: false}; 56 | } 57 | setResponse({ status: "loading" }); 58 | setShouldFetch(true); 59 | }; 60 | createEffect(on(onWindowFocus,(onWindowFocus) => { 61 | if (onWindowFocus && _options && _options.refetchOnWindowFocus === true){ 62 | if (_options && _options.lazy === true){ 63 | _options = {..._options, lazy: false}; 64 | } 65 | setResponse({ status: "loading" }); 66 | setShouldFetch(true); 67 | } 68 | }, { defer: true })); 69 | const [response, setResponse] = createSignal>( 70 | _options !== undefined && _options.initialState !== undefined 71 | ? { 72 | status: "ok", 73 | body: _options.initialState, 74 | } 75 | : _options && _options.lazy === true 76 | ? { status: "lazy" } 77 | : { status: "loading" } 78 | ); 79 | createEffect(() => { 80 | if (!initialized()) return; 81 | 82 | if (internalOptions.requiresAuthentication && !user()) { 83 | setShouldFetch(false); 84 | setResponse({ status: "requiresAuthentication" }); 85 | return; 86 | } 87 | if (!shouldFetch()) return; 88 | if (_options && _options.lazy === true) return; 89 | const abortController = new AbortController(); 90 | const cacheKey = extractName(promiseFactory)+'-'+JSON.stringify(_options); 91 | const cached = queryCache[cacheKey]; 92 | if (untrack(response).status !== "ok" && cached) { 93 | setResponse({ 94 | status: "cached", 95 | body: cached as R, 96 | }); 97 | } 98 | (async () => { 99 | const result = await promiseFactory({ 100 | ..._options, 101 | abortSignal: abortController.signal, 102 | }); 103 | if (abortController.signal.aborted) { 104 | setResponse({ status: "aborted" }); 105 | return; 106 | } 107 | if (result.status === "ok") queryCache[cacheKey] = result.body; 108 | setResponse({...result}); 109 | setShouldFetch(false); 110 | })(); 111 | onCleanup (() => abortController.abort()); 112 | }); 113 | createEffect(on([user,refetchMountedQueries],() => { 114 | if (_options && _options.lazy === true){ 115 | _options = {..._options, lazy: false}; 116 | } 117 | setShouldFetch(true); 118 | }, { defer: true })); 119 | return { 120 | response, 121 | refetch, 122 | }; 123 | }; 124 | 125 | const Mutation = ( 126 | promiseFactory: (options: RequestOptions) => Promise>, 127 | internalOptions: InternalOptions, 128 | _options?: MutateRequestOptions 129 | ) => { 130 | const { user, setRefetchMountedQueries } = useWunderGraph(); 131 | const [response, setResponse] = createSignal>({ status: "none" }); 132 | const mutate = async (options?: MutateRequestOptions) => { 133 | if (internalOptions.requiresAuthentication && !user()) { 134 | setResponse({ status: "requiresAuthentication" }); 135 | return; 136 | } 137 | const combinedOptions: MutateRequestOptions = { 138 | refetchMountedQueriesOnSuccess: 139 | options !== undefined && options.refetchMountedQueriesOnSuccess !== undefined 140 | ? options.refetchMountedQueriesOnSuccess 141 | : _options?.refetchMountedQueriesOnSuccess, 142 | input: options !== undefined && options.input !== undefined ? options.input : _options?.input, 143 | abortSignal: 144 | options !== undefined && options.abortSignal !== undefined ? options.abortSignal : _options?.abortSignal, 145 | }; 146 | setResponse({ status: "loading" }); 147 | const result = await promiseFactory(combinedOptions); 148 | setResponse({...result}); 149 | if (result.status === "ok" && combinedOptions.refetchMountedQueriesOnSuccess === true) { 150 | setRefetchMountedQueries(new Date()); 151 | } 152 | } 153 | return { 154 | response, 155 | mutate, 156 | }; 157 | }; 158 | 159 | const Subscription = ( 160 | subscriptionFactory: (options: RequestOptions, cb: (response: Response) => void) => void, 161 | internalOptions: InternalOptions, 162 | options?: SubscriptionRequestOptions 163 | ) => { 164 | const { user, initialized, refetchMountedQueries } = useWunderGraph(); 165 | const [response, setResponse] = createSignal>({ status: "loading" }); 166 | const [lastInit, setLastInit] = createSignal(); 167 | 168 | const computedInit = createMemo(() => { 169 | if (lastInit() === undefined) { 170 | setLastInit(initialized()); 171 | return initialized(); 172 | } 173 | if (options?.stopOnWindowBlur) { 174 | return initialized(); 175 | } 176 | if (initialized()) { 177 | setLastInit(true); 178 | return true; 179 | } 180 | return lastInit() as boolean; 181 | }); 182 | 183 | createEffect(on([user, computedInit, refetchMountedQueries], ([user, computedInit, refetchMountedQueries]) => { 184 | if (!computedInit) { 185 | return; 186 | } 187 | if (internalOptions.requiresAuthentication && !user) { 188 | setResponse({ status: "requiresAuthentication" }); 189 | return; 190 | } 191 | const controller = new AbortController(); 192 | subscriptionFactory( 193 | { 194 | ...options, 195 | abortSignal: controller.signal, 196 | }, 197 | (res) => { 198 | if (!controller.signal.aborted) setResponse({...res}); 199 | } 200 | ); 201 | onCleanup (() => controller.abort()) 202 | })); 203 | return { 204 | response, 205 | }; 206 | }; 207 | 208 | export const useLoadingComplete = (...responses: Accessor>[]) => { 209 | const [loading, setLoading] = createSignal(true); 210 | createEffect(() => { 211 | const isLoading = responses.some((r) => r().status === "loading"); 212 | if (isLoading !== loading()) setLoading(isLoading); 213 | }); 214 | return loading; 215 | }; 216 | 217 | export const useQuery = { 218 | Hello: (options?: RequestOptions) => { 219 | const { client } = useWunderGraph(); 220 | return Query(client.query.Hello, { requiresAuthentication: false }, options); 221 | }, 222 | Messages: (options?: RequestOptions) => { 223 | const { client } = useWunderGraph(); 224 | return Query(client.query.Messages, { requiresAuthentication: false }, options); 225 | }, 226 | MockQuery: (options?: RequestOptions) => { 227 | const { client } = useWunderGraph(); 228 | return Query(client.query.MockQuery, { requiresAuthentication: false }, options); 229 | }, 230 | UserInfo: (options?: RequestOptions) => { 231 | const { client } = useWunderGraph(); 232 | return Query(client.query.UserInfo, { requiresAuthentication: true }, options); 233 | }, 234 | }; 235 | 236 | export const useMutation = { 237 | AddMessage: (options: MutateRequestOptions) => { 238 | const { client } = useWunderGraph(); 239 | return Mutation(client.mutation.AddMessage, { requiresAuthentication: true }, options); 240 | }, 241 | ChangeUserName: (options: MutateRequestOptions) => { 242 | const { client } = useWunderGraph(); 243 | return Mutation(client.mutation.ChangeUserName, { requiresAuthentication: true }, options); 244 | }, 245 | DeleteAllMessagesByUserEmail: (options: MutateRequestOptions) => { 246 | const { client } = useWunderGraph(); 247 | return Mutation(client.mutation.DeleteAllMessagesByUserEmail, { requiresAuthentication: true }, options); 248 | }, 249 | }; 250 | export const useLiveQuery = { 251 | Hello: (options?: SubscriptionRequestOptions) => { 252 | const { client } = useWunderGraph(); 253 | return Subscription(client.liveQuery.Hello, { requiresAuthentication: false }, options); 254 | }, 255 | Messages: (options?: SubscriptionRequestOptions) => { 256 | const { client } = useWunderGraph(); 257 | return Subscription(client.liveQuery.Messages, { requiresAuthentication: false }, options); 258 | }, 259 | MockQuery: (options?: SubscriptionRequestOptions) => { 260 | const { client } = useWunderGraph(); 261 | return Subscription(client.liveQuery.MockQuery, { requiresAuthentication: false }, options); 262 | }, 263 | UserInfo: (options?: SubscriptionRequestOptions) => { 264 | const { client } = useWunderGraph(); 265 | return Subscription(client.liveQuery.UserInfo, { requiresAuthentication: true }, options); 266 | }, 267 | }; 268 | -------------------------------------------------------------------------------- /src/lib/provider.tsx: -------------------------------------------------------------------------------- 1 | import { Client } from "../../components/generated/wundergraph.client"; 2 | import type { UserListener } from "@wundergraph/sdk"; 3 | import type { User } from "../../components/generated/wundergraph.server"; 4 | 5 | import { 6 | createContext, 7 | Component, 8 | createSignal, 9 | Accessor, 10 | Setter, 11 | onCleanup, 12 | onMount, 13 | } from "solid-js"; 14 | 15 | export interface Config { 16 | client: Client; 17 | user: Accessor; 18 | initialized: Accessor; 19 | initializing: Accessor; 20 | onWindowFocus: Accessor; 21 | onWindowBlur: Accessor; 22 | refetchMountedQueries: Accessor; 23 | setRefetchMountedQueries: Setter; 24 | queryCache: { [key: string]: Object }; 25 | } 26 | 27 | export const WunderGraphContext = createContext(); 28 | 29 | export interface Props { 30 | endpoint?: string; 31 | } 32 | 33 | export const WunderGraphProvider: Component = (props) => { 34 | let onFocus = () => {}, 35 | onBlur = () => {}; 36 | const [user, setUser] = createSignal(); 37 | const [onWindowBlur, setOnWindowBlur] = createSignal(new Date()); 38 | const [onWindowFocus, setOnWindowFocus] = createSignal(new Date()); 39 | const [refetchMountedQueries, setRefetchMountedQueries] = createSignal( 40 | new Date() 41 | ); 42 | const [initialized, setInitialized] = createSignal(false); 43 | const [initializing, setInitializing] = createSignal(false); 44 | const queryCache: { [key: string]: Object } = {}; 45 | const client = new Client({ baseURL: props.endpoint }); 46 | client.setLogoutCallback(() => { 47 | Object.keys(queryCache).forEach((key) => delete queryCache[key]); 48 | }); 49 | const userListener: UserListener = (userOrNull: User | null) => { 50 | if (userOrNull === null) { 51 | setUser(undefined); 52 | } else { 53 | setUser(userOrNull); 54 | } 55 | }; 56 | client.setUserListener(userListener); 57 | (async () => { 58 | await client.fetchUser(); 59 | setInitialized(true); 60 | })(); 61 | onFocus = async () => { 62 | setInitializing(true); 63 | await client.fetchUser(); 64 | setOnWindowFocus(new Date()); 65 | setInitialized(true); 66 | setInitializing(false); 67 | }; 68 | onBlur = () => { 69 | setInitialized(false); 70 | setOnWindowBlur(new Date()); 71 | }; 72 | onMount(() => { 73 | window.addEventListener("focus", onFocus); 74 | window.addEventListener("blur", onBlur); 75 | }); 76 | onCleanup(() => { 77 | window.removeEventListener("focus", onFocus); 78 | window.removeEventListener("blur", onBlur); 79 | }); 80 | return ( 81 | 94 | {props.children} 95 | 96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/[...all].tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "solid-js"; 2 | 3 | const NotFound:Component = () => { 4 | return ( 5 |
6 |

7 | Page not found 8 |

9 |
10 | ) 11 | } 12 | 13 | export default NotFound; -------------------------------------------------------------------------------- /src/pages/admin/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMutation, useQuery, useWunderGraph } from "../../lib/hooks"; 2 | import { Component, createEffect, createSignal, For, Show } from "solid-js"; 3 | import { ResponseOK, User } from "@wundergraph/sdk"; 4 | import { 5 | DeleteAllMessagesByUserEmailResponse, 6 | MessagesResponse, 7 | } from "../../../.wundergraph/generated/models"; 8 | import { MessagesResponseData } from "../../../components/generated/models"; 9 | 10 | type Aggregation = [string, number][]; 11 | type Messages = MessagesResponseData["findManymessages"]; 12 | 13 | const getMessagesByUser = (messages: Messages = []) => { 14 | let result: Aggregation = Object.entries( 15 | messages.reduce((res: any, { users }) => { 16 | return { 17 | ...res, 18 | [users.email]: (res[users.email] || 0) + 1, 19 | }; 20 | }, {}) 21 | ); 22 | return result; 23 | }; 24 | 25 | const AdminPage: Component = () => { 26 | const { user } = useWunderGraph(); 27 | const [email, setEmail] = createSignal(""); 28 | const { response: loadMessages } = useQuery.Messages({ 29 | refetchOnWindowFocus: true, 30 | }); 31 | const [messages, setMessages] = createSignal([]); 32 | createEffect(() => { 33 | if (loadMessages().status === "ok" || loadMessages().status == "cached") { 34 | setMessages( 35 | (loadMessages() as ResponseOK).body.data 36 | ?.findManymessages || [] 37 | ); 38 | } 39 | if (loadMessages().status === "requiresAuthentication") { 40 | console.log("Messages requiresAuthentication"); 41 | setMessages([]); 42 | } 43 | }); 44 | const { mutate: deleteAllMessagesFrom, response: deletedMessages } = 45 | useMutation.DeleteAllMessagesByUserEmail({ 46 | refetchMountedQueriesOnSuccess: true, 47 | }); 48 | const disabled = () => 49 | user() ? !user()!.roles!.includes("superadmin") : true; 50 | createEffect(() => setEmail(user()?.email || "")); 51 | const messageAggregation = () => getMessagesByUser(messages()); 52 | 53 | return ( 54 |
55 |

Admin

56 | 57 |

Delete all messages by user email

58 |
59 | 67 | 68 | 74 | 75 | 76 |

77 | Only superadmin users can delete other users messages 78 |

79 |
80 | 81 | 82 |

83 | {`deleted: ${ 84 | ( 85 | deletedMessages() as ResponseOK 86 | ).body.data?.deleteManymessages?.count 87 | } messages`} 88 |

89 |
90 |
91 | 92 |

Messages By User

93 |
94 | 98 | 99 | 100 | Email 101 | Message Count 102 | 103 | 104 | 105 | 106 | {([email, count]) => ( 107 | 108 | {email} 109 | {count} 110 | 111 | )} 112 | 113 | 114 | 115 | } 116 | > 117 |

118 | Only superadmin users can see other users Email 119 |

120 |
121 |
122 |
123 | ); 124 | }; 125 | 126 | export default AdminPage; 127 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Query, 3 | useLiveQuery, 4 | useMutation, 5 | useQuery, 6 | useWunderGraph, 7 | } from "../lib/hooks"; 8 | import { createEffect, createSignal, For, Show } from "solid-js"; 9 | import { Component } from "solid-js"; 10 | import { 11 | MessagesResponse, 12 | UserInfoResponse, 13 | } from "../../.wundergraph/generated/models"; 14 | import { ResponseOK } from "@wundergraph/sdk"; 15 | import { MessagesResponseData } from "../../components/generated/models"; 16 | 17 | type Messages = MessagesResponseData["findManymessages"]; 18 | 19 | const Chat: Component = () => { 20 | const { 21 | user, 22 | client: { login, logout }, 23 | } = useWunderGraph(); 24 | 25 | const [message, setMessage] = createSignal(""); 26 | 27 | const { mutate: addMessage, response: messageAdded } = useMutation.AddMessage( 28 | { refetchMountedQueriesOnSuccess: true } 29 | ); 30 | const { response: loadMessages } = useLiveQuery.Messages({ 31 | stopOnWindowBlur: false, 32 | }); 33 | const [messages, setMessages] = createSignal([]); 34 | 35 | const { response: userInfo } = useQuery.UserInfo({ 36 | refetchOnWindowFocus: true, 37 | }); 38 | 39 | createEffect(() => { 40 | if (loadMessages().status === "ok" || loadMessages().status == "cached") { 41 | setMessages( 42 | ( 43 | (loadMessages() as ResponseOK).body.data 44 | ?.findManymessages || [] 45 | ).reverse() 46 | ); 47 | } 48 | if (loadMessages().status === "requiresAuthentication") { 49 | console.log("Display messages requires Authentication"); 50 | setMessages([]); 51 | } 52 | }); 53 | createEffect(() => { 54 | if (messageAdded().status === "ok") { 55 | setMessage(""); 56 | } else if (messageAdded().status === "requiresAuthentication") { 57 | console.log("messageAdded : requiresAuthentication"); 58 | } 59 | }); 60 | 61 | return ( 62 | <> 63 |

Add Message

64 |
65 | setMessage(e.currentTarget.value)} 70 | /> 71 | 81 |
82 | 83 | 87 |

88 | Please Login to be able to use the chat! 89 |

90 | 91 | 92 | } 93 | > 94 | <> 95 |

User

96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ).body.data 119 | ?.findFirstusers?.lastlogin && ( 120 | 126 | ) 127 | } 128 | > 129 | 130 | 131 | 132 | 133 |
Name{user()?.name}
Email{user()?.email}
Roles{JSON.stringify(user()?.roles)}
Last Login 121 | { 122 | (userInfo() as ResponseOK).body 123 | .data?.findFirstusers?.lastlogin 124 | } 125 | loading...
134 | 135 | 143 |
144 | 145 |
146 | 147 | {messages() !== null && messages().length !== 0 && ( 148 | <> 149 |

Messages

150 | 151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | {(message) => ( 166 | 167 | 168 | 169 | 170 | )} 171 | 172 | 173 |
frommessage
{message.users.name}{message.message}
174 |
175 | 176 | )} 177 | 178 | ); 179 | }; 180 | 181 | export default Chat; 182 | -------------------------------------------------------------------------------- /src/pages/lazyload.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, createSignal, Show } from "solid-js"; 2 | import { 3 | useLoadingComplete, 4 | useMutation, 5 | useQuery, 6 | useWunderGraph, 7 | } from "../lib/hooks"; 8 | import Spinner from "solidjs-material-spinner"; 9 | 10 | export interface QueryOptions { 11 | initialstate: boolean; 12 | lazy: boolean; 13 | refetchOnWindowFocus: boolean; 14 | } 15 | const LazyLoad: Component = ({ 16 | lazy, 17 | initialstate, 18 | refetchOnWindowFocus, 19 | }) => { 20 | const { user } = useWunderGraph(); 21 | const messages = useQuery.Messages( 22 | initialstate 23 | ? { 24 | initialState: { 25 | data: { 26 | findManymessages: [ 27 | { 28 | id: 123456, 29 | message: "initial message", 30 | users: { 31 | id: 1, 32 | name: "webmaster", 33 | email: "webmaster@solidgraph.ovh", 34 | }, 35 | }, 36 | ], 37 | }, 38 | }, 39 | refetchOnWindowFocus, 40 | } 41 | : { 42 | lazy, 43 | refetchOnWindowFocus, 44 | } 45 | ); 46 | const loading = useLoadingComplete(messages.response); 47 | const [message, setMessage] = createSignal(""); 48 | const { mutate: addMessage, response: messageAdded } = useMutation.AddMessage( 49 | { refetchMountedQueriesOnSuccess: false } 50 | ); 51 | const [fectchQueriesOnSuccess, setFectchQueriesOnSuccess] = 52 | createSignal(true); 53 | createEffect(() => { 54 | if (messageAdded().status === "ok") { 55 | setMessage(""); 56 | } else if (messageAdded().status === "requiresAuthentication") { 57 | console.log("messageAdded : requiresAuthentication"); 58 | } 59 | }); 60 | return ( 61 | <> 62 |
63 | setMessage(e.currentTarget.value)} 68 | /> 69 | 79 |     80 | 81 | 86 | setFectchQueriesOnSuccess(event.currentTarget.checked) 87 | } 88 | /> 89 |
90 | 91 |
92 |
93 | 97 |

98 | Please Login on Home page to test mutation! 99 |

100 |
101 | } 102 | > 103 | <> 104 | 105 | 106 |

107 |
108 | messages.refetch()}>Refetch} 111 | > 112 |
113 | 114 |
115 |
116 |

117 |
{JSON.stringify(messages.response(), null, 2)}
118 |
119 | 120 | ); 121 | }; 122 | 123 | export default LazyLoad; 124 | -------------------------------------------------------------------------------- /src/pages/mock.tsx: -------------------------------------------------------------------------------- 1 | import { useWunderGraph, useQuery } from "../lib/hooks"; 2 | import { Component, Show } from "solid-js"; 3 | import { MockQueryResponse } from "../../.wundergraph/generated/models"; 4 | import { ResponseOK } from "@wundergraph/sdk"; 5 | 6 | const MockPage: Component = () => { 7 | const { response: mockQuery } = useQuery.MockQuery(); 8 | return ( 9 |

10 | mock 11 | 16 | {JSON.stringify( 17 | (mockQuery() as ResponseOK).body.data || {}, 18 | null, 19 | 2 20 | )} 21 | 22 | ) 23 | } 24 | > 25 | loading... 26 | 27 |

28 | ); 29 | }; 30 | 31 | export default MockPage; 32 | -------------------------------------------------------------------------------- /src/pages/testquery.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, createSignal, Match, Switch } from "solid-js"; 2 | import LazyLoad, { QueryOptions } from "./lazyload"; 3 | 4 | const TestQuery: Component = () => { 5 | const options: QueryOptions = { 6 | lazy: true, 7 | initialstate: false, 8 | refetchOnWindowFocus: false, 9 | }; 10 | 11 | const [choice, setChoice] = createSignal(1); 12 | const [fetchOnFocus, setFectchOnFocus] = createSignal(3); 13 | 14 | return ( 15 | <> 16 |

Initial conditions:

17 |
18 | setChoice(1)} 23 | checked 24 | /> 25 | 26 |
27 |
28 | setChoice(2)} 33 | /> 34 | 35 |
36 |
37 | setChoice(3)} 42 | /> 43 | 44 |
45 |

46 |
47 | 48 | 53 | setFectchOnFocus(event.currentTarget.checked ? 3 : 0) 54 | } 55 | /> 56 |
57 |

58 | 59 | 60 | 61 | 66 | 67 | 68 | 73 | 74 | 75 | 80 | 81 | 82 | 87 | 88 | 89 | 94 | 95 | 96 | 101 | 102 | 103 | 104 | ); 105 | }; 106 | 107 | export default TestQuery; 108 | -------------------------------------------------------------------------------- /src/pages/updateuser.tsx: -------------------------------------------------------------------------------- 1 | import { useMutation, useWunderGraph } from "../lib/hooks"; 2 | import { Component, createEffect, createSignal, Show } from "solid-js"; 3 | 4 | const UpdateUser: Component = () => { 5 | const { 6 | user, 7 | client: { login }, 8 | } = useWunderGraph(); 9 | const [userName, setUserName] = createSignal(user()?.name || ""); 10 | const { mutate: changeName, response: newName } = useMutation.ChangeUserName({ 11 | input: { newName: "" }, 12 | }); 13 | createEffect(() => { 14 | if (newName().status === "ok") { 15 | setUserName(""); 16 | console.dir(newName()); 17 | } 18 | }); 19 | 20 | return ( 21 |
22 | 30 | 31 | 37 | 38 | 39 |

40 | You must log in to change user name 41 |

42 |
43 |
44 | ); 45 | }; 46 | 47 | export default UpdateUser; 48 | -------------------------------------------------------------------------------- /src/solidjs-material-spinner.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'solidjs-material-spinner'; -------------------------------------------------------------------------------- /styles/App.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .nav ul { 8 | display: flex; 9 | justify-content: space-between; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | padding: 0; 13 | margin-bottom: 5em; 14 | } 15 | 16 | .nav li { 17 | flex: 1; 18 | padding: 1em 0; 19 | max-width: 10em; 20 | list-style: none; 21 | text-align: center; 22 | background: #030152; 23 | border-radius: 6px; 24 | cursor: pointer; 25 | } 26 | 27 | .nav a { 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"], 12 | "noEmit": true, 13 | "isolatedModules": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | {"routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]} -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | build: { 7 | target: 'esnext', 8 | polyfillDynamicImport: false, 9 | }, 10 | }); 11 | --------------------------------------------------------------------------------