| We use in prod variant of no 1. [0]. Why? Because: * it's sql * it's extremely lightweight (built on pure, functional combinators) * it allows us to use more complex patterns ie. convention where every json field ends with Json which is automatically parsed; which, unlike datatype alone, allows us to create composable query to fetch arbitrarily nested graphs and promoting single [$] key ie. to return list of emails as `string[]` not `{ email: string }[]` with `select email as [$] from Users` etc. * has convenience combinators for things like constructing where clauses from monodb like queries * all usual queries like CRUD, exists etc. and some more complex sql-wise but simple function-api-wise ie. insertIgnore list of objects, merge1n, upsert etc all have convenient function apis and allow for composing whatever more is needed for the project We resort to runtime type assertions [1] which works well for this and all other i/o; runtime type assertions are necessary for cases when your running service is incorrectly attached to old or future remote schema/api (there are other protections against it but still happens). [0] https://github.com/appliedblockchain/tsql [1] https://github.com/appliedblockchain/assert-combinators |