Using git patch or how to live with your packages not updating

There’s a longstanding bug in react-native that has been active since 2018. Korean text is broken on iOS (especially if you move the input cursor to the beginning of the line)

https://github.com/facebook/react-native/issues/19339

Various fixes were merged and reverted over the years (they cause problems in some other places)

Recently (2022 – 4 years later), a better fix were merged into react-native’s main branch

https://github.com/facebook/react-native/pull/32523

However it’s still unclear when this will get released, and it will take even more time for us to upgrade react-native

In order to use this in our final product, we’ll need to patch react-native manually

  1. Download react-native normally via npm install
  2. Patch node_modules/react-native/Libraries/Text/TextInput/RCTBaseTexinputView.m
  3. Build the project

Generating the patch

Let’s download the react-native repository

git clone https://github.com/facebook/react-native.git

You’ll find that it take a veeery long time

Let’s just get the last 1,000 commits instead

git clone --depth 1000 https://github.com/facebook/react-native.git

Much faster!

Next, reset the branch to the commit we want to extract (You can get the commit ID from the pull request, look at the end)

git reset --hard 1a83dc36ce0af33ac7a3c311354fce4bfa5ba1a3

Next, generate the patch file

git format-patch -1 main Libraries/Text/TextInput/RCTBaseTextInputView.m

Explanation

  • git format-patch: generate patch file
  • -1: from last 1 commit
  • main: from main branch
  • Libraries/Text/TextInput/RCTBaseTextInputView.m: patch to the file, omit to generate patch for the whole commit

Applying the patch

A cursory search on the internet will give you some npm package that you should install to apply a patch file. However, most *nix environment have a tool called patch already installed. We can utilize this

Let’s add some script to `package.json`:

    "patch-3882": "patch -st -p1 -d node_modules/react-native < patches/3882-fix-reactnative-textinput.patch",
    "pod": "pod-install",
    "postinstall": "npm run patch-3882; npm run pod",
    "postuninstall": "npm run patch-3882; npm run pod",

Explanation

  • postinstall / postuninstall will run after each package installation with npm. This will ensure our version of react-native stays patched. Also this will work on your existing build pipelines
  • patch-3882: name of your custom build action
  • patch: the patch command
  • -st: run silently, ignore errors, assume the best
  • -p1: convert git’s format to patch format by truncating the first level of directory from the patch file
  • -d: specify the working directory, in this case, react-native’s
  • < patchfile: pipe the patchfile into the patch command, this is not an input parameter

Gotchas:

  • don’t use “&&” to combine commands, use “;” if you are using Microsoft AppCenter to build your binaries. “&&” will give an “unexpected end of file” error due to the shell on the build agent

How to install nvm

This is a follow up to [How to install npm the right way]. It turns out that while convenient for Node development, nvm is notoriously slow. Thanks to reddit user sscotth we can solve that quite easily.

First, install nvm normally

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

Then find the lines nvm added to your .rc file (bashrc or zshrc), delete that shit

# export NVM_DIR="$HOME/.nvm"
# [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
# [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

Next, add this to your .rc file

declare -a NODE_GLOBALS=(`find ~/.nvm/versions/node -maxdepth 3 -type l -wholename '*/bin/*' | xargs -n1 basename | sort | uniq`)

NODE_GLOBALS+=("node")
NODE_GLOBALS+=("nvm")

load_nvm () {
    export NVM_DIR=~/.nvm
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
}

for cmd in "${NODE_GLOBALS[@]}"; do
    eval "${cmd}(){ unset -f ${NODE_GLOBALS}; load_nvm; ${cmd} \[email protected] }"
done

Your globally installed programs like create-react-app will still use the current version of node, while it only loads once and not boggle down your terminal startup everytime.

Win-win

Self-note: Resume / recover a MongoDB change stream

Introduction

  • Sometimes replicator needs to be restarted
  • We cannot afford to lose one or two entries in time-series since it would throw the statistics off, and in exceptional cases, lost the max and min value

Resume & recover 

Change streams are resumable by specifying a resumeAfter token when opening the cursor. For the resumeAfter token, use the _id value of the change stream event document. Passing the _id value to the change stream attempts to resume notifications starting after the specified operation.

IMPORTANT

In the example below, resumeToken contains the change stream notification id. The resumeAfter takes a parameter that must resolve to a resume token. Passing the resumeToken to the resumeAfter modifier directs the change stream to attempt to resume notifications starting after the operation specified.

copy
copied

If the featureCompatibilityVersion (fcv) is set to "4.0" or greater, newly opened change streams return a hex-encoded string for the resume token data, i.e. the _id._data value. This change allows for the ability to compare and sort the resume tokens. If the fcv is 3.6, newly opened change streams return a BinData for the resume token data.

IMPORTANT

The fcv value at the time of the cursor’s opening determine the resume token data type. That is, the modification of the fcv does not affect the resume tokens for change streams already opened before the fcv change.

Regardless of the fcv value, a 4.0 replica set or a sharded cluster can resume a change stream using either the BinData or string resume token.

As such, a 4.0 deployment can use a resume token from a change stream opened on a collection from a 3.6 deployment.

Implementation

Structure of a Mongo change event

{
   _id : { <BSON Object> },
   "operationType" : "<operation>",
   "fullDocument" : { <document> },
   "ns" : {
      "db" : "<database>",
      "coll" : "<collection"
   },
   "documentKey" : { "_id" : <ObjectId> },
   "updateDescription" : {
      "updatedFields" : { <document> },
      "removedFields" : [ "<field>", ... ]
   }
   "clusterTime" : <Timestamp>,
   "txnNumber" : <NumberLong>,
   "lsid" : {
      "id" : <UUID>,
      "uid" : <BinData>
   }
}

Pay attention to _id

Metadata related to the operation.

Use this document as a resumeToken for the resumeAfter parameter when resuming a change stream.

If the featureCompatibilityVersion (fcv) is set to "4.0" or greater, newly opened change streams return a hex-encoded string for the resume token data, i.e. the _id._data value. This change allows for the ability to compare and sort the resume tokens. If the fcv is 3.6, newly opened change streams return a BinData for the resume token data.

IMPORTANT

The fcv value at the time of the cursor’s opening determine the resume token data type. That is, the modification of the fcv does not affect the resume tokens for change streams already opened before the fcv change.

Regardless of the fcv value, a 4.0 replica set or a sharded cluster can resume a change stream using either the BinData or string resume token.

This field is BSON so it

  • Can’t be saved with JSON.stringify
  • Can’t be cast to ObjectID (wrong format, different than MongoDB documentation )

Solution: Include bson

const BSON = require(‘bson’);

function saveId(cb) {
if (lastId) {
let lastIdBuffer = bson.serialize(lastId);
fs.writeFile(ID_FILE, lastIdBuffer, (err) => {
if (err) {
logger.error(‘[saveId]’, err);
}
return cb && cb(err);
});
}
}



function loadId(cb) {
fs.readFile(ID_FILE, (err, data) => {
let buffer;
if (!err && data) {
buffer = bson.deserialize(data);
}
return cb && cb(err, buffer);
});
}

  • Every one second, write the latest _id to disk
  • Reload the _id object from disk on startup
  • Use it to resume the change stream with

const pipeline = [
{
$match: { ‘ns.db’: config.MONGO_DB_NAME },
}
];

let changeStreamOptions = {};

changeStreamOptions[‘resumeAfter’] = resumeToken;

const changeStream = db.watch(pipeline, changeStreamOptions);
changeStream.on(‘change’, (change) => {

}

Reference

db.watch(pipeline, options)

New in version 4.0: Requires featureCompatibilityVersion (fCV) set to "4.0" or greater. For more information on fCV, see setFeatureCompatibilityVersion.

Opens a change stream cursor for a database to report on all its non-system collections.

A sequence of one or more of the following aggregation stages:

See Aggregation for complete documentation on the aggregation framework.

Optional. Additional options that modify the behavior of db.watch().

You must pass an empty array [] to the pipeline parameter if you are not specifying a pipeline but are passing the options document.

The options document can contain the following fields and values:

Optional. Directs db.watch() to attempt resuming notifications starting after the operation specified in the resume token.

Each change stream event document includes a resume token as the _idfield. Pass the entire _id field of the change event document that represents the operation you want to resume after.

resumeAfter is mutually exclusive with startAtOperationTime.

Optional. By default, db.watch() returns the delta of those fields modified by an update operation, instead of the entire updated document.

Set fullDocument to "updateLookup" to direct db.watch() to look up the most current majority-committed version of the updated document. db.watch() returns a fullDocument field with the document lookup in addition to the updateDescription delta.

Optional. Specifies the maximum number of change events to return in each batch of the response from the MongoDB cluster.

Has the same functionality as cursor.batchSize().

Optional. The maximum amount of time in milliseconds the server waits for new data changes to report to the change stream cursor before returning an empty batch.

Defaults to 1000 milliseconds.

Optional. The starting point for the change stream. If the specified starting point is in the past, it must be in the time range of the oplog. To check the time range of the oplog, see rs.printReplicationInfo().

startAtOperationTime is mutually exclusive with resumeAfter.

SEE ALSO

db.collection.watch() and Mongo.watch()

To generate a new ObjectId, use ObjectId() with no argument:

copy
copied

In this example, the value of x would be:

copy
copied

To generate a new ObjectId using ObjectId() with a unique hexadecimal string:

copy
copied

In this example, the value of y would be:

copy
copied

Access the str attribute of an ObjectId() object, as follows:

copy
copied

This operation will return the following hexadecimal string:

copy
copied

Create a new ObjectID instance

class ObjectID()Arguments:id (string) – Can be a 24 byte hex string, 12 byte binary string or a Number.Returns:object instance of ObjectID.

Return the ObjectID id as a 24 byte hex string representation

toHexString()Returns:string return the 24 byte hex string representation.

Examples

Generate a 24 character hex string representation of the ObjectID

How to connect to MySQL / MariaDB using Node.JS (the right way)

Use a connection pool. It helps

  • Conserve resource, connections got recycled
  • Better reliability: it automatically reconnects when there’s a problem

How? Simple, instead of creating a connection, just create a pool. It’s designed as a drop in replacement for client.query()

var mysql = require('mysql');
var pool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'example.org',
  user            : 'bob',
  password        : 'secret',
  database        : 'my_db'
});

pool.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

is a shorthand for

var mysql = require('mysql');
var pool  = mysql.createPool(...);

pool.getConnection(function(err, connection) {
  if (err) throw err; // not connected!

  // Use the connection
  connection.query('SELECT something FROM sometable', function (error, results, fields) {
    // When done with the connection, release it.
    connection.release();

    // Handle error after the release.
    if (error) throw error;

    // Don't use the connection here, it has been returned to the pool.
  });
});

How to install Node.JS

“Isn’t it just simply googling ‘how to install Node'” you asked? Yes it isn’t.

  • The default Node installer will require administrator privilege
  • The node process will require admin privilege also if installed that way (which is a bad idea: every time you need to update node you must sudo)
  • You can’t easily switch node version if you work with multiple projects

Introducing nvm (node version manager), a script that installs node in your local user directory, requiring no sudo while compromising none of the quality

How to use it? According to Zoltan:

On Mac

The best way to install Node.js on Mac is nvm.

https://github.com/creationix/nvm

You have to have on your Mac the Command Line Tools. Or you install the full XCode from App Store either just use the small Command Line Tools installer:

$ xcode-select --install

(If you’ve just installed XCode, don’t forget to launch it first and accepting the Terms and Conditions.)

You can use the install script for nvm installation.

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

However, I would encourage you to use the manual installation process. Nothing special there. Firstly, you just clone the whole repo in a subfolder in your home directory. (~/.nvm) Secondly, you add two extra lines to your console script.

Please follow these steps on NVM Readme: https://github.com/creationix/nvm#git-install

You have to relaunch your Terminals. Maybe you have to log out and log back to activate the new settings.

List your installed node versions:

$ nvm list

List the available node versions in the cloud:

$ nvm ls-remote

You can use the combination of this two commands to see only the last 9 lines from the huge list of versions: $ nvm ls-remote | tail -n9

It is safe if you choose one of the most recent LTS (long time support) version and install it with the following command:

$ nvm install 10.3.0

Setup this version as the default.

$ nvm use 10.3.0
$ nvm alias default 10.3.0

Check your node version with

$ node -v

You should see v10.3.0 if you installed the above version.

You can update your npm to the latest.

$ npm install -g npm

After the update, the npm version, npm -v, should be at least 6.1.0 or above.

A little extra tip. Remember for the following command because it simplifies the update process. 😉

Let’s say, you would like to stay on the stable, LTS version and you would like to keep all the global package what you’ve already installed. Here is the solution:

$ nvm install 8 --reinstall-packages-from=8 --latest-npm

It updates your Node.js version to the latest version 8 and install the latest npm, plus it setup all your previously installed global packages.

Alternatives for installing Node.js, but not suggested:

On Linux

Please avoid to install Node.js with apt-get on Ubuntu. If you already installed Node.js with the built in package manager, please remove that. (sudo apt-get purge nodejs && sudo apt-get autoremove && sudo apt-get autoclean)

The installation process on Linux is the same as on OSX.

With the provided script:

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

(Please read the instructions under OSX section.)

$ nvm list
$ nvm ls-remote
$ nvm install 10.3.0
$ nvm use 10.3.0
$ nvm alias default 10.3.0
$ node -v
$ npm install -g npm
$ npm -v

One more thing! Don’t forget to run the following command, which increases the amount of inotify watches.

$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

On Windows

On Windows, if you don’t need more version from Node.js, you can use the official installer.

Install also Git for Windows.

Additionally, don’t forget to read this instruction, which is very interesting not just for Ember developers, but for everybody who uses Node.js on Windows.

Plus install and run ember-cli-windows

$ npm install -g ember-cli-windows
$ ember-cli-windows

More here: https://github.com/felixrieseberg/ember-cli-windows

Always run your PowerShell or CMD.exe as Administrator.

Don’t forget to run these two commands in PowerShell (as Administrator):

$ Set-ExecutionPolicy Unrestricted -scope Process
$ ember-cli-windows

Log out and log back in Windows.

Try to upgrade npm and after install the latest ember-cli

$ npm install -g npm
$ npm install -g ember-cli

I would suggest, experiment with different shells. Which worked better for you? PowerShell, Git Shell, the original CMD.exe? Please, share your Windows experiment in a comment.