diff --git a/.gitignore b/.gitignore index e14c41bb..9f0a7bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,6 @@ release node_modules bin ./package-lock.json -config slim.report.json diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..04204c7c --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +config diff --git a/examples/nodejs/config/dev.yml b/examples/nodejs/config/dev.yml new file mode 100644 index 00000000..c744949c --- /dev/null +++ b/examples/nodejs/config/dev.yml @@ -0,0 +1,128 @@ +# When production mode is 'true' only queries +# from the allow list are permitted. +production: false + +# Secret key for general encryption operations like +# encrypting the cursor data +secret_key: supercalifajalistics + +# Subscriptions poll the database to query for updates +# this sets the duration (in seconds) between requests. +# Defaults to 5 seconds +subs_poll_every_seconds: 5 + +# Default limit value to be used on queries and as the max +# limit on all queries where a limit is defined as a query variable. +# Defaults to 20 +default_limit: 20 + +# Disables all aggregation functions like count, sum, etc +# disable_agg_functions: false + +# Disables all functions like count, length, etc +# disable_functions: false + +# Enables using camel case terms in GraphQL which are converted +# to snake case in SQL +# enable_camelcase: false + +# Set session variable "user.id" to the user id +# Enable this if you need the user id in triggers, etc +# Note: This will not work with subscriptions +set_user_id: false + +# DefaultBlock ensures that in anonymous mode (role 'anon') all tables +# are blocked from queries and mutations. To open access to tables in +# anonymous mode they have to be added to the 'anon' role config. +default_block: false + +# Define additional variables here to be used with filters +# Variables used require a type suffix eg. $user_id:bigint +variables: + #admin_account_id: "5" + admin_account_id: "sql:select id from users where admin = true limit 1" + +# Define variables set to values extracted from http headers +header_variables: + remote_ip: "X-Forwarded-For" + +# Field and table names that you wish to block +blocklist: + - ar_internal_metadata + - schema_migrations + - secret + - password + - encrypted + - token + +# resolvers: +# - name: payments +# type: remote_api +# table: customers +# column: stripe_id +# json_path: data +# debug: false +# url: http://payments/payments/$id +# pass_headers: +# - cookie +# set_headers: +# - name: Host +# value: 0.0.0.0 +# # - name: Authorization +# # value: Bearer + +tables: + - # You can create new fields that have a + # real db table backing them + name: me + table: users + + - name: users + order_by: + new_users: ["created_at desc", "id asc"] + id: ["id asc"] + +# Variables used require a type suffix eg. $user_id:bigint +#roles_query: "SELECT * FROM users WHERE id = $user_id:bigint" + +roles: + # if `auth.type` is set to a valid auth type then + # all tables are blocked for the anon role unless + # added to the role like below. + # - name: anon + # tables: + # - name: users + # query: + # limit: 10 + + - name: user + tables: + - name: me + query: + filters: ["{ id: { _eq: $user_id } }"] + + # - name: products + # query: + # limit: 50 + # filters: ["{ user_id: { eq: $user_id } }"] + # disable_functions: false + + # insert: + # filters: ["{ user_id: { eq: $user_id } }"] + # presets: + # - user_id: "$user_id" + # - created_at: "now" + + # update: + # filters: ["{ user_id: { eq: $user_id } }"] + # presets: + # - updated_at: "now" + + # delete: + # block: true + + # - name: admin + # match: id = 1000 + # tables: + # - name: users + # filters: [] diff --git a/examples/nodejs/config/prod.yml b/examples/nodejs/config/prod.yml new file mode 100644 index 00000000..4746aff3 --- /dev/null +++ b/examples/nodejs/config/prod.yml @@ -0,0 +1,21 @@ +# Inherit config from this other config file +# so I only need to overwrite some values +inherits: dev + +# When production mode is 'true' only queries +# from the allow list are permitted. +production: true + +# Secret key for general encryption operations like +# encrypting the cursor data +secret_key: supercalifajalistics + +# Subscriptions poll the database to query for updates +# this sets the duration (in seconds) between requests. +# Defaults to 5 seconds +subs_poll_every_seconds: 5 + +# Default limit value to be used on queries and as the max +# limit on all queries where a limit is defined as a query variable. +# Defaults to 20 +default_limit: 20 diff --git a/examples/nodejs/index.js b/examples/nodejs/index.js index c167a9b9..687c9fb8 100644 --- a/examples/nodejs/index.js +++ b/examples/nodejs/index.js @@ -1,6 +1,6 @@ import graphjin from "graphjin"; -import express from "express"; -import http from "http"; +// import express from "express"; +// import http from "http"; import pg from "pg" const { Client } = pg @@ -17,27 +17,45 @@ await db.connect() // config can either be a file (eg. `dev.yml`) or an object // const config = { production: true, default_limit: 50 }; -var gj = await graphjin("./config", "dev.yml", db); -var app = express(); -var server = http.createServer(app); +// var app = express(); +// var server = http.createServer(app); -const res1 = await gj.subscribe( - "subscription getUpdatedUser { users(id: $userID) { id email } }", - null, - { userID: 2 }) +// const res1 = await gj.subscribe( +// "subscription getUpdatedUser { users(id: $userID) { id email } }", +// null, +// { userID: 2 }) -res1.data(function(res) { - console.log(">", res.data()) -}) +// res1.data(function(res) { +// console.log(">", res.data()) +// }) -app.get('/', async function(req, resp) { - const res2 = await gj.query( - "query getUser { users(id: $id) { id email } }", - { id: 1 }, - { userID: 1 }) +var gj = await graphjin("./config", "dev.yml", db); - resp.send(res2.data()); -}); -server.listen(3000); -console.log('Express server started on port %s', server.address().port); \ No newline at end of file +const q = ` +query test { + organization_users (where: { organization_id: $id} ) { + role + users { + id + email + } + } + } +` + +for (let i = 0; i < 10000;i++) { + const res = await gj.query(q, { id: 1 }) + console.log(i, JSON.stringify(res.data())) +// console.log(res.sql()) +} +process.exit(0); + +// app.get('/', async function(req, resp) { + + +// resp.send(res2.data()); +// }); + +// server.listen(3000); +// console.log('Express server started on port %s', server.address().port); \ No newline at end of file diff --git a/package.json b/package.json index cd30072e..fd18a9f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "graphjin", - "version": "2.0.4", - "description": "GraphJin - Build APIs in 5 minutes with GraphQL. An instant GraphQL to SQL compiler.", + "version": "2.0.5", + "description": "GraphJin - Build APIs in 5 minutes with GraphQL", "type": "module", "main": "wasm/js/graphjin.js", "files": [ diff --git a/wasm/args.go b/wasm/args.go index 76772ac7..58615345 100644 --- a/wasm/args.go +++ b/wasm/args.go @@ -15,8 +15,8 @@ type queryArgs struct { } func newQueryArgs(args []js.Value) (qa queryArgs, jsErr js.Value) { - if len(args) < 2 { - err := errors.New("required arguments: query, variables") + if len(args) < 1 { + err := errors.New("required arguments: query") return qa, toJSError(err) } query := args[0] @@ -24,14 +24,14 @@ func newQueryArgs(args []js.Value) (qa queryArgs, jsErr js.Value) { if query.Type() != js.TypeString || query.String() == "" { return qa, toJSError(errors.New("query argument missing")) } - qa.query = query.String() + return processQueryArgs(qa, args) } func newQueryByNameArgs(args []js.Value) (qa queryArgs, jsErr js.Value) { - if len(args) < 2 { - err := errors.New("required arguments: name, variables") + if len(args) < 1 { + err := errors.New("required arguments: name") return qa, toJSError(err) } name := args[0] @@ -39,27 +39,21 @@ func newQueryByNameArgs(args []js.Value) (qa queryArgs, jsErr js.Value) { if name.Type() != js.TypeString || name.String() == "" { return qa, toJSError(errors.New("query argument missing")) } - qa.query = name.String() + return processQueryArgs(qa, args) } func processQueryArgs(qa queryArgs, args []js.Value) (queryArgs, js.Value) { - var err error + if len(args) == 1 { + return qa, js.Null() + } vars := args[1] - opts := args[2] if vars.Type() != js.TypeObject && - vars.Type() != js.TypeNull && vars.Type() != js.TypeUndefined { - err = errors.New("variables argument can only be a string or null") - } - - if opts.Type() != js.TypeObject && - opts.Type() != js.TypeNull && opts.Type() != js.TypeUndefined { - err = errors.New("options argument can only be a object or null") - } - - if err != nil { + vars.Type() != js.TypeNull && + vars.Type() != js.TypeUndefined { + err := errors.New("variables argument can only be a string or null") return qa, toJSError(err) } @@ -68,6 +62,18 @@ func processQueryArgs(qa queryArgs, args []js.Value) (queryArgs, js.Value) { qa.vars = json.RawMessage(val.String()) } + if len(args) == 2 { + return qa, js.Null() + } + opts := args[2] + + if opts.Type() != js.TypeObject && + opts.Type() != js.TypeNull && + opts.Type() != js.TypeUndefined { + err := errors.New("options argument can only be a object or null") + return qa, toJSError(err) + } + if v := opts.Get("userID"); v.Type() == js.TypeString || v.Type() == js.TypeNumber { qa.userID = optVal(v) } diff --git a/wasm/config/dev.yml b/wasm/config/dev.yml new file mode 100644 index 00000000..c744949c --- /dev/null +++ b/wasm/config/dev.yml @@ -0,0 +1,128 @@ +# When production mode is 'true' only queries +# from the allow list are permitted. +production: false + +# Secret key for general encryption operations like +# encrypting the cursor data +secret_key: supercalifajalistics + +# Subscriptions poll the database to query for updates +# this sets the duration (in seconds) between requests. +# Defaults to 5 seconds +subs_poll_every_seconds: 5 + +# Default limit value to be used on queries and as the max +# limit on all queries where a limit is defined as a query variable. +# Defaults to 20 +default_limit: 20 + +# Disables all aggregation functions like count, sum, etc +# disable_agg_functions: false + +# Disables all functions like count, length, etc +# disable_functions: false + +# Enables using camel case terms in GraphQL which are converted +# to snake case in SQL +# enable_camelcase: false + +# Set session variable "user.id" to the user id +# Enable this if you need the user id in triggers, etc +# Note: This will not work with subscriptions +set_user_id: false + +# DefaultBlock ensures that in anonymous mode (role 'anon') all tables +# are blocked from queries and mutations. To open access to tables in +# anonymous mode they have to be added to the 'anon' role config. +default_block: false + +# Define additional variables here to be used with filters +# Variables used require a type suffix eg. $user_id:bigint +variables: + #admin_account_id: "5" + admin_account_id: "sql:select id from users where admin = true limit 1" + +# Define variables set to values extracted from http headers +header_variables: + remote_ip: "X-Forwarded-For" + +# Field and table names that you wish to block +blocklist: + - ar_internal_metadata + - schema_migrations + - secret + - password + - encrypted + - token + +# resolvers: +# - name: payments +# type: remote_api +# table: customers +# column: stripe_id +# json_path: data +# debug: false +# url: http://payments/payments/$id +# pass_headers: +# - cookie +# set_headers: +# - name: Host +# value: 0.0.0.0 +# # - name: Authorization +# # value: Bearer + +tables: + - # You can create new fields that have a + # real db table backing them + name: me + table: users + + - name: users + order_by: + new_users: ["created_at desc", "id asc"] + id: ["id asc"] + +# Variables used require a type suffix eg. $user_id:bigint +#roles_query: "SELECT * FROM users WHERE id = $user_id:bigint" + +roles: + # if `auth.type` is set to a valid auth type then + # all tables are blocked for the anon role unless + # added to the role like below. + # - name: anon + # tables: + # - name: users + # query: + # limit: 10 + + - name: user + tables: + - name: me + query: + filters: ["{ id: { _eq: $user_id } }"] + + # - name: products + # query: + # limit: 50 + # filters: ["{ user_id: { eq: $user_id } }"] + # disable_functions: false + + # insert: + # filters: ["{ user_id: { eq: $user_id } }"] + # presets: + # - user_id: "$user_id" + # - created_at: "now" + + # update: + # filters: ["{ user_id: { eq: $user_id } }"] + # presets: + # - updated_at: "now" + + # delete: + # block: true + + # - name: admin + # match: id = 1000 + # tables: + # - name: users + # filters: [] diff --git a/wasm/config/prod.yml b/wasm/config/prod.yml new file mode 100644 index 00000000..4746aff3 --- /dev/null +++ b/wasm/config/prod.yml @@ -0,0 +1,21 @@ +# Inherit config from this other config file +# so I only need to overwrite some values +inherits: dev + +# When production mode is 'true' only queries +# from the allow list are permitted. +production: true + +# Secret key for general encryption operations like +# encrypting the cursor data +secret_key: supercalifajalistics + +# Subscriptions poll the database to query for updates +# this sets the duration (in seconds) between requests. +# Defaults to 5 seconds +subs_poll_every_seconds: 5 + +# Default limit value to be used on queries and as the max +# limit on all queries where a limit is defined as a query variable. +# Defaults to 20 +default_limit: 20 diff --git a/wasm/graphjin.wasm b/wasm/graphjin.wasm index 66ad263c..ad06319b 100755 Binary files a/wasm/graphjin.wasm and b/wasm/graphjin.wasm differ diff --git a/website/posts/6-special-stuff.md b/website/posts/6-specials.md similarity index 99% rename from website/posts/6-special-stuff.md rename to website/posts/6-specials.md index 6142ccb4..fde07957 100644 --- a/website/posts/6-special-stuff.md +++ b/website/posts/6-specials.md @@ -1,10 +1,10 @@ --- chapter: 6 -title: Special Stuff +title: Specials description: Graph queries, Array columns, Remote joins, etc --- -# Special Stuff +# Specials This is all the really cool special stuff that you'll need but databases don't support or is hard to build. For example you want to build an feed for your website or join your query with an API call to Stripe, etc