├── .gitignore
├── .gitattributes
├── img
└── logo.png
├── jest.config.js
├── .travis.yml
├── tsconfig.json
├── LICENSE
├── package.json
├── README.md
├── src
└── index.ts
└── test
└── index.test.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .npmrc
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.config.js linguist-detectable=false
2 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedaviddelta/scope-extensions-js/HEAD/img/logo.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | globals: {
4 | "ts-jest": {
5 | tsConfig: {
6 | target: "ES6",
7 | strict: false,
8 | esModuleInterop: true
9 | }
10 | }
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - lts/*
5 |
6 | before_script:
7 | - npm run build
8 |
9 | script:
10 | - npm test
11 |
12 | deploy:
13 | - provider: npm
14 | email: $NPM_EMAIL
15 | api_token: $NPM_TOKEN
16 | skip_cleanup: true
17 | on:
18 | tags: true
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "declaration": true,
5 | "rootDir": "./src",
6 | "outDir": "./dist",
7 | "strict": true
8 | },
9 | "include": [
10 | "src/**/*.ts"
11 | ],
12 | "exclude": [
13 | "node_modules",
14 | "test/**/*.ts"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 TheDavidDelta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scope-extensions-js",
3 | "version": "1.1.0",
4 | "description": "Package for using Kotlin's Scope Function Extensions on JavaScript and TypeScript",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "build": "tsc",
9 | "test": "jest"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/TheDavidDelta/scope-extensions-js.git"
14 | },
15 | "keywords": [
16 | "kotlin",
17 | "scope",
18 | "functions",
19 | "extensions",
20 | "scoping",
21 | "let",
22 | "also",
23 | "run",
24 | "apply",
25 | "with"
26 | ],
27 | "author": "TheDavidDelta",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/TheDavidDelta/scope-extensions-js/issues"
31 | },
32 | "homepage": "https://github.com/TheDavidDelta/scope-extensions-js#readme",
33 | "devDependencies": {
34 | "typescript": "^3.8.3",
35 | "jest": "^23.6.0",
36 | "@types/jest": "^23.3.14",
37 | "ts-jest": "^23.10.5",
38 | "braces": ">=2.3.1"
39 | },
40 | "files": [
41 | "dist/**/*",
42 | "img/logo.png"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scope-extensions-js
2 |
3 |
4 |
5 | [](https://travis-ci.com/TheDavidDelta/scope-extensions-js)
6 | [](https://www.npmjs.com/package/scope-extensions-js)
7 | [](./LICENSE)
8 |
9 | Package for using [Kotlin's Scope Function Extensions](https://kotlinlang.org/docs/reference/scope-functions.html) on JavaScript and TypeScript.
10 |
11 | It also supports the use of the new [Optional Chaining Operator](https://github.com/tc39/proposal-optional-chaining), bringing the logic of [Kotlin's Null Safe Calls](https://kotlinlang.org/docs/reference/null-safety.html) to the JavaScript world.
12 |
13 | ## Installation
14 |
15 | Just install the package using NPM
16 |
17 | ```shell
18 | npm i --save scope-extensions-js
19 | ```
20 |
21 | and import it directly to any file.
22 |
23 | ```javascript
24 | require("scope-extensions-js");
25 | ```
26 |
27 | You can also use ES6 syntax.
28 |
29 | ```javascript
30 | import "scope-extensions-js";
31 | ```
32 |
33 | For browser, reference directly to `node_modules` path
34 |
35 | ```html
36 |
37 | ```
38 |
39 | or use it without installation by CDNs (`unpkg`/`jsdelivr`).
40 |
41 | ```html
42 |
43 | ```
44 |
45 | ```html
46 |
47 | ```
48 |
49 | Note that the `type="module"` tag is not needed.
50 |
51 | ## Usage
52 |
53 | Simply call any value with `let`, `also`, `run` or `apply` and it'll be passed as the argument or the context of a scope function.
54 |
55 | ```typescript
56 | const obj = { name: "Daniel", age: 30 };
57 |
58 | obj.let(it => {
59 | return it.age < 0 ? it.age : 0;
60 | }).also(it => {
61 | console.log(it);
62 | }); // prints 30
63 | ```
64 |
65 | This way, you can execute a block of code only if a value is neither null nor undefined.
66 |
67 | ```typescript
68 | const str: string | null = await getData();
69 |
70 | // later
71 | str?.also(it => {
72 | console.log(`Already initialized: ${it}`);
73 | }) ?? console.log("Still not initialized");
74 | ```
75 |
76 | The above code is equivalent to this
77 |
78 | ```typescript
79 | if (str != null && str != undefined)
80 | console.log(`Already initialized: ${str!}`);
81 | else
82 | console.log("Still not initialized");
83 | ```
84 |
85 | The usage of `takeIf` & `takeUnless` is a bit different. You can call any value with `takeIf` and it will return the caller instance if the predicate is `true`, or `undefined` if it's `false` (and vice versa when using `takeUnless`).
86 |
87 | ```typescript
88 | const account = await getCurrent();
89 |
90 | account.takeIf(it => {
91 | return list.includes(it.id);
92 | })?.also(it => {
93 | console.log(it);
94 | }) ?? console.log("Not included");
95 | ```
96 |
97 | ## Differences
98 |
99 | We could group the 4 main extensions into 2 groups of 2 each, based on both the argument type and the return value:
100 | + `let` & `also` receive the caller instance as a function parameter, and `run` & `apply` receive the caller instance as the function context (`this`).
101 | + `let` & `run` return the function result (`return`) value, and `also` & `apply` return the caller instance (`this`).
102 |
103 | Summed up in this table:
104 |
105 | | | **`it` argument** | **`this` context** |
106 | |--------------------|:-----------------:|:------------------:|
107 | | **Returns result** | `let` | `run` |
108 | | **Returns `this`** | `also` | `apply` |
109 |
110 |
111 | Note that `let` & `also` can be called with standard lambda/arrow functions, but because JavaScript arrow functions don't have an own `this` context, `run` & `apply` have to be called with standard functions.
112 |
113 | Here is an example of each one of them:
114 | + `let`
115 | ```typescript
116 | const data: Array | null = await idsFromFile();
117 |
118 | const str = data?.let(it =>
119 | processToString(it);
120 | ) ?? "empty";
121 | ```
122 | + `also`
123 | ```typescript
124 | const list: Array = model.getNames();
125 |
126 | const filtered = list.also(it =>
127 | it.slice(0, 4);
128 | ).also(it =>
129 | applyFilter(filter, it);
130 | ).also(console.log);
131 |
132 | // same as
133 | const filtered = list.also(it => {
134 | it.slice(0, 4);
135 | applyFilter(filter, it);
136 | console.log(it);
137 | });
138 | ```
139 | + `run`
140 | ```typescript
141 | const list: Array