# Node and Typescript

# React Native on Android

### Installing Dev Environment

On ubuntu you can install the android SDK with `apt-get install android-sdk` .

You'll need a target device to test your app against. You can plug it in and use adb to do the hard work. Make sure to install[ the Expo App](https://play.google.com/store/apps/details?id=host.exp.exponent&gl=US) on your target device - this will serve as a host on the device that allows it to run your new app in development and do hot-reloading of your code.

### Creating a Project

Using npx[ create a new project with the tooling provided by React Native](https://reactnative.dev/docs/environment-setup) like so:

```shell
npx create-expo-app AwesomeProject
cd AwesomeProject
npm start # you can also use: npx expo start

```

### Running the Project

Start the app with `npm run android` you might get error messages about not knowing where android home is. On ubuntu android home installed from deb packages is normally at **`/usr/lib/android-sdk`** [(source)](https://stackoverflow.com/questions/47630630/android-sdk-ubuntu-default-path) so just run:

`export ANDROID_HOME=/usr/lib/android-sdk` before you try to launch the app.

---

### Theming

I'm looking at using [Native Base](https://docs.nativebase.io/install-expo) as a component library in my apps.

### Persisting Data

React Native is pretty modular so you need to use libraries to do most things. Us[e a storage library ](https://reactnative.directory/?search=storage)to store data. This[ MMKV storage library](https://github.com/ammarahm-ed/react-native-mmkv-storage) appears to be pretty efficient and allows you to store state across multiple databases very easily and quickly

### Navigation

Using[ react-navigation](https://reactnavigation.org) to manage navigation within the app and show different views. The[ hello world tutorial](https://reactnavigation.org/docs/hello-react-navigation) shows how this works

### Using the Camera

The[ expo-camera ](https://docs.expo.dev/versions/latest/sdk/camera/)library seems to be a nice way to do cross-device camera stuff. I got it working very quickly.

<details id="bkmrk-working-minimal-exam"><summary>Working Minimal Example Camera App</summary>

```typescript
import { StatusBar } from 'expo-status-bar';
import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

export default function App() {
  const [type, setType] = useState(CameraType.back);
  const [permission, requestPermission] = Camera.useCameraPermissions();

  if (!permission) {
    return (<View><Text>No camera m8</Text></View>)
  }

  if (!permission.granted) {
    return(<View>
        <Text>Clicky to grant perms m8</Text>
        <Button title='Grant Perms' onPress={requestPermission}/>
      </View>)
  }

  function toggleCameraType() {
    setType(current => (current === CameraType.back ? CameraType.front : CameraType.back));
  }

  return (
    <View style={styles.container}>
      <Camera style={styles.camera} type={type}>
        <View style={styles.buttonContainer}>
          <TouchableOpacity style={styles.button} onPress={toggleCameraType}>
            <Text style={styles.text}>Flip Camera</Text>
          </TouchableOpacity>
        </View>
      </Camera>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  camera:{
    flex: 1,
  },
  button: {
    flex: 1,
    alignSelf: 'flex-end',
    alignItems: 'center',
  },
  buttonContainer: {
    flex: 1,
    flexDirection: 'row',
    backgroundColor: 'transparent',
    margin: 64,
  },
  text:{

  }
});


```

</details>\### 

# Joplin Development

[Joplin](https://wiki.jamesravey.me/books/pkm/page/personal-knowledge-management#bkmrk-joplin) features a rich plugin architecture which allows third party developers to build plugins to enable non-standard behaviours.

Joplin is written in Javascript/Typescript and built on top of electron. Plugins must therefore run in an electron runtime.

### Finding Global Settings Values

There are some use cases that might require you to know what settings the user has entered for areas of the application outside of your plugin (for example you might need to know if the user has sync enabled so that you can avoid destructive operations while synchronisation is happening).

The solution is to use the [joplin.settings.globalValue](https://joplinapp.org/api/references/plugin_api/classes/joplinsettings.html#globalvalue) api function. The global settings keys are not documented and could change. The dev team suggest that developers look for useful values in[ Settings.ts ](https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142)in the main project repo.

### Publishing Plugins

The official Joplin plugin marketplace/selector retrieves items from[ this github repo](https://github.com/joplin/plugins) which in turns checks npm for packages that are published with the keyword `joplin-plugin`. So all you need to do is ensure that your project follows the standard structure of a joplin plugin (e.g. comes with a manifest.json, has the corresponding npm steps) and has this keyword.

The [yeoman](https://yeoman.io/) template provided by the joplin team comes pre-configured ready for publication of plugins.

# Knex

- ORM for node.js with Typescript compatibility

# NPM and Gitlab

Gitlab has a built in package repository that can be used as a stand in for NPM's global repo.

Best practice is to map a [scope](https://docs.npmjs.com/cli/v6/using-npm/scope) to your repository in your `.npmrc` file and in your package's `package.json` file.

Gitlab uses CI tokens to authenticate against the npm repository.

# Publishing Type Definitions

It can be useful to be able to publish these types in custom NPM repos e.g.[ Gitlab](https://wiki.jamesravey.me/books/node-and-typescript/page/npm-and-gitlab "NPM and Gitlab")

### Configuring `package.json` File

#### Including `src` and `dist` folders

use `files` key in`package.json`to indicate which directories should be published ([https://stackoverflow.com/questions/67523877/index-d-ts-file-not-published-to-npm](https://stackoverflow.com/questions/67523877/index-d-ts-file-not-published-to-npm)). By default you likely want to publish both the source code in `src` and the compiled/transpiled typescript-&gt;js files in `dist`

e.g.

```json
{
  "files": [
    "dist",
    "src"
  ],
}

```

#### Including types and main

#### You also need to include a `types` property which tells npm where your defined types (`index.d.ts`) sits - this is likely to be in `dist/index.d.ts` if you follow the conventions in this document.

#### Main module

In a node module you'd probably use `src/index.js` but we want to use `dist/index.js` - this will be the compiled version of our typescript module.

### Configuring `tsconfig.json`

You probably want something like this:

```json
{
  "compilerOptions": {
      "sourceMap": true,
      "outDir": "dist",
      "strict": true,
      "lib": [
          "esnext"
      ],
      "allowJs": true,
      "declaration": true,
      "esModuleInterop": true
  },
  
  "include": ["src/**/*"],
}

```

Use dist for our `outdir` so that the output from transpilation goes to the right place. Use `declaration: true` to have tsc produce an `index.d.ts`declaration file and plonk it into the `dist` folder so that it is picked up by the package.json we configured above.

# AMQPLib and RabbitMQ

#### Channel closed by server: 406 (PRECONDITION-FAILED) with message "PRECONDITION\_FAILED

As explained by [this article](https://www.grzegorowski.com/rabbitmq-406-channel-closed-precondition-failed) this implies that your channel has consumed a message without ACKing or NACKing it and it has timed out. Make sure to ACK or NACK all messages when you receive them - typically after processing just in case something goes wrong there.

```typescript
this.channel?.consume(q?.queue, (msg: amqplib.ConsumeMessage | null) => {

            if(!msg){
                throw new Error(`Received a null message - weirdness.`)
            }

            try{

                const result = callback(msg)

                this.channel?.ack(msg)

                return result

            }catch(err) {
                console.log(err)
                this.channel?.nack(msg)
            }
})

```

# Debugging Complex TS Projects in VSCode

### `launch.json` Config File

This page assumes a build config based on the one in [Publishing Type Definitions](https://wiki.jamesravey.me/books/node-and-typescript/page/publishing-type-definitions "Publishing Type Definitions") with a `src` and a `dist` folder.

We can use `npm link` to link `project1` to `project2` and then we can add the dist folders from both projects to `outFiles` so that they are picked up by the vscode debugger.

We also want to make sure that `sourceMaps` is set to true so that the debugger picks up the source maps in the dist folders and maps them onto the corresponding `src` folders.

```json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceFolder}/project1/dist/index.js",
            "preLaunchTask": "Build All",
            "outFiles": [
                "${workspaceFolder}/project1/dist/**/*.js",
                "${workspaceFolder}/project2/dist/**/*.js"
            ],
            "sourceMaps": true,
            "sourceMapRenames": true
        }
    ]
}

```

The launch definition can fire off a pre-launch task so we can use that to always ensure that the transpiled js code is up to date before we launch it. The below configuration defines the builds and dependencies between them

### `tasks.json` Configuration

This file contains build tasks that the launch.json file can depend on before launching.

```json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "typescript",
			"tsconfig": "projecta/tsconfig.json",
			"problemMatcher": [
				"$tsc"
			],
			"group": "build",
			"label": "tsc: build - projecta/tsconfig.json",
			"dependsOn":["tsc: build - projectb/tsconfig.json"]
		},
		{
			"type": "typescript",
			"tsconfig": "projectb/tsconfig.json",
			"problemMatcher": [
				"$tsc"
			],
			"group": "build",
			"label": "tsc: build - projectb/tsconfig.json"
		},
		{
			"label":"Build All",
			"dependsOn":[
				"tsc: build - projecta/tsconfig.json",
				"tsc: build - projectb/tsconfig.json"
			]
		}
	]
}

```

We can define two typescript build operations - linking them together with dependencies and we can also define a `Build All` job which is just a dummy job that doesn't do anything but requires the ts builds to complete before it can be considered complete itself.

# Handling File Uploads in Express

### Express and Multi-Part Uploads

Historically I've used the express [Multer](https://expressjs.com/en/resources/middleware/multer.html) middleware and associated typescript types (`@types/multer`).

Multer accepts files encoded as part of a `multipart/form-data` request. The file is added to the `req` object as `req.file : Express.Multer.File`

This is done by adding a middleware to the route that accepts files like so:

```typescript
import multer from 'multer'


...


const uploadMiddleware = multer({limits:{
    fileSize: config.uploads.maxSize
}})

router.post('/blah', 
    uploadMiddleware.single('file'), 
    controllerFunction );



```

### Storing Uploaded Files in S3

It is possible to use minio to immediately pass the file through to an S3 layer via the buffer object. Using a Minio client object:

```typescript
async function handleFileUpload(req: Express.Request, res: Express.Response) {
  
  const fileObj : Express.Multer.File = req.file
  let file = await Minio.putObject(config.minio.bucket, objName, fileObj.buffer);
  
  return {"status":"uploaded", file}

}

```

### Dealing with CSVs

Use [neat-csv](https://github.com/sindresorhus/neat-csv) for a promisy experience

# Logging and Winston

Winston is a fancy logging library for node.

### Using Common Loggers Between Packages

As per [this stackoverflow](https://stackoverflow.com/questions/49271570/winston-attempt-to-write-logs-with-no-transports) post ([mirror](https://archive.jamesravey.me/archive/1667998491.438072/index.html)):

Declare and export your winston logger object and from different locations within your app.

# Fetch

The Fetch API was[ adopted as a standard replacement for XMLHTTPRequest](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) in the last few years. It is usually available by default in browser code. However it is not offered out-of-the-box by Node.js

### Cross-Fetch

The [cross-fetch](https://www.npmjs.com/package/cross-fetch) library provides cross-platform fetch API compatibility for node-like environments.

It can be installed with `npm i --save cross-fetch`.

It can be used as an ES6 module via

```
import fetch from 'cross-fetch';
```

# npmrc

npmrc files registry namespaces and credentials that allow users to authenticate against private (and public) npm registries.

NPM looks for a `.npmrc` file in the current directory and prioritises this over the one in your user `$HOME` directory.

# Node Version Management

[Volta](https://volta.sh/) is a modern NodeJS runtime and tooling manager similar to [NVM](https://github.com/nvm-sh/nvm). An advantage of Volta over nvm is that it can store metadata about the running version of node in a `package.json` file an install the correct version of node.

Install specific tools and runtimes with `volta install node@<version>  npm@<version>`

#### Example Package.json Content

When you run `volta pin node@18.19.0` you might find that something similar to the below is injected into your package.json file:

```json
  "volta": {
    "node": "18.19.0"
  }
```