diff --git a/.vscode/launch.json b/.vscode/launch.json index 4098572..27193ed 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "node", "request": "launch", "name": "Launch Program", - "program": "${workspaceFolder}\\dist\\out-tsc\\index.js", + "program": "${workspaceFolder}\\dist\\out-tsc\\demo\\test.js", "outFiles": [ "${workspaceFolder}/**/*.js" ] diff --git a/README.md b/README.md index 4911ea1..48b18d3 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,9 @@ main(); - [x] from - [x] where - [x] select -- [ ] selectMany +- [x] selectMany - [x] join +- [x] leftJoin - [ ] groupJoin - [ ] orderBy - [ ] orderByDescending diff --git a/package-lock.json b/package-lock.json index edb98a1..00d6794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.2.tgz", + "integrity": "sha512-D8uQwKYUw2KESkorZ27ykzXgvkDJYXVEihGklgfp5I4HUP8D6IxtcdLTMB1emjQiWzV7WZ5ihm1cxIzVwjoleQ==", + "dev": true + }, "@types/events": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", @@ -60,6 +66,15 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/mocha": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.1.0.tgz", + "integrity": "sha512-FHnScYtV66SQBo8o2bdU83atlSorfCX2ZYluk2VqI4ZaL0bkVEwuMZL5pHLRgJOTZ7vv24x4nH3gljdRD6zsSQ==", + "dev": true, + "requires": { + "@types/node": "9.6.5" + } + }, "@types/node": { "version": "9.6.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.5.tgz", @@ -93,6 +108,27 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -115,6 +151,12 @@ "concat-map": "0.0.1" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", @@ -133,6 +175,54 @@ "lazy-cache": "1.0.4" } }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.8" + } + }, + "chalk": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -154,12 +244,42 @@ } } }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -167,6 +287,27 @@ "dev": true, "optional": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, "fs-extra": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", @@ -184,6 +325,12 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -204,6 +351,12 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, "handlebars": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", @@ -216,6 +369,18 @@ "uglify-js": "2.8.29" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "highlight.js": { "version": "9.12.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", @@ -287,6 +452,12 @@ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", @@ -308,6 +479,48 @@ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", + "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -339,6 +552,12 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "progress": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", @@ -399,6 +618,62 @@ "amdefine": "1.0.1" } }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "ts-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.0.tgz", + "integrity": "sha512-+CQev+4J7BAUNUnW9piRzSfSZZWeFCjgUjMSgGs4+dJ2RZa86NVW9MOlP4e6/kEHTyOqdxHxcIMd7KgmY/ynVw==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.4.0", + "diff": "3.5.0", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.5.4", + "yn": "2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "typedoc": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.11.1.tgz", @@ -509,6 +784,12 @@ "decamelize": "1.2.0", "window-size": "0.1.0" } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true } } } diff --git a/package.json b/package.json index 0cece4a..e09c4fb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "LINQ for Javascript, written by TypeScript", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha -r ts-node/register src/**/*.test.ts" }, "repository": { "type": "git", @@ -18,7 +18,12 @@ }, "homepage": "https://github.com/jinhduong/eslinq#readme", "devDependencies": { + "@types/chai": "^4.1.2", + "@types/mocha": "^5.1.0", "@types/node": "^9.6.5", + "chai": "^4.1.2", + "mocha": "^5.1.1", + "ts-node": "^6.0.0", "typedoc": "^0.11.1", "typescript": "^2.8.1" } diff --git a/src/demo/test.ts b/src/demo/test.ts new file mode 100644 index 0000000..34521f8 --- /dev/null +++ b/src/demo/test.ts @@ -0,0 +1,43 @@ +import { Queryable } from "../implements/queryable"; + +const queryable = new Queryable<{ name, nationId, overall, skills }>(); + +let promiseApi = new Promise((resolve, reject) => { + // Fake data from api + setTimeout(() => { + console.log('...get data'); + resolve([ + { name: 'Ronaldo', overall: 96, nationId: 1, skills: [97, 90, 86, 95] }, + { name: 'Messi', overall: 98, nationId: 2, skills: [97, 90, 86, 95] }, + { name: 'Mbappe', overall: 86, nationId: 3, skills: [97, 90, 86, 95] }, + { name: 'Salah', overall: 89, nationId: 4, skills: [97, 90, 86, 95] } + ]); + }, 1000); +}) + +let staticLoopkup = [ + { id: 1, name: 'Portugal', areaId: 1 }, + { id: 2, name: 'Argentina', areaId: 2 }, + { id: 3, name: 'France', areaId: 1 }, + { id: 4, name: 'Egypt', areaId: 3 } +] + +let staticAreas = [ + { id: 1, areaName: 'Euro' }, + { id: 2, areaName: 'South America' }, +] + +async function main() { + // Just query not execute query + let query = queryable + .from(promiseApi) + .where(x => x.overall > 90) + .selectMany(x => x.skills); + + const data = await query.toList(); + console.log(data); +} + +main(); + + diff --git a/src/implements/methods.ts b/src/implements/methods.ts index 57cae02..bfac369 100644 --- a/src/implements/methods.ts +++ b/src/implements/methods.ts @@ -11,10 +11,11 @@ import { SkipClause } from "../methods/skip"; import { SkipWhileClause } from "../methods/skipWhile"; import { TakeWhileClause } from "../methods/takeWhile"; import { JoinClause } from "../methods/join"; +import { LeftJoinClause } from "../methods/leftJoin"; +import { SelectManyClause } from "../methods/selectMany"; export class IteratorMethods implements Methods { - // Contains all iterators _iteratorCollection: Array> = []; @@ -38,11 +39,21 @@ export class IteratorMethods implements Methods { return this as any; } + selectMany(iterator: (entity: T) => S): Methods { + this._iteratorCollection.push(new SelectManyClause(iterator)) + return this as any; + } + join(source: S[] | Promise, iterator: (aEntity: T, bEntity: S) => boolean): Methods { this._iteratorCollection.push(new JoinClause(source, iterator)); return this as any; } + leftJoin(source: S[] | Promise, iterator: (aEntity: T, bEntity: S) => boolean): Methods { + this._iteratorCollection.push(new LeftJoinClause(source, iterator)); + return this as any; + } + toList(): Promise { // From cache diff --git a/src/implements/queryable.ts b/src/implements/queryable.ts index c2b3f8a..3449661 100644 --- a/src/implements/queryable.ts +++ b/src/implements/queryable.ts @@ -7,7 +7,7 @@ import { Methods } from "../intefaces/methods.interface"; export class Queryable implements IQueryable { _iteratorCollection: Array> = []; - from(source: any[] | Promise | T[] | Promise): Methods { + from(source: Promise | T[] | Promise | any[]): Methods { return new IteratorMethods(this._iteratorCollection, source); } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e22e0d6..4e4effb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1 @@ -import { Queryable } from "./implements/queryable"; - -const queryable = new Queryable<{ name, overall, nationId }>(); - -let promiseApi = new Promise((resolve, reject) => { - // Fake data from api - setTimeout(() => { - console.log('...get data'); - resolve([ - { name: 'Ronaldo', overall: 96, nationId: 1 }, - { name: 'Messi', overall: 98, nationId: 2 }, - { name: 'Mbappe', overall: 86, nationId: 3 }, - ]); - }, 1000); -}) - -let staticLoopkup = [ - { id: 1, name: 'Portugal' }, - { id: 2, name: 'Argentina' }, - { id: 3, name: 'France' }, -] - -async function main() { - // Just query not execute query - let query = queryable - .from(promiseApi) - .join(staticLoopkup, (x, y) => x.nationId === y.id) - .select(item => { - return { - playerName: item.x.name, - nation: item.y.name - }; - }); - - const data = await query.toList(); - console.log(data); -} - -main(); - - +export * from './implements/queryable'; \ No newline at end of file diff --git a/src/intefaces/methods.interface.ts b/src/intefaces/methods.interface.ts index 8246c2b..2542001 100644 --- a/src/intefaces/methods.interface.ts +++ b/src/intefaces/methods.interface.ts @@ -2,11 +2,13 @@ export interface Methods { // Query methods where(iterator: (entity: T) => boolean): Methods; select(iterator: (entity: T) => S): Methods; + selectMany(iterator: (entity: T, index?: number) => S): Methods; take(value: number): Methods; skip(value: number): Methods; skipWhile(iterator: (entity: T) => boolean): Methods; takeWhile(iterator: (entity: T) => boolean): Methods; join(source: S[] | Promise, iterator: (aEntity: T, bEntity: S) => boolean): Methods<{ x: T, y: S }>; + leftJoin(source: S[] | Promise, iterator: (aEntity: T, bEntity: S) => boolean): Methods; // Execute methods toList(): Promise; diff --git a/src/methods/leftJoin.ts b/src/methods/leftJoin.ts new file mode 100644 index 0000000..6453fb7 --- /dev/null +++ b/src/methods/leftJoin.ts @@ -0,0 +1,34 @@ +import { IIterator } from "../intefaces/iterator.interface"; + +export class LeftJoinClause implements IIterator { + + _iterator: (item1: T, item2: S) => boolean; + _anotherSource: S[] | Promise = []; + + execute(source: any[] | T[]): T[] | any[] { + if (source) { + let _result = []; + for (let i = 0, li = source.length; i < li; i++) { + let _flag = false; + for (let j = 0, lj = (this._anotherSource as S[]).length; j < lj; j++) { + if (this._iterator(source[i], this._anotherSource[j])) { + _result.push(Object.assign({} + , source[i] + , this._anotherSource[j])) + _flag = true; break; + } + } + if (!_flag) _result.push(Object.assign({} + , source[i], null + )) + } + return _result; + } + return source; + } + + constructor(anotherSource: S[] | Promise, func: (item1: T, item2: S) => boolean) { + this._iterator = func; + this._anotherSource = anotherSource; + } +} \ No newline at end of file diff --git a/src/methods/selectMany.ts b/src/methods/selectMany.ts new file mode 100644 index 0000000..25814cb --- /dev/null +++ b/src/methods/selectMany.ts @@ -0,0 +1,28 @@ +import { IIterator } from "../intefaces/iterator.interface"; +import { SelectClause } from "./select"; + +export class SelectManyClause implements IIterator { + + _iterator: (item: T, index?: number) => boolean; + + execute(source: any[] | T[]): T[] | any[] { + if (!source) return null; + + let _result = new SelectClause(this._iterator).execute(source); + + if (!_result) return null; + + let _tmp = []; + for (let i = 0, li = _result.length; i < li; i++) { + if (Array.isArray(_result[i])) + _tmp = _tmp.concat(_result[i]) + else + _tmp.push(_result[i]) + } + return _tmp; + } + + constructor(func: (item: T, index?: number) => any) { + this._iterator = func; + } +} \ No newline at end of file diff --git a/src/test/join.test.ts b/src/test/join.test.ts new file mode 100644 index 0000000..292625f --- /dev/null +++ b/src/test/join.test.ts @@ -0,0 +1,37 @@ +import { promiseApi, staticAreas, staticLoopkup } from './source'; +import { expect } from 'chai'; +import { Queryable } from '../implements/queryable'; + +describe('join', () => { + async function run() { + const queryable = new Queryable<{ name, nationId, overall }>(); + let query = queryable + .from(promiseApi) + .join(staticLoopkup, (x, y) => x.nationId === y.id) + .join(staticAreas, (x, y) => x.y.areaId === y.id) + .select(item => { + return { + playerName: item.x.x.name, + nation: item.x.y.name, + area: item.y.areaName + } + }); + return { + data: await query.toList(), + originalData: await promiseApi as any[], + distinct: staticLoopkup.map(x => x.areaId).filter(function (item, pos, self) { + return self.indexOf(item) == pos; + }) + } + } + + it('items equal num of records from last array', async () => { + const rs = await run(); + expect(rs.data.length).to.equal(rs.distinct.length) + }); + + it('result data always equal or less than original', async () => { + const rs = await run(); + expect(rs.data.length).to.be.most(rs.distinct.length) + }) +}); \ No newline at end of file diff --git a/src/test/leftJoin.test.ts b/src/test/leftJoin.test.ts new file mode 100644 index 0000000..7d0d2d2 --- /dev/null +++ b/src/test/leftJoin.test.ts @@ -0,0 +1,38 @@ +import { promiseApi, staticAreas, staticLoopkup } from './source'; +import { expect } from 'chai'; +import { Queryable } from '../implements/queryable'; + +describe('left join', () => { + async function run() { + const queryable = new Queryable<{ name, nationId, overall }>(); + let query = queryable + .from(promiseApi) + .join(staticLoopkup, (x, y) => x.nationId === y.id) + .leftJoin(staticAreas, (x, y) => x.y.areaId === y.id) + .select(item => { + return { + name: item.x.name, + area: item.areaName + } + }); + return { + data: await query.toList(), + originalData: await promiseApi as any[] + } + } + + it('keep num of items', async () => { + const rs = await run(); + expect(rs.data.length).to.equal(rs.originalData.length) + }); + + it('last one didn\'t join', async () => { + const rs = await run(); + expect(rs.data[rs.data.length - 1].area).to.equal(undefined); + }) + + it('first one get exists data', async () => { + const rs = await run(); + expect(rs.data[0].area).not.equal(undefined); + }) +}); \ No newline at end of file diff --git a/src/test/selectMany.test.ts b/src/test/selectMany.test.ts new file mode 100644 index 0000000..a236369 --- /dev/null +++ b/src/test/selectMany.test.ts @@ -0,0 +1,27 @@ +import { promiseApi, staticAreas, staticLoopkup } from './source'; +import { expect } from 'chai'; +import { Queryable } from '../implements/queryable'; + +describe('selectMany', () => { + async function run() { + const queryable = new Queryable<{ name, nationId, overall, skills }>(); + let query = queryable + .from(promiseApi) + .selectMany(x => x.skills); + + return { + data: await query.toList(), + originalData: await promiseApi as any[], + distinct: staticLoopkup.map(x => x.areaId).filter(function (item, pos, self) { + return self.indexOf(item) == pos; + }) + } + } + + it('is fatten list (this case is number)', async () => { + const rs = await run(); + expect(rs.data).to.satisfy((item) => { + return rs.data.every((num) => Number.isInteger(num as number)); + }); + }); +}); \ No newline at end of file diff --git a/src/test/source.ts b/src/test/source.ts new file mode 100644 index 0000000..eebcbea --- /dev/null +++ b/src/test/source.ts @@ -0,0 +1,26 @@ +export let promiseApi = new Promise((resolve, reject) => { + // Fake data from api + setTimeout(() => { + console.log('...get data'); + resolve([ + { name: 'Ronaldo', overall: 96, nationId: 1, skills: [97, 90, 86, 95] }, + { name: 'Messi', overall: 98, nationId: 2, skills: [97, 90, 86, 95] }, + { name: 'Mbappe', overall: 86, nationId: 3, skills: [97, 90, 86, 95] }, + { name: 'Salah', overall: 89, nationId: 4, skills: [97, 90, 86, 95] } + ]); + }, 1000); +}) + +export let staticLoopkup = [ + { id: 1, name: 'Portugal', areaId: 1 }, + { id: 2, name: 'Argentina', areaId: 2 }, + { id: 3, name: 'France', areaId: 1 }, + { id: 4, name: 'Egypt', areaId: 3 } +] + +export let staticAreas = [ + { id: 1, areaName: 'Euro' }, + { id: 2, areaName: 'South America' }, +] + +