Fork me on GitHub
#clojurescript
<
2021-07-13
>
Simon06:07:36

Translating this .js

const { google } = require('googleapis');
const sheets = google.sheets('v4')
to this:
(ns functions.index
  (:require ;...
            ["googleapis" :as google]))

(def sheets (.sheets google "v4"))

thheller06:07:32

(:require ["googleapis$google"] :as google) (google/sheets "v4")

thheller06:07:58

or your variant, slightly longer but with :refer not :as (:require ["googleapis" :refer (google)])

🙌 2
thheller06:07:52

this const { google } in JS is destructuring, so basically const api = require("googleapis"); const sheets = api.google.sheets("v4")

Simon06:07:11

But i get a runtime error:

TypeError: (intermediate value) is not a function

thheller06:07:32

from those few lines of code or from something you do later? what is the full trace?

Simon06:07:11

sorry, just give me a few moments. To provide you something useful 😄

Simon06:07:23

i suspect its either something with the go macro or the sheets. Interestingly depending on how import them i don't get any warnings

Simon06:07:43

Now like this i don't get any warnings:

(ns functions.index
  (:require ["firebase-functions" :as fun]
            [cljs.core.async :as async :refer (chan go)]
            ["googleapis" :refer (google)]))

(def sheets (.sheets google "v4"))

thheller06:07:17

yeah the :as wasn't valid so that would have thrown

Simon06:07:25

Now i do this:

(ns functions.index
  (:require ["firebase-functions" :as fun]
            ["googleapis" :refer (google)])
  (:require-macros [cljs.core.async.macros :refer (go)]))

(def sheets (.sheets google "v4"))

Simon06:07:12

and get the error

TypeError: Cannot read property 'chan' of undefined

thheller06:07:26

that isn't what I meant. I meant you had ["googleapis" :as google] and that would have been invalid. the core.async is valid either way unless you are on a really old core.async version

2
Simon06:07:27

but i dont use chan anywhere?

thheller06:07:06

well if you ONLY use :require-macros that is a problem. you'll need the :require also

thheller06:07:26

[cljs.core.async :as async :refer (chan go)] is definitely the way to go nowadays

2
Simon06:07:54

okay great. with the go macro settled i'm back at the previous error:

TypeError: (intermediate value) is not a function
>      at switch__25716__auto__ (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/functions/index.cljs:12:5)
>      at /Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/functions/index.cljs:12:5
>      at Function.functions$index$book_consultation_$_state_machine__25717__auto____1 [as cljs$core$IFn$_invoke$arity$1] (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/functions.index.js:130:4)
>      at Object.cljs$core$async$impl$ioc_helpers$run_state_machine [as run_state_machine] (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/cljs/core/async/impl/ioc_helpers.cljs:43:3)
>      at Object.cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped [as run_state_machine_wrapped] (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/cljs/core/async/impl/ioc_helpers.cljs:45:1)
>      at /Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/functions/index.cljs:12:5
>      at Immediate.cljs$core$async$impl$dispatch$process_messages [as _onImmediate] (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/cljs/core/async/impl/dispatch.cljs:26:7)
>      at processImmediate (node:internal/timers:464:21)

thheller06:07:33

so whats in line 12? the snippet above doesn't have 12 lines 😉

😅 2
Simon06:07:02

(ns functions.index
  (:require ["firebase-functions" :as fun]
            ["googleapis" :refer (google)]
            [cljs.core.async :as async :refer (chan go)]))

(def sheets (.sheets google "v4"))

(defn book_consultation [req, res]
  ;/book_consultation
  (let [query (.. req -query)
        email (.-email query)]
    (go (let [request
              #js
               {:spreadsheetId "XXX"
                :range "Sheet1!A1:C1"
                :valueInputOption "RAW"
                :resource #js {:values [[41 42 43] [44 45 46]]}
                :auth (new
                       (.JWT (.-auth google))
                       #js
                        {:keyFile "./key.json"
                         :scopes [""]})}]
          (try
            (let [response (.-data (.append (.. sheets -spreadsheets -values) request))]
              (.log js/console (.stringify js/JSON response nil 2)))
            (catch js/Object ^js err (.error js/console err)))))))

Simon06:07:57

There it is. Just had to sanitize it a little

Simon06:07:44

and line 12 is the go form

thheller06:07:28

yeah the go unfortunately messes up some stack traces. why is this in a go though? doesn't need to be? and thus shouldn't be 🙂

2
thheller06:07:16

this looks incorrect (new (.JWT (.-auth google)). just remove the go and you'll get a better more accurate stacktrace

thheller06:07:08

:resource #js {:values [[41 42 43] [44 45 46]] this also is likely invalid

thheller06:07:42

and this :scopes [""]

thheller06:07:54

#js is slightly annoying for nested stuff

Simon06:07:50

#js {:values #js [#js [41 42 43] #js [44 45 46]]}

thheller06:07:33

there really needs to be a recursive #js variant 😛

😄 2
thheller06:07:46

but yeah that would be correct

Simon06:07:16

Beginning execution of "book_consultation"
⚠  functions: TypeError: (intermediate value) is not a function
    at functions$index$book_consultation (/Users/simonchristensen/Documents/Developer/movenation/calculator/.shadow-cljs/builds/firebase-functions/dev/out/cljs-runtime/functions/index.cljs:10:3)
10:3 points to the starting paranthesis of the let form
(ns functions.index
  (:require ["firebase-functions" :as fun]
            ["googleapis" :refer (google)]
            [cljs.core.async :as async :refer (chan go)]))

(def sheets (.sheets google "v4"))

(defn book_consultation [req, res]
  ;/book_consultation
  (let [query (.. req -query)
; ^ 10:3 TypeError: (intermediate value) is not a function
        email (.-email query)
        request
        #js
         {:spreadsheetId "XXX"
          :range "Sheet1!A1:C1"
          :valueInputOption "RAW"
          :resource #js {:values #js [#js [41 42 43] #js [44 45 46]]}
          :auth (new
                 (.JWT (.-auth google))
                 #js
                  {:keyFile "./key.json"
                   :scopes #js [""]})}]
    (try
      (let [response (.-data (.append (.. sheets -spreadsheets -values) request))]
        (.log js/console (.stringify js/JSON response nil 2)))
      (catch js/Object ^js err (.error js/console err)))))

Simon07:07:55

I can't find the error...

Simon07:07:49

It is worth mentioning that this is the code i am trying to translate:

const functions = require('firebase-functions');
const { google } = require('googleapis');
const sheets = google.sheets('v4');

const auth = new google.auth.JWT({
  keyFile:
    './key.json',
  scopes: [
    '',
  ],
});


exports.book_consultation =
  functions.https.onRequest(async (req, res) => {
    const request = {
      // The ID of the spreadsheet to update.
      spreadsheetId:
        'XXX', 

      range: 'Sheet1!A1:C1', 

      valueInputOption: 'RAW', 

      resource: {
        // TODO: Add desired properties to the request body.
        values: [
          [41, 42, 43],
          [44, 45, 46],
        ],
      },
      auth,
    };

    try {
      const response = (
        await sheets.spreadsheets.values.append(
          request,
        )
      ).data;
      // TODO: Change code below to process the `response` object:
      console.log(
        JSON.stringify(response, null, 2),
      );
    } catch (err) {
      console.error(err);
    }
  });

Simon12:07:31

I think for now i will just keep this serverless function written in Node.JS. I'm not sure it is worth it to translate it to CLJS that anyways will be compiled back to Node.JS

Simon12:07:12

Thanks for taking the time to help me out anyways @U05224H0W and the fast response 🙂

thheller17:07:31

so I was correct. this is invalid (new (.JWT (.-auth google)) needs to be (new (.. google -auth -JWT))

Simon12:07:30

Just for reference i managed to finally get it working! 😄 🎉

(ns functions.index
  (:require ["firebase-functions" :as fun]
            ["googleapis" :refer (google)]
            [cljs.core.async :refer [go]]
            [cljs.core.async.interop :refer-macros [<p!]]))

(defn book_consultation [req, res]
  ;/book_consultation
  (let [body (js->clj (.. req -body) :keywordize-keys true)
        {:keys [email tocity], {:keys [city_name latitude longitude]} :geolocation} body
        timestamp (.now js/Date)
        auth (new (.. google -auth -JWT)
                  (clj->js {:keyFile "./key.json"
                            :scopes  [""]}))
        request (clj->js
                 {:spreadsheetId "XXXabc"
                  :range "bookingdata!A1"
                  :valueInputOption "USER_ENTERED"
                  :resource {:values  [[email tocity city_name latitude longitude timestamp]]}
                  :auth auth})]
    (go (let [sheets (.sheets google "v4")
              response (<p! (.append (.. sheets -spreadsheets -values) request))
              data (.-data response)]
          (try
            (.log js/console (.stringify js/JSON data nil 2))
            (.send (.status res 200) data)
            (catch js/Error err (js/console.error (ex-cause err))))))))

(def main
  #js{:book_consultation (.onRequest fun/https book_consultation)})

Simon07:07:49

It is worth mentioning that this is the code i am trying to translate:

const functions = require('firebase-functions');
const { google } = require('googleapis');
const sheets = google.sheets('v4');

const auth = new google.auth.JWT({
  keyFile:
    './key.json',
  scopes: [
    '',
  ],
});


exports.book_consultation =
  functions.https.onRequest(async (req, res) => {
    const request = {
      // The ID of the spreadsheet to update.
      spreadsheetId:
        'XXX', 

      range: 'Sheet1!A1:C1', 

      valueInputOption: 'RAW', 

      resource: {
        // TODO: Add desired properties to the request body.
        values: [
          [41, 42, 43],
          [44, 45, 46],
        ],
      },
      auth,
    };

    try {
      const response = (
        await sheets.spreadsheets.values.append(
          request,
        )
      ).data;
      // TODO: Change code below to process the `response` object:
      console.log(
        JSON.stringify(response, null, 2),
      );
    } catch (err) {
      console.error(err);
    }
  });

Dominik Visca11:07:44

Hey folks, I'm trying to get into ClojureScript to escape the hell which is modern webdev with JavaScript. My problem with all the attempts to leave JS kind of behind is the necessity to use large packages like OpenLayers. Sometimes it's already a pain to use these in JS frameworks. I noticed a cljsjs package exists for openlayers. And yes, I'm sure it's usable for starters but has someone some kind of insight how usable it is for larger projects where I have to use the nitty gritty features of a library like OpenLayers?

p-himik11:07:29

No idea about OpenLayers in particular, but it shouldn't be much harder to use any JS library from CLJS given that interop is pretty straightforward. One note though - don't use cljsjs. Use a regular NPM package instead - modern build tools allow it.

dgb2311:07:53

If you use a JS library heavily it might be worth it to wrap them generically. OpenLayers looks really useful. But at a glance it seems like it has a very imperative API and all of the configuration data is wrapped with classes. In Clojure you can either write some generic macros, or likely better, you can define your configurations and effects as data structures and interpret them to talk to the JS lib.

dgb2311:07:16

“just use interop” is the generally recommended approach (KISS), but I think there is a lot of value in translation or at least in attempting it. Honeysql is a library that does that for SQL for example.

dgb2312:07:29

I recently (like two months ago) struggled with a similar problem. Had to use a js library with a mutable API (not from ClojureScript). It was claimed to be “data-driven”, however the objects I passed actually got mutated, so in order to see what I initially passed I had to deep copy them beforehand etc.

dgb2312:07:10

so what I did was wrapping it into an interface and deep copy everything I pass through it to keep my sanity.

valtteri12:07:07

I’m happy to share experiences if there’s something particular you’d like to know.

Dominik Visca12:07:14

Mh... ok. Thanks so far! Yes, OpenLayers relies heavily on classes, this is always some form of problem I stumble upon. Everything I need is in this package but I always hate to use this OO approach.

Dominik Visca12:07:14

@U6N4HSMFW Oh, this looks promising! A 'fullstack' GIS app with ClojureScript for reference is exactly what I was looking for, thank you!

👌 2
valtteri12:07:42

From developer experience point of view interop is on par with native JS (equally horrible). But what makes it tolerable is that you can pretty easily narrow down the imperative parts to certain files and keep the rest of your code clean.

💥 2
💯 2
Dominik Visca12:07:35

I'll dig through your code but for now it looks like it's at least possible to build some heavy WebGIS application with ClojureScript so I can keep looking into it!

sova-soars-the-sora15:07:31

Is there a nice page that documents all those :require statement conventions coming to CLJS?

p-himik15:07:02

You mean this? > Support Macros that Expand to `require` Statements

Simon18:07:04

if you are using shadow-cljs you can refer (pun intended 😄 ) to their guide: https://shadow-cljs.github.io/docs/UsersGuide.html#_using_npm_packages

🙂 4
Simon18:07:51

Just wanted to share this beautiful piece of code before i had to ruin it by changing the button text depending on the request-state

(defn- submit-button [email city geolocation]
  (let [request-state (r/atom :ready)]
    (fn [email city geolocation]
      [:div.field
       [:div.control
        [:button.button.is-primary
         (merge {:type "submit" :on-click  #(submit % @email city geolocation)}
                (case @request-state
                  :loading {:class "is-loading"}
                  :success {:class "is-success "}
                  :error {:class "is-danger"}
                  {}))
         "Book Consultation"]]]))) 

p-himik19:07:11

Could be made even more beautiful by switching from a form-2 component to reagent.core/with-let. ;)

pyb21:07:15

Hey everybody, what's the most up-to-date way to create a reagent app with cider ? I haven't used it for a couple of years. It seems all the old methods (lein reagent template, etc) are broken/buggy.

p-himik22:07:02

The most up to date can only be guaranteed if you do it by hand. And it's not that hard - the smallest setup is just two files, at least in the case of shadow-cljs.

pyb22:07:46

Rather, "the most up-to-date" as in what people actually use that isn't bitrotten

pyb22:07:11

Surely not everybody writes project.clj's by hand ?

p-himik22:07:54

What's the problem with that? The bare minimum is just a few lines - no reason to create a script or even a template for that unless you churn out projects every week. :)

p-himik22:07:02

But, to answer it in a more correct way - of course, not everybody does that. Perhaps, someone can link some template that works for them.

👍 2
sova-soars-the-sora23:07:00

I enjoy tonsky/rum for front-end stuff