Add TreeModel and HasSubtree implementation
This commit is contained in:
parent
3d836afa59
commit
f63891ef99
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@extollo/lib",
|
"name": "@extollo/lib",
|
||||||
"version": "0.13.10",
|
"version": "0.14.0",
|
||||||
"description": "The framework library that lifts up your code.",
|
"description": "The framework library that lifts up your code.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
@ -47,7 +47,7 @@
|
|||||||
"typedoc": "^0.20.36",
|
"typedoc": "^0.20.36",
|
||||||
"typedoc-plugin-pages-fork": "^0.0.1",
|
"typedoc-plugin-pages-fork": "^0.0.1",
|
||||||
"typedoc-plugin-sourcefile-url": "^1.0.6",
|
"typedoc-plugin-sourcefile-url": "^1.0.6",
|
||||||
"typescript": "^4.2.3",
|
"typescript": "^4.7.4",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"ws": "^8.8.0",
|
"ws": "^8.8.0",
|
||||||
"zod": "^3.11.6"
|
"zod": "^3.11.6"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
lockfileVersion: 5.3
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@atao60/fse-cli': ^0.1.6
|
'@atao60/fse-cli': ^0.1.6
|
||||||
@ -50,7 +50,7 @@ specifiers:
|
|||||||
typedoc: ^0.20.36
|
typedoc: ^0.20.36
|
||||||
typedoc-plugin-pages-fork: ^0.0.1
|
typedoc-plugin-pages-fork: ^0.0.1
|
||||||
typedoc-plugin-sourcefile-url: ^1.0.6
|
typedoc-plugin-sourcefile-url: ^1.0.6
|
||||||
typescript: ^4.2.3
|
typescript: ^4.7.4
|
||||||
uuid: ^8.3.2
|
uuid: ^8.3.2
|
||||||
ws: ^8.8.0
|
ws: ^8.8.0
|
||||||
wtfnode: ^0.9.1
|
wtfnode: ^0.9.1
|
||||||
@ -92,11 +92,11 @@ dependencies:
|
|||||||
reflect-metadata: 0.1.13
|
reflect-metadata: 0.1.13
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
ssh2: 1.1.0
|
ssh2: 1.1.0
|
||||||
ts-node: 9.1.1_typescript@4.2.3
|
ts-node: 9.1.1_typescript@4.7.4
|
||||||
typedoc: 0.20.36_typescript@4.2.3
|
typedoc: 0.20.36_typescript@4.7.4
|
||||||
typedoc-plugin-pages-fork: 0.0.1
|
typedoc-plugin-pages-fork: 0.0.1
|
||||||
typedoc-plugin-sourcefile-url: 1.0.6_typedoc@0.20.36
|
typedoc-plugin-sourcefile-url: 1.0.6_typedoc@0.20.36
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
ws: 8.8.0
|
ws: 8.8.0
|
||||||
zod: 3.11.6
|
zod: 3.11.6
|
||||||
@ -106,8 +106,8 @@ devDependencies:
|
|||||||
'@types/mocha': 9.0.0
|
'@types/mocha': 9.0.0
|
||||||
'@types/sinon': 10.0.6
|
'@types/sinon': 10.0.6
|
||||||
'@types/wtfnode': 0.7.0
|
'@types/wtfnode': 0.7.0
|
||||||
'@typescript-eslint/eslint-plugin': 4.26.0_942c48837be95e76bb4156c78358cdbe
|
'@typescript-eslint/eslint-plugin': 4.26.0_cgxss3zaaghocyaq4idsceujcy
|
||||||
'@typescript-eslint/parser': 4.26.0_eslint@7.27.0+typescript@4.2.3
|
'@typescript-eslint/parser': 4.26.0_4x6mfymjsyurtmri255xnln3g4
|
||||||
chai: 4.3.4
|
chai: 4.3.4
|
||||||
eslint: 7.27.0
|
eslint: 7.27.0
|
||||||
mocha: 9.1.3
|
mocha: 9.1.3
|
||||||
@ -160,6 +160,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==}
|
resolution: {integrity: sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.13.14
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@babel/runtime/7.14.6:
|
/@babel/runtime/7.14.6:
|
||||||
@ -216,8 +218,8 @@ packages:
|
|||||||
dotenv: 10.0.0
|
dotenv: 10.0.0
|
||||||
mkdirp: 1.0.4
|
mkdirp: 1.0.4
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
ts-node: 10.4.0_9033393ac6e1960c4e4feb229fa8feef
|
ts-node: 10.4.0_7qobpvvcbeswdxz4g6mzc6lzhy
|
||||||
typescript: 4.5.2
|
typescript: 4.7.4
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@swc/core'
|
- '@swc/core'
|
||||||
@ -465,7 +467,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-kdBHgE9+M1Os7UqWZtiLhKye5reFl8cPBYyCsP2fatwZRz7F7GdIxIHZ20Kkc0hYBfbXE+lzPOTUU1I0qgjtHA==}
|
resolution: {integrity: sha512-kdBHgE9+M1Os7UqWZtiLhKye5reFl8cPBYyCsP2fatwZRz7F7GdIxIHZ20Kkc0hYBfbXE+lzPOTUU1I0qgjtHA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/eslint-plugin/4.26.0_942c48837be95e76bb4156c78358cdbe:
|
/@typescript-eslint/eslint-plugin/4.26.0_cgxss3zaaghocyaq4idsceujcy:
|
||||||
resolution: {integrity: sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg==}
|
resolution: {integrity: sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -476,8 +478,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/experimental-utils': 4.26.0_eslint@7.27.0+typescript@4.2.3
|
'@typescript-eslint/experimental-utils': 4.26.0_4x6mfymjsyurtmri255xnln3g4
|
||||||
'@typescript-eslint/parser': 4.26.0_eslint@7.27.0+typescript@4.2.3
|
'@typescript-eslint/parser': 4.26.0_4x6mfymjsyurtmri255xnln3g4
|
||||||
'@typescript-eslint/scope-manager': 4.26.0
|
'@typescript-eslint/scope-manager': 4.26.0
|
||||||
debug: 4.3.1
|
debug: 4.3.1
|
||||||
eslint: 7.27.0
|
eslint: 7.27.0
|
||||||
@ -485,13 +487,13 @@ packages:
|
|||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
regexpp: 3.1.0
|
regexpp: 3.1.0
|
||||||
semver: 7.3.5
|
semver: 7.3.5
|
||||||
tsutils: 3.21.0_typescript@4.2.3
|
tsutils: 3.21.0_typescript@4.7.4
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/experimental-utils/4.26.0_eslint@7.27.0+typescript@4.2.3:
|
/@typescript-eslint/experimental-utils/4.26.0_4x6mfymjsyurtmri255xnln3g4:
|
||||||
resolution: {integrity: sha512-TH2FO2rdDm7AWfAVRB5RSlbUhWxGVuxPNzGT7W65zVfl8H/WeXTk1e69IrcEVsBslrQSTDKQSaJD89hwKrhdkw==}
|
resolution: {integrity: sha512-TH2FO2rdDm7AWfAVRB5RSlbUhWxGVuxPNzGT7W65zVfl8H/WeXTk1e69IrcEVsBslrQSTDKQSaJD89hwKrhdkw==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -500,7 +502,7 @@ packages:
|
|||||||
'@types/json-schema': 7.0.7
|
'@types/json-schema': 7.0.7
|
||||||
'@typescript-eslint/scope-manager': 4.26.0
|
'@typescript-eslint/scope-manager': 4.26.0
|
||||||
'@typescript-eslint/types': 4.26.0
|
'@typescript-eslint/types': 4.26.0
|
||||||
'@typescript-eslint/typescript-estree': 4.26.0_typescript@4.2.3
|
'@typescript-eslint/typescript-estree': 4.26.0_typescript@4.7.4
|
||||||
eslint: 7.27.0
|
eslint: 7.27.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
eslint-utils: 3.0.0_eslint@7.27.0
|
eslint-utils: 3.0.0_eslint@7.27.0
|
||||||
@ -509,7 +511,7 @@ packages:
|
|||||||
- typescript
|
- typescript
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/parser/4.26.0_eslint@7.27.0+typescript@4.2.3:
|
/@typescript-eslint/parser/4.26.0_4x6mfymjsyurtmri255xnln3g4:
|
||||||
resolution: {integrity: sha512-b4jekVJG9FfmjUfmM4VoOItQhPlnt6MPOBUL0AQbiTmm+SSpSdhHYlwayOm4IW9KLI/4/cRKtQCmDl1oE2OlPg==}
|
resolution: {integrity: sha512-b4jekVJG9FfmjUfmM4VoOItQhPlnt6MPOBUL0AQbiTmm+SSpSdhHYlwayOm4IW9KLI/4/cRKtQCmDl1oE2OlPg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -521,10 +523,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 4.26.0
|
'@typescript-eslint/scope-manager': 4.26.0
|
||||||
'@typescript-eslint/types': 4.26.0
|
'@typescript-eslint/types': 4.26.0
|
||||||
'@typescript-eslint/typescript-estree': 4.26.0_typescript@4.2.3
|
'@typescript-eslint/typescript-estree': 4.26.0_typescript@4.7.4
|
||||||
debug: 4.3.1
|
debug: 4.3.1
|
||||||
eslint: 7.27.0
|
eslint: 7.27.0
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@ -542,7 +544,7 @@ packages:
|
|||||||
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
|
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/typescript-estree/4.26.0_typescript@4.2.3:
|
/@typescript-eslint/typescript-estree/4.26.0_typescript@4.7.4:
|
||||||
resolution: {integrity: sha512-GHUgahPcm9GfBuy3TzdsizCcPjKOAauG9xkz9TR8kOdssz2Iz9jRCSQm6+aVFa23d5NcSpo1GdHGSQKe0tlcbg==}
|
resolution: {integrity: sha512-GHUgahPcm9GfBuy3TzdsizCcPjKOAauG9xkz9TR8kOdssz2Iz9jRCSQm6+aVFa23d5NcSpo1GdHGSQKe0tlcbg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -557,8 +559,8 @@ packages:
|
|||||||
globby: 11.0.3
|
globby: 11.0.3
|
||||||
is-glob: 4.0.1
|
is-glob: 4.0.1
|
||||||
semver: 7.3.5
|
semver: 7.3.5
|
||||||
tsutils: 3.21.0_typescript@4.2.3
|
tsutils: 3.21.0_typescript@4.7.4
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@ -1024,7 +1026,7 @@ packages:
|
|||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
nan: 2.14.2
|
nan: 2.16.0
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -1550,7 +1552,7 @@ packages:
|
|||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
wordwrap: 1.0.0
|
wordwrap: 1.0.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
uglify-js: 3.13.8
|
uglify-js: 3.17.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-flag/3.0.0:
|
/has-flag/3.0.0:
|
||||||
@ -2061,8 +2063,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
|
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/nan/2.14.2:
|
/nan/2.16.0:
|
||||||
resolution: {integrity: sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==}
|
resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==}
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -2720,7 +2722,7 @@ packages:
|
|||||||
bcrypt-pbkdf: 1.0.2
|
bcrypt-pbkdf: 1.0.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
cpu-features: 0.0.2
|
cpu-features: 0.0.2
|
||||||
nan: 2.14.2
|
nan: 2.16.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/standard-as-callback/2.1.0:
|
/standard-as-callback/2.1.0:
|
||||||
@ -2870,7 +2872,7 @@ packages:
|
|||||||
resolution: {integrity: sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=}
|
resolution: {integrity: sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ts-node/10.4.0_9033393ac6e1960c4e4feb229fa8feef:
|
/ts-node/10.4.0_7qobpvvcbeswdxz4g6mzc6lzhy:
|
||||||
resolution: {integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==}
|
resolution: {integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2896,11 +2898,11 @@ packages:
|
|||||||
create-require: 1.1.1
|
create-require: 1.1.1
|
||||||
diff: 4.0.2
|
diff: 4.0.2
|
||||||
make-error: 1.3.6
|
make-error: 1.3.6
|
||||||
typescript: 4.5.2
|
typescript: 4.7.4
|
||||||
yn: 3.1.1
|
yn: 3.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ts-node/9.1.1_typescript@4.2.3:
|
/ts-node/9.1.1_typescript@4.7.4:
|
||||||
resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==}
|
resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -2912,7 +2914,7 @@ packages:
|
|||||||
diff: 4.0.2
|
diff: 4.0.2
|
||||||
make-error: 1.3.6
|
make-error: 1.3.6
|
||||||
source-map-support: 0.5.19
|
source-map-support: 0.5.19
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
yn: 3.1.1
|
yn: 3.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2923,14 +2925,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tsutils/3.21.0_typescript@4.2.3:
|
/tsutils/3.21.0_typescript@4.7.4:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tweetnacl/0.14.5:
|
/tweetnacl/0.14.5:
|
||||||
@ -2993,10 +2995,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typedoc: '>=0.16.0'
|
typedoc: '>=0.16.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
typedoc: 0.20.36_typescript@4.2.3
|
typedoc: 0.20.36_typescript@4.7.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/typedoc/0.20.36_typescript@4.2.3:
|
/typedoc/0.20.36_typescript@4.7.4:
|
||||||
resolution: {integrity: sha512-qFU+DWMV/hifQ9ZAlTjdFO9wbUIHuUBpNXzv68ZyURAP9pInjZiO4+jCPeAzHVcaBCHER9WL/+YzzTt6ZlN/Nw==}
|
resolution: {integrity: sha512-qFU+DWMV/hifQ9ZAlTjdFO9wbUIHuUBpNXzv68ZyURAP9pInjZiO4+jCPeAzHVcaBCHER9WL/+YzzTt6ZlN/Nw==}
|
||||||
engines: {node: '>= 10.8.0'}
|
engines: {node: '>= 10.8.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -3014,23 +3016,16 @@ packages:
|
|||||||
shelljs: 0.8.4
|
shelljs: 0.8.4
|
||||||
shiki: 0.9.3
|
shiki: 0.9.3
|
||||||
typedoc-default-themes: 0.12.10
|
typedoc-default-themes: 0.12.10
|
||||||
typescript: 4.2.3
|
typescript: 4.7.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/typescript/4.2.3:
|
/typescript/4.7.4:
|
||||||
resolution: {integrity: sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==}
|
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
|
||||||
|
|
||||||
/typescript/4.5.2:
|
/uglify-js/3.17.0:
|
||||||
resolution: {integrity: sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==}
|
resolution: {integrity: sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==}
|
||||||
engines: {node: '>=4.2.0'}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/uglify-js/3.13.8:
|
|
||||||
resolution: {integrity: sha512-PvFLMFIQHfIjFFlvAch69U2IvIxK9TNzNWt1SxZGp9JZ/v70yvqIQuiJeVPPtUMOzoNt+aNRDk4wgxb34wvEqA==}
|
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -63,24 +63,24 @@ export class ShellDirective extends Directive {
|
|||||||
globalRegistry.forceContextOverride()
|
globalRegistry.forceContextOverride()
|
||||||
|
|
||||||
// Create the ts-node compiler service.
|
// Create the ts-node compiler service.
|
||||||
const replService = tsNode.createRepl()
|
// const replService = tsNode.createRepl()
|
||||||
const service = tsNode.create({...replService.evalAwarePartialHost})
|
// const service = tsNode.create({...replService.evalAwarePartialHost})
|
||||||
replService.setService(service)
|
// replService.setService(service)
|
||||||
|
|
||||||
// We global these values into the REPL's state directly (using the `state` object
|
// We global these values into the REPL's state directly (using the `state` object
|
||||||
// above), but since we're using a separate ts-node interpreter, we need to make it
|
// above), but since we're using a separate ts-node interpreter, we need to make it
|
||||||
// aware of the globals using declaration syntax.
|
// aware of the globals using declaration syntax.
|
||||||
replService.evalCode(`
|
// replService.evalCode(`
|
||||||
declare const lib: typeof import('@extollo/lib');
|
// declare const lib: typeof import('@extollo/lib');
|
||||||
declare const app: typeof lib['Application'];
|
// declare const app: typeof lib['Application'];
|
||||||
declare const globalRegistry: typeof lib['globalRegistry'];
|
// declare const globalRegistry: typeof lib['globalRegistry'];
|
||||||
`)
|
// `)
|
||||||
|
|
||||||
// Print the welome message and start the interpreter
|
// Print the welome message and start the interpreter
|
||||||
this.nativeOutput(this.options.welcome)
|
this.nativeOutput(this.options.welcome)
|
||||||
this.repl = repl.start({
|
this.repl = repl.start({
|
||||||
// Causes the REPL to use the ts-node interpreter service:
|
// Causes the REPL to use the ts-node interpreter service:
|
||||||
eval: !this.option('js', false) ? (...args) => replService.nodeEval(...args) : undefined,
|
// eval: !this.option('js', false) ? (...args) => replService.nodeEval(...args) : undefined,
|
||||||
prompt: this.options.prompt,
|
prompt: this.options.prompt,
|
||||||
useGlobal: true,
|
useGlobal: true,
|
||||||
useColors: true,
|
useColors: true,
|
||||||
|
@ -12,12 +12,11 @@ import {
|
|||||||
Awaitable,
|
Awaitable,
|
||||||
collect,
|
collect,
|
||||||
Collection,
|
Collection,
|
||||||
ErrorWithContext,
|
|
||||||
globalRegistry,
|
globalRegistry,
|
||||||
hasOwnProperty,
|
hasOwnProperty,
|
||||||
logIfDebugging,
|
logIfDebugging,
|
||||||
withErrorContext,
|
|
||||||
} from '../util'
|
} from '../util'
|
||||||
|
import {ErrorWithContext, withErrorContext} from '../util/error/ErrorWithContext'
|
||||||
import {Factory} from './factory/Factory'
|
import {Factory} from './factory/Factory'
|
||||||
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
||||||
import {ClosureFactory} from './factory/ClosureFactory'
|
import {ClosureFactory} from './factory/ClosureFactory'
|
||||||
@ -529,7 +528,7 @@ export class Container {
|
|||||||
return realized
|
return realized
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
makeStack: Container.makeStack,
|
makeStack: Container.makeStack.map(x => typeof x === 'string' ? x : (x?.name || 'unknown')).toArray(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if ( result ) {
|
if ( result ) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import {collect, Collection, logIfDebugging} from '../../util'
|
import {collect, Collection} from '../../util'
|
||||||
|
import {logIfDebugging} from '../../util/support/debug'
|
||||||
import {
|
import {
|
||||||
DependencyKey,
|
DependencyKey,
|
||||||
DependencyRequirement,
|
DependencyRequirement,
|
||||||
@ -100,9 +101,7 @@ export const Inject = (key?: DependencyKey, { debug = false } = {}): PropertyDec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( debug ) {
|
|
||||||
logIfDebugging('extollo.di.decoration', '[DEBUG] @Inject() - key:', key, 'property:', property, 'target:', target, 'target constructor:', target?.constructor, 'type:', type)
|
logIfDebugging('extollo.di.decoration', '[DEBUG] @Inject() - key:', key, 'property:', property, 'target:', target, 'target constructor:', target?.constructor, 'type:', type)
|
||||||
}
|
|
||||||
|
|
||||||
Reflect.defineMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, propertyMetadata, target?.constructor || target)
|
Reflect.defineMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, propertyMetadata, target?.constructor || target)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {DependencyKey} from '../types'
|
import {DependencyKey} from '../types'
|
||||||
import {ErrorWithContext} from '../../util'
|
import {ErrorWithContext} from '../../util/error/ErrorWithContext'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error thrown when a dependency key that has not been registered is passed to a resolver.
|
* Error thrown when a dependency key that has not been registered is passed to a resolver.
|
||||||
|
@ -51,6 +51,6 @@ export abstract class AbstractFactory<T> {
|
|||||||
return this.token
|
return this.token
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.token.name ?? '(unknown token)'
|
return this.token?.name ?? '(unknown token)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
Instantiable,
|
Instantiable,
|
||||||
PropertyDependency,
|
PropertyDependency,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import {Collection} from '../../util'
|
import {Collection, logIfDebugging} from '../../util'
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +58,7 @@ export class Factory<T> extends AbstractFactory<T> {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
||||||
|
logIfDebugging('extollo.di.injection', 'Factory.getInjectedProperties() target:', currentToken, 'loaded:', loadedMeta)
|
||||||
if ( loadedMeta ) {
|
if ( loadedMeta ) {
|
||||||
meta.concat(loadedMeta)
|
meta.concat(loadedMeta)
|
||||||
}
|
}
|
||||||
|
@ -222,17 +222,45 @@ export abstract class AbstractBuilder<T> extends AppClass {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Apply a WHERE ... IS NULL constraint to the query. */
|
||||||
|
whereNull(field: string): this {
|
||||||
|
return this.whereRawValue(field, 'IS', 'NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply a WHERE ... IS NOT NULL constraint to the query. */
|
||||||
|
whereNotNull(field: string): this {
|
||||||
|
return this.whereRawValue(field, 'IS NOT', 'NULL')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a new WHERE constraint to the query, without escaping `operand`. Prefer `where()`.
|
* Apply a new WHERE constraint to the query, without escaping `operand`. Prefer `where()`.
|
||||||
* @param field
|
* @param field
|
||||||
* @param operator
|
* @param operator
|
||||||
* @param operand
|
* @param operand
|
||||||
*/
|
*/
|
||||||
whereRaw(field: string, operator: ConstraintOperator, operand: string): this {
|
whereRawValue(field: string, operator: ConstraintOperator, operand: string): this {
|
||||||
this.createConstraint('AND', field, operator, raw(operand))
|
this.createConstraint('AND', field, operator, raw(operand))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add raw SQL as a constraint to the query.
|
||||||
|
* @param clause
|
||||||
|
*/
|
||||||
|
whereRaw(clause: string|QuerySafeValue): this {
|
||||||
|
if ( !(clause instanceof QuerySafeValue) ) {
|
||||||
|
clause = raw(clause)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.constraints.push(raw(clause))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply an impossible constraint to the query, causing it to match 0 rows. */
|
||||||
|
whereMatchNone(): this {
|
||||||
|
return this.whereRaw('1=0')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a new WHERE NOT constraint to the query.
|
* Apply a new WHERE NOT constraint to the query.
|
||||||
* @param field
|
* @param field
|
||||||
|
@ -316,6 +316,8 @@ export class PostgreSQLDialect extends SQLDialect {
|
|||||||
const field: string = constraint.field.split('.').map(x => `"${x}"`)
|
const field: string = constraint.field.split('.').map(x => `"${x}"`)
|
||||||
.join('.')
|
.join('.')
|
||||||
statements.push(`${indent}${statements.length < 1 ? '' : constraint.preop + ' '}${field} ${constraint.operator} ${this.escape(constraint.operand).value}`)
|
statements.push(`${indent}${statements.length < 1 ? '' : constraint.preop + ' '}${field} ${constraint.operator} ${this.escape(constraint.operand).value}`)
|
||||||
|
} else if ( constraint instanceof QuerySafeValue ) {
|
||||||
|
statements.push(`${indent}${statements.length < 1 ? '' : 'AND '}${constraint.toString()}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export type EscapeValue = null | undefined | string | number | boolean | Date |
|
|||||||
export type EscapeValueObject = { [field: string]: EscapeValue }
|
export type EscapeValueObject = { [field: string]: EscapeValue }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper class whose value is save to inject directly into a query.
|
* A wrapper class whose value is safe to inject directly into a query.
|
||||||
*/
|
*/
|
||||||
export class QuerySafeValue {
|
export class QuerySafeValue {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -20,12 +20,14 @@ export * from './model/ModelResultIterable'
|
|||||||
export * from './model/events'
|
export * from './model/events'
|
||||||
export * from './model/Model'
|
export * from './model/Model'
|
||||||
export * from './model/ModelSerializer'
|
export * from './model/ModelSerializer'
|
||||||
|
export * from './model/TreeModel'
|
||||||
|
|
||||||
export * from './model/relation/RelationBuilder'
|
export * from './model/relation/RelationBuilder'
|
||||||
export * from './model/relation/Relation'
|
export * from './model/relation/Relation'
|
||||||
export * from './model/relation/HasOneOrMany'
|
export * from './model/relation/HasOneOrMany'
|
||||||
export * from './model/relation/HasOne'
|
export * from './model/relation/HasOne'
|
||||||
export * from './model/relation/HasMany'
|
export * from './model/relation/HasMany'
|
||||||
|
export * from './model/relation/HasSubtree'
|
||||||
export * from './model/relation/decorators'
|
export * from './model/relation/decorators'
|
||||||
|
|
||||||
export * from './model/scope/Scope'
|
export * from './model/scope/Scope'
|
||||||
|
@ -19,7 +19,7 @@ import {HasOne} from './relation/HasOne'
|
|||||||
import {HasMany} from './relation/HasMany'
|
import {HasMany} from './relation/HasMany'
|
||||||
import {HasOneOrMany} from './relation/HasOneOrMany'
|
import {HasOneOrMany} from './relation/HasOneOrMany'
|
||||||
import {Scope, ScopeClosure} from './scope/Scope'
|
import {Scope, ScopeClosure} from './scope/Scope'
|
||||||
import {LocalBus} from '../../support/bus/LocalBus'
|
import {LocalBus} from '../../support/bus/LocalBus' // need the specific import to prevent circular dependencies
|
||||||
import {ModelEvent} from './events/ModelEvent'
|
import {ModelEvent} from './events/ModelEvent'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +149,8 @@ export abstract class Model<T extends Model<T>> extends LocalBus<ModelEvent<T>>
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public static query<T2 extends Model<T2>>(): ModelBuilder<T2> {
|
public static query<T2 extends Model<T2>>(): ModelBuilder<T2> {
|
||||||
const builder = <ModelBuilder<T2>> Container.getContainer().make<ModelBuilder<T2>>(ModelBuilder, this)
|
const di = Container.getContainer()
|
||||||
|
const builder = <ModelBuilder<T2>> di.make<ModelBuilder<T2>>(ModelBuilder, this)
|
||||||
const source: QuerySource = this.querySource()
|
const source: QuerySource = this.querySource()
|
||||||
|
|
||||||
builder.connection(this.getConnection())
|
builder.connection(this.getConnection())
|
||||||
@ -164,35 +165,17 @@ export abstract class Model<T extends Model<T>> extends LocalBus<ModelEvent<T>>
|
|||||||
builder.field(field.databaseKey)
|
builder.field(field.databaseKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
if ( Array.isArray(this.prototype.with) ) {
|
const inst = di.make<T2>(this)
|
||||||
// Try to get the eager-loaded relations statically, if possible
|
|
||||||
for (const relation of this.prototype.with) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
builder.with(relation)
|
|
||||||
}
|
|
||||||
} else if ( this.constructor.length < 1 ) {
|
|
||||||
// Otherwise, if we can instantiate the model without any arguments,
|
|
||||||
// do that and get the eager-loaded relations directly.
|
|
||||||
const inst = Container.getContainer().make<Model<any>>(this)
|
|
||||||
if ( Array.isArray(inst.with) ) {
|
if ( Array.isArray(inst.with) ) {
|
||||||
|
// Try to get the eager-loaded relations statically, if possible
|
||||||
for (const relation of inst.with) {
|
for (const relation of inst.with) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
builder.with(relation)
|
builder.with(relation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ( this.prototype.scopes ) {
|
|
||||||
// Same thing here. Try to get the scopes statically, if possible
|
|
||||||
builder.withScopes(this.prototype.scopes)
|
|
||||||
} else if ( this.constructor.length < 1 ) {
|
|
||||||
// Otherwise, try to instantiate the model if possible and load the scopes that way
|
|
||||||
const inst = Container.getContainer().make<Model<any>>(this)
|
|
||||||
builder.withScopes(inst.scopes)
|
builder.withScopes(inst.scopes)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
@ -1008,13 +991,13 @@ export abstract class Model<T extends Model<T>> extends LocalBus<ModelEvent<T>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( typeof relFn === 'function' ) {
|
if ( typeof relFn === 'function' ) {
|
||||||
const rel = relFn.apply(relFn, this)
|
const rel = relFn.bind(this)()
|
||||||
if ( rel instanceof Relation ) {
|
if ( rel instanceof Relation ) {
|
||||||
return rel
|
return rel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return a Relation.`)
|
throw new TypeError(`Cannot get relation of name: ${String(name)}. Method does not return a Relation.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,7 +78,7 @@ export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
|||||||
*/
|
*/
|
||||||
public with(relationName: keyof T): this {
|
public with(relationName: keyof T): this {
|
||||||
if ( !this.eagerLoadRelations.includes(relationName) ) {
|
if ( !this.eagerLoadRelations.includes(relationName) ) {
|
||||||
// Try to load the Relation so we fail if the name is invalid
|
// Try to load the Relation, so we fail if the name is invalid
|
||||||
this.make<T>(this.ModelClass).getRelation(relationName)
|
this.make<T>(this.ModelClass).getRelation(relationName)
|
||||||
this.eagerLoadRelations.push(relationName)
|
this.eagerLoadRelations.push(relationName)
|
||||||
}
|
}
|
||||||
@ -86,6 +86,15 @@ export class ModelBuilder<T extends Model<T>> extends AbstractBuilder<T> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent a relation from being eager-loaded.
|
||||||
|
* @param relationName
|
||||||
|
*/
|
||||||
|
public without(relationName: keyof T): this {
|
||||||
|
this.eagerLoadRelations = this.eagerLoadRelations.filter(name => name !== relationName)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all global scopes from this query.
|
* Remove all global scopes from this query.
|
||||||
*/
|
*/
|
||||||
|
@ -70,6 +70,11 @@ export class ModelResultIterable<T extends Model<T>> extends AbstractResultItera
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected async processEagerLoads(results: Collection<T>): Promise<void> {
|
protected async processEagerLoads(results: Collection<T>): Promise<void> {
|
||||||
|
if ( results.isEmpty() ) {
|
||||||
|
// Nothing to load relations for, so no reason to perform more queries
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const eagers = this.builder.getEagerLoadedRelations()
|
const eagers = this.builder.getEagerLoadedRelations()
|
||||||
const model = this.make<T>(this.ModelClass)
|
const model = this.make<T>(this.ModelClass)
|
||||||
|
|
||||||
@ -78,9 +83,10 @@ export class ModelResultIterable<T extends Model<T>> extends AbstractResultItera
|
|||||||
|
|
||||||
const relation = model.getRelation(name)
|
const relation = model.getRelation(name)
|
||||||
const select = relation.buildEagerQuery(this.builder, results)
|
const select = relation.buildEagerQuery(this.builder, results)
|
||||||
|
const resultCount = await select.get().count()
|
||||||
|
|
||||||
const allRelated = await select.get().collect()
|
const allRelated = resultCount ? await select.get().collect() : collect()
|
||||||
allRelated.each(result => {
|
results.each(result => {
|
||||||
const resultRelation = result.getRelation(name as any)
|
const resultRelation = result.getRelation(name as any)
|
||||||
const resultRelated = resultRelation.matchResults(allRelated as any)
|
const resultRelated = resultRelation.matchResults(allRelated as any)
|
||||||
resultRelation.setValue(resultRelated as any)
|
resultRelation.setValue(resultRelated as any)
|
||||||
|
88
src/orm/model/TreeModel.ts
Normal file
88
src/orm/model/TreeModel.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import {Model} from './Model'
|
||||||
|
import {Collection, Maybe} from '../../util'
|
||||||
|
import {HasSubtree} from './relation/HasSubtree'
|
||||||
|
import {Related} from './relation/decorators'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model implementation with helpers for querying tree-structured data.
|
||||||
|
*
|
||||||
|
* This works by using a modified pre-order traversal to number the tree nodes
|
||||||
|
* with a left- and right-side numbers. For example:
|
||||||
|
*
|
||||||
|
* ```txt
|
||||||
|
* (1) A (14)
|
||||||
|
* |
|
||||||
|
* (2) B (9) (10) C (11) (12) D (14)
|
||||||
|
* |
|
||||||
|
* (3) E (6) (7) G (8)
|
||||||
|
* |
|
||||||
|
* (4) F (5)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* These numbers are stored, by default, in `left_num` and `right_num` columns.
|
||||||
|
* The `subtree()` method returns a `HasSubtree` relation which loads the subtree
|
||||||
|
* of a model and recursively nests the nodes.
|
||||||
|
*
|
||||||
|
* You can use the `children()` helper method to get a collection of the immediate
|
||||||
|
* children of this node, which also have the subtree set.
|
||||||
|
*
|
||||||
|
* To query the model without loading the entire subtree, use the `without()`
|
||||||
|
* method on the `ModelBuilder`. For example:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* MyModel.query<MyModel>().without('subtree')
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export abstract class TreeModel<T extends TreeModel<T>> extends Model<T> {
|
||||||
|
|
||||||
|
/** The table column where the left tree number is stored. */
|
||||||
|
public static readonly leftTreeField = 'left_num'
|
||||||
|
|
||||||
|
/** The table column where the right tree number is stored. */
|
||||||
|
public static readonly rightTreeField = 'right_num'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override to eager-load the subtree by default
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected with: (keyof T)[] = ['subtree']
|
||||||
|
|
||||||
|
/** Get the left tree number for this model. */
|
||||||
|
public leftTreeNum(): Maybe<number> {
|
||||||
|
const ctor = this.constructor as typeof TreeModel
|
||||||
|
return this.originalSourceRow?.[ctor.leftTreeField]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the right tree number for this model. */
|
||||||
|
public rightTreeNum(): Maybe<number> {
|
||||||
|
const ctor = this.constructor as typeof TreeModel
|
||||||
|
return this.originalSourceRow?.[ctor.rightTreeField]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if this node has no children. */
|
||||||
|
public isLeaf(): boolean {
|
||||||
|
const left = this.leftTreeNum()
|
||||||
|
const right = this.rightTreeNum()
|
||||||
|
return Boolean(left && right && (right - left === 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if the given `node` exists within the subtree of this node. */
|
||||||
|
public contains(node: this): boolean {
|
||||||
|
const num = node.leftTreeNum()
|
||||||
|
const left = this.leftTreeNum()
|
||||||
|
const right = this.rightTreeNum()
|
||||||
|
return Boolean(num && left && right && (left < num && right > num))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The subtree nodes of this model, recursively nested. */
|
||||||
|
@Related()
|
||||||
|
public subtree(): HasSubtree<T> {
|
||||||
|
const ctor = this.constructor as typeof TreeModel
|
||||||
|
return this.make<HasSubtree<T>>(HasSubtree, this, ctor.leftTreeField)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the immediate children of this model. */
|
||||||
|
public children(): Collection<T> {
|
||||||
|
return this.subtree().getValue()
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,7 @@ export abstract class HasOneOrMany<T extends Model<T>, T2 extends Model<T2>, V e
|
|||||||
public applyScope(where: AbstractBuilder<T2>): void {
|
public applyScope(where: AbstractBuilder<T2>): void {
|
||||||
where.where(subq => {
|
where.where(subq => {
|
||||||
subq.where(this.qualifiedForeignKey, '=', this.parentValue)
|
subq.where(this.qualifiedForeignKey, '=', this.parentValue)
|
||||||
.whereRaw(this.qualifiedForeignKey, 'IS NOT', 'NULL')
|
.whereNotNull(this.qualifiedForeignKey)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
147
src/orm/model/relation/HasSubtree.ts
Normal file
147
src/orm/model/relation/HasSubtree.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import {TreeModel} from '../TreeModel'
|
||||||
|
import {Relation, RelationNotLoadedError} from './Relation'
|
||||||
|
import {collect, Collection, Maybe} from '../../../util'
|
||||||
|
import {RelationBuilder} from './RelationBuilder'
|
||||||
|
import {raw} from '../../dialect/SQLDialect'
|
||||||
|
import {AbstractBuilder} from '../../builder/AbstractBuilder'
|
||||||
|
import {ModelBuilder} from '../ModelBuilder'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A relation that recursively loads the subtree of a model using
|
||||||
|
* modified preorder traversal.
|
||||||
|
*/
|
||||||
|
export class HasSubtree<T extends TreeModel<T>> extends Relation<T, T, Collection<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the relation is loaded, the immediate children of the node.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected instances: Maybe<Collection<T>>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly model: T,
|
||||||
|
protected readonly leftTreeField: string,
|
||||||
|
) {
|
||||||
|
super(model, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get parentValue(): any {
|
||||||
|
return this.model.key()
|
||||||
|
}
|
||||||
|
|
||||||
|
public query(): RelationBuilder<T> {
|
||||||
|
return this.builder()
|
||||||
|
.select(raw('*'))
|
||||||
|
.orderByAscending(this.leftTreeField)
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyScope(where: AbstractBuilder<T>): void {
|
||||||
|
const left = this.model.leftTreeNum()
|
||||||
|
const right = this.model.rightTreeNum()
|
||||||
|
if ( !left || !right ) {
|
||||||
|
where.whereMatchNone()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
where.where(this.leftTreeField, '>', left)
|
||||||
|
.where(this.leftTreeField, '<', right)
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildEagerQuery(parentQuery: ModelBuilder<T>, result: Collection<T>): ModelBuilder<T> {
|
||||||
|
const query = this.model.query().without('subtree')
|
||||||
|
|
||||||
|
if ( result.isEmpty() ) {
|
||||||
|
return query.whereMatchNone()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.each(inst => {
|
||||||
|
const left = inst.leftTreeNum()
|
||||||
|
const right = inst.rightTreeNum()
|
||||||
|
if ( !left || !right ) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query.where(where => {
|
||||||
|
where.where(this.leftTreeField, '>', left)
|
||||||
|
.where(this.leftTreeField, '<', right)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchResults(possiblyRelated: Collection<T>): Collection<T> {
|
||||||
|
const modelLeft = this.model.leftTreeNum()
|
||||||
|
const modelRight = this.model.rightTreeNum()
|
||||||
|
if ( !modelLeft || !modelRight ) {
|
||||||
|
return collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return possiblyRelated.filter(inst => {
|
||||||
|
const instLeft = inst.leftTreeNum()
|
||||||
|
return Boolean(instLeft && instLeft > modelLeft && instLeft < modelRight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public setValue(related: Collection<T>): void {
|
||||||
|
// `related` contains a flat collection of the subtree nodes, ordered by left key ascending
|
||||||
|
// We will loop through the related nodes and recursively call `setValue` for our immediate
|
||||||
|
// children to build the tree.
|
||||||
|
|
||||||
|
type ReduceState = {
|
||||||
|
currentChild: T,
|
||||||
|
currentSubtree: Collection<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = this.instances = collect()
|
||||||
|
const firstChild = related.pop()
|
||||||
|
if ( !firstChild ) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalState = related.reduce<ReduceState>((state: ReduceState, node: T) => {
|
||||||
|
if ( state.currentChild.contains(node) ) {
|
||||||
|
// `node` belongs in the subtree of `currentChild`, not this node
|
||||||
|
state.currentSubtree.push(node)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've hit the end of the subtree for `currentChild`, so set the child's
|
||||||
|
// subtree relation value and move on to the next child.
|
||||||
|
state.currentChild.subtree().setValue(state.currentSubtree)
|
||||||
|
children.push(state.currentChild)
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentChild: node,
|
||||||
|
currentSubtree: collect(),
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
currentChild: firstChild,
|
||||||
|
currentSubtree: collect(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Do this one last time, since the reducer isn't called for the last node in the collection
|
||||||
|
if ( finalState ) {
|
||||||
|
finalState.currentChild.subtree().setValue(finalState.currentSubtree)
|
||||||
|
children.push(finalState.currentChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.instances = children.sortBy(inst => inst.getOriginalValues()?.[this.leftTreeField])
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValue(): Collection<T> {
|
||||||
|
if ( !this.instances ) {
|
||||||
|
throw new RelationNotLoadedError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.instances
|
||||||
|
}
|
||||||
|
|
||||||
|
public isLoaded(): boolean {
|
||||||
|
return Boolean(this.instances)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(): Promise<Collection<T>> {
|
||||||
|
return this.fetch().collect()
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,9 @@ export abstract class Relation<T extends Model<T>, T2 extends Model<T2>, V exten
|
|||||||
|
|
||||||
/** Get a collection of the results of this relation. */
|
/** Get a collection of the results of this relation. */
|
||||||
public fetch(): ResultCollection<T2> {
|
public fetch(): ResultCollection<T2> {
|
||||||
return this.query().get()
|
return this.query()
|
||||||
|
.where(where => this.applyScope(where))
|
||||||
|
.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolve the result of this relation. */
|
/** Resolve the result of this relation. */
|
||||||
@ -106,6 +108,9 @@ export abstract class Relation<T extends Model<T>, T2 extends Model<T2>, V exten
|
|||||||
|
|
||||||
/** Get a new builder instance for this relation. */
|
/** Get a new builder instance for this relation. */
|
||||||
public builder(): RelationBuilder<T2> {
|
public builder(): RelationBuilder<T2> {
|
||||||
return this.make(RelationBuilder, this)
|
const relatedCtor = this.related.constructor as typeof Model
|
||||||
|
return this.make<RelationBuilder<T2>>(RelationBuilder, this)
|
||||||
|
.connection(relatedCtor.getConnection())
|
||||||
|
.from(relatedCtor.tableName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ export function isConstraintItem(what: unknown): what is ConstraintItem {
|
|||||||
/**
|
/**
|
||||||
* Type alias for something that can be either a single constraint or a group of them.
|
* Type alias for something that can be either a single constraint or a group of them.
|
||||||
*/
|
*/
|
||||||
export type Constraint = ConstraintItem | ConstraintGroup
|
export type Constraint = ConstraintItem | ConstraintGroup | QuerySafeValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type alias for an item that refers to a field on a table.
|
* Type alias for an item that refers to a field on a table.
|
||||||
|
@ -14,11 +14,11 @@ type MaybeCollectionIndex = CollectionIndex | undefined
|
|||||||
type ComparisonFunction<T> = (item: CollectionItem<T>, otherItem: CollectionItem<T>) => number
|
type ComparisonFunction<T> = (item: CollectionItem<T>, otherItem: CollectionItem<T>) => number
|
||||||
|
|
||||||
import { WhereOperator, applyWhere, whereMatch } from './where'
|
import { WhereOperator, applyWhere, whereMatch } from './where'
|
||||||
import {Awaitable, Awaited, Either, isLeft, Maybe, MethodsOf, right, unright} from '../support/types'
|
import {Awaitable, Awaited, Either, isLeft, Maybe, MethodsOf, MethodType, right, unright} from '../support/types'
|
||||||
import {AsyncCollection} from './AsyncCollection'
|
import {AsyncCollection} from './AsyncCollection'
|
||||||
import {ArrayIterable} from './ArrayIterable'
|
import {ArrayIterable} from './ArrayIterable'
|
||||||
|
|
||||||
const collect = <T>(items: CollectionItem<T>[]): Collection<T> => Collection.collect(items)
|
const collect = <T>(items: CollectionItem<T>[] = []): Collection<T> => Collection.collect(items)
|
||||||
const toString = (item: unknown): string => String(item)
|
const toString = (item: unknown): string => String(item)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -381,8 +381,12 @@ class Collection<T> {
|
|||||||
* @param method
|
* @param method
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
mapCall<T2 extends MethodsOf<T>>(method: T2, ...params: Parameters<T[T2]>): Collection<ReturnType<T[T2]>> {
|
mapCall<T2 extends MethodsOf<T>>(method: T2, ...params: Parameters<MethodType<T, T2>>): Collection<ReturnType<MethodType<T, T2>>> {
|
||||||
return this.map(x => x[method](...params))
|
// This is dumb, but I'm not sure how else to resolve it. The types check out, but TypeScript loses track of the fact that
|
||||||
|
// typeof x[method] === MethodType<T, T2>, so it assumes we're indexing an object incorrectly.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
return this.map((x: T) => x[method](...params))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -390,7 +394,7 @@ class Collection<T> {
|
|||||||
* @param method
|
* @param method
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
async awaitMapCall<T2 extends MethodsOf<T>>(method: T2, ...params: Parameters<T[T2]>): Promise<Collection<Awaited<ReturnType<T[T2]>>>> {
|
async awaitMapCall<T2 extends MethodsOf<T>>(method: T2, ...params: Parameters<MethodType<T, T2>>): Promise<Collection<Awaited<ReturnType<MethodType<T, T2>>>>> {
|
||||||
return this.mapCall(method, ...params).awaitAll()
|
return this.mapCall(method, ...params).awaitAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,10 @@ export type MethodsOf<T, TMethod = (...args: any[]) => any> = {
|
|||||||
[K in keyof T]: T[K] extends TMethod ? K : never
|
[K in keyof T]: T[K] extends TMethod ? K : never
|
||||||
}[keyof T]
|
}[keyof T]
|
||||||
|
|
||||||
|
export type MethodType<TClass, TKey extends keyof TClass, TMethod = (...args: any[]) => any> = {
|
||||||
|
[K in keyof TClass]: TClass[K] extends TMethod ? TClass[K] : never
|
||||||
|
}[TKey]
|
||||||
|
|
||||||
export type Awaited<T> = T extends PromiseLike<infer U> ? U : T
|
export type Awaited<T> = T extends PromiseLike<infer U> ? U : T
|
||||||
|
|
||||||
export type Integer = TypeTag<'@extollo/lib.Integer'> & number
|
export type Integer = TypeTag<'@extollo/lib.Integer'> & number
|
||||||
|
Loading…
Reference in New Issue
Block a user