Notes on enhancements and bugfixes to Bhagavad Gita v1.1 Next.js web app

Last updated on 14th Aug. 2024

Quick Info

Github repo of app: https://github.com/ravisiyer/gita
App deployed at: https://gita-rsi.vercel.app/

Details

To show selection of authors for translations and commentaries, I was exploring whether there could be a single graphql query that does that for me ....

Article has JS code example that does the filtering: filtering out zero total from nested result in graphql query, https://stackoverflow.com/questions/69069915/filtering-out-zero-total-from-nested-result-in-graphql-query



Given below are some of the queries I tried:

query MyQuery {
  allGitaAuthors {
    nodes {
      id
      name
      gitaCommentariesByAuthorId {
        totalCount
      }
      gitaTranslationsByAuthorId {
        totalCount
      }
    }
  }
}

...

query MyQuery {
  allGitaAuthors {
    nodes {
      id
      name
      gitaCommentariesByAuthorId(condition: {languageId: 1}) {
        totalCount
      }
      gitaTranslationsByAuthorId(condition: {languageId: 1}) {
        totalCount
      }
    }
  }
}

....

[Checking out fragment possibility]
{
  x: allGitaAuthors(condition: {id: 1}) {
    ...f
  }
  y: allGitaAuthors(condition: {id: 2}) {
    ...f
  }
}

fragment f on GitaAuthorsConnection {
  nodes {
    name
    id
  }
}
----

query MyQuery {
  allGitaAuthors {
    nodes {
      id
      name
      CommL1: gitaCommentariesByAuthorId(condition: {languageId: 1}) {
        ...GitaCommentariesConnectionFragment
      }
      CommL2: gitaCommentariesByAuthorId(condition: {languageId: 2}) {
        ...GitaCommentariesConnectionFragment
      }
      CommL3: gitaCommentariesByAuthorId(condition: {languageId: 3}) {
        ...GitaCommentariesConnectionFragment
      }
      TransL1: gitaTranslationsByAuthorId(condition: {languageId: 1}) {
        ...GitaTranslationsConnectionFragment
      }
      TransL2: gitaTranslationsByAuthorId(condition: {languageId: 2}) {
        ...GitaTranslationsConnectionFragment
      }
      TransL3: gitaTranslationsByAuthorId(condition: {languageId: 3}) {
        ...GitaTranslationsConnectionFragment
      }
    }
  }
}

fragment GitaCommentariesConnectionFragment on GitaCommentariesConnection {
  totalCount
}

fragment GitaTranslationsConnectionFragment on GitaTranslationsConnection {
  totalCount
}
-----

The above query was not satisfactory .... I decided to drop further exploration of single query and settle for option of first getting languages and then for each langauge fire a query with condition on languageId. The json object that I create from this is a single json object incorporating key data from all the queries. The queries I am using now:
For all languages:
query {
          allGitaLanguages(orderBy: ID_ASC) {
            nodes {
              id
              language
            }
          }
        }
---
For all authors with commentaries and translations count for each other:
query {
          allGitaAuthors {
            nodes {
              id
              name
              gitaCommentariesByAuthorId(condition: {languageId: ${languageId}}) {
                totalCount
              }
              gitaTranslationsByAuthorId(condition: {languageId: ${languageId}}) {
                totalCount
              }
            }
          }
        }
----
I wrote a function for above query (languages query function was already there but I added orderby). Then I wrote a small console program - writeallauthorsbylanguageId.mjs which ran the queries, composed the single JSON result object and then wrote it to a file. The key code of the program is given below.

Single JSON object structure with results of above is an array of objects with each object having languageId property followed by allGitaAuthorsForLanguageId property which holds an array of nodes returned by the above allGitaAuthors related query.

I used the following code (commented push code worked except for not guaranteeing order in result array as per order in languages array):
let dataLanguages = await getAllLanguages();
let allAuthorsByLanguageId = Array(dataLanguages.allGitaLanguages.length);
await Promise.all(
  dataLanguages.allGitaLanguages.map(async (gitaLanguage, index) => {
    const dataAuthors = await getAuthorsByLanguageId(gitaLanguage.id);
    allAuthorsByLanguageId[index] = {
      languageId: gitaLanguage.id,
      allGitaAuthorsForLanguageId: dataAuthors.allGitaAuthorsForLanguageId,
    };
    // push sometimes results in languageId 3 coming before 2 in the array.
    // Want to avoid that.
    // allAuthorsByLanguageId.push({
    //   languageId: gitaLanguage.id,
    //   allGitaAuthorsForLanguageId: dataAuthors.allGitaAuthorsForLanguageId,
    // });
  })
);

----

Initially I wrote the above code in a separate project. After it was stable, when I moved these create json console .js programs to Gita app project, I got this warning from Node.js, "Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension." followed by an error on an import statement. Below article seems to cover same/similar issue but most suggested solutions seem rather involved to me: How to run a standalone cli script using a nextjs project?, https://stackoverflow.com/questions/72196364/how-to-run-a-standalone-cli-script-using-a-nextjs-project 

I fixed the issues by doing the following:
a) Named js files as .mjs
b) Created an apolloclient.mjs file with code from ../apolloclient.ts but with import statement modified as follows:
import pkg from "@apollo/client";
const { ApolloClient, InMemoryCache } = pkg;
// Above style to avoid node.js console execution error about @apollo/client being a commonJS module
c) In jsondata.mjs used similar style as follows:
import pkg from "@apollo/client";
const { gql } = pkg;
// Above style to avoid node.js console execution error about @apollo/client being a commonJS module
---
Then in jsondata.mjs I used apolloclient.mjs (b) instead of ../apolloClient.ts :
import createApolloClient from "./apolloclient.mjs";
----
Note that I chose to have a separate jsondata.mjs with gql queries need by these console programs instead of adding the gql queries to the main ../app/lib/data.js file having the gql queries used by Gita app.
====
I was able to recreate the json files created earlier in the other separate project.

I have pushed all the code including the json files to the GitHub repo for the project in branch 'settings'. Later I plan to merge it into main branch. 
==============


// Subset is needed to handle nested GitaCommentariesConnection and GitaTranslationsConnection partial 
// data in allAuthorsByLanguageID object defined below.
// Ref: Making TypeScript's Partial type work for nested objects, https://grrr.tech/posts/2021/typescript-partial/

type Subset<K> = {
  [attr in keyof K]?: K[attr] extends object
    ? Subset<K[attr]>
    : K[attr] extends object | null
    ? Subset<K[attr]> | null
    : K[attr] extends object | null | undefined
    ? Subset<K[attr]> | null | undefined
    : K[attr];
};

export type allAuthorsByLanguageIdT = {
  languageId: GitaLanguage["id"];
  allGitaAuthorsForLanguageId: Subset<GitaAuthor>[];
};

export const allAuthorsByLanguageId: allAuthorsByLanguageIdT[] = [
  {
    languageId: 1,
    allGitaAuthorsForLanguageId: [
      {
        __typename: "GitaAuthor",
        id: 1,
        name: "Swami Ramsukhdas",
        gitaCommentariesByAuthorId: {
          __typename: "GitaCommentariesConnection",
          totalCount: 0,
        },
        gitaTranslationsByAuthorId: {
          __typename: "GitaTranslationsConnection",
          totalCount: 0,
        },
      },
...
--------------------

Going to chapter 1, verse 1 from initial display of home page seems to not work the first time - noted it twice in recent days.
On server, a console log entry is:
GET /? 200 in 3785ms
Retrying it works.
...
Above problem seems to be a flaky problem as it did not happen when I ran 'npm run dev',open the home page and then went to Chapter 1, verse 1 using SCV in menu. It worked first time.
----------

Add some notes in a separate post: Using a dynamic number of headless UI listboxes (with multiple selections) in a page, https://raviswdev.blogspot.com/2024/08/using-dynamic-number-of-headless-ui.html .
---------------------
Picks up thread from notes in above separate post. ...

Does FormData.get() always return string? I tried to get an answer for it from the reference info. The return type of FormData.get() seems to be FormDataEntryValue ! null, based on VSCode intellisense.
Getting type info. for FormDataEntryValue was quite difficult. I think I have got the related ref. page(s) but I am not sure as it is not a standard site like MDN (MDN FormData ref. does not seem to mention FormDataEntryValue).
https://xhr.spec.whatwg.org/#formdataentryvalue : typedef (File or USVString) FormDataEntryValue;

https://github.com/mdn/sprints/issues/1181 : "The spec says it is a string or a File." The spec links to above xhr.spec... page.

So looks like for my needs, I have to presume it returns a string and not primitive data type like int [which is to be expected, I think, but I wanted to be sure].

=============
VSCode - search uncommented code only (JS) 
The Reg. Exp below works to show only uncommented console.log lines
^((?!//|/\*).)*console
Ref: Visual Studio : Search Uncommented Code only, https://stackoverflow.com/questions/7527468/visual-studio-search-uncommented-code-only : Azfar answer

====================

To Dos:

Done: Verse page has to handle no cookie scenario. As of now, I am showing all languages and all authors' data.

Seems to be done: This seems to have got resolved due to above. ... Settings page showing default values on no cookie is confusing, as of now as verse page shows all languages, translations and commentaties if there is no cookie.

Done: Am I handling failure of setCookie? ... Next.js cookies().set documentation does not mention any return value. I think it may be just setting the cookie in the response header sent to browser. As of now, I think I should simply change the settings saved message to sent settings to browser as cookie and perhaps change the save settings button label also appropriately. ... Later I can spend time to browse for conventions followed for handling of such scenarios. I tried to quickly browse for it but did not get suitable results. ... Done.

Done: When all language entries are unchecked can corresponding language checkbox be automatically unchecked?

===============
Done: Select All when language box is checked seems to force user to uncheck entries he does not want. An alternative UI would be to just select first translator or commentator.

=============

Released version 1.2 .. See: Gita web app (Next.js, open source) v1.2: Facility to select individual translators and commentators for a language; Notes for students & self-learners,  https://raviswdev.blogspot.com/2024/08/gita-web-app-nextjs-open-source-v12.html .

Comments