You would benefit from using a streaming subscription. If you use a live query, you have to manually maintain a "cursor" (ie last message you fetched) and update your graphql subscription accordingly every time a new message is received or sent. If you don't you'll be refetching the same messages repeatedly, and they'll just grow as more messages are added.
With a streaming subscription this is all taken care of, you simply do something like this:
1) Load the last 50 messages (or whatever number of existing messages suits you)
2) Run your streaming subscription using the most recent of the messages from above as you cursor
3) When you send a message, do nothing and let the subscription handle getting the message you just sent
I built an ERP-like application for a small production company entirely with Airtable as the backend, but wrapped in a GraphQL schema. The Airtable app fetches the schema of a base when you load it, complete with all type columns, data types, select options, lookup ids, etc. You can use that to generate a GraphQL schema and fetch the data from Airtable in your resolvers. The rate-limiting and general speed when fetching a lot of data makes it unsuited for things that need to scale, but having the excellent Airtable UI for raw data entry and a custom application for everything else (complex data entry, reporting, kpis and such) was very nice.
That said, if I were to do it again today I'd probably go with Hasura or something more suited as an actual backend.
Beyond a certain size and complexity (which is not that much), I find that the argument of typescript (and other typed languages) being less productive than untyped dynamic ones, is not true when you look at it as a whole. It might feel slower, especially to begin with, but once you get used to the language and semantics you save a vast amount of time, by the bugs you _don't_ debug and by not having to jump through the code all the time to find out what that function or module was called or what parameters it accepted. This of course is less true if you're using a lot of untyped packages, but as you said, most do have types either natively or in the DefinitelyTyped project. For most modules it's also feasible to declare the module typings manually, even doing it gradually for the parts that you happen to need at a given time.
With a streaming subscription this is all taken care of, you simply do something like this: 1) Load the last 50 messages (or whatever number of existing messages suits you) 2) Run your streaming subscription using the most recent of the messages from above as you cursor 3) When you send a message, do nothing and let the subscription handle getting the message you just sent