diff --git a/.github/workflows/npm_publish_light.yml b/.github/workflows/npm_publish_light.yml
index 93b87820..61f96e49 100644
--- a/.github/workflows/npm_publish_light.yml
+++ b/.github/workflows/npm_publish_light.yml
@@ -4,14 +4,19 @@
# could build it locally.
# Performs a basic build test by installing "cohort_sdk_client" into
# "cohort_banking_initiator_js", "cohort_banking_replicator_js" apps and then builds apps.
+# If tests building of initiator and replicator fails then SDK version is removed from NPM.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
name: Build and Publish Cohort SDK NPMs
on:
workflow_dispatch:
env:
SDK_JS_PACKAGE_NAME: "@kindredgroup/cohort_sdk_js"
+ SDK_JS_PACKAGE_NAME_SHORT: "cohort_sdk_js"
SDK_CLIENT_PACKAGE_NAME: "@kindredgroup/cohort_sdk_client"
+ SDK_CLIENT_PACKAGE_NAME_SHORT: "cohort_sdk_client"
+ REGISTRY: "https://npm.pkg.github.com"
+ API_URL: "https://api.github.com/orgs/kindredgroup/packages/npm"
+ API_VERSION: "2022-11-28"
jobs:
npm:
strategy:
@@ -52,7 +57,7 @@ jobs:
pwd
ls -l
npm ci --foreground-scripts
- ../../scripts/github-actions-ci/set-npm-dev-version.sh ${{ env.SDK_JS_PACKAGE_NAME }} "SDK_JS_PACKAGE_VERSION"
+ ../../scripts/cohort/github-actions-ci/set-npm-dev-version.sh ${{ env.SDK_JS_PACKAGE_NAME }} "SDK_JS_PACKAGE_VERSION"
echo ""
- name: Build and Publish ${{ matrix.settings.dirSdkJs }}
@@ -82,7 +87,7 @@ jobs:
rm package-lock.json || true
echo "D: npm install $SDK_JS_PACKAGE_NAME@$SDK_JS_PACKAGE_VERSION --foreground-scripts"
npm install "$SDK_JS_PACKAGE_NAME@$SDK_JS_PACKAGE_VERSION" --foreground-scripts
- ../scripts/github-actions-ci/set-npm-dev-version.sh ${{ env.SDK_CLIENT_PACKAGE_NAME }} "SDK_CLIENT_PACKAGE_VERSION"
+ ../scripts/cohort/github-actions-ci/set-npm-dev-version.sh ${{ env.SDK_CLIENT_PACKAGE_NAME }} "SDK_CLIENT_PACKAGE_VERSION"
echo ""
- name: Build and Publish ${{ matrix.settings.dirSdkJsClient }} NPM
@@ -115,6 +120,23 @@ jobs:
npm install "$SDK_CLIENT_PACKAGE_NAME@$SDK_CLIENT_PACKAGE_VERSION" --foreground-scripts
npm run build --foreground-scripts
+ - name: Unublish SDK NPMs on failure
+ if: ${{ failure() }}
+ shell: bash
+ env:
+ SDK_JS_PACKAGE_VERSION: ${{ steps.sdk-js-version.outputs.SDK_JS_PACKAGE_VERSION }}
+ SDK_CLIENT_PACKAGE_VERSION: ${{ steps.sdk-client-version.outputs.SDK_CLIENT_PACKAGE_VERSION }}
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |-
+ currentDir=$(pwd)
+ cd ${{ matrix.settings.dirSdkJsClient }}
+ ../scripts/cohort/github-actions-ci/unpublish-sdk-version.sh "$SDK_CLIENT_PACKAGE_NAME_SHORT" "$SDK_CLIENT_PACKAGE_VERSION"
+ echo ""
+ cd $currentDir
+
+ cd ${{ matrix.settings.dirSdkJs }}
+ ../../scripts/cohort/github-actions-ci/unpublish-sdk-version.sh "$SDK_JS_PACKAGE_NAME_SHORT" "$SDK_JS_PACKAGE_VERSION"
+
- name: Build ${{ matrix.settings.dirCohortReplicator }}
env:
SDK_CLIENT_PACKAGE_VERSION: ${{ steps.sdk-client-version.outputs.SDK_CLIENT_PACKAGE_VERSION }}
@@ -128,4 +150,21 @@ jobs:
rm package-lock.json || true
echo "D: npm install $SDK_CLIENT_PACKAGE_NAME@$SDK_CLIENT_PACKAGE_VERSION --foreground-scripts"
npm install "$SDK_CLIENT_PACKAGE_NAME@$SDK_CLIENT_PACKAGE_VERSION" --foreground-scripts
- npm run build --foreground-scripts
\ No newline at end of file
+ npm run build --foreground-scripts
+
+ - name: Unublish SDK NPMs on failure
+ if: ${{ failure() }}
+ shell: bash
+ env:
+ SDK_JS_PACKAGE_VERSION: ${{ steps.sdk-js-version.outputs.SDK_JS_PACKAGE_VERSION }}
+ SDK_CLIENT_PACKAGE_VERSION: ${{ steps.sdk-client-version.outputs.SDK_CLIENT_PACKAGE_VERSION }}
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |-
+ currentDir=$(pwd)
+ cd ${{ matrix.settings.dirSdkJsClient }}
+ ../scripts/cohort/github-actions-ci/unpublish-sdk-version.sh "$SDK_CLIENT_PACKAGE_NAME_SHORT" "$SDK_CLIENT_PACKAGE_VERSION"
+ echo ""
+ cd $currentDir
+
+ cd ${{ matrix.settings.dirSdkJs }}
+ ../../scripts/cohort/github-actions-ci/unpublish-sdk-version.sh "$SDK_JS_PACKAGE_NAME_SHORT" "$SDK_JS_PACKAGE_VERSION"
\ No newline at end of file
diff --git a/cohort_banking_initiator_js/README.md b/cohort_banking_initiator_js/README.md
index 5ce97f59..1f108f5d 100644
--- a/cohort_banking_initiator_js/README.md
+++ b/cohort_banking_initiator_js/README.md
@@ -26,6 +26,9 @@ Cohort Initiator JS is TypeScript app. When launched it connects to Postgres dat
### Setup your machine
#### Install development ecosystem for Rust
+
+This step is curently required but in the future we might be able to optimise build process and remove it. In the readme of "packages/cohort_sdk_js", please read more why it is compiled locally on user machine.
+
Refer to https://www.rust-lang.org/tools/install
Check:
@@ -39,6 +42,8 @@ info: This is the version for the rustup toolchain manager, not the rustc compil
info: The currently active `rustc` version is `rustc 1.72.0 (5680fa18f 2023-08-23)`
```
+For reader convenience this step is also described in the `../cohort_sdk_client` readme. When making changes to install procedure, please update another location as well.
+
#### Start Postgres server
```
@@ -64,7 +69,7 @@ Please refer to Talos Certifier readme.
#### Generate data file
-Generate data JSON file containing some number of random banking transactions.
+Generate data JSON file containing some number of bank accounts.
```
cd $TALOS/
@@ -91,25 +96,24 @@ make withenv RECIPE=cohort_banking.migrate_db
make withenv RECIPE=cohort_banking.preload_db args="--accounts $accounts_file"
```
-
### Building Cohort Replicator and Initiator apps
These sample apps depend on `@kindredgroup/cohort_sdk_client` package. It is hosted at [GitHub Packages](https://npm.pkg.github.com) repository.
#### Authenticate to repository GitHub Packages
-TODO: (explain where to obtain token)
```
-touch .npmrc-talos
-echo "@kindredgroup:registry=https://npm.pkg.github.com" >> .npmrc-talos
-NPM_CONFIG_USERCONFIG=.npmrc-talos
-npm login --registry https://npm.pkg.github.com
+touch $(pwd)/.npmrc-talos
+echo "@kindredgroup:registry=https://npm.pkg.github.com" >> $(pwd)/.npmrc-talos
+
+# Using this method the ".npmrc-talos" file will be updated with your token. Do not commit the file!
+NPM_CONFIG_USERCONFIG=$(pwd)/.npmrc-talos npm login --registry https://npm.pkg.github.com
# password is your GitHub token
# Obtain token at GitHub website Profile -> Settings -> Developer settings -> Personal access tokens -> Tokens (classic)
```
#### For local development
-This section describes how to install cohort client SDK for local development of cohort.
+This section describes how to install cohort client SDK for local development of cohort (or for running examples locally).
In the Talos project at GitHub, under ["Packages"](https://github.com/orgs/kindredgroup/packages?repo_name=talos) find `cohort_sdk_client` and the version number you need to install locally.
diff --git a/cohort_sdk_client/README.md b/cohort_sdk_client/README.md
index c8f3d1f2..ba398895 100644
--- a/cohort_sdk_client/README.md
+++ b/cohort_sdk_client/README.md
@@ -1,4 +1,4 @@
-# The JavaScript binding for Cohort SDK
+JsDecision# The JavaScript binding for Cohort SDK
## About
This module contains JavaScript SDK for creating Talos Cohort Initiator or Replicator applications in JavaScript. It is bundled as NPM package and available in [GitHub Packages NPM repository](https://github.com/orgs/kindredgroup/packages?repo_name=talos).
@@ -15,6 +15,38 @@ The example of SDK usage can be found in `cohort_banking_initiator_js` and in `c
The SDK allows users to create certification transaction initiator and certification transaction replicator in JavaScript either as a single app or as two separate applications.
+### Installing
+As a prerequisite for installing this package we require that Rust development environment is setup on the user machine. The main logic of SDK is implemented in `../packages/cohort_sdk`. In order for this code to be available to JavaScript in `@kindredgroup/cohort_sdk_js` it must be compiled into the native library for client architecture. There are two ways to achieve this:
+
+1. On CI. When making NPM package `@kindredgroup/cohort_sdk_js` we configure build process so that it produces NPM with multiple optional dependencies each for dedicated architecture, such as for Linux, for ARM64 Mac, for Intel Mac and etc. This approach requires different build environments on CI. Most of our developers use ARM64 Mac, while CI does not have build image for it. We are looking for solution. In the meanwhile, please refer to option #2.
+2. On the user machine. When installing `@kindredgroup/cohort_sdk_js` NPM as dependency also install native sources and compile native them locally. Then bundle compiled native lib in `@kindredgroup/cohort_sdk_js`. As such, for prerequisites of installing this package we require that Rust development environment is setup on the user machine.
+
+#### Install development ecosystem for Rust
+
+This step is curently required but in the future we might be able to optimise build process and remove it.
+
+Refer to https://www.rust-lang.org/tools/install
+
+Check:
+```
+cargo --version && rustc --version && rustup --version
+
+cargo 1.72.0 (103a7ff2e 2023-08-15)
+rustc 1.72.0 (5680fa18f 2023-08-23)
+rustup 1.26.0 (5af9b9484 2023-04-05)
+info: This is the version for the rustup toolchain manager, not the rustc compiler.
+info: The currently active `rustc` version is `rustc 1.72.0 (5680fa18f 2023-08-23)`
+```
+
+#### Install package
+```
+npm -i @kindredgroup/cohort_sdk_client@SOME_VERSION --foreground-scripts
+```
+
+The above step will download `cohort_sdk_client` and `cohort_sdk_js` dependencies. The `cohort_sdk_js` dependency will come with Rust code and native comilation will begin. Once finished, we will have `@kindredgroup/cohort_sdk_js` and `@kindredgroup/cohort_sdk_client` under `./node_modules/`
+
+For reader convenience this step is also described in the `../cohort_banking_initiator_js` readme. When making changes to install procedure, please update another location as well.
+
# The Initiator API
The role of the Initiator is to validate transactions locally, submit updates for certification and apply submitted updates out-of-order.
@@ -34,7 +66,7 @@ const fnOooInstaller = async (...) => {
const response = await initiator.certify(fnMakeNewRequestCallback, fnOooInstaller)
-if (response.decision === Decision.Committed) {
+if (response.decision === JsDecision.Committed) {
// do something
}
@@ -82,39 +114,31 @@ Before SDK can issue a certification request to Talos Certifier it needs some de
1. Identifiers and version numbers of all objects involved in your transaction. These are known as `readset`, `writeset` and `readvers`.
2. The copy of your transaction as one serializable object. It makes sense to describe your transaction as JSON object and serialise it to string. This is known as `statemap`.
-Above mentioned reads, writes and statemap fields together are known as certification candidate details. Talos and SDK does not use statemap. The statemap is given back to you along with the response to your certification request. When you receive response, you will use details encoded in statemap to understand what transaction has been certified just now and what to do next. In the example app, which simulates a banking transaction to transfer money between two accounts, we define the statemap as action with some parameters:
+Above mentioned reads, writes and statemap fields together are known as certification candidate details. You may ask whether statemap is optional? Indeed, as you are passing the arrow function to `fnOooInstaller` callback you have the context of your request. From the perspective of Initiator app, the answer is "yes, it is optional". However, the statemap will also be received by your Replicator app. Replicator may be implemented as a separate process. Replicator will know what needs to be updated in the database by reading statemap.
-```json
-[
- {
- "TRANSFER": {
- "from": "account 1",
- "to": "account 2",
- "amount": "100.00"
- }
- }
-]
-```
-This is free form object. When Talos certifies the transaction, we know that the request to transfer 100.00 from account1 to account2 can go ahead, as installer function has all the necessary details to implement this transfer.
-You may ask whether statemap is optional? Indeed, as you are passing the arrow function to `fnOooInstaller` callback you have the context of your request. So, from the perspective of Initiator app, the answer is "yes, it is optional". However, the statemap will also be received by your Replicator app. Replicator may be implemented as a separate process but it still needs access to the statemap.
+Read about `statemap` in the end of this document. See section "About Statemap".
### About "JsCertificationRequestPayload"
-Talos is making a certification decision purely based on two major pieces of data. 1) the decisions it made to previous requests issued by you or by other cohorts 2) the state of objects in your database. In order to have a full picture, Talos Certifier needs to know not only _the state of your objects taking part in the current transaction_ but also the _global state of your cohort_. The global state is known as the snapshot version. So, you need to pass your objects (candidate) and your current global state (snapshot). Check this structure `JsCertificationRequestPayload`.
+Talos is making a certification decision purely based on two major pieces of data. 1) the decisions it made to previous requests issued by you or by other cohorts 2) the state of objects in your database. In order to have a full picture, Talos Certifier needs to know not only _the state of your objects taking part in the current transaction_ but also the _global state of your cohort_. The global state is known as the snapshot version. You need to pass your objects (candidate) and your current global state (snapshot). Check this structure `JsCertificationRequestPayload`.
-The response to your certification request will be asynchronously received by SDK. However, the async aspect of it is hidden from you for simplicity of usage. Your call to `await .certify(...)` will block until request is available or until it times out. The optional value of `JsCertificationRequestPayload.timeoutMs` attribute allows you to specify how long you are willing to wait for that specific response. If not provided, then value will be taken from `JsInitiatorConfig.timeoutMs`.
+The response to your certification request will be asynchronously received by SDK. However, the async aspect of it is hidden from you for simplicity of usage. Your call to `await .certify(...)` will block until request is available or until it times out. Use the optional config `JsCertificationRequestPayload.timeoutMs` attribute to specify how long you are willing to wait for that specific response. If not provided, then SDK will use value from `JsInitiatorConfig.timeoutMs`.
### About "JsCertificationCandidateCallbackResponse"
What happens when you need to certify some transaction but:
-- the data you have passed to Talos resulted in abort decision because it was outdated?
-- there was a short network disruption communicating with either Kafka or your local DB (during execution of "New Request Callback")?
+- The data you have passed to Talos resulted in abort decision because it was outdated?
+- There was a short network disruption communicating with either Kafka or your local DB (during execution of "New Request Callback")?
-Most likely you will want to retry your request. The SDK implements retry with incremental random backoff logic. It is configurable via `JsInitiatorConfig.retry*` settings. Upon failure of "current" attempt the "New Request Callback" which you have provided to `.certify(here, ...)` will be invoked again (after some short delay). As your database is used in the concurrent fashion by multiple instances of your cohort and by other processes in your application, it is very likely that state of your objects or the global state changed while SDK was processing this current attempt. So, the retry mechanism inside SDK will make sure that request will be re-issued, however, you, as cohort developer, should make sure that you are providing the latest information about your state to Talos. So, once attempt to certify fails, your "New Request Callback" will be invoked again, and you will load a new fresh state from DB. By default, this "invoke Talos -> fail -> sleep -> reload -> invoke Talos" loop may go on until we got successful response from Talos, until we timed out or until we exhausted max number of attempts. The SDK allows you to tap in and cancel a certification request by providing any reason in `JsCertificationCandidateCallbackResponse.cancellationReason`. Why would you want to cancel the transaction? It depends on the state of your objects, what if object was deleted from DB or became a read-only etc. It depends on your application.
+Most likely you will want to retry your request. The SDK implements retry with incremental random backoff logic. It is configurable via `JsInitiatorConfig.retry*` settings. Upon failure of "current" attempt the SDK will sleep a little and invoke "New Request Callback" again. Remember that you have provided this callback `.certify(here, ...)`. As your database is used in the concurrent fashion by multiple instances of your cohort and by other processes in your application, it is very likely that state of your objects or the global state changed while SDK was processing this current attempt. So, the retry logic inside SDK will make sure that request will be re-issued, however, you, as cohort developer, should make sure that you are providing the latest information about your state to Talos. So, once attempt to certify fails, SDK will enter into re-try - sleep loop. On every loop iteration your "New Request Callback" will be invoked again, and you will load a new fresh state from DB. By default, this "invoke Talos -> fail -> sleep -> reload -> invoke Talos" loop may go on until a) we've got successful response from Talos, b) until we timed out or c) until we exhausted max number of attempts. The SDK allows you to break off that loop and cancel a certification request by providing any reason in `JsCertificationCandidateCallbackResponse.cancellationReason`. Why would you want to cancel the transaction? It depends on the state of your objects. What if object was deleted from DB or it became a read-only etc. It depends on your application.
## About "Out of Order Install Callback"
-If your business transaction requires a certification from Talos, it is expected that you will not do any changes to objects taking part in your transaction (you will not update database records) until the decision is received from Talos. Only after certification decision is received you will proceed with business transaction. Typically, this is going to be some database update, for example, you will update balances of relevant bank accounts, hence "transfer" money between them. This step is done inside "Out of Order Install Callback". SDK will invoke this callback only when Talos approved your transaction, in other words, when Talos checks that there are no conflicting requests to update your objects.
+If your business transaction requires a certification from Talos, it is expected that you will not do any changes to objects taking part in your transaction (you will not update database records) until the decision is received from Talos. Only after certification decision is received you will proceed with business transaction. Typically, this is going to be some database update, for example, you will update balances of relevant bank accounts, hence "transfer" money between them. This step is done inside "Out of Order Install Callback". SDK will invoke this callback only when Talos approved your transaction, in other words, when Talos checks that there are no conflicting requests to update your objects.
+
+What is the benefit of having out of order callback if its responsibility overlaps with "Statemap Installer Callback" found in Replicator?
+You may wish not to implement this callback and rely on Replicator to do all your DB changes. Just keep in mind that Replicator will do it "later". How much later will depends on the overall load on the replicator and other dependent transactions which are still in-flight. If you did not implement out of order callback then it is possible to finish the call to `let response = await initiator.certify(...)`, have "go ahead" decision from Talos in the response variable, but your DB will not see this change. If, at that point, you returned response to user via HTTP and user went to query DB via another endpoint, it could be that user will not see the change yet (Replicator may still be processing the backlog of other transactions). On the other hand, with out of order callback in place, once the call to `let response = await initiator.certify(...)` finished, your DB is already updated and you may rely on that change in your following logic.
+
The shape of the callback function looks like this:
```TypeScript
@@ -140,7 +164,7 @@ export interface OutOfOrderRequest {
```
- `OutOfOrderRequest.xid` - the unique ID of certification transaction attempt
-- `newVersion` - when certified your objects state should be set to this new version. IF YOUR OBJECTS ARE ALREADY ON VERSION BIGGER OR EQUAL TO THIS, YOU SHOULD NOT UPDATE DB AT ALL. (In other words - if your objects were updated by some other transaction then you are risking overriding these changes, hence you should not update your objects.)
+- `newVersion` - when transaction is certified, the state of all objects from this transaction should be set to the new version. IF YOUR OBJECTS ARE ALREADY ON VERSION BIGGER OR EQUAL TO THIS, YOU SHOULD NOT UPDATE DB AT ALL. (In other words - if your objects were updated by some other transaction then you are risking overriding these changes, hence you should not update your objects.) In the example app, we use `SQL WHERE` filter: `UDPATE ... SET version = $newVersion WHERE version < $newVersion AND ...`
- `safepoint` - it is expected that your global state did not move past this version. IF YOUR COHORT SNAPSHOT IS SMALLER THAN THIS, YOU SHOULD NOT UPDATE DB AT ALL (In other words - if your global state has not reached the point when it is safe to update it, then no updates should be made.)
What response to return from "Out of Order Install Callback"?
@@ -257,7 +281,7 @@ The callback has the shape of simple getter function which returns the "global s
async (): Promise
```
- It receives no parameters.
-- It returns the snapshot version as number
+- It returns the snapshot version as number.
This callback is invoked only once during startup of Replicator server. It fetches current snapshot. Replicator uses current snapshot version as a point in time marker which tells Replictor not to react to any messages older than that version.
@@ -268,7 +292,7 @@ This statemap installer callback looks like this:
async (data: JsStatemapAndSnapshot): Promise
```
- It receives `JsStatemapAndSnapshot`
-- There is no return value, if function completes successfully SDK will proceed to handling the next transaction.
+- There is no return value, if function completes successfully SDK will proceed to the next transaction.
```TypeScript
export interface JsStatemapAndSnapshot {
@@ -297,9 +321,28 @@ However, there are few rules which Talos protocol expects you to implement.
- When callback is invoked, it is possible that your business objects are already updated. In this case, the job of callback is to update the snapshot table only.
- This may happen if replicator and initiator belong to the same cohort, for example, out of order installer in initiator may have executed and updated our business objects before the replicator. However, installer should never write to snapshot table.
- When replicator belong to different cohort, it is just catching up on the changes made by other cohorts, hence it may not encounter the state when business objects are updated already. Unless there was some contingency, like unexpected restart.
-- When updating business objects, also update their versions so that versions match with snapshot version.
+- When updating business objects, also update their versions so that versions match with snapshot version. Also version should only be incremented.
-What value to write into snapshot table? Use this attribute: `JsStatemapAndSnapshot.version`
+What value to write into snapshot table? Use this attribute: `JsStatemapAndSnapshot.version`
What version to set into business objects? Use this attribute: `JsStatemapAndSnapshot.version`
-## About Statemap
\ No newline at end of file
+## About Statemap
+
+The `statemap` is a reserved attribute of certification request. This attribute is not being interpreted by Talos or by SDK. It is reserved to pass around the context of transaction between request and response. You give this data to SDK with request and SDK will presents it back to your Initiator and to your Replicator callbacks when handling "go ahead" decision from Talos. FYI: The statemap data is part of Candidate message only. Replicator gets it by listening to request messages in the certification topic. SDK hides these details internally.
+
+Talos and SDK do not read statemap. The statemap is given back to you in the callback after certification decision is made. You will use details encoded in statemap to understand what transaction has been certified just now and what to do next. In the example app, which simulates a banking transaction to transfer money between two accounts, we define the statemap as action with some parameters:
+
+```json
+[
+ {
+ "TRANSFER": {
+ "from": "account 1",
+ "to": "account 2",
+ "amount": "100.00"
+ }
+ }
+]
+```
+This is free form object. When Talos certifies the transaction, we know that the request to transfer 100.00 from account1 to account2 can go ahead, so installer function has all the necessary details to implement this transfer.
+
+We find it easier to understand the statemap by comparing it to JSON request sent as HTTP POST. For example, when REST API client is invoking HTTP POST it has no knowledge how this request will be handled on the server side. The POST payload usually contains all the details needed to express the intention of user. Both the client and the server agree on the structure of JSON. Similar concept is applicable between Initiator and Replicator. The Initiator makes no assumption how many cohorts are present in the deployment, what are their DB schemas etc. Initiator only knows that statemap will be delivered to all cohorts and these cohorts may choose to update their internal databases. That means all cohorts participating in the transaction should know how to parse statemap. The "schema" of the statemap is meant to be agreed and understood by cohorts only and not by Talos.
\ No newline at end of file
diff --git a/packages/cohort_sdk_js/README.md b/packages/cohort_sdk_js/README.md
index 8d8ab50b..4f7da5f7 100644
--- a/packages/cohort_sdk_js/README.md
+++ b/packages/cohort_sdk_js/README.md
@@ -1 +1,16 @@
-# The internal JavaScript binding for Cohort SDK
\ No newline at end of file
+# The internal JavaScript binding for Cohort SDK
+
+The "cohort_sdk_js" module is a bridge between Rust and JavaScript utilising Node-API - an API for building native Addons. In our case the addon is written in Rust. So, the actual Talos Cohort SDK can be found in `packages/cohort_sdk` package. The `cohort_sdk_js` should be considered an internal project as it is not designed to be consumed directly by JS apps. Applications should use `cohort_sdk_client`.
+
+When publishing it is being packaged as __incomplete__ NPM module and published to GitHub Packages NPM repository. Currently, the packaging process is bundling together Rust source code of `../packages/cohort_sdk` implementation along with its project-level dependencies.
+
+The content of NPM module published to repository is missing `index.js`, `index.d.ts` and _native library_ with SDK implementation. All these missig files are generated by [NAPI-RS](https://napi.rs/) during **package install time**.
+
+When `cohort_sdk_js` NPM package is installed via `npm -i` the "postinstall" hook is invoking `cohort_sdk_js/scripts/postinstall.sh` which in turn compiles Rust code for your architecture, then generates JS code and moves files to their indended destinations so that we end up with finished working NPM package. All this happens on the client machine under `./node_modules/@kindredgroup/cohort_sdk_js`. The build process is verbose if installation is invoked with additional flag: `npm -i @kindredgroup/cohort_sdk_js@SOME_VERSION --forground-scripts`, otherwise native compilation happens in the background.
+
+As such, for prerequisites of installing this package we require that Rust development environment is setup on the user machine.
+
+The procedure is explained in the `../cohort_sdk_client` read me.
+
+
+
diff --git a/scripts/github-actions-ci/set-npm-dev-version.sh b/scripts/cohort/github-actions-ci/set-npm-dev-version.sh
similarity index 100%
rename from scripts/github-actions-ci/set-npm-dev-version.sh
rename to scripts/cohort/github-actions-ci/set-npm-dev-version.sh
diff --git a/scripts/cohort/github-actions-ci/unpublish-sdk-version.sh b/scripts/cohort/github-actions-ci/unpublish-sdk-version.sh
new file mode 100755
index 00000000..e7bfb9db
--- /dev/null
+++ b/scripts/cohort/github-actions-ci/unpublish-sdk-version.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# This script is designed to be invoked from GitHub Actions CI only.
+#
+# Unpublishes given NPM from GitHub Packages NPM repository. Since simple npm unpublish
+# is not supported by GitHub Packages we use "DELETE ../versions/$id" function
+# from GitHub REST API
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+echo Executing "$0"
+
+packageName=$1
+versionName=$2
+
+if [ "$packageName" == "" ]; then
+ echo "Missing the first parameter - package name to be unpublished"
+ exit 1
+fi
+
+if [ "$versionName" == "" ]; then
+ echo "Missing the second parameter - version name to be unpublished"
+ exit 1
+fi
+
+unpublishVersion="$packageName@$versionName"
+
+echo "Current directory:"
+pwd
+ls -l
+echo "Current version is"
+npm version
+echo "Unpublishing faulty version: $unpublishVersion"
+allVersionsArray=$(curl -L -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $NODE_AUTH_TOKEN" -H "X-GitHub-Api-Version: $API_VERSION" "$API_URL/$packageName/versions")
+echo $allVersionsArray > ./tmp_all_versions.json
+lookupPackageVersionId=$(echo "$allVersionsArray" | jq --arg versionName "$versionName" '.[] | select(.name == $versionName) | .id')
+if [ "$lookupPackageVersionId" == "" ];
+then
+ echo "E: Unable to find package version id"
+ echo "D: allVersionsArray="
+ cat ./tmp_all_versions.json
+
+ # echo ""
+ # echo "D: echo allVersionsArray | yq .[]"
+ # echo "$allVersionsArray" | jq '.[]'
+ # echo ""
+ # echo "D: versionName=$versionName"
+ # echo "D: echo allVersionsArray | yq .[] | select(.name == versionName)"
+ # echo "$allVersionsArray" | jq --arg versionName "$versionName" '.[] | select(.name == $versionName)'
+ # echo ""
+ # echo "D: full jq command result"
+ # echo "$allVersionsArray" | jq --arg versionName "$versionName" '.[] | select(.name == $versionName) | .id'
+
+ rm ./tmp_all_versions.json
+ echo ""
+ echo "D: curl command used:"
+ echo "D: curl -L -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer ...' -H 'X-GitHub-Api-Version: $API_VERSION' $API_URL/$packageName/versions"
+else
+ echo "Deleting $lookupPackageVersionId of $unpublishVersion"
+ echo "D: curl -L -X DELETE -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer ...' -H 'X-GitHub-Api-Version: $API_VERSION' $API_URL/$packageName/versions/$lookupPackageVersionId"
+ curl -L -X DELETE -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $NODE_AUTH_TOKEN" -H "X-GitHub-Api-Version: $API_VERSION" "$API_URL/$packageName/versions/$lookupPackageVersionId"
+fi
\ No newline at end of file