updates
This commit is contained in:
parent
e7a6dcf601
commit
a41ead28ca
@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,iDAAiD;AAEjD,2BAA6B;AAC7B,8CAAmD;AACnD,6BAA6B;AAE7B,+BAGc;AAGD,QAAA,QAAQ,GAAG,CAAO,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAmB,EAAE;IAEnE,MAAM,GAAG,GAAG,IAAI,uCAAiC,CAAC;QAC9C,QAAQ,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,wDAAwD,CAAC;QAChE,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,QAAQ;KACrB,CAAC,CAAC;AACP,CAAC,CAAA,CAAA;AAED,MAAa,OAAO;;AAApB,0BAyBC;AAxBU,aAAK,GAAG,IAAI,CAAC;AACb,iBAAS,GAAG,IAAI,CAAC;AACjB,YAAI,GAAG,CAAO,OAAe,EAAE,QAAgB,EAAE,QAAgB,EAAE,EAAE;IACxE,MAAM,KAAK,GAAG,MAAM,gBAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC1D,aAAO,CAAC;QACJ,OAAO,EAAE;YACL,eAAe,EAAE,UAAU,KAAK,EAAE;SACrC;KACJ,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,OAAO,CAAC,SAAS,GAAG,IAAI,aAAa,CAAC,aAAa,CAAC;QAChD,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,KAAK;KACrB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,KAAK,CAAC;AACzB,CAAC,CAAA,CAAA;AAEM,uBAAe,GAAG,CAAO,WAAmB,EAAE,OAAO,EAAE,EAAE;IAC5D,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;QACxB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAO,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAC5D;IACD,MAAM,OAAO,GAAQ,WAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACnE,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxF,CAAC,CAAA,CAAA;AAEQ,QAAA,IAAI,GAAG,GAAS,EAAE;IAE3B,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAc,EAAE,EAAE;QAChD,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,eAAe,CAAC,iBAAiB,EAAE,uCAAuC,CAAC,CAAC;IAC1F,4EAA4E;IAC5E,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,+BAAyB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC,CAAA,CAAA;AAED,+BAA6B"}
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,iDAAiD;AACjD,2BAA6B;AAC7B,8CAAmD;AACnD,6BAA6B;AAE7B,+BAGc;AAGD,QAAA,QAAQ,GAAG,CAAO,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAmB,EAAE;IAEnE,MAAM,GAAG,GAAG,IAAI,uCAAiC,CAAC;QAC9C,QAAQ,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,wDAAwD,CAAC;QAChE,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,QAAQ;KACrB,CAAC,CAAC;AACP,CAAC,CAAA,CAAA;AAED,MAAa,OAAO;;AAApB,0BAyBC;AAxBU,aAAK,GAAG,IAAI,CAAC;AACb,iBAAS,GAAG,IAAI,CAAC;AACjB,YAAI,GAAG,CAAO,OAAe,EAAE,QAAgB,EAAE,QAAgB,EAAE,EAAE;IACxE,MAAM,KAAK,GAAG,MAAM,gBAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC1D,aAAO,CAAC;QACJ,OAAO,EAAE;YACL,eAAe,EAAE,UAAU,KAAK,EAAE;SACrC;KACJ,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,OAAO,CAAC,SAAS,GAAG,IAAI,aAAa,CAAC,aAAa,CAAC;QAChD,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,KAAK;KACrB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,KAAK,CAAC;AACzB,CAAC,CAAA,CAAA;AAEM,uBAAe,GAAG,CAAO,WAAmB,EAAE,OAAO,EAAE,EAAE;IAC5D,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;QACxB,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAO,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAC5D;IACD,MAAM,OAAO,GAAQ,WAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACnE,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;AACxF,CAAC,CAAA,CAAA;AAEQ,QAAA,IAAI,GAAG,GAAS,EAAE;IAE3B,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAc,EAAE,EAAE;QAChD,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,eAAe,CAAC,iBAAiB,EAAE,uCAAuC,CAAC,CAAC;IAC1F,4EAA4E;IAC5E,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,+BAAyB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC,CAAA,CAAA;AAED,+BAA6B"}
|
||||||
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plastichub/magento",
|
"name": "@plastichub/magento",
|
||||||
"version": "1.0.15",
|
"version": "1.0.16",
|
||||||
"description": "",
|
"description": "",
|
||||||
"types": "./index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@ -29,14 +29,10 @@
|
|||||||
"@plastichub/fs": "^0.13.25",
|
"@plastichub/fs": "^0.13.25",
|
||||||
"chalk": "^2.4.1",
|
"chalk": "^2.4.1",
|
||||||
"cli-spinners": "^2.6.0",
|
"cli-spinners": "^2.6.0",
|
||||||
"defaults": "^1.0.3",
|
|
||||||
"fast-glob": "^3.1.1",
|
"fast-glob": "^3.1.1",
|
||||||
"ora": "^2.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"portable-fetch": "^3.0.0",
|
"ora": "^6.0.1",
|
||||||
"readline": "^1.3.0",
|
"typescript": "^3.9.10"
|
||||||
"typescript": "^3.9.10",
|
|
||||||
"yargs": "^15.0.2",
|
|
||||||
"yarn": "^1.22.10"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bevry/links": "^1.1.1",
|
"@bevry/links": "^1.1.1",
|
||||||
|
|||||||
13
src/_cli.ts
13
src/_cli.ts
@ -1,13 +0,0 @@
|
|||||||
// tweaks and handlers
|
|
||||||
export const defaults = () => {
|
|
||||||
// default command
|
|
||||||
const DefaultCommand = 'summary';
|
|
||||||
if (process.argv.length === 2) {
|
|
||||||
process.argv.push(DefaultCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently no default handler, display only :
|
|
||||||
process.on('unhandledRejection', (reason: string) => {
|
|
||||||
console.error('Unhandled rejection, reason: ', reason);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
55
src/argv.ts
55
src/argv.ts
@ -1,55 +0,0 @@
|
|||||||
import * as CLI from 'yargs';
|
|
||||||
import {
|
|
||||||
warn, error,
|
|
||||||
default_path,
|
|
||||||
Options, OutputFormat, OutputTarget, inspect
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
|
|
||||||
const LIGHT = 'http://google.co.uk';
|
|
||||||
const HEAVY = 'http://0.0.0.0:5555/app/xcf?debug=true&xblox=debug&xgrid=debug&davinci=debug&userDirectory=/PMaster/x4mm/user;'
|
|
||||||
|
|
||||||
// default options for all commands
|
|
||||||
export const defaultOptions = (yargs: CLI.Argv) => {
|
|
||||||
return yargs.option('url', {
|
|
||||||
default: LIGHT,
|
|
||||||
describe: 'The URL to analyze'
|
|
||||||
}).option('format', {
|
|
||||||
default: 'text',
|
|
||||||
describe: 'Normal human readable text or JSON [text|json]'
|
|
||||||
}).option('target', {
|
|
||||||
default: 'console',
|
|
||||||
describe: 'Output target [console|file]'
|
|
||||||
}).option('path', {
|
|
||||||
default: '',
|
|
||||||
describe: 'The target location on the local filesystem for --target=file'
|
|
||||||
}).option('debug', {
|
|
||||||
default: 'false',
|
|
||||||
describe: 'Enable internal debug message'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sanitizes faulty user argv options for all commands.
|
|
||||||
export const sanitize = (argv: CLI.Arguments): Options => {
|
|
||||||
const args = argv as Options;
|
|
||||||
args.cwd = args.cwd || process.cwd();
|
|
||||||
if (!args.url) {
|
|
||||||
// internal user error, should never happen!
|
|
||||||
error('Invalid url, abort');
|
|
||||||
return process.exit();
|
|
||||||
}
|
|
||||||
// path given but target is not file, correct to file
|
|
||||||
if (args.path && args.target !== OutputTarget.FILE) {
|
|
||||||
args.target = OutputTarget.FILE;
|
|
||||||
}
|
|
||||||
// target is file but no path given, correct to default file
|
|
||||||
if (args.target === OutputTarget.FILE && !args.path) {
|
|
||||||
args.path = default_path(args.cwd, args.url);
|
|
||||||
}
|
|
||||||
// format string not valid
|
|
||||||
if (!(argv.format as string in OutputFormat)) {
|
|
||||||
warn(`Unknown output format ${argv.format}! Default to ${OutputFormat.text}`);
|
|
||||||
args.format = OutputFormat.text;
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
};
|
|
||||||
1
src/custom.d.ts
vendored
1
src/custom.d.ts
vendored
@ -1,2 +1 @@
|
|||||||
declare module 'portable-fetch';
|
|
||||||
declare module 'url';
|
declare module 'url';
|
||||||
@ -1,18 +0,0 @@
|
|||||||
export const sizeToString = (bytes: number, si: boolean = true) => {
|
|
||||||
var units;
|
|
||||||
var u;
|
|
||||||
var b = bytes;
|
|
||||||
var thresh = si ? 1000 : 1024;
|
|
||||||
if (Math.abs(b) < thresh) {
|
|
||||||
return b + ' B';
|
|
||||||
}
|
|
||||||
units = si
|
|
||||||
? ['kB', 'MB', 'GB', 'TB']
|
|
||||||
: ['KiB', 'MiB', 'GiB', 'TiB'];
|
|
||||||
u = -1;
|
|
||||||
do {
|
|
||||||
b /= thresh;
|
|
||||||
++u;
|
|
||||||
} while (Math.abs(b) >= thresh && u < units.length - 1);
|
|
||||||
return b.toFixed(1) + ' ' + units[u];
|
|
||||||
};
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import * as configuration from './configuration';
|
import * as configuration from './configuration';
|
||||||
import * as debug from '@plastichub/core/debug';
|
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { sync as read } from '@plastichub/fs/read';
|
import { sync as read } from '@plastichub/fs/read';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
# http://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = tab
|
|
||||||
indent_size = 4
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[{package.json,.travis.yml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
21
src/lib/core/.gitattributes
vendored
21
src/lib/core/.gitattributes
vendored
@ -1,21 +0,0 @@
|
|||||||
# Set the default behavior, in case users don't have core.autocrlf set
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
# Files that should always be normalized and converted to native line
|
|
||||||
# endings on checkout.
|
|
||||||
*.js text
|
|
||||||
*.json text
|
|
||||||
*.ts text
|
|
||||||
*.md text
|
|
||||||
*.yml text
|
|
||||||
LICENSE text
|
|
||||||
|
|
||||||
# Files that are truly binary and should not be modified
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.jar binary
|
|
||||||
*.zip binary
|
|
||||||
*.psd binary
|
|
||||||
*.enc binary
|
|
||||||
7
src/lib/core/.github/CONTRIBUTING.md
vendored
7
src/lib/core/.github/CONTRIBUTING.md
vendored
@ -1,7 +0,0 @@
|
|||||||
# Thank You
|
|
||||||
|
|
||||||
We very much welcome contributions to Dojo 2.
|
|
||||||
|
|
||||||
Because we have so many repositories that are part of Dojo 2, we have located our [Contributing Guidelines](https://github.com/dojo/meta/blob/master/CONTRIBUTING.md) in our [Dojo 2 Meta Repository](https://github.com/dojo/meta#readme).
|
|
||||||
|
|
||||||
Look forward to working with you on Dojo 2!!!
|
|
||||||
28
src/lib/core/.github/ISSUE_TEMPLATE.md
vendored
28
src/lib/core/.github/ISSUE_TEMPLATE.md
vendored
@ -1,28 +0,0 @@
|
|||||||
<!--
|
|
||||||
Thank you for contributing to Dojo 2.
|
|
||||||
|
|
||||||
Our issue tracker is for bugs for Dojo 2.
|
|
||||||
|
|
||||||
Please make sure you have read our Contributing Guidelines
|
|
||||||
available at: https://github.com/dojo/meta/blob/master/CONTRIBUTING.md
|
|
||||||
|
|
||||||
For general questions and discussion, join us on Gitter.im at: https://gitter.im/dojo/dojo2
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Bug / Enhancement** <!-- delete as appropriate -->
|
|
||||||
|
|
||||||
<!-- Summary of enhancement or bug-->
|
|
||||||
|
|
||||||
Package Version: <!-- package version -->
|
|
||||||
|
|
||||||
**Code**
|
|
||||||
|
|
||||||
<!-- a self contained example of code that demonstrates the issue -->
|
|
||||||
|
|
||||||
**Expected behavior:**
|
|
||||||
|
|
||||||
<!-- What did you expect to happen -->
|
|
||||||
|
|
||||||
**Actual behavior:**
|
|
||||||
|
|
||||||
<!-- What was the actual behavior? -->
|
|
||||||
20
src/lib/core/.github/PULL_REQUEST_TEMPLATE.md
vendored
20
src/lib/core/.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,20 +0,0 @@
|
|||||||
**Type:** bug / feature
|
|
||||||
|
|
||||||
The following has been addressed in the PR:
|
|
||||||
|
|
||||||
* [ ] There is a related issue
|
|
||||||
* [ ] All code has been formatted with [`prettier`](https://prettier.io/) as per the [readme code style guidelines](./../#code-style)
|
|
||||||
* [ ] Unit or Functional tests are included in the PR
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Our bots should ensure:
|
|
||||||
|
|
||||||
* [ ] All contributors have signed a CLA
|
|
||||||
* [ ] The PR passes CI testing
|
|
||||||
* [ ] Code coverage is maintained
|
|
||||||
* [ ] The PR has been reviewed and approved
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Description:**
|
|
||||||
|
|
||||||
Resolves #???
|
|
||||||
18
src/lib/core/.gitignore
vendored
18
src/lib/core/.gitignore
vendored
@ -1,18 +0,0 @@
|
|||||||
.sublimets
|
|
||||||
.tscache
|
|
||||||
.tsconfig*.json
|
|
||||||
*.js
|
|
||||||
*.js.map
|
|
||||||
!/*.js
|
|
||||||
dist
|
|
||||||
deploy_key
|
|
||||||
node_modules
|
|
||||||
/typings/**
|
|
||||||
bower_components
|
|
||||||
tests/typings/dist/
|
|
||||||
.baseDir.ts
|
|
||||||
html-report
|
|
||||||
coverage-final.lcov
|
|
||||||
coverage-unmapped.json
|
|
||||||
/_build
|
|
||||||
/_apidoc
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/_build
|
|
||||||
/docs
|
|
||||||
/html-report
|
|
||||||
/resources
|
|
||||||
/support
|
|
||||||
/tests
|
|
||||||
/typings
|
|
||||||
.editorconfig
|
|
||||||
.gitattributes
|
|
||||||
.gitignore
|
|
||||||
.npmignore
|
|
||||||
.travis.yml
|
|
||||||
.tscache
|
|
||||||
.vscode
|
|
||||||
Gruntfile.js
|
|
||||||
tsconfig.json
|
|
||||||
tslint.json
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- '6'
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
# Please get your own free key if you want to test yourself
|
|
||||||
- SAUCE_USERNAME: dojo2-ts-ci
|
|
||||||
- SAUCE_ACCESS_KEY: e92610e3-834e-4bec-a3b5-6f7b9d874601
|
|
||||||
- BROWSERSTACK_USERNAME: dylanschiemann2
|
|
||||||
- BROWSERSTACK_ACCESS_KEY: 4Q2g8YAc9qeZzB2hECnS
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- openssl
|
|
||||||
before_install:
|
|
||||||
- if [ ${TRAVIS_BRANCH-""} == "master" ] && [ -n ${encrypted_12c8071d2874_key-""} ]; then openssl aes-256-cbc -K
|
|
||||||
$encrypted_12c8071d2874_key -iv $encrypted_12c8071d2874_iv
|
|
||||||
-in deploy_key.enc -out deploy_key -d; fi
|
|
||||||
install:
|
|
||||||
- travis_retry npm install grunt-cli
|
|
||||||
- travis_retry npm install
|
|
||||||
script:
|
|
||||||
- grunt
|
|
||||||
- grunt intern:browserstack --test-reporter
|
|
||||||
- grunt uploadCoverage
|
|
||||||
- grunt dist
|
|
||||||
- if [ "$TRAVIS_BRANCH" = "master" ]; then grunt doc; fi
|
|
||||||
notifications:
|
|
||||||
slack:
|
|
||||||
secure: ZuM/LrujGyUs8r/KGYtxaqRHNVyAMhQi+nrbfhxxqrfXqfFoikMqwGSlY4728MvOEj3ptJhcqjxHTp+rnajXTHkEXJ6y7yfIGekd5523dGVooESIu6oFjmh0iIVxJWLhxQ+DwABRYX0FsPAIMZCCVuK/0EM6Pw00uPwiPjmeRkylV+lUkEoWkuKCTxuaBDgY4ITDGprDVGmtJRQp7Weov4IOlfGLVdoupVd1AmXq5OyvpW4fVtBKR+aph0jAqSVOMFIas77njKKoSPzxGtqf1110MQ1QJc7yTY6J6XFZEPpv15cRjo/LRZFx3xf/hwAufxnZR0L1bxvuVG9mfn4q8m1Wgq6JyoWfg+DC12KSfo9Y2MJCIi/mAh5NkiP6wzsryhnQVLDKlnN5P611DuxeImsxYXMoZMqoZ/eT2djdm2PkJCwGilCRM1aTU2dfqpwbdM4aD88FzCfICigNuaIXhzinDrrBQtIn6EoRkqw0T1lplMxeG+lt2SLpcPsZmtjNVrFqb8lNQPm1RWOuo9toCPYeKnIBZt3wzTuEoj27nTVarNLQK1S74IyP8kKvnjtEeUGD0/a4ow9FnUCnARNfSx62ksB5sJV5DV4zi5tzuowTZsvyUM6tpt1U3SiKEIDIteurdS1G7vCNArucvP5cxCVqYbrgaFdRmJMEwvrAqXc=
|
|
||||||
on_success: change
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
The [Contributing Guidelines](https://github.com/dojo/meta/blob/master/CONTRIBUTING.md)
|
|
||||||
for all Dojo 2 packages can be found in the [Dojo 2 Meta Repository](https://github.com/dojo/meta#readme).
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
module.exports = function (grunt) {
|
|
||||||
require('grunt-dojo2').initConfig(grunt, {
|
|
||||||
dtsGenerator: {
|
|
||||||
options: {
|
|
||||||
main: 'dojo-core/main'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
typedoc: {
|
|
||||||
options: {
|
|
||||||
ignoreCompilerErrors: true, // Remove this once compile errors are resolved
|
|
||||||
}
|
|
||||||
},
|
|
||||||
intern: {
|
|
||||||
version: 4
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
The "New" BSD License
|
|
||||||
*********************
|
|
||||||
|
|
||||||
Copyright (c) 2015 - 2017, [JS Foundation](https://js.foundation/)
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the JS Foundation nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Code for string.codePointAt, string.fromCodePoint, and string.repeat
|
|
||||||
adapted from polyfills by Mathias Bynens, under the MIT license:
|
|
||||||
|
|
||||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
## The `@dojo/core` repository has been deprecated and merged into [`@dojo/framework`](https://github.com/dojo/framework)
|
|
||||||
|
|
||||||
You can read more about this change on our [blog](https://dojo.io/blog/). We will continue providing patches for `core` and other Dojo 2 repositories, and a [CLI migration tool](https://github.com/dojo/cli-upgrade) is available to aid in migrating projects from v2 to v3.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
# Dojo 2 core
|
|
||||||
|
|
||||||
[](https://travis-ci.org/dojo/core)
|
|
||||||
[](https://codecov.io/github/dojo/core?branch=master)
|
|
||||||
[](https://badge.fury.io/js/%40dojo%2Fcore)
|
|
||||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fdojo%2Fcore?ref=badge_shield)
|
|
||||||
|
|
||||||
This package provides a set of language helpers, utility functions, and classes for writing TypeScript applications. It includes APIs for feature detection, asynchronous operations, basic event handling,
|
|
||||||
and making HTTP requests.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To use `@dojo/core`, install the package along with its required peer dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @dojo/core
|
|
||||||
|
|
||||||
# peer dependencies
|
|
||||||
npm install @dojo/has
|
|
||||||
npm install @dojo/shim
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- [Feature Detection](#feature-detection)
|
|
||||||
- [Language Utilities](#language-utilities)
|
|
||||||
- [lang](#lang)
|
|
||||||
- [load](#load)
|
|
||||||
- [string](#string)
|
|
||||||
- [UrlSearchParams](#urlsearchparams)
|
|
||||||
- [Event Handling](#event-handling)
|
|
||||||
- [HTTP Requests](#http-requests)
|
|
||||||
- [Promises and Asynchronous Operations](#promises-and-asynchronous-operations)
|
|
||||||
- [Promise](#promise)
|
|
||||||
- [Task](#task)
|
|
||||||
|
|
||||||
### Feature Detection
|
|
||||||
|
|
||||||
Using the latest Web technologies isn't always as straightforward as developers would like due to differing support across platforms. [`@dojo/core/has`](docs/has.md) provides a simple feature detection API that makes it easy to
|
|
||||||
detect which platforms support which features.
|
|
||||||
|
|
||||||
### Language Utilities
|
|
||||||
|
|
||||||
The core package provides modules offering language utilities. Some of these are heavily based
|
|
||||||
on methods in the ES2015 proposal; others are additional APIs for commonly-performed tasks.
|
|
||||||
|
|
||||||
#### lang
|
|
||||||
|
|
||||||
The [`@dojo/core/lang` module](docs/lang.md) contains various utility functions for tasks such as copying objects
|
|
||||||
and creating late-bound or partially applied functions.
|
|
||||||
|
|
||||||
### load
|
|
||||||
The [`@dojo/core/load` module](docs/load.md) can be used to dynamically load modules or other arbitrary resources via plugins.
|
|
||||||
|
|
||||||
#### string
|
|
||||||
|
|
||||||
The [`@dojo/core/stringExtras` module](docs/stringExtras.md) contains various string functions that are not available as part of the ES2015 String APIs.
|
|
||||||
|
|
||||||
#### UrlSearchParams
|
|
||||||
|
|
||||||
The [`@dojo/core/UrlSearchParams` class](docs/UrlSearchParams.md) can be used to parse and generate URL query strings.
|
|
||||||
|
|
||||||
#### Event handling
|
|
||||||
|
|
||||||
The [`@dojo/core/on` module](docs/on.md) contains methods to handle events across types of listeners. It also includes methods to handle different event use cases including only firing
|
|
||||||
once and pauseable events.
|
|
||||||
|
|
||||||
#### HTTP requests
|
|
||||||
|
|
||||||
The [`@dojo/core/request` module](docs/request.md) contains methods to simplify making HTTP requests. It can handle
|
|
||||||
making requests in both node and the browser through the same methods.
|
|
||||||
|
|
||||||
### Promises and Asynchronous Operations
|
|
||||||
|
|
||||||
#### Promise
|
|
||||||
|
|
||||||
The `@dojo/core/Promise` class is an implementation of the ES2015 Promise API that also includes static state inspection and a `finally` method for cleanup actions.
|
|
||||||
|
|
||||||
`@dojo/core/async` contains a number of classes and utility modules to simplify working with asynchronous operations.
|
|
||||||
|
|
||||||
#### Task
|
|
||||||
|
|
||||||
The `@dojo/core/async/Task` class is an extension of `@dojo/core/Promise` that provides cancelation support.
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
This repository uses [`prettier`](https://prettier.io/) for code styling rules and formatting. A pre-commit hook is installed automatically and configured to run `prettier` against all staged files as per the configuration in the project's `package.json`.
|
|
||||||
|
|
||||||
An additional npm script to run `prettier` (with write set to `true`) against all `src` and `test` project files is available by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run prettier
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
To start working with this package, clone the repository and run `npm install`.
|
|
||||||
|
|
||||||
In order to build the project run `grunt dev` or `grunt dist`.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
Test cases MUST be written using [Intern](https://theintern.github.io) using the Object test interface and Assert assertion interface.
|
|
||||||
|
|
||||||
90% branch coverage MUST be provided for all code submitted to this repository, as reported by istanbul’s combined coverage results for all supported platforms.
|
|
||||||
|
|
||||||
To test locally in node run:
|
|
||||||
|
|
||||||
`grunt test`
|
|
||||||
|
|
||||||
To test against browsers with a local selenium server run:
|
|
||||||
|
|
||||||
`grunt test:local`
|
|
||||||
|
|
||||||
To test against BrowserStack or Sauce Labs run:
|
|
||||||
|
|
||||||
`grunt test:browserstack`
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
`grunt test:saucelabs`
|
|
||||||
|
|
||||||
## Licensing information
|
|
||||||
|
|
||||||
© 2004–2018 [JS Foundation](https://js.foundation/) & contributors. [New BSD](http://opensource.org/licenses/BSD-3-Clause) license.
|
|
||||||
|
|
||||||
Some string functions (`codePointAt`, `fromCodePoint`, and `repeat`) adopted from polyfills by Mathias Bynens,
|
|
||||||
under the [MIT](http://opensource.org/licenses/MIT) license.
|
|
||||||
|
|
||||||
See [LICENSE](LICENSE) for details.
|
|
||||||
|
|
||||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fdojo%2Fcore?ref=badge_large)
|
|
||||||
|
|
||||||
<!-- doc-viewer-config
|
|
||||||
{
|
|
||||||
"api": "docs/api.json",
|
|
||||||
"pages": [
|
|
||||||
"docs/UrlSearchParams.md",
|
|
||||||
"docs/has.md",
|
|
||||||
"docs/lang.md",
|
|
||||||
"docs/load.md",
|
|
||||||
"docs/on.md",
|
|
||||||
"docs/request.md",
|
|
||||||
"docs/stringExtras.md"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-->
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
comment:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- feature/*
|
|
||||||
coverage:
|
|
||||||
notify:
|
|
||||||
slack:
|
|
||||||
default:
|
|
||||||
url: "secret:Edc+HC1cJt1JJoV0jwebOEPNfk5DXBFBMMWgjsFRUJUaymGWE8gQ/D8oCHq42U7cXaWEtKDc3fa4hBtdtVyZTsae/MXdu1x4HNkBuLx2ZI3WKOerVZRaJ6bXkA9EtvSXa6gjVouiGGIrnjkb4DxRS72m4bqnFKMHSGkkzgComjk="
|
|
||||||
threshold: 2
|
|
||||||
attachments: "sunburst, diff"
|
|
||||||
Binary file not shown.
@ -1,151 +0,0 @@
|
|||||||
# UrlSearchParams
|
|
||||||
|
|
||||||
The `UrlSearchParams` object makes working with URL query parameters a little easier.
|
|
||||||
|
|
||||||
## Creating a Url Search Params Object
|
|
||||||
|
|
||||||
### With search params string
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
```
|
|
||||||
|
|
||||||
### With object of search params
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams({
|
|
||||||
a: 'b',
|
|
||||||
b: 'c',
|
|
||||||
c: 'a'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Instance methods
|
|
||||||
|
|
||||||
### `append`
|
|
||||||
|
|
||||||
Adds the value to the values that are associated with the key.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const key = 'a';
|
|
||||||
const value = 'e';
|
|
||||||
|
|
||||||
searchParams.append(key, value);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `delete`
|
|
||||||
|
|
||||||
Removes the key from the search params.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const key = 'a';
|
|
||||||
|
|
||||||
searchParams.delete(key);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `get`
|
|
||||||
|
|
||||||
Retrieves the first value for the key provided.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=first&a=second');
|
|
||||||
|
|
||||||
const key = 'a';
|
|
||||||
|
|
||||||
const result = searchParams.get(key);
|
|
||||||
|
|
||||||
result === 'first'; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `getAll`
|
|
||||||
|
|
||||||
Retrieves all the values for the key provided.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=first&a=second');
|
|
||||||
|
|
||||||
const key = 'a';
|
|
||||||
|
|
||||||
const result = searchParams.getAll(key);
|
|
||||||
|
|
||||||
result[0] === 'first'; // true
|
|
||||||
result[1] === 'second'; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `has`
|
|
||||||
|
|
||||||
Returns true if the key exists within the search params and false if it is not.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const key = 'd';
|
|
||||||
|
|
||||||
const result = searchParams.has(key);
|
|
||||||
|
|
||||||
result === false; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `keys`
|
|
||||||
|
|
||||||
Returns an array of the keys of the search params.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const result = searchParams.keys();
|
|
||||||
|
|
||||||
result instanceof Array; // true
|
|
||||||
result[0] === 'a'; // true
|
|
||||||
result[1] === 'b'; // true
|
|
||||||
result[2] === 'c'; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `set`
|
|
||||||
|
|
||||||
Sets the value of a key (clears previous values).
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const key = 'a';
|
|
||||||
const value = 'e';
|
|
||||||
|
|
||||||
searchParams.set(key, value);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `toString`
|
|
||||||
|
|
||||||
Return a string of the search params.
|
|
||||||
|
|
||||||
```
|
|
||||||
import { UrlSearchParams } from '@dojo/core/UrlSearchParams';
|
|
||||||
|
|
||||||
const searchParams = new UrlSearchParams('a=b&b=c&c=a');
|
|
||||||
|
|
||||||
const result = searchParams.toString();
|
|
||||||
|
|
||||||
result === 'a=b&b=c&c=a'; // true
|
|
||||||
```
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
|||||||
# has
|
|
||||||
|
|
||||||
## Detecting Features
|
|
||||||
|
|
||||||
The default export of `dojo-core/has` is a function which accepts a single parameter: the name of the feature to test for.
|
|
||||||
If the feature is available, a truthy value is returned, otherwise a falsy value is returned:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import has from 'dojo-core/has';
|
|
||||||
|
|
||||||
if (has('dom-addeventlistener')) {
|
|
||||||
element.addEventListener('click', function () { /* ... */ });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding Feature Detections
|
|
||||||
|
|
||||||
It's important to be able to add new feature tests that aren't provided out-of-the-box by `dojo-core/has`.
|
|
||||||
This can be done easily by using the `add` function exported by the `has` module. It accepts two parameters:
|
|
||||||
the name of the feature, and either an immediate value indicating its availability or a function that resolves to a
|
|
||||||
value.
|
|
||||||
|
|
||||||
When a function is passed, the feature will be lazily evaluated - i.e. the function is not executed until the feature is
|
|
||||||
actually requested. The return value is then cached for future calls for the same feature.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { add as hasAdd } from 'dojo-core/has';
|
|
||||||
hasAdd('dom-queryselector', 'querySelector' in document && 'querySelectorAll' in document);
|
|
||||||
|
|
||||||
// Lazily executed; useful if a polyfill is loaded after page load
|
|
||||||
hasAdd('typedarray', function () {
|
|
||||||
return 'ArrayBuffer' in window;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Accessing the Feature Cache
|
|
||||||
|
|
||||||
`dojo-core/has` maintains object hashes containing keys that correspond to all features that have been both
|
|
||||||
registered _and_ requested. The value associated with each feature name key corresponds to that feature's availability
|
|
||||||
in the current environment. The object hash containing evaluated features is accessible via the `cache` export.
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
# lang
|
|
||||||
|
|
||||||
## Module Exports
|
|
||||||
|
|
||||||
### `assign`
|
|
||||||
|
|
||||||
Copies values of own properties from the source object(s) to the target object.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { assign } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var target = {
|
|
||||||
foo: 'bar'
|
|
||||||
};
|
|
||||||
|
|
||||||
var source = {
|
|
||||||
bar: 'foo'
|
|
||||||
};
|
|
||||||
|
|
||||||
assign(target, source);
|
|
||||||
|
|
||||||
target.foo === 'bar'; // true
|
|
||||||
target.bar === 'foo'; // true
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `create`
|
|
||||||
|
|
||||||
Creates a new object from the given prototype, and copies all enumerable own properties of one or more source objects to the newly created target object.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { create } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var oldObj = {
|
|
||||||
foo: 'bar',
|
|
||||||
obj: {
|
|
||||||
bar: 'foo'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var newObj = create(oldObj, {
|
|
||||||
bar: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
newObj.bar === 'foo'; // true
|
|
||||||
newObj.foo === 'bar'; // true
|
|
||||||
newObj.obj.bar === 'foo'; // true
|
|
||||||
|
|
||||||
oldObj.foo = 'foo';
|
|
||||||
oldObj.obj.bar = 'bar';
|
|
||||||
|
|
||||||
newObj.foo === 'bar'; // true
|
|
||||||
newObj.obj.bar === 'bar'; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `deepAssign`
|
|
||||||
|
|
||||||
Copies the values of all enumerable own properties of one or more source objects to the target object, recursively copying all nested objects and arrays as well.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { deepAssign } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var oldObj = {
|
|
||||||
foo: 'bar',
|
|
||||||
obj: {
|
|
||||||
bar: 'foo'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var newObj = deepAssign(oldObj, {
|
|
||||||
bar: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
newObj.bar === 'foo'; // true
|
|
||||||
newObj.foo === 'bar'; // true
|
|
||||||
newObj.obj.bar === 'foo'; // true
|
|
||||||
|
|
||||||
oldObj.foo = 'foo';
|
|
||||||
oldObj.obj.bar = 'bar';
|
|
||||||
|
|
||||||
newObj.foo === 'bar'; // true
|
|
||||||
newObj.obj.bar === 'bar'; // true
|
|
||||||
```
|
|
||||||
|
|
||||||
### `mixin`
|
|
||||||
|
|
||||||
Copies values of own and inherited properties from the source object(s) to the target object.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { mixin } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
foo: 'bar',
|
|
||||||
fooObj: {
|
|
||||||
bar: 'foo'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = mixin({}, obj);
|
|
||||||
|
|
||||||
result.foo === 'bar'; // true
|
|
||||||
result.fooObj.bar === 'foo'; // true
|
|
||||||
|
|
||||||
obj.fooObj.bar = 'bar';
|
|
||||||
|
|
||||||
result.fooObj.bar === 'bar'; // true
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `deepMixin`
|
|
||||||
|
|
||||||
Copies the values of all enumerable (own or inherited) properties of one or more source objects to the target object, recursively copying all nested objects and arrays as well.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { deepMixin } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
foo: 'bar',
|
|
||||||
fooObj: {
|
|
||||||
bar: 'foo'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = deepMixin({}, obj);
|
|
||||||
|
|
||||||
result.foo === 'bar'; // true
|
|
||||||
result.fooObj.bar === 'foo'; // true
|
|
||||||
|
|
||||||
obj.fooObj.bar = 'bar';
|
|
||||||
|
|
||||||
result.fooObj.bar === 'bar'; // false
|
|
||||||
result.fooObj.bar === 'foo'; // true
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `duplicate`
|
|
||||||
|
|
||||||
Creates a new object using the provided source's prototype as the prototype for the new object, and then deep copies the provided source's values into the new target.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { duplicate } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var oldObj = {
|
|
||||||
foo: 'bar'
|
|
||||||
};
|
|
||||||
|
|
||||||
var newObj = duplicate(oldObj);
|
|
||||||
|
|
||||||
oldObj.foo = 'foo';
|
|
||||||
|
|
||||||
oldObj.foo === 'foo';
|
|
||||||
newObj.foo === 'bar';
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `partial`
|
|
||||||
|
|
||||||
Returns a function which invokes the given function with the given arguments prepended to its argument list. Like `Function.prototype.bind`, but does not alter execution context.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { partial } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var add = function (a, b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
var addFive = partial(add, 5);
|
|
||||||
|
|
||||||
var result = addFive(4);
|
|
||||||
|
|
||||||
result === 9;
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `isIdentical`
|
|
||||||
|
|
||||||
Determines whether two values are the same (including NaN).
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { isIdentical } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
isIdentical(1, 1); // true
|
|
||||||
isIdentical(NaN, NaN); // true
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### `lateBind`
|
|
||||||
|
|
||||||
Creates a function that calls the current method on an object with given arguments.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { lateBind } from '@dojo/core/lang';
|
|
||||||
|
|
||||||
var person = {
|
|
||||||
speak: function (name) {
|
|
||||||
return 'hi, ' + name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var personSpeak = lateBind(person, 'speak', 'name');
|
|
||||||
|
|
||||||
personSpeak() === 'hi, name'; // true
|
|
||||||
|
|
||||||
person.speak = function (name) {
|
|
||||||
return 'bye, ' + name;
|
|
||||||
};
|
|
||||||
|
|
||||||
personSpeak() === 'bye, name';
|
|
||||||
|
|
||||||
```
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
# load
|
|
||||||
|
|
||||||
## Module Exports
|
|
||||||
|
|
||||||
### 'isPlugin'
|
|
||||||
|
|
||||||
Tests a value to determine whether is a plugin (an object with a `load` method).
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { isPlugin } from '@dojo/core/load';
|
|
||||||
|
|
||||||
// true
|
|
||||||
isPlugin({
|
|
||||||
load() {}
|
|
||||||
normalize() {}
|
|
||||||
});
|
|
||||||
|
|
||||||
isPlugin(1); // false
|
|
||||||
isPlugin([]); // false
|
|
||||||
isPlugin([]); // false
|
|
||||||
// false
|
|
||||||
isPlugin({
|
|
||||||
observer() {}
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 'load'
|
|
||||||
|
|
||||||
Dynamically loads a module or other resource.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import load, { useDefault } from '@dojo/core/load';
|
|
||||||
|
|
||||||
// Load a single module
|
|
||||||
load('mymodule').then(([ myModule ]: [ any ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load multiple modules
|
|
||||||
load('namespace/first', 'namespace/second').then(([ first, second ]: [ any, any ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load modules with relative ids as relative to the current module
|
|
||||||
load(require, './first', './second').then(([ first, second ]: [ any, any ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Automatically map modules to their default export
|
|
||||||
load('namespace/first').then(useDefault).then(([ first ]: [ any ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load a custom resource via a plugin
|
|
||||||
load(require, 'plugin!./template.html').then(([ html ]: [ string ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using Plugins
|
|
||||||
|
|
||||||
AMD-style plugins can be used with `load` by passing in module ids with the format `{pluginId}!{resourceId}`. First, the plugin will be loaded, and then the resource id (the string that follows the `!` in the mid passed to `core/load`) will be normalized and passed to the plugin's `load` method. If the plugin module does not actually have a `load` method, then the resource id is ignored, and the module is returned as-is. Plugins can also expose an optional `normalize` method that is passed the resource id and a resolver method (which will be either `require.toUrl`, `require.resolve`, or an identity function, depending on the environment). Note that if no `normalize` method is provided, then the provided resource id will be resolved using `require.toUrl` or `require.resolve`, depending on the environment. Again, if the plugin module has a default export, the `normalize` method MUST exist on that object.
|
|
||||||
|
|
||||||
Note: the plugins that can be used with `load` loosely follow the [amdjs plugin API](https://github.com/amdjs/amdjs-api/blob/master/LoaderPlugins.md), with the following exceptions:
|
|
||||||
|
|
||||||
1. The plugin's `load` method does not receive a contextual `require` or a configuration object.
|
|
||||||
2. Rather than execute a callback when the resource has loaded, the plugin's `load` method instead must return a promise that resolves to that resource.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Plugin that does not use the default export.
|
|
||||||
import { Load } from '@dojo/core/load';
|
|
||||||
|
|
||||||
export function normalize(resourceId: string, resolver: (id: string) => string): string {
|
|
||||||
return resolver(resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function load(resourceId: string, load: Load) {
|
|
||||||
// This plugin does nothing more than load the resource id with `core/load`.
|
|
||||||
return load(resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// The same plugin, but using the default export
|
|
||||||
import { Load } from '@dojo/core/load';
|
|
||||||
|
|
||||||
const plugin = {
|
|
||||||
normalize(resourceId: string, resolver: (id: string) => string): string {
|
|
||||||
return resolver(resourceId);
|
|
||||||
},
|
|
||||||
|
|
||||||
load(resourceId: string, load: Load) {
|
|
||||||
// This plugin does nothing more than load the resource id with `core/load`.
|
|
||||||
return load(resourceId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export default plugin;
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import load from '@dojo/core/load';
|
|
||||||
|
|
||||||
// 1. The module with the id `plugin` is loaded.
|
|
||||||
// 2. If `plugin` is not actually a plugin, then `plugin` itself is returned.
|
|
||||||
// 3. If the plugin has a normalize method, then "some/resource/id" is passed to it,
|
|
||||||
// and the return value is used as the resource id.
|
|
||||||
// 4. The resource id is passed to the plugin's `load` method.
|
|
||||||
// 5. The loaded resource is used to resolve the `load` promise.
|
|
||||||
load('plugin!some/resource/id').then(([ resource ]: [ any ]) => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# On
|
|
||||||
|
|
||||||
`dojo-core/on` provides event handling support with methods to attach and emit events.
|
|
||||||
|
|
||||||
## `emit`
|
|
||||||
|
|
||||||
Dispatch event to target.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { emit } from '@dojo/core/on';
|
|
||||||
|
|
||||||
var button = document.getElementById('button');
|
|
||||||
var DOMEventObject = {
|
|
||||||
type: 'click',
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true
|
|
||||||
};
|
|
||||||
|
|
||||||
emit(button, DOMEventObject);
|
|
||||||
|
|
||||||
```
|
|
||||||
## `on`
|
|
||||||
|
|
||||||
Adds event listener to target.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { on } from '@dojo/core/on';
|
|
||||||
|
|
||||||
var button = document.getElementById('button');
|
|
||||||
|
|
||||||
on(button, 'click', function (event) {
|
|
||||||
console.log(event.target.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
## `once`
|
|
||||||
|
|
||||||
Attach an event that can only be called once to a target.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { once } from '@dojo/core/on';
|
|
||||||
|
|
||||||
var button = document.getElementById('button');
|
|
||||||
once(button, 'click', function (event) {
|
|
||||||
console.log(event.target.id);
|
|
||||||
console.log('this event has been removed')
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
## `pausable`
|
|
||||||
|
|
||||||
Attach an event that can be paused to a target.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { pausable } from '@dojo/core/on';
|
|
||||||
|
|
||||||
var button = document.getElementById('button');
|
|
||||||
var buttonClickHandle = pausable(button, 'click', function (event) {
|
|
||||||
console.log(event.target.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonClickHandle.pause(); // when paused the event will not fire
|
|
||||||
buttonClickHandle.resume(); // after resuming the event will begin to fire again if triggered
|
|
||||||
|
|
||||||
```
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
# request
|
|
||||||
|
|
||||||
This modules provides 4 methods (get, post, delete, and put) to simplify sending http requests. Each of these methods returns a promise that resolves with the response.
|
|
||||||
|
|
||||||
* request
|
|
||||||
* get
|
|
||||||
* post
|
|
||||||
* delete
|
|
||||||
* put
|
|
||||||
|
|
||||||
## Making Requests
|
|
||||||
|
|
||||||
Making requests is similar to using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
|
||||||
|
|
||||||
A GET request,
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const json = await request('http://www.example.com').then(response => response.json());
|
|
||||||
```
|
|
||||||
|
|
||||||
A POST request,
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const response = await request.post('http://www.example.com', { body: JSON.stringify(myValues)}).then(response => response.json());
|
|
||||||
```
|
|
||||||
|
|
||||||
## Observables
|
|
||||||
|
|
||||||
Several observables are available to provide deeper insight into the state of a request.
|
|
||||||
|
|
||||||
### Monitoring Upload Progress
|
|
||||||
|
|
||||||
Upload progress can be monitored with the `upload` observable on the `Request` object. Since upload events automatically cause a preflight request, they can be disabled by setting `includeUploadProgress: false`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const req = request.post('http://www.example.com/', {
|
|
||||||
body: someLargeString
|
|
||||||
});
|
|
||||||
|
|
||||||
req.upload.subscribe(totalUploadedBytes => {
|
|
||||||
// do something with uploaded bytes
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that while the Node.js provider will emit a single upload event when it is done uploading, it cannot emit more granular upload events with `string` or `Buffer` body types. To receive more frequent upload events, you can use the `bodyStream` option to provide a `Readable` with the body content. Upload events will be emitted as the data is read from the stream.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
request.post('http://www.example.com/', {
|
|
||||||
bodyStream: fs.createReadStream('some-large-file')
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring Download Progress
|
|
||||||
|
|
||||||
You can monitor download progress by subscribing to the `download` observable on the `Response` object.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
request("http://www.example/some-large-file").then(response => {
|
|
||||||
response.download.subscribe(totalBytesDownloaded => {
|
|
||||||
// do something with totalBytesDownloaded
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Receiving Raw Data
|
|
||||||
|
|
||||||
You can receive the raw data from a response with the `data` observable. Depending on the provider, the value might be a `string`, or a `Buffer`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
request("http://www.example/some-large-file").then(response => {
|
|
||||||
response.data.subscribe(chunk => {
|
|
||||||
// do something with chunk
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
# stringExtras
|
|
||||||
|
|
||||||
## `escapeRegExp`
|
|
||||||
|
|
||||||
Escapes a string to safely be included in regular expressions.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { escapeRegExp } from '@dojo/core/string';
|
|
||||||
|
|
||||||
const str = 'cat file.js | grep -c';
|
|
||||||
|
|
||||||
const result = escapeRegExp(str);
|
|
||||||
|
|
||||||
result === 'cat file\\.js \\| grep -c'; // true
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## `escapeXml`
|
|
||||||
|
|
||||||
Escapes XML (or HTML) content in a string.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { escapeXml } from '@dojo/core/string';
|
|
||||||
|
|
||||||
const badCode = "<script>alert('hi')</script>";
|
|
||||||
|
|
||||||
const sanitized = escapeXml(badCode);
|
|
||||||
|
|
||||||
sanitized === '<script>alert('hi')</script>'; // true
|
|
||||||
```
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"capabilities+": {
|
|
||||||
"project": "Dojo 2",
|
|
||||||
"name": "@dojo/core",
|
|
||||||
"fixSessionCapabilities": false,
|
|
||||||
"browserstack.debug": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"environments": [
|
|
||||||
{ "browserName": "node" }
|
|
||||||
],
|
|
||||||
|
|
||||||
"suites": [
|
|
||||||
"./_build/tests/unit/all.js"
|
|
||||||
],
|
|
||||||
|
|
||||||
"functionalSuites": [
|
|
||||||
"./_build/tests/functional/all.js"
|
|
||||||
],
|
|
||||||
|
|
||||||
"plugins": [
|
|
||||||
"./_build/tests/plugins/echo-service.js"
|
|
||||||
],
|
|
||||||
|
|
||||||
"browser": {
|
|
||||||
"loader": "./node_modules/grunt-dojo2/lib/intern/internLoader.js",
|
|
||||||
"suites+": [
|
|
||||||
"./_build/tests/unit/all-browser.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"node": {
|
|
||||||
"suites+": [
|
|
||||||
"./_build/tests/unit/all-node.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"coverage": [
|
|
||||||
"./_build/src/**/*.js"
|
|
||||||
],
|
|
||||||
|
|
||||||
"configs": {
|
|
||||||
"browserstack": {
|
|
||||||
"tunnel": "browserstack",
|
|
||||||
|
|
||||||
"environments+": [
|
|
||||||
{ "browserName": "internet explorer", "version": "11", "os": "WINDOWS", "os_version": [ "8.1", "10" ] },
|
|
||||||
{ "browserName": "edge" },
|
|
||||||
{ "browserName": "firefox", "platform": "WINDOWS" },
|
|
||||||
{ "browserName": "chrome", "platform": "WINDOWS" },
|
|
||||||
{ "browserName": "safari", "version": "10.1", "platform": "MAC" }
|
|
||||||
],
|
|
||||||
|
|
||||||
"maxConcurrency": 5
|
|
||||||
},
|
|
||||||
|
|
||||||
"node-loader": {
|
|
||||||
"node": {
|
|
||||||
"loader": "./node_modules/grunt-dojo2/lib/intern/internLoader.js",
|
|
||||||
"suites+": [
|
|
||||||
"./_build/tests/unit/all-node-loader.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"local": {
|
|
||||||
"tunnel": "selenium",
|
|
||||||
"tunnelOptions": {
|
|
||||||
"hostname": "localhost",
|
|
||||||
"port": 4444
|
|
||||||
},
|
|
||||||
|
|
||||||
"environments+": [
|
|
||||||
{ "browserName": "chrome" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"saucelabs": {
|
|
||||||
"tunnel": "saucelabs",
|
|
||||||
"tunnelOptions": {},
|
|
||||||
|
|
||||||
"defaultTimeout": 10000,
|
|
||||||
"environments+": [
|
|
||||||
{ "browserName": "internet explorer", "version": [ "11.0" ], "platform": "Windows 7" },
|
|
||||||
{ "browserName": "firefox", "version": "43", "platform": "Windows 10" },
|
|
||||||
{ "browserName": "chrome", "platform": "Windows 10" }
|
|
||||||
],
|
|
||||||
"maxConcurrency": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@dojo/core",
|
|
||||||
"version": "2.0.1-pre",
|
|
||||||
"description": "Basic utilites for common TypeScript development",
|
|
||||||
"engines": {
|
|
||||||
"npm": ">=3.0.0"
|
|
||||||
},
|
|
||||||
"homepage": "https://dojo.io",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/dojo/core/issues"
|
|
||||||
},
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"private": true,
|
|
||||||
"main": "main.js",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/dojo/core.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prepublish": "grunt peerDepInstall",
|
|
||||||
"precommit": "lint-staged",
|
|
||||||
"prettier": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'",
|
|
||||||
"test": "grunt test"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "~1.8.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@dojo/has": "^2.0.0",
|
|
||||||
"@dojo/shim": "^2.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@dojo/loader": "^2.0.0",
|
|
||||||
"@theintern/leadfoot": "~2.0.2",
|
|
||||||
"@types/benchmark": "~1.0.0",
|
|
||||||
"@types/chai": "~4.0.0",
|
|
||||||
"@types/express": "~4.0.39",
|
|
||||||
"@types/glob": "~5.0.0",
|
|
||||||
"@types/grunt": "~0.4.0",
|
|
||||||
"@types/multer": "~1.3.3",
|
|
||||||
"@types/node": "~9.6.5",
|
|
||||||
"@types/sinon": "~1.16.0",
|
|
||||||
"benchmark": "^1.0.0",
|
|
||||||
"express": "~4.15.3",
|
|
||||||
"grunt": "^1.0.1",
|
|
||||||
"grunt-contrib-clean": ">=1.0.0",
|
|
||||||
"grunt-contrib-copy": ">=1.0.0",
|
|
||||||
"grunt-dojo2": "latest",
|
|
||||||
"grunt-postcss": "^0.8.0",
|
|
||||||
"grunt-text-replace": ">=0.4.0",
|
|
||||||
"grunt-ts": ">=5.0.0",
|
|
||||||
"grunt-tslint": "5.0.1",
|
|
||||||
"grunt-typings": "^0.1.5",
|
|
||||||
"husky": "0.14.3",
|
|
||||||
"intern": "~4.1.0",
|
|
||||||
"lint-staged": "6.0.0",
|
|
||||||
"multer": "~1.3.0",
|
|
||||||
"prettier": "1.9.2",
|
|
||||||
"remap-istanbul": ">=0.6.3",
|
|
||||||
"sinon": "~1.17.6",
|
|
||||||
"tslint": "5.8.0",
|
|
||||||
"typescript": "~2.6.1"
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.{ts,tsx}": [
|
|
||||||
"prettier --write",
|
|
||||||
"git add"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"singleQuote": true,
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": true,
|
|
||||||
"parser": "typescript",
|
|
||||||
"printWidth": 120,
|
|
||||||
"arrowParens": "always"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
@ -1,350 +0,0 @@
|
|||||||
import { Hash } from './interfaces';
|
|
||||||
|
|
||||||
export interface KwArgs {
|
|
||||||
dayOfMonth?: number;
|
|
||||||
hours?: number;
|
|
||||||
milliseconds?: number;
|
|
||||||
minutes?: number;
|
|
||||||
month: number;
|
|
||||||
seconds?: number;
|
|
||||||
year: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OperationKwArgs {
|
|
||||||
days?: number;
|
|
||||||
hours?: number;
|
|
||||||
milliseconds?: number;
|
|
||||||
minutes?: number;
|
|
||||||
months?: number;
|
|
||||||
seconds?: number;
|
|
||||||
years?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The properties of a complete date
|
|
||||||
*/
|
|
||||||
export interface DateProperties {
|
|
||||||
dayOfMonth: number;
|
|
||||||
readonly dayOfWeek: number;
|
|
||||||
readonly daysInMonth: number;
|
|
||||||
hours: number;
|
|
||||||
readonly isLeapYear: boolean;
|
|
||||||
milliseconds: number;
|
|
||||||
minutes: number;
|
|
||||||
month: number;
|
|
||||||
seconds: number;
|
|
||||||
year: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const days = [NaN, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
||||||
|
|
||||||
const isLeapYear = (function() {
|
|
||||||
const date = new Date();
|
|
||||||
function isLeapYear(year: number): boolean {
|
|
||||||
date.setFullYear(year, 1, 29);
|
|
||||||
return date.getDate() === 29;
|
|
||||||
}
|
|
||||||
return isLeapYear;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const operationOrder = ['years', 'months', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'];
|
|
||||||
const operationHash: Hash<string> = Object.create(null, {
|
|
||||||
days: { value: 'Date' },
|
|
||||||
hours: { value: 'UTCHours' },
|
|
||||||
milliseconds: { value: 'UTCMilliseconds' },
|
|
||||||
minutes: { value: 'UTCMinutes' },
|
|
||||||
months: { value: 'Month' },
|
|
||||||
seconds: { value: 'UTCSeconds' },
|
|
||||||
years: { value: 'FullYear' }
|
|
||||||
});
|
|
||||||
|
|
||||||
export class DateObject implements DateProperties {
|
|
||||||
static parse(str: string): DateObject {
|
|
||||||
return new DateObject(Date.parse(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
static now(): DateObject {
|
|
||||||
return new DateObject(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly _date: Date;
|
|
||||||
readonly utc: DateProperties;
|
|
||||||
|
|
||||||
constructor(value: number);
|
|
||||||
constructor(value: string);
|
|
||||||
constructor(value: Date);
|
|
||||||
constructor(value: KwArgs);
|
|
||||||
constructor();
|
|
||||||
constructor(value?: any) {
|
|
||||||
let _date: Date;
|
|
||||||
if (!arguments.length) {
|
|
||||||
_date = new Date();
|
|
||||||
} else if (value instanceof Date) {
|
|
||||||
_date = new Date(+value);
|
|
||||||
} else if (typeof value === 'number' || typeof value === 'string') {
|
|
||||||
_date = new Date(<any>value);
|
|
||||||
} else {
|
|
||||||
_date = new Date(
|
|
||||||
value.year,
|
|
||||||
value.month - 1,
|
|
||||||
value.dayOfMonth || 1,
|
|
||||||
value.hours || 0,
|
|
||||||
value.minutes || 0,
|
|
||||||
value.seconds || 0,
|
|
||||||
value.milliseconds || 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._date = _date;
|
|
||||||
const self = this;
|
|
||||||
this.utc = {
|
|
||||||
get isLeapYear(this: DateObject): boolean {
|
|
||||||
return isLeapYear(this.year);
|
|
||||||
},
|
|
||||||
get daysInMonth(this: DateObject): number {
|
|
||||||
const month = this.month;
|
|
||||||
|
|
||||||
if (month === 2 && this.isLeapYear) {
|
|
||||||
return 29;
|
|
||||||
}
|
|
||||||
return days[month];
|
|
||||||
},
|
|
||||||
|
|
||||||
get year(): number {
|
|
||||||
return self._date.getUTCFullYear();
|
|
||||||
},
|
|
||||||
set year(year: number) {
|
|
||||||
self._date.setUTCFullYear(year);
|
|
||||||
},
|
|
||||||
|
|
||||||
get month(): number {
|
|
||||||
return self._date.getUTCMonth() + 1;
|
|
||||||
},
|
|
||||||
set month(month: number) {
|
|
||||||
self._date.setUTCMonth(month - 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
get dayOfMonth(): number {
|
|
||||||
return self._date.getUTCDate();
|
|
||||||
},
|
|
||||||
set dayOfMonth(day: number) {
|
|
||||||
self._date.setUTCDate(day);
|
|
||||||
},
|
|
||||||
|
|
||||||
get hours(): number {
|
|
||||||
return self._date.getUTCHours();
|
|
||||||
},
|
|
||||||
set hours(hours: number) {
|
|
||||||
self._date.setUTCHours(hours);
|
|
||||||
},
|
|
||||||
|
|
||||||
get minutes(): number {
|
|
||||||
return self._date.getUTCMinutes();
|
|
||||||
},
|
|
||||||
set minutes(minutes: number) {
|
|
||||||
self._date.setUTCMinutes(minutes);
|
|
||||||
},
|
|
||||||
|
|
||||||
get seconds(): number {
|
|
||||||
return self._date.getUTCSeconds();
|
|
||||||
},
|
|
||||||
set seconds(seconds: number) {
|
|
||||||
self._date.setUTCSeconds(seconds);
|
|
||||||
},
|
|
||||||
|
|
||||||
get milliseconds(): number {
|
|
||||||
return self._date.getUTCMilliseconds();
|
|
||||||
},
|
|
||||||
set milliseconds(milliseconds: number) {
|
|
||||||
self._date.setUTCMilliseconds(milliseconds);
|
|
||||||
},
|
|
||||||
|
|
||||||
get dayOfWeek(): number {
|
|
||||||
return self._date.getUTCDay();
|
|
||||||
},
|
|
||||||
|
|
||||||
toString: function(): string {
|
|
||||||
return self._date.toUTCString();
|
|
||||||
}
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isLeapYear(): boolean {
|
|
||||||
return isLeapYear(this.year);
|
|
||||||
}
|
|
||||||
|
|
||||||
get daysInMonth(): number {
|
|
||||||
const month = this.month;
|
|
||||||
|
|
||||||
if (month === 2 && this.isLeapYear) {
|
|
||||||
return 29;
|
|
||||||
}
|
|
||||||
return days[month];
|
|
||||||
}
|
|
||||||
|
|
||||||
get year(): number {
|
|
||||||
return this._date.getFullYear();
|
|
||||||
}
|
|
||||||
set year(year: number) {
|
|
||||||
const dayOfMonth = this.dayOfMonth;
|
|
||||||
|
|
||||||
this._date.setFullYear(year);
|
|
||||||
|
|
||||||
if (this.dayOfMonth < dayOfMonth) {
|
|
||||||
this.dayOfMonth = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get month(): number {
|
|
||||||
return this._date.getMonth() + 1;
|
|
||||||
}
|
|
||||||
set month(month: number) {
|
|
||||||
const dayOfMonth = this.dayOfMonth;
|
|
||||||
|
|
||||||
this._date.setMonth(month - 1);
|
|
||||||
|
|
||||||
if (this.dayOfMonth < dayOfMonth) {
|
|
||||||
this.dayOfMonth = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get dayOfMonth(): number {
|
|
||||||
return this._date.getDate();
|
|
||||||
}
|
|
||||||
set dayOfMonth(day: number) {
|
|
||||||
this._date.setDate(day);
|
|
||||||
}
|
|
||||||
|
|
||||||
get hours(): number {
|
|
||||||
return this._date.getHours();
|
|
||||||
}
|
|
||||||
set hours(hours: number) {
|
|
||||||
this._date.setHours(hours);
|
|
||||||
}
|
|
||||||
|
|
||||||
get minutes(): number {
|
|
||||||
return this._date.getMinutes();
|
|
||||||
}
|
|
||||||
set minutes(minutes: number) {
|
|
||||||
this._date.setMinutes(minutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
get seconds(): number {
|
|
||||||
return this._date.getSeconds();
|
|
||||||
}
|
|
||||||
set seconds(seconds: number) {
|
|
||||||
this._date.setSeconds(seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
get milliseconds(): number {
|
|
||||||
return this._date.getMilliseconds();
|
|
||||||
}
|
|
||||||
set milliseconds(milliseconds: number) {
|
|
||||||
this._date.setMilliseconds(milliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
get time(): number {
|
|
||||||
return this._date.getTime();
|
|
||||||
}
|
|
||||||
set time(time: number) {
|
|
||||||
this._date.setTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
get dayOfWeek(): number {
|
|
||||||
return this._date.getDay();
|
|
||||||
}
|
|
||||||
get timezoneOffset(): number {
|
|
||||||
return this._date.getTimezoneOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
add(value: number): DateObject;
|
|
||||||
add(value: OperationKwArgs): DateObject;
|
|
||||||
add(value: any): DateObject {
|
|
||||||
const result = new DateObject(this.time);
|
|
||||||
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
result.time += value;
|
|
||||||
} else {
|
|
||||||
// Properties have to be added in a particular order to properly handle
|
|
||||||
// date overshoots in month and year calculations
|
|
||||||
operationOrder.forEach((property: string): void => {
|
|
||||||
if (!(property in value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateMethod = operationHash[property];
|
|
||||||
(<any>result._date)[`set${dateMethod}`]((<any>this._date)[`get${dateMethod}`]() + value[property]);
|
|
||||||
|
|
||||||
if ((property === 'years' || property === 'months') && result.dayOfMonth < this.dayOfMonth) {
|
|
||||||
// Set the day of the month to 0 to move the date to the first day of the previous
|
|
||||||
// month to fix overshoots when adding a month and the date is the 31st or adding
|
|
||||||
// a year and the date is the 29th
|
|
||||||
result.dayOfMonth = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
compare(value: DateObject): number {
|
|
||||||
const result = this.time - value.time;
|
|
||||||
if (result > 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (result < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
compareDate(value: KwArgs): number {
|
|
||||||
const left = new DateObject(this);
|
|
||||||
const right = new DateObject(value);
|
|
||||||
|
|
||||||
left._date.setHours(0, 0, 0, 0);
|
|
||||||
right._date.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return left.compare(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
compareTime(value: KwArgs): number {
|
|
||||||
const left = new DateObject(this);
|
|
||||||
const right = new DateObject(value);
|
|
||||||
|
|
||||||
left._date.setFullYear(0, 0, 0);
|
|
||||||
right._date.setFullYear(0, 0, 0);
|
|
||||||
|
|
||||||
return left.compare(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
return this._date.toString();
|
|
||||||
}
|
|
||||||
toDateString(): string {
|
|
||||||
return this._date.toDateString();
|
|
||||||
}
|
|
||||||
toTimeString(): string {
|
|
||||||
return this._date.toTimeString();
|
|
||||||
}
|
|
||||||
toLocaleString(): string {
|
|
||||||
return this._date.toLocaleString();
|
|
||||||
}
|
|
||||||
toLocaleDateString(): string {
|
|
||||||
return this._date.toLocaleDateString();
|
|
||||||
}
|
|
||||||
toLocaleTimeString(): string {
|
|
||||||
return this._date.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
toISOString(): string {
|
|
||||||
return this._date.toISOString();
|
|
||||||
}
|
|
||||||
toJSON(key?: any): string {
|
|
||||||
return this._date.toJSON(key);
|
|
||||||
}
|
|
||||||
valueOf(): number {
|
|
||||||
return this._date.valueOf();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DateObject;
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
import { createCompositeHandle } from './lang';
|
|
||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No operation function to replace own once instance is destoryed
|
|
||||||
*/
|
|
||||||
function noop(): Promise<boolean> {
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No op function used to replace own, once instance has been destoryed
|
|
||||||
*/
|
|
||||||
function destroyed(): never {
|
|
||||||
throw new Error('Call made to destroyed method');
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Destroyable {
|
|
||||||
/**
|
|
||||||
* register handles for the instance
|
|
||||||
*/
|
|
||||||
private handles: Handle[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.handles = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register handles for the instance that will be destroyed when `this.destroy` is called
|
|
||||||
*
|
|
||||||
* @param {Handle} handle The handle to add for the instance
|
|
||||||
* @returns {Handle} a handle for the handle, removes the handle for the instance and calls destroy
|
|
||||||
*/
|
|
||||||
own(handles: Handle | Handle[]): Handle {
|
|
||||||
const handle = Array.isArray(handles) ? createCompositeHandle(...handles) : handles;
|
|
||||||
const { handles: _handles } = this;
|
|
||||||
_handles.push(handle);
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
_handles.splice(_handles.indexOf(handle));
|
|
||||||
handle.destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destrpys all handers registered for the instance
|
|
||||||
*
|
|
||||||
* @returns {Promise<any} a promise that resolves once all handles have been destroyed
|
|
||||||
*/
|
|
||||||
destroy(): Promise<any> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.handles.forEach((handle) => {
|
|
||||||
handle && handle.destroy && handle.destroy();
|
|
||||||
});
|
|
||||||
this.destroy = noop;
|
|
||||||
this.own = destroyed;
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Destroyable;
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import Map from '@dojo/shim/Map';
|
|
||||||
import { Handle, EventType, EventObject } from './interfaces';
|
|
||||||
import { Destroyable } from './Destroyable';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of computed regular expressions, keyed by string
|
|
||||||
*/
|
|
||||||
const regexMap = new Map<string, RegExp>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines is the event type glob has been matched
|
|
||||||
*
|
|
||||||
* @returns boolean that indicates if the glob is matched
|
|
||||||
*/
|
|
||||||
export function isGlobMatch(globString: string | symbol, targetString: string | symbol): boolean {
|
|
||||||
if (typeof targetString === 'string' && typeof globString === 'string' && globString.indexOf('*') !== -1) {
|
|
||||||
let regex: RegExp;
|
|
||||||
if (regexMap.has(globString)) {
|
|
||||||
regex = regexMap.get(globString)!;
|
|
||||||
} else {
|
|
||||||
regex = new RegExp(`^${globString.replace(/\*/g, '.*')}$`);
|
|
||||||
regexMap.set(globString, regex);
|
|
||||||
}
|
|
||||||
return regex.test(targetString);
|
|
||||||
} else {
|
|
||||||
return globString === targetString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventedCallback<T = EventType, E extends EventObject<T> = EventObject<T>> = {
|
|
||||||
/**
|
|
||||||
* A callback that takes an `event` argument
|
|
||||||
*
|
|
||||||
* @param event The event object
|
|
||||||
*/
|
|
||||||
|
|
||||||
(event: E): boolean | void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CustomEventTypes<T extends EventObject<any> = EventObject<any>> {
|
|
||||||
[index: string]: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type which is either a targeted event listener or an array of listeners
|
|
||||||
* @template T The type of target for the events
|
|
||||||
* @template E The event type for the events
|
|
||||||
*/
|
|
||||||
export type EventedCallbackOrArray<T = EventType, E extends EventObject<T> = EventObject<T>> =
|
|
||||||
| EventedCallback<T, E>
|
|
||||||
| EventedCallback<T, E>[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event Class
|
|
||||||
*/
|
|
||||||
export class Evented<
|
|
||||||
M extends CustomEventTypes = {},
|
|
||||||
T = EventType,
|
|
||||||
O extends EventObject<T> = EventObject<T>
|
|
||||||
> extends Destroyable {
|
|
||||||
// The following member is purely so TypeScript remembers the type of `M` when extending so
|
|
||||||
// that the utilities in `on.ts` will work https://github.com/Microsoft/TypeScript/issues/20348
|
|
||||||
// tslint:disable-next-line
|
|
||||||
protected __typeMap__?: M;
|
|
||||||
/**
|
|
||||||
* map of listeners keyed by event type
|
|
||||||
*/
|
|
||||||
protected listenersMap: Map<T | keyof M, EventedCallback<T, O>[]> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits the event object for the specified type
|
|
||||||
*
|
|
||||||
* @param event the event to emit
|
|
||||||
*/
|
|
||||||
emit<K extends keyof M>(event: M[K]): void;
|
|
||||||
emit(event: O): void;
|
|
||||||
emit(event: any): void {
|
|
||||||
this.listenersMap.forEach((methods, type) => {
|
|
||||||
if (isGlobMatch(type as any, event.type)) {
|
|
||||||
[...methods].forEach((method) => {
|
|
||||||
method.call(this, event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Catch all handler for various call signatures. The signatures are defined in
|
|
||||||
* `BaseEventedEvents`. You can add your own event type -> handler types by extending
|
|
||||||
* `BaseEventedEvents`. See example for details.
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* interface WidgetBaseEvents extends BaseEventedEvents {
|
|
||||||
* (type: 'properties:changed', handler: PropertiesChangedHandler): Handle;
|
|
||||||
* }
|
|
||||||
* class WidgetBase extends Evented {
|
|
||||||
* on: WidgetBaseEvents;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @return {any}
|
|
||||||
*/
|
|
||||||
on<K extends keyof M>(type: K, listener: EventedCallbackOrArray<K, M[K]>): Handle;
|
|
||||||
on(type: T, listener: EventedCallbackOrArray<T, O>): Handle;
|
|
||||||
on(type: any, listener: EventedCallbackOrArray<any, any>): Handle {
|
|
||||||
if (Array.isArray(listener)) {
|
|
||||||
const handles = listener.map((listener) => this._addListener(type, listener));
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
handles.forEach((handle) => handle.destroy());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this._addListener(type, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addListener(type: T | keyof M, listener: EventedCallback<T, O>) {
|
|
||||||
const listeners = this.listenersMap.get(type) || [];
|
|
||||||
listeners.push(listener);
|
|
||||||
this.listenersMap.set(type, listeners);
|
|
||||||
return {
|
|
||||||
destroy: () => {
|
|
||||||
const listeners = this.listenersMap.get(type) || [];
|
|
||||||
listeners.splice(listeners.indexOf(listener), 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Evented;
|
|
||||||
@ -1,180 +0,0 @@
|
|||||||
import Map from '@dojo/shim/Map';
|
|
||||||
import WeakMap from '@dojo/shim/WeakMap';
|
|
||||||
import { Iterable, IterableIterator } from '@dojo/shim/iterator';
|
|
||||||
import { Handle } from './interfaces';
|
|
||||||
import '@dojo/shim/Symbol';
|
|
||||||
import List from './List';
|
|
||||||
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
interface Entry<V> {
|
|
||||||
readonly handle: Handle;
|
|
||||||
readonly value: V;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State<V extends object> {
|
|
||||||
readonly entryMap: Map<Identity, Entry<V>>;
|
|
||||||
readonly idMap: WeakMap<V, Identity>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateStateMap = new WeakMap<IdentityRegistry<any>, State<any>>();
|
|
||||||
|
|
||||||
function getState<V extends object>(instance: IdentityRegistry<V>): State<V> {
|
|
||||||
return privateStateMap.get(instance)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registry identities can be strings or symbols. Note that the empty string is allowed.
|
|
||||||
*/
|
|
||||||
export type Identity = string | symbol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A registry of values, mapped by identities.
|
|
||||||
*/
|
|
||||||
export class IdentityRegistry<V extends object> implements Iterable<[Identity, V]> {
|
|
||||||
constructor() {
|
|
||||||
privateStateMap.set(this, {
|
|
||||||
entryMap: new Map<Identity, Entry<V>>(),
|
|
||||||
idMap: new WeakMap<V, Identity>()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up a value by its identifier.
|
|
||||||
*
|
|
||||||
* Throws if no value has been registered for the given identifier.
|
|
||||||
*
|
|
||||||
* @param id The identifier
|
|
||||||
* @return The value
|
|
||||||
*/
|
|
||||||
get(id: Identity): V {
|
|
||||||
const entry = getState(this).entryMap.get(id);
|
|
||||||
if (!entry) {
|
|
||||||
throw new Error(`Could not find a value for identity '${id.toString()}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether the value has been registered.
|
|
||||||
* @param value The value
|
|
||||||
* @return `true` if the value has been registered, `false` otherwise
|
|
||||||
*/
|
|
||||||
contains(value: V): boolean {
|
|
||||||
return getState(this).idMap.has(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove from the registry the value for a given identifier.
|
|
||||||
* @param id The identifier
|
|
||||||
* @return `true` if the value was removed, `false` otherwise
|
|
||||||
*/
|
|
||||||
delete(id: Identity): boolean {
|
|
||||||
const entry = getState(this).entryMap.get(id);
|
|
||||||
if (!entry) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.handle.destroy();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether a value has been registered for the given identifier.
|
|
||||||
* @param id The identifier
|
|
||||||
* @return `true` if a value has been registered, `false` otherwise
|
|
||||||
*/
|
|
||||||
has(id: Identity): boolean {
|
|
||||||
return getState(this).entryMap.has(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up the identifier for which the given value has been registered.
|
|
||||||
*
|
|
||||||
* Throws if the value hasn't been registered.
|
|
||||||
*
|
|
||||||
* @param value The value
|
|
||||||
* @return The identifier otherwise
|
|
||||||
*/
|
|
||||||
identify(value: V): Identity | undefined {
|
|
||||||
if (!this.contains(value)) {
|
|
||||||
throw new Error('Could not identify non-registered value');
|
|
||||||
}
|
|
||||||
|
|
||||||
return getState(this).idMap.get(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a new value with a new identity.
|
|
||||||
*
|
|
||||||
* Throws if a different value has already been registered for the given identity,
|
|
||||||
* or if the value has already been registered with a different identity.
|
|
||||||
*
|
|
||||||
* @param id The identifier
|
|
||||||
* @param value The value
|
|
||||||
* @return A handle for deregistering the value. Note that when called repeatedly with
|
|
||||||
* the same identifier and value combination, the same handle is returned
|
|
||||||
*/
|
|
||||||
register(id: Identity, value: V): Handle {
|
|
||||||
const entryMap = getState<V>(this).entryMap;
|
|
||||||
const existingEntry = entryMap.get(id);
|
|
||||||
if (existingEntry && existingEntry.value !== value) {
|
|
||||||
const str = id.toString();
|
|
||||||
throw new Error(`A value has already been registered for the given identity (${str})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingId = this.contains(value) ? this.identify(value) : null;
|
|
||||||
if (existingId && existingId !== id) {
|
|
||||||
const str = existingId.toString();
|
|
||||||
throw new Error(`The value has already been registered with a different identity (${str})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding the same value with the same id is a noop, return the original handle.
|
|
||||||
if (existingEntry && existingId) {
|
|
||||||
return existingEntry.handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handle = {
|
|
||||||
destroy: () => {
|
|
||||||
handle.destroy = noop;
|
|
||||||
getState(this).entryMap.delete(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
entryMap.set(id, { handle, value });
|
|
||||||
getState(this).idMap.set(value, id);
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries(): IterableIterator<[Identity, V]> {
|
|
||||||
const values = new List<[Identity, V]>();
|
|
||||||
|
|
||||||
getState(this).entryMap.forEach((value: Entry<V>, key: Identity) => {
|
|
||||||
values.add([key, value.value]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return values.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
values(): IterableIterator<V> {
|
|
||||||
const values = new List<V>();
|
|
||||||
|
|
||||||
getState(this).entryMap.forEach((value: Entry<V>, key: Identity) => {
|
|
||||||
values.add(value.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return values.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
ids(): IterableIterator<Identity> {
|
|
||||||
return getState(this).entryMap.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator](): IterableIterator<[Identity, V]> {
|
|
||||||
return this.entries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IdentityRegistry;
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
import { isArrayLike, isIterable, Iterable, IterableIterator, ShimIterator } from '@dojo/shim/iterator';
|
|
||||||
import WeakMap from '@dojo/shim/WeakMap';
|
|
||||||
|
|
||||||
const listItems: WeakMap<List<any>, any[]> = new WeakMap<List<any>, any[]>();
|
|
||||||
|
|
||||||
function getListItems<T>(list: List<T>): T[] {
|
|
||||||
return (listItems.get(list) || []) as T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class List<T> {
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
get size(): number {
|
|
||||||
return getListItems(this).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source?: Iterable<T> | ArrayLike<T>) {
|
|
||||||
listItems.set(this, []);
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
if (isArrayLike(source)) {
|
|
||||||
for (let i = 0; i < source.length; i++) {
|
|
||||||
this.add(source[i]);
|
|
||||||
}
|
|
||||||
} else if (isIterable(source)) {
|
|
||||||
for (const item of source) {
|
|
||||||
this.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add(value: T): this {
|
|
||||||
getListItems(this).push(value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
listItems.set(this, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(idx: number): boolean {
|
|
||||||
if (idx < this.size) {
|
|
||||||
getListItems(this).splice(idx, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries(): IterableIterator<[number, T]> {
|
|
||||||
return new ShimIterator<[number, T]>(getListItems(this).map<[number, T]>((value, index) => [index, value]));
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(fn: (value: T, idx: number, list: this) => void, thisArg?: any): void {
|
|
||||||
getListItems(this).forEach(fn.bind(thisArg ? thisArg : this));
|
|
||||||
}
|
|
||||||
|
|
||||||
has(idx: number): boolean {
|
|
||||||
return this.size > idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
includes(value: T): boolean {
|
|
||||||
return getListItems(this).indexOf(value) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
indexOf(value: T): number {
|
|
||||||
return getListItems(this).indexOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
join(separator: string = ','): string {
|
|
||||||
return getListItems(this).join(separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
keys(): IterableIterator<number> {
|
|
||||||
return new ShimIterator<number>(getListItems(this).map<number>((_, index) => index));
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndexOf(value: T): number {
|
|
||||||
return getListItems(this).lastIndexOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
push(value: T): void {
|
|
||||||
this.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pop(): T | undefined {
|
|
||||||
return getListItems(this).pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
splice(start: number, deleteCount?: number, ...newItems: T[]): T[] {
|
|
||||||
return getListItems(this).splice(
|
|
||||||
start,
|
|
||||||
deleteCount === undefined ? this.size - start : deleteCount,
|
|
||||||
...newItems
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
values(): IterableIterator<T> {
|
|
||||||
return new ShimIterator<T>(getListItems(this).map<T>((value) => value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default List;
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An entry in a MatchRegistry. Each Entry contains a test to determine whether the Entry is applicable, and a value for
|
|
||||||
* the entry.
|
|
||||||
*/
|
|
||||||
interface Entry<T> {
|
|
||||||
readonly test: Test | null;
|
|
||||||
readonly value: T | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A registry of values tagged with matchers.
|
|
||||||
*/
|
|
||||||
export class MatchRegistry<T> {
|
|
||||||
protected _defaultValue: T | undefined;
|
|
||||||
private readonly _entries: Entry<T>[] | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new MatchRegistry, optionally containing a given default value.
|
|
||||||
*/
|
|
||||||
constructor(defaultValue?: T) {
|
|
||||||
this._defaultValue = defaultValue;
|
|
||||||
this._entries = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the first entry in this registry that matches the given arguments. If no entry matches and the registry
|
|
||||||
* was created with a default value, that value will be returned. Otherwise, an exception is thrown.
|
|
||||||
*
|
|
||||||
* @param ...args Arguments that will be used to select a matching value.
|
|
||||||
* @returns the matching value, or a default value if one exists.
|
|
||||||
*/
|
|
||||||
match(...args: any[]): T {
|
|
||||||
const entries = this._entries ? this._entries.slice(0) : [];
|
|
||||||
let entry: Entry<T>;
|
|
||||||
|
|
||||||
for (let i = 0; (entry = entries[i]); ++i) {
|
|
||||||
if (entry.value && entry.test && entry.test.apply(null, args)) {
|
|
||||||
return entry.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._defaultValue !== undefined) {
|
|
||||||
return this._defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('No match found');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a test + value pair with this registry.
|
|
||||||
*
|
|
||||||
* @param test The test that will be used to determine if the registered value matches a set of arguments.
|
|
||||||
* @param value A value being registered.
|
|
||||||
* @param first If true, the newly registered test and value will be the first entry in the registry.
|
|
||||||
*/
|
|
||||||
register(test: Test | null, value: T | null, first?: boolean): Handle {
|
|
||||||
let entries = this._entries;
|
|
||||||
let entry: Entry<T> | null = {
|
|
||||||
test: test,
|
|
||||||
value: value
|
|
||||||
};
|
|
||||||
|
|
||||||
(<any>entries)[first ? 'unshift' : 'push'](entry);
|
|
||||||
|
|
||||||
return {
|
|
||||||
destroy: function(this: Handle) {
|
|
||||||
this.destroy = function(): void {};
|
|
||||||
let i = 0;
|
|
||||||
if (entries && entry) {
|
|
||||||
while ((i = entries.indexOf(entry, i)) > -1) {
|
|
||||||
entries.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test = value = entries = entry = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The interface that a test function must implement.
|
|
||||||
*/
|
|
||||||
export interface Test {
|
|
||||||
(...args: any[]): boolean | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MatchRegistry;
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
import { from as arrayFrom } from '@dojo/shim/array';
|
|
||||||
import { isArrayLike, isIterable, Iterable, IterableIterator, ShimIterator } from '@dojo/shim/iterator';
|
|
||||||
import Map from '@dojo/shim/Map';
|
|
||||||
import '@dojo/shim/Symbol';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map implmentation that supports multiple keys for specific value.
|
|
||||||
*
|
|
||||||
* @param T Accepts the type of the value
|
|
||||||
*/
|
|
||||||
export class MultiMap<T> implements Map<any[], T> {
|
|
||||||
private _map: Map<any, any>;
|
|
||||||
private _key: symbol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* @param iterator an array or iterator of tuples to initialize the map with.
|
|
||||||
*/
|
|
||||||
constructor(iterable?: ArrayLike<[any[], T]> | Iterable<[any[], T]>) {
|
|
||||||
this._map = new Map<any, any>();
|
|
||||||
this._key = Symbol();
|
|
||||||
if (iterable) {
|
|
||||||
if (isArrayLike(iterable)) {
|
|
||||||
for (let i = 0; i < iterable.length; i++) {
|
|
||||||
const value = iterable[i];
|
|
||||||
this.set(value[0], value[1]);
|
|
||||||
}
|
|
||||||
} else if (isIterable(iterable)) {
|
|
||||||
for (const value of iterable) {
|
|
||||||
this.set(value[0], value[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value for the array of keys provided
|
|
||||||
*
|
|
||||||
* @param keys The array of keys to store the value against
|
|
||||||
* @param value the value of the map entry
|
|
||||||
*
|
|
||||||
* @return the multi map instance
|
|
||||||
*/
|
|
||||||
set(keys: any[], value: T): this {
|
|
||||||
let map = this._map;
|
|
||||||
let childMap;
|
|
||||||
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
if (map.get(keys[i])) {
|
|
||||||
map = map.get(keys[i]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
childMap = new Map<any, any>();
|
|
||||||
map.set(keys[i], childMap);
|
|
||||||
map = childMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.set(this._key, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value entry for the array of keys
|
|
||||||
*
|
|
||||||
* @param keys The array of keys to look up the value for
|
|
||||||
*
|
|
||||||
* @return The value if found otherwise `undefined`
|
|
||||||
*/
|
|
||||||
get(keys: any[]): T | undefined {
|
|
||||||
let map = this._map;
|
|
||||||
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
map = map.get(keys[i]);
|
|
||||||
|
|
||||||
if (!map) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map.get(this._key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a boolean indicating if the key exists in the map
|
|
||||||
*
|
|
||||||
* @return boolean true if the key exists otherwise false
|
|
||||||
*/
|
|
||||||
has(keys: any[]): boolean {
|
|
||||||
let map = this._map;
|
|
||||||
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
map = map.get(keys[i]);
|
|
||||||
if (!map) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the size of the map, based on the number of unique keys
|
|
||||||
*/
|
|
||||||
get size(): number {
|
|
||||||
return arrayFrom(this.keys()).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the entry for the key provided.
|
|
||||||
*
|
|
||||||
* @param keys the key of the entry to remove
|
|
||||||
* @return boolean trus if the entry was deleted, false if the entry was not found
|
|
||||||
*/
|
|
||||||
delete(keys: any[]): boolean {
|
|
||||||
let map = this._map;
|
|
||||||
const path = [this._map];
|
|
||||||
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
|
||||||
map = map.get(keys[i]);
|
|
||||||
path.push(map);
|
|
||||||
if (!map) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
map.delete(this._key);
|
|
||||||
|
|
||||||
for (let i = keys.length - 1; i >= 0; i--) {
|
|
||||||
map = path[i].get(keys[i]);
|
|
||||||
if (map.size) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
path[i].delete(keys[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an iterator that yields each value in the map
|
|
||||||
*
|
|
||||||
* @return An iterator containing the instance's values.
|
|
||||||
*/
|
|
||||||
values(): IterableIterator<T> {
|
|
||||||
const values: T[] = [];
|
|
||||||
|
|
||||||
const getValues = (map: Map<any, any>) => {
|
|
||||||
map.forEach((value, key) => {
|
|
||||||
if (key === this._key) {
|
|
||||||
values.push(value);
|
|
||||||
} else {
|
|
||||||
getValues(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getValues(this._map);
|
|
||||||
return new ShimIterator<T>(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an iterator that yields each key array in the map
|
|
||||||
*
|
|
||||||
* @return An iterator containing the instance's keys.
|
|
||||||
*/
|
|
||||||
keys(): IterableIterator<any[]> {
|
|
||||||
const finalKeys: any[][] = [];
|
|
||||||
|
|
||||||
const getKeys = (map: Map<any, any>, keys: any[] = []) => {
|
|
||||||
map.forEach((value, key) => {
|
|
||||||
if (key === this._key) {
|
|
||||||
finalKeys.push(keys);
|
|
||||||
} else {
|
|
||||||
const nextKeys = [...keys, key];
|
|
||||||
getKeys(value, nextKeys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getKeys(this._map);
|
|
||||||
return new ShimIterator<any[]>(finalKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator that yields each key/value pair as an array.
|
|
||||||
*
|
|
||||||
* @return An iterator for each key/value pair in the instance.
|
|
||||||
*/
|
|
||||||
entries(): IterableIterator<[any[], T]> {
|
|
||||||
const finalEntries: [any[], T][] = [];
|
|
||||||
|
|
||||||
const getKeys = (map: Map<any, any>, keys: any[] = []) => {
|
|
||||||
map.forEach((value, key) => {
|
|
||||||
if (key === this._key) {
|
|
||||||
finalEntries.push([keys, value]);
|
|
||||||
} else {
|
|
||||||
const nextKeys = [...keys, key];
|
|
||||||
getKeys(value, nextKeys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getKeys(this._map);
|
|
||||||
return new ShimIterator<[any[], T]>(finalEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes a given function for each map entry. The function
|
|
||||||
* is invoked with three arguments: the element value, the
|
|
||||||
* element key, and the associated Map instance.
|
|
||||||
*
|
|
||||||
* @param callback The function to execute for each map entry,
|
|
||||||
* @param context The value to use for `this` for each execution of the calback
|
|
||||||
*/
|
|
||||||
forEach(callback: (value: T, key: any[], mapInstance: MultiMap<T>) => any, context?: {}): void {
|
|
||||||
const entries = this.entries();
|
|
||||||
|
|
||||||
for (const value of entries) {
|
|
||||||
callback.call(context, value[1], value[0], this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all keys and their associated values.
|
|
||||||
*/
|
|
||||||
clear(): void {
|
|
||||||
this._map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator](): IterableIterator<[any[], T]> {
|
|
||||||
return this.entries();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.toStringTag] = 'MultiMap';
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MultiMap;
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
import ObservableShim, { ObservableObject, Subscribable, SubscriptionObserver } from '@dojo/shim/Observable';
|
|
||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import { Iterable } from '@dojo/shim/iterator';
|
|
||||||
|
|
||||||
function isSubscribable(object: any): object is Subscribable<any> {
|
|
||||||
return object && object.subscribe !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Observable<T> extends ObservableShim<T> {
|
|
||||||
static of<T>(...items: T[]): Observable<T> {
|
|
||||||
return <Observable<T>>super.of(...items);
|
|
||||||
}
|
|
||||||
|
|
||||||
static from<T>(item: Iterable<T> | ArrayLike<T> | ObservableObject): Observable<T> {
|
|
||||||
return <Observable<T>>super.from(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
static defer<T>(deferFunction: () => Subscribable<T>): Observable<T> {
|
|
||||||
return new Observable<T>((observer) => {
|
|
||||||
const trueObservable = deferFunction();
|
|
||||||
|
|
||||||
return trueObservable.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
return observer.next(value);
|
|
||||||
},
|
|
||||||
error(errorValue?: any) {
|
|
||||||
return observer.error(errorValue);
|
|
||||||
},
|
|
||||||
complete(completeValue?: any) {
|
|
||||||
observer.complete(completeValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toPromise(): Promise<T> {
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
|
||||||
this.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
resolve(value);
|
|
||||||
},
|
|
||||||
error(error: any) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
map<U>(mapFunction: (x: T) => U): Observable<U> {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
if (typeof mapFunction !== 'function') {
|
|
||||||
throw new TypeError('Map parameter must be a function');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Observable<U>((observer: SubscriptionObserver<U>) => {
|
|
||||||
self.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
try {
|
|
||||||
const result: U = mapFunction(value);
|
|
||||||
return observer.next(result);
|
|
||||||
} catch (e) {
|
|
||||||
return observer.error(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error(errorValue?: any) {
|
|
||||||
return observer.error(errorValue);
|
|
||||||
},
|
|
||||||
complete(completeValue?: any) {
|
|
||||||
return observer.complete(completeValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(filterFunction: (x: T) => boolean): Observable<T> {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
if (typeof filterFunction !== 'function') {
|
|
||||||
throw new TypeError('Filter argument must be a function');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Observable<T>((observer: SubscriptionObserver<T>) => {
|
|
||||||
self.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
try {
|
|
||||||
if (filterFunction(value)) {
|
|
||||||
return observer.next(value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return observer.error(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error(errorValue?: any) {
|
|
||||||
return observer.error(errorValue);
|
|
||||||
},
|
|
||||||
complete(completeValue?: any) {
|
|
||||||
return observer.complete(completeValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): Observable<T[]> {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
return new Observable<T[]>((observer) => {
|
|
||||||
const values: T[] = [];
|
|
||||||
|
|
||||||
self.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
values.push(value);
|
|
||||||
},
|
|
||||||
error(errorValue?: any) {
|
|
||||||
return observer.error(errorValue);
|
|
||||||
},
|
|
||||||
complete(completeValue?: any) {
|
|
||||||
observer.next(values);
|
|
||||||
observer.complete(completeValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeAll(concurrent: number): Observable<any> {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
return new Observable<Observable<any>>((observer) => {
|
|
||||||
let active: any[] = [];
|
|
||||||
let queue: any[] = [];
|
|
||||||
|
|
||||||
function checkForComplete() {
|
|
||||||
if (active.length === 0 && queue.length === 0) {
|
|
||||||
observer.complete();
|
|
||||||
} else if (queue.length > 0 && active.length < concurrent) {
|
|
||||||
const item = queue.shift();
|
|
||||||
|
|
||||||
if (isSubscribable(item)) {
|
|
||||||
const itemIndex = active.length;
|
|
||||||
active.push(item);
|
|
||||||
|
|
||||||
item.subscribe({
|
|
||||||
next(value: any) {
|
|
||||||
observer.next(value);
|
|
||||||
},
|
|
||||||
complete() {
|
|
||||||
active.splice(itemIndex, 1);
|
|
||||||
checkForComplete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
observer.next(item);
|
|
||||||
checkForComplete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.subscribe({
|
|
||||||
next(value: T) {
|
|
||||||
queue.push(value);
|
|
||||||
},
|
|
||||||
complete() {
|
|
||||||
checkForComplete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for convienence, re-export some interfaces from shim
|
|
||||||
export { Observable, Subscribable, SubscriptionObserver as Observer };
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import { Handle, EventObject, EventType } from './interfaces';
|
|
||||||
import Map from '@dojo/shim/Map';
|
|
||||||
import Evented, { CustomEventTypes, isGlobMatch, EventedCallbackOrArray } from './Evented';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of the Evented class that queues up events when no listeners are
|
|
||||||
* listening. When a listener is subscribed, the queue will be published to the listener.
|
|
||||||
* When the queue is full, the oldest events will be discarded to make room for the newest ones.
|
|
||||||
*
|
|
||||||
* @property maxEvents The number of events to queue before old events are discarded. If zero (default), an unlimited number of events is queued.
|
|
||||||
*/
|
|
||||||
class QueuingEvented<
|
|
||||||
M extends CustomEventTypes = {},
|
|
||||||
T = EventType,
|
|
||||||
O extends EventObject<T> = EventObject<T>
|
|
||||||
> extends Evented<M, T, O> {
|
|
||||||
private _queue: Map<string | symbol, EventObject[]> = new Map();
|
|
||||||
|
|
||||||
public maxEvents = 0;
|
|
||||||
|
|
||||||
emit<K extends keyof M>(event: M[K]): void;
|
|
||||||
emit(event: O): void;
|
|
||||||
emit(event: any): void {
|
|
||||||
super.emit(event);
|
|
||||||
|
|
||||||
let hasMatch = false;
|
|
||||||
|
|
||||||
this.listenersMap.forEach((method, type) => {
|
|
||||||
// Since `type` is generic, the compiler doesn't know what type it is and `isGlobMatch` requires `string | symbol`
|
|
||||||
if (isGlobMatch(type as any, event.type)) {
|
|
||||||
hasMatch = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasMatch) {
|
|
||||||
let queue = this._queue.get(event.type);
|
|
||||||
|
|
||||||
if (!queue) {
|
|
||||||
queue = [];
|
|
||||||
this._queue.set(event.type, queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.push(event);
|
|
||||||
|
|
||||||
if (this.maxEvents > 0) {
|
|
||||||
while (queue.length > this.maxEvents) {
|
|
||||||
queue.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
on<K extends keyof M>(type: K, listener: EventedCallbackOrArray<K, M[K]>): Handle;
|
|
||||||
on(type: T, listener: EventedCallbackOrArray<T, O>): Handle;
|
|
||||||
on(type: any, listener: EventedCallbackOrArray<any, any>): Handle {
|
|
||||||
let handle = super.on(type, listener);
|
|
||||||
|
|
||||||
this.listenersMap.forEach((method, listenerType) => {
|
|
||||||
this._queue.forEach((events, queuedType) => {
|
|
||||||
if (isGlobMatch(listenerType as any, queuedType)) {
|
|
||||||
events.forEach((event) => this.emit(event));
|
|
||||||
this._queue.delete(queuedType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default QueuingEvented;
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
import { QueueItem, queueTask } from './queue';
|
|
||||||
|
|
||||||
function getQueueHandle(item: QueueItem): Handle {
|
|
||||||
return {
|
|
||||||
destroy: function(this: Handle) {
|
|
||||||
this.destroy = function() {};
|
|
||||||
item.isActive = false;
|
|
||||||
item.callback = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KwArgs {
|
|
||||||
deferWhileProcessing?: boolean;
|
|
||||||
queueFunction?: (callback: (...args: any[]) => any) => Handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Scheduler {
|
|
||||||
protected readonly _boundDispatch: () => void;
|
|
||||||
protected _deferred: QueueItem[] | null = null;
|
|
||||||
protected _isProcessing: boolean;
|
|
||||||
protected readonly _queue: QueueItem[];
|
|
||||||
protected _task: Handle | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether any callbacks registered during should be added to the current batch (`false`)
|
|
||||||
* or deferred until the next batch (`true`, default).
|
|
||||||
*/
|
|
||||||
deferWhileProcessing: boolean | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows users to specify the function that should be used to schedule callbacks.
|
|
||||||
* If no function is provided, then `queueTask` will be used.
|
|
||||||
*/
|
|
||||||
queueFunction: (callback: (...args: any[]) => any) => Handle;
|
|
||||||
|
|
||||||
protected _defer(callback: (...args: any[]) => void): Handle {
|
|
||||||
const item: QueueItem = {
|
|
||||||
isActive: true,
|
|
||||||
callback: callback
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this._deferred) {
|
|
||||||
this._deferred = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._deferred.push(item);
|
|
||||||
|
|
||||||
return getQueueHandle(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _dispatch(): void {
|
|
||||||
this._isProcessing = true;
|
|
||||||
if (this._task) {
|
|
||||||
this._task.destroy();
|
|
||||||
this._task = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const queue = this._queue;
|
|
||||||
let item: QueueItem | undefined;
|
|
||||||
|
|
||||||
while ((item = queue.shift())) {
|
|
||||||
if (item.isActive && item.callback) {
|
|
||||||
item.callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isProcessing = false;
|
|
||||||
|
|
||||||
let deferred: QueueItem[] | null = this._deferred;
|
|
||||||
if (deferred && deferred.length) {
|
|
||||||
this._deferred = null;
|
|
||||||
|
|
||||||
let item: QueueItem | undefined;
|
|
||||||
while ((item = deferred.shift())) {
|
|
||||||
this._schedule(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _schedule(item: QueueItem): void {
|
|
||||||
if (!this._task) {
|
|
||||||
this._task = this.queueFunction(this._boundDispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._queue.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(kwArgs?: KwArgs) {
|
|
||||||
this.deferWhileProcessing = kwArgs && 'deferWhileProcessing' in kwArgs ? kwArgs.deferWhileProcessing : true;
|
|
||||||
this.queueFunction = kwArgs && kwArgs.queueFunction ? kwArgs.queueFunction : queueTask;
|
|
||||||
|
|
||||||
this._boundDispatch = this._dispatch.bind(this);
|
|
||||||
this._isProcessing = false;
|
|
||||||
this._queue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule(callback: (...args: any[]) => void): Handle {
|
|
||||||
if (this._isProcessing && this.deferWhileProcessing) {
|
|
||||||
return this._defer(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
const item: QueueItem = {
|
|
||||||
isActive: true,
|
|
||||||
callback: callback
|
|
||||||
};
|
|
||||||
|
|
||||||
this._schedule(item);
|
|
||||||
|
|
||||||
return getQueueHandle(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Scheduler;
|
|
||||||
@ -1,193 +0,0 @@
|
|||||||
import { Hash } from './interfaces';
|
|
||||||
import { duplicate } from './lang';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with string keys and string or string array values that describes a query string.
|
|
||||||
*/
|
|
||||||
export type ParamList = Hash<string | string[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a query string, returning a ParamList object.
|
|
||||||
*/
|
|
||||||
function parseQueryString(input: string): ParamList {
|
|
||||||
const query: Hash<string[]> = {};
|
|
||||||
const splits = input.split('&');
|
|
||||||
|
|
||||||
for (let i = 0; i < splits.length; i++) {
|
|
||||||
const entry = splits[i];
|
|
||||||
const indexOfFirstEquals = entry.indexOf('=');
|
|
||||||
let key: string;
|
|
||||||
let value = '';
|
|
||||||
|
|
||||||
if (indexOfFirstEquals >= 0) {
|
|
||||||
key = entry.slice(0, indexOfFirstEquals);
|
|
||||||
value = entry.slice(indexOfFirstEquals + 1);
|
|
||||||
} else {
|
|
||||||
key = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
key = key ? decodeURIComponent(key) : '';
|
|
||||||
value = value ? decodeURIComponent(value) : '';
|
|
||||||
|
|
||||||
if (key in query) {
|
|
||||||
query[key].push(value);
|
|
||||||
} else {
|
|
||||||
query[key] = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a set of URL query search parameters.
|
|
||||||
*/
|
|
||||||
export class UrlSearchParams {
|
|
||||||
/**
|
|
||||||
* Constructs a new UrlSearchParams from a query string, an object of parameters and values, or another
|
|
||||||
* UrlSearchParams.
|
|
||||||
*/
|
|
||||||
constructor(input?: string | ParamList | UrlSearchParams) {
|
|
||||||
let list: ParamList = {};
|
|
||||||
|
|
||||||
if (input instanceof UrlSearchParams) {
|
|
||||||
// Copy the incoming UrlSearchParam's internal list
|
|
||||||
list = <ParamList>duplicate(input._list);
|
|
||||||
} else if (typeof input === 'object') {
|
|
||||||
// Copy the incoming object, assuming its property values are either arrays or strings
|
|
||||||
list = {};
|
|
||||||
for (const key in input) {
|
|
||||||
const value = (<ParamList>input)[key];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
list[key] = value.length ? value.slice() : [''];
|
|
||||||
} else if (value == null) {
|
|
||||||
list[key] = [''];
|
|
||||||
} else {
|
|
||||||
list[key] = [<string>value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (typeof input === 'string') {
|
|
||||||
// Parse the incoming string as a query string
|
|
||||||
list = parseQueryString(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._list = list as Hash<string[] | undefined>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps property keys to arrays of values. The value for any property that has been set will be an array containing
|
|
||||||
* at least one item. Properties that have been deleted will have a value of 'undefined'.
|
|
||||||
*/
|
|
||||||
protected readonly _list: Hash<string[] | undefined>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a new value to the set of values for a key.
|
|
||||||
* @param key The key to add a value for
|
|
||||||
* @param value The value to add
|
|
||||||
*/
|
|
||||||
append(key: string, value: string): void {
|
|
||||||
if (!this.has(key)) {
|
|
||||||
this.set(key, value);
|
|
||||||
} else {
|
|
||||||
const values = this._list[key];
|
|
||||||
if (values) {
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all values for a key.
|
|
||||||
* @param key The key whose values are to be removed
|
|
||||||
*/
|
|
||||||
delete(key: string): void {
|
|
||||||
// Set to undefined rather than deleting the key, for better consistency across browsers.
|
|
||||||
// If a deleted key is re-added, most browsers put it at the end of iteration order, but IE maintains
|
|
||||||
// its original position. This approach maintains the original position everywhere.
|
|
||||||
this._list[key] = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first value associated with a key.
|
|
||||||
* @param key The key to return the first value for
|
|
||||||
* @return The first string value for the key
|
|
||||||
*/
|
|
||||||
get(key: string): string | undefined {
|
|
||||||
if (!this.has(key)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const value = this._list[key];
|
|
||||||
return value ? value[0] : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the values associated with a key.
|
|
||||||
* @param key The key to return all values for
|
|
||||||
* @return An array of strings containing all values for the key
|
|
||||||
*/
|
|
||||||
getAll(key: string): string[] | undefined {
|
|
||||||
if (!this.has(key)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return this._list[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a key has been set to any value, false otherwise.
|
|
||||||
* @param key The key to test for existence
|
|
||||||
* @return A boolean indicating if the key has been set
|
|
||||||
*/
|
|
||||||
has(key: string): boolean {
|
|
||||||
return Array.isArray(this._list[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of all keys which have been set.
|
|
||||||
* @return An array of strings containing all keys set in the UrlSearchParams instance
|
|
||||||
*/
|
|
||||||
keys(): string[] {
|
|
||||||
const keys: string[] = [];
|
|
||||||
|
|
||||||
for (const key in this._list) {
|
|
||||||
if (this.has(key)) {
|
|
||||||
keys.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value associated with a key.
|
|
||||||
* @param key The key to set the value of
|
|
||||||
*/
|
|
||||||
set(key: string, value: string): void {
|
|
||||||
this._list[key] = [value];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns this object's data as an encoded query string.
|
|
||||||
* @return A string in application/x-www-form-urlencoded format containing all of the set keys/values
|
|
||||||
*/
|
|
||||||
toString(): string {
|
|
||||||
const query: string[] = [];
|
|
||||||
|
|
||||||
for (const key in this._list) {
|
|
||||||
if (!this.has(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = this._list[key];
|
|
||||||
if (values) {
|
|
||||||
const encodedKey = encodeURIComponent(key);
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
query.push(encodedKey + (values[i] ? '=' + encodeURIComponent(values[i]) : ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.join('&');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UrlSearchParams;
|
|
||||||
@ -1,531 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
import WeakMap from '@dojo/shim/WeakMap';
|
|
||||||
import { createHandle } from './lang';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that provides the necessary APIs to be MapLike
|
|
||||||
*/
|
|
||||||
export interface MapLike<K, V> {
|
|
||||||
get(key: K): V;
|
|
||||||
set(key: K, value?: V): this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal type guard that determines if an value is MapLike or not
|
|
||||||
*
|
|
||||||
* @param value The value to guard against
|
|
||||||
*/
|
|
||||||
function isMapLike(value: any): value is MapLike<any, any> {
|
|
||||||
return value && typeof value.get === 'function' && typeof value.set === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Indexable {
|
|
||||||
[method: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The types of objects or maps where advice can be applied
|
|
||||||
*/
|
|
||||||
export type Targetable = MapLike<string | symbol, any> | Indexable;
|
|
||||||
|
|
||||||
type AdviceType = 'before' | 'after' | 'around';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A meta data structure when applying advice
|
|
||||||
*/
|
|
||||||
interface Advised {
|
|
||||||
readonly id?: number;
|
|
||||||
advice?: Function;
|
|
||||||
previous?: Advised;
|
|
||||||
next?: Advised;
|
|
||||||
readonly receiveArguments?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that dispatches advice which is decorated with additional
|
|
||||||
* meta data about the advice to apply
|
|
||||||
*/
|
|
||||||
interface Dispatcher {
|
|
||||||
[type: string]: Advised | undefined;
|
|
||||||
(): any;
|
|
||||||
target: any;
|
|
||||||
before?: Advised;
|
|
||||||
around?: Advised;
|
|
||||||
after?: Advised;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinPointDispatchAdvice<T> {
|
|
||||||
before?: JoinPointBeforeAdvice[];
|
|
||||||
after?: JoinPointAfterAdvice<T>[];
|
|
||||||
readonly joinPoint: Function;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinPointAfterAdvice<T> {
|
|
||||||
/**
|
|
||||||
* Advice which is applied *after*, receiving the result and arguments from the join point.
|
|
||||||
*
|
|
||||||
* @param result The result from the function being advised
|
|
||||||
* @param args The arguments that were supplied to the advised function
|
|
||||||
* @returns The value returned from the advice is then the result of calling the method
|
|
||||||
*/
|
|
||||||
(result: T, ...args: any[]): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinPointAroundAdvice<T> {
|
|
||||||
/**
|
|
||||||
* Advice which is applied *around*. The advising function receives the original function and
|
|
||||||
* needs to return a new function which will then invoke the original function.
|
|
||||||
*
|
|
||||||
* @param origFn The original function
|
|
||||||
* @returns A new function which will invoke the original function.
|
|
||||||
*/
|
|
||||||
(origFn: GenericFunction<T>): (...args: any[]) => T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinPointBeforeAdvice {
|
|
||||||
/**
|
|
||||||
* Advice which is applied *before*, receiving the original arguments, if the advising
|
|
||||||
* function returns a value, it is passed further along taking the place of the original
|
|
||||||
* arguments.
|
|
||||||
*
|
|
||||||
* @param args The arguments the method was called with
|
|
||||||
*/
|
|
||||||
(...args: any[]): any[] | void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GenericFunction<T> {
|
|
||||||
(...args: any[]): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A weak map of dispatchers used to apply the advice
|
|
||||||
*/
|
|
||||||
const dispatchAdviceMap = new WeakMap<Function, JoinPointDispatchAdvice<any>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A UID for tracking advice ordering
|
|
||||||
*/
|
|
||||||
let nextId = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that advises a join point
|
|
||||||
*
|
|
||||||
* @param dispatcher The current advice dispatcher
|
|
||||||
* @param type The type of before or after advice to apply
|
|
||||||
* @param advice The advice to apply
|
|
||||||
* @param receiveArguments If true, the advice will receive the arguments passed to the join point
|
|
||||||
* @return The handle that will remove the advice
|
|
||||||
*/
|
|
||||||
function adviseObject(
|
|
||||||
dispatcher: Dispatcher | undefined,
|
|
||||||
type: AdviceType,
|
|
||||||
advice: Function | undefined,
|
|
||||||
receiveArguments?: boolean
|
|
||||||
): Handle {
|
|
||||||
let previous = dispatcher && dispatcher[type];
|
|
||||||
let advised: Advised | undefined = {
|
|
||||||
id: nextId++,
|
|
||||||
advice: advice,
|
|
||||||
receiveArguments: receiveArguments
|
|
||||||
};
|
|
||||||
|
|
||||||
if (previous) {
|
|
||||||
if (type === 'after') {
|
|
||||||
// add the listener to the end of the list
|
|
||||||
// note that we had to change this loop a little bit to workaround a bizarre IE10 JIT bug
|
|
||||||
while (previous.next && (previous = previous.next)) {}
|
|
||||||
previous.next = advised;
|
|
||||||
advised.previous = previous;
|
|
||||||
} else {
|
|
||||||
// add to the beginning
|
|
||||||
if (dispatcher) {
|
|
||||||
dispatcher.before = advised;
|
|
||||||
}
|
|
||||||
advised.next = previous;
|
|
||||||
previous.previous = advised;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dispatcher && (dispatcher[type] = advised);
|
|
||||||
}
|
|
||||||
|
|
||||||
advice = previous = undefined;
|
|
||||||
|
|
||||||
return createHandle(function() {
|
|
||||||
let { previous = undefined, next = undefined } = advised || {};
|
|
||||||
|
|
||||||
if (dispatcher && !previous && !next) {
|
|
||||||
dispatcher[type] = undefined;
|
|
||||||
} else {
|
|
||||||
if (previous) {
|
|
||||||
previous.next = next;
|
|
||||||
} else {
|
|
||||||
dispatcher && (dispatcher[type] = next);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next) {
|
|
||||||
next.previous = previous;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (advised) {
|
|
||||||
delete advised.advice;
|
|
||||||
}
|
|
||||||
dispatcher = advised = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advise a join point (function) with supplied advice
|
|
||||||
*
|
|
||||||
* @param joinPoint The function to be advised
|
|
||||||
* @param type The type of advice to be applied
|
|
||||||
* @param advice The advice to apply
|
|
||||||
*/
|
|
||||||
function adviseJoinPoint<F extends GenericFunction<T>, T>(
|
|
||||||
this: any,
|
|
||||||
joinPoint: F,
|
|
||||||
type: AdviceType,
|
|
||||||
advice: JoinPointBeforeAdvice | JoinPointAfterAdvice<T> | JoinPointAroundAdvice<T>
|
|
||||||
): F {
|
|
||||||
let dispatcher: F;
|
|
||||||
if (type === 'around') {
|
|
||||||
dispatcher = getJoinPointDispatcher(advice.apply(this, [joinPoint]));
|
|
||||||
} else {
|
|
||||||
dispatcher = getJoinPointDispatcher(joinPoint);
|
|
||||||
// cannot have undefined in map due to code logic, using !
|
|
||||||
const adviceMap = dispatchAdviceMap.get(dispatcher)!;
|
|
||||||
if (type === 'before') {
|
|
||||||
(adviceMap.before || (adviceMap.before = [])).unshift(<JoinPointBeforeAdvice>advice);
|
|
||||||
} else {
|
|
||||||
(adviceMap.after || (adviceMap.after = [])).push(advice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that resolves or creates the dispatcher for a given join point
|
|
||||||
*
|
|
||||||
* @param target The target object or map
|
|
||||||
* @param methodName The name of the method that the dispatcher should be resolved for
|
|
||||||
* @return The dispatcher
|
|
||||||
*/
|
|
||||||
function getDispatcherObject(target: Targetable, methodName: string | symbol): Dispatcher {
|
|
||||||
const existing = isMapLike(target) ? target.get(methodName) : target && target[methodName];
|
|
||||||
let dispatcher: Dispatcher;
|
|
||||||
|
|
||||||
if (!existing || existing.target !== target) {
|
|
||||||
/* There is no existing dispatcher, therefore we will create one */
|
|
||||||
dispatcher = <Dispatcher>function(this: Dispatcher): any {
|
|
||||||
let executionId = nextId;
|
|
||||||
let args = arguments;
|
|
||||||
let results: any;
|
|
||||||
let before = dispatcher.before;
|
|
||||||
|
|
||||||
while (before) {
|
|
||||||
if (before.advice) {
|
|
||||||
args = before.advice.apply(this, args) || args;
|
|
||||||
}
|
|
||||||
before = before.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispatcher.around && dispatcher.around.advice) {
|
|
||||||
results = dispatcher.around.advice(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
let after = dispatcher.after;
|
|
||||||
while (after && after.id !== undefined && after.id < executionId) {
|
|
||||||
if (after.advice) {
|
|
||||||
if (after.receiveArguments) {
|
|
||||||
let newResults = after.advice.apply(this, args);
|
|
||||||
results = newResults === undefined ? results : newResults;
|
|
||||||
} else {
|
|
||||||
results = after.advice.call(this, results, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
after = after.next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isMapLike(target)) {
|
|
||||||
target.set(methodName, dispatcher);
|
|
||||||
} else {
|
|
||||||
target && (target[methodName] = dispatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
dispatcher.around = {
|
|
||||||
advice: function(target: any, args: any[]): any {
|
|
||||||
return existing.apply(target, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatcher.target = target;
|
|
||||||
} else {
|
|
||||||
dispatcher = existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the dispatcher function for a given joinPoint (method/function)
|
|
||||||
*
|
|
||||||
* @param joinPoint The function that is to be advised
|
|
||||||
*/
|
|
||||||
function getJoinPointDispatcher<F extends GenericFunction<T>, T>(joinPoint: F): F {
|
|
||||||
function dispatcher(this: Function, ...args: any[]): T {
|
|
||||||
// cannot have undefined in map due to code logic, using !
|
|
||||||
const { before, after, joinPoint } = dispatchAdviceMap.get(dispatcher)!;
|
|
||||||
if (before) {
|
|
||||||
args = before.reduce((previousArgs, advice) => {
|
|
||||||
const currentArgs = advice.apply(this, previousArgs);
|
|
||||||
return currentArgs || previousArgs;
|
|
||||||
}, args);
|
|
||||||
}
|
|
||||||
let result = joinPoint.apply(this, args);
|
|
||||||
if (after) {
|
|
||||||
result = after.reduce((previousResult, advice) => {
|
|
||||||
return advice.apply(this, [previousResult].concat(args));
|
|
||||||
}, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We want to "clone" the advice that has been applied already, if this
|
|
||||||
* joinPoint is already advised */
|
|
||||||
if (dispatchAdviceMap.has(joinPoint)) {
|
|
||||||
// cannot have undefined in map due to code logic, using !
|
|
||||||
const adviceMap = dispatchAdviceMap.get(joinPoint)!;
|
|
||||||
let { before, after } = adviceMap;
|
|
||||||
if (before) {
|
|
||||||
before = before.slice(0);
|
|
||||||
}
|
|
||||||
if (after) {
|
|
||||||
after = after.slice(0);
|
|
||||||
}
|
|
||||||
dispatchAdviceMap.set(dispatcher, {
|
|
||||||
joinPoint: adviceMap.joinPoint,
|
|
||||||
before,
|
|
||||||
after
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
/* Otherwise, this is a new joinPoint, so we will create the advice map afresh */
|
|
||||||
dispatchAdviceMap.set(dispatcher, { joinPoint });
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatcher as F;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply advice *after* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The after advice
|
|
||||||
*/
|
|
||||||
function afterJoinPoint<F extends GenericFunction<T>, T>(joinPoint: F, advice: JoinPointAfterAdvice<T>): F {
|
|
||||||
return adviseJoinPoint(joinPoint, 'after', advice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "after" advice to be executed after the original method.
|
|
||||||
* The advising function will receive the original method's return value and arguments object.
|
|
||||||
* The value it returns will be returned from the method when it is called (even if the return value is undefined).
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the original method's return value and arguments object
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
function afterObject(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: (originalReturn: any, originalArgs: IArguments) => any
|
|
||||||
): Handle {
|
|
||||||
return adviseObject(getDispatcherObject(target, methodName), 'after', advice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "after" advice to be executed after the original method.
|
|
||||||
* The advising function will receive the original method's return value and arguments object.
|
|
||||||
* The value it returns will be returned from the method when it is called (even if the return value is undefined).
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the original method's return value and arguments object
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function after(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: (originalReturn: any, originalArgs: IArguments) => any
|
|
||||||
): Handle;
|
|
||||||
/**
|
|
||||||
* Apply advice *after* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The after advice
|
|
||||||
*/
|
|
||||||
export function after<F extends GenericFunction<T>, T>(joinPoint: F, advice: JoinPointAfterAdvice<T>): F;
|
|
||||||
export function after<F extends GenericFunction<T>, T>(
|
|
||||||
joinPointOrTarget: F | Targetable,
|
|
||||||
methodNameOrAdvice: string | symbol | JoinPointAfterAdvice<T>,
|
|
||||||
objectAdvice?: (originalReturn: any, originalArgs: IArguments) => any
|
|
||||||
): Handle | F {
|
|
||||||
if (typeof joinPointOrTarget === 'function') {
|
|
||||||
return afterJoinPoint(joinPointOrTarget, <JoinPointAfterAdvice<T>>methodNameOrAdvice);
|
|
||||||
} else {
|
|
||||||
return afterObject(joinPointOrTarget, <string | symbol>methodNameOrAdvice, objectAdvice!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply advice *around* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The around advice
|
|
||||||
*/
|
|
||||||
export function aroundJoinPoint<F extends GenericFunction<T>, T>(joinPoint: F, advice: JoinPointAroundAdvice<T>): F {
|
|
||||||
return adviseJoinPoint<F, T>(joinPoint, 'around', advice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "around" advice around the original method.
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the original function
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function aroundObject(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: ((previous: Function) => Function)
|
|
||||||
): Handle {
|
|
||||||
let dispatcher: Dispatcher | undefined = getDispatcherObject(target, methodName);
|
|
||||||
let previous = dispatcher.around;
|
|
||||||
let advised: Function | undefined;
|
|
||||||
if (advice) {
|
|
||||||
advised = advice(function(this: Dispatcher): any {
|
|
||||||
if (previous && previous.advice) {
|
|
||||||
return previous.advice(this, arguments);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatcher.around = {
|
|
||||||
advice: function(target: any, args: any[]): any {
|
|
||||||
return advised ? advised.apply(target, args) : previous && previous.advice && previous.advice(target, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return createHandle(function() {
|
|
||||||
advised = dispatcher = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "around" advice around the original method.
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the original function
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function around(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: ((previous: Function) => Function)
|
|
||||||
): Handle;
|
|
||||||
/**
|
|
||||||
* Apply advice *around* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The around advice
|
|
||||||
*/
|
|
||||||
export function around<F extends GenericFunction<T>, T>(joinPoint: F, advice: JoinPointAroundAdvice<T>): F;
|
|
||||||
export function around<F extends GenericFunction<T>, T>(
|
|
||||||
joinPointOrTarget: F | Targetable,
|
|
||||||
methodNameOrAdvice: string | symbol | JoinPointAroundAdvice<T>,
|
|
||||||
objectAdvice?: ((previous: Function) => Function)
|
|
||||||
): Handle | F {
|
|
||||||
if (typeof joinPointOrTarget === 'function') {
|
|
||||||
return aroundJoinPoint(joinPointOrTarget, <JoinPointAroundAdvice<T>>methodNameOrAdvice);
|
|
||||||
} else {
|
|
||||||
return aroundObject(joinPointOrTarget, <string | symbol>methodNameOrAdvice, objectAdvice!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply advice *before* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The before advice
|
|
||||||
*/
|
|
||||||
export function beforeJoinPoint<F extends GenericFunction<any>>(joinPoint: F, advice: JoinPointBeforeAdvice): F {
|
|
||||||
return adviseJoinPoint(joinPoint, 'before', advice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "before" advice to be executed before the original method.
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the same arguments as the original, and may return new arguments
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function beforeObject(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: (...originalArgs: any[]) => any[] | void
|
|
||||||
): Handle {
|
|
||||||
return adviseObject(getDispatcherObject(target, methodName), 'before', advice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches "before" advice to be executed before the original method.
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the same arguments as the original, and may return new arguments
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function before(
|
|
||||||
target: Targetable,
|
|
||||||
methodName: string | symbol,
|
|
||||||
advice: (...originalArgs: any[]) => any[] | void
|
|
||||||
): Handle;
|
|
||||||
/**
|
|
||||||
* Apply advice *before* the supplied joinPoint (function)
|
|
||||||
*
|
|
||||||
* @param joinPoint A function that should have advice applied to
|
|
||||||
* @param advice The before advice
|
|
||||||
*/
|
|
||||||
export function before<F extends GenericFunction<any>>(joinPoint: F, advice: JoinPointBeforeAdvice): F;
|
|
||||||
export function before<F extends GenericFunction<T>, T>(
|
|
||||||
joinPointOrTarget: F | Targetable,
|
|
||||||
methodNameOrAdvice: string | symbol | JoinPointBeforeAdvice,
|
|
||||||
objectAdvice?: ((...originalArgs: any[]) => any[] | void)
|
|
||||||
): Handle | F {
|
|
||||||
if (typeof joinPointOrTarget === 'function') {
|
|
||||||
return beforeJoinPoint(joinPointOrTarget, <JoinPointBeforeAdvice>methodNameOrAdvice);
|
|
||||||
} else {
|
|
||||||
return beforeObject(joinPointOrTarget, <string | symbol>methodNameOrAdvice, objectAdvice!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches advice to be executed after the original method.
|
|
||||||
* The advising function will receive the same arguments as the original method.
|
|
||||||
* The value it returns will be returned from the method when it is called *unless* its return value is undefined.
|
|
||||||
*
|
|
||||||
* @param target Object whose method will be aspected
|
|
||||||
* @param methodName Name of method to aspect
|
|
||||||
* @param advice Advising function which will receive the same arguments as the original method
|
|
||||||
* @return A handle which will remove the aspect when destroy is called
|
|
||||||
*/
|
|
||||||
export function on(target: Targetable, methodName: string | symbol, advice: (...originalArgs: any[]) => any): Handle {
|
|
||||||
return adviseObject(getDispatcherObject(target, methodName), 'after', advice, true);
|
|
||||||
}
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
import { Thenable } from '@dojo/shim/interfaces';
|
|
||||||
import { isArrayLike, isIterable, Iterable } from '@dojo/shim/iterator';
|
|
||||||
import Promise, { Executor } from '@dojo/shim/Promise';
|
|
||||||
import '@dojo/shim/Symbol';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a list of values, and if any are ExtensiblePromise objects, insert the wrapped Promise in its place,
|
|
||||||
* otherwise use the original object. We use this to help use the native Promise methods like `all` and `race`.
|
|
||||||
*
|
|
||||||
* @param iterable The list of objects to iterate over
|
|
||||||
* @returns {any[]} The list of objects, as an array, with ExtensiblePromises being replaced by Promises.
|
|
||||||
*/
|
|
||||||
export function unwrapPromises(iterable: Iterable<any> | any[]): any[] {
|
|
||||||
const unwrapped: any[] = [];
|
|
||||||
|
|
||||||
if (isArrayLike(iterable)) {
|
|
||||||
for (let i = 0; i < iterable.length; i++) {
|
|
||||||
const item = iterable[i];
|
|
||||||
unwrapped.push(item instanceof ExtensiblePromise ? item._promise : item);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const item of iterable) {
|
|
||||||
unwrapped.push(item instanceof ExtensiblePromise ? item._promise : item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unwrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DictionaryOfPromises<T> = { [_: string]: T | Promise<T> | Thenable<T> };
|
|
||||||
export type ListOfPromises<T> = Iterable<T | Thenable<T>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An extensible base to allow Promises to be extended in ES5. This class basically wraps a native Promise object,
|
|
||||||
* giving an API like a native promise.
|
|
||||||
*/
|
|
||||||
export class ExtensiblePromise<T> {
|
|
||||||
/**
|
|
||||||
* Return a rejected promise wrapped in an ExtensiblePromise
|
|
||||||
*
|
|
||||||
* @param reason The reason for the rejection
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static reject<T>(reason?: any): ExtensiblePromise<never>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a rejected promise wrapped in an ExtensiblePromise
|
|
||||||
*
|
|
||||||
* @param reason The reason for the rejection
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static reject<T>(reason?: any): ExtensiblePromise<T> {
|
|
||||||
return new this((resolve, reject) => reject(reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a resolved promise wrapped in an ExtensiblePromise
|
|
||||||
*
|
|
||||||
* @param value The value to resolve the promise with
|
|
||||||
*
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static resolve<P extends ExtensiblePromise<void>>(): P;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a resolved promise wrapped in an ExtensiblePromise
|
|
||||||
*
|
|
||||||
* @param value The value to resolve the promise with
|
|
||||||
*
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static resolve<T, P extends ExtensiblePromise<T>>(value: T | PromiseLike<T>): P;
|
|
||||||
static resolve(value?: any | PromiseLike<any>): ExtensiblePromise<any> {
|
|
||||||
return new this((resolve, reject) => resolve(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: DictionaryOfPromises<T>): ExtensiblePromise<{ [key: string]: T }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: (T | Thenable<T>)[]): ExtensiblePromise<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: T | Thenable<T>): ExtensiblePromise<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: ListOfPromises<T>): ExtensiblePromise<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(
|
|
||||||
iterable: DictionaryOfPromises<T> | ListOfPromises<T>
|
|
||||||
): ExtensiblePromise<T[] | { [key: string]: T }> {
|
|
||||||
if (!isArrayLike(iterable) && !isIterable(iterable)) {
|
|
||||||
const promiseKeys = Object.keys(iterable);
|
|
||||||
|
|
||||||
return new this((resolve, reject) => {
|
|
||||||
Promise.all(promiseKeys.map((key) => iterable[key])).then((promiseResults: T[]) => {
|
|
||||||
const returnValue: { [key: string]: T } = {};
|
|
||||||
|
|
||||||
promiseResults.forEach((value: T, index: number) => {
|
|
||||||
returnValue[promiseKeys[index]] = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(returnValue);
|
|
||||||
}, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new this((resolve, reject) => {
|
|
||||||
Promise.all(unwrapPromises(<Iterable<T>>iterable)).then(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when one of the passed in objects have resolved
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns {ExtensiblePromise}
|
|
||||||
*/
|
|
||||||
static race<T>(iterable: Iterable<T | PromiseLike<T>> | (T | PromiseLike<T>)[]): ExtensiblePromise<T> {
|
|
||||||
return new this((resolve, reject) => {
|
|
||||||
Promise.race(unwrapPromises(iterable)).then(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Promise}
|
|
||||||
* The wrapped promise
|
|
||||||
*/
|
|
||||||
readonly _promise: Promise<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new extended Promise.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* @param executor
|
|
||||||
* The executor function is called immediately when the Promise is instantiated. It is responsible for
|
|
||||||
* starting the asynchronous operation when it is invoked.
|
|
||||||
*
|
|
||||||
* The executor must call either the passed `resolve` function when the asynchronous operation has completed
|
|
||||||
* successfully, or the `reject` function when the operation fails.
|
|
||||||
*/
|
|
||||||
constructor(executor: Executor<T>) {
|
|
||||||
this._promise = new Promise<T>(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a callback to be invoked when the wrapped Promise is rejected.
|
|
||||||
*
|
|
||||||
* @param {Function} onRejected A function to call to handle the error. The parameter to the function will be the caught error.
|
|
||||||
*
|
|
||||||
* @returns {ExtensiblePromise}
|
|
||||||
*/
|
|
||||||
catch<TResult = never>(
|
|
||||||
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
|
|
||||||
): ExtensiblePromise<T | TResult> {
|
|
||||||
return this.then(undefined, onRejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a callback to be invoked when the wrapped Promise resolves or is rejected.
|
|
||||||
*
|
|
||||||
* @param {Function} onFulfilled A function to call to handle the resolution. The paramter to the function will be the resolved value, if any.
|
|
||||||
* @param {Function} onRejected A function to call to handle the error. The parameter to the function will be the caught error.
|
|
||||||
*
|
|
||||||
* @returns {ExtensiblePromise}
|
|
||||||
*/
|
|
||||||
then<TResult1 = T, TResult2 = never>(
|
|
||||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
||||||
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | void) | undefined | null
|
|
||||||
): ExtensiblePromise<TResult1 | TResult2> {
|
|
||||||
const executor: Executor<TResult1> = (resolve, reject) => {
|
|
||||||
function handler(rejected: boolean, valueOrError: T | TResult1 | Error) {
|
|
||||||
const callback: ((value: any) => any) | null | undefined = rejected ? onRejected : onFulfilled;
|
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
try {
|
|
||||||
resolve(callback(valueOrError));
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
} else if (rejected) {
|
|
||||||
reject(valueOrError);
|
|
||||||
} else {
|
|
||||||
resolve(valueOrError as TResult1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._promise.then(handler.bind(null, false), handler.bind(null, true));
|
|
||||||
};
|
|
||||||
|
|
||||||
return new (this.constructor as typeof ExtensiblePromise)(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly [Symbol.toStringTag]: 'Promise';
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExtensiblePromise;
|
|
||||||
@ -1,384 +0,0 @@
|
|||||||
import { Thenable } from '@dojo/shim/interfaces';
|
|
||||||
import { isArrayLike, isIterable, Iterable } from '@dojo/shim/iterator';
|
|
||||||
import { Executor } from '@dojo/shim/Promise';
|
|
||||||
import ExtensiblePromise, { DictionaryOfPromises, ListOfPromises, unwrapPromises } from './ExtensiblePromise';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describe the internal state of a task.
|
|
||||||
*/
|
|
||||||
export const enum State {
|
|
||||||
Fulfilled = 0,
|
|
||||||
Pending = 1,
|
|
||||||
Rejected = 2,
|
|
||||||
Canceled = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type guard that determines if `value` is a `Task`
|
|
||||||
* @param value The value to guard
|
|
||||||
*/
|
|
||||||
export function isTask<T>(value: any): value is Task<T> {
|
|
||||||
return Boolean(value && typeof value.cancel === 'function' && Array.isArray(value.children) && isThenable(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a given value has a `then` method.
|
|
||||||
* @param {any} value The value to check if is Thenable
|
|
||||||
* @returns {is Thenable<T>} A type guard if the value is thenable
|
|
||||||
*/
|
|
||||||
export function isThenable<T>(value: any): value is Thenable<T> {
|
|
||||||
return value && typeof value.then === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task is an extension of Promise that supports cancellation and the Task#finally method.
|
|
||||||
*/
|
|
||||||
export class Task<T> extends ExtensiblePromise<T> {
|
|
||||||
/**
|
|
||||||
* Return a Task that resolves when one of the passed in objects have resolved
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns {Task}
|
|
||||||
*/
|
|
||||||
static race<T>(iterable: Iterable<T | Thenable<T>> | (T | Thenable<T>)[]): Task<T> {
|
|
||||||
return new this((resolve, reject) => {
|
|
||||||
Promise.race(unwrapPromises(iterable)).then(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a rejected promise wrapped in a Task
|
|
||||||
*
|
|
||||||
* @param reason The reason for the rejection
|
|
||||||
* @returns A task
|
|
||||||
*/
|
|
||||||
static reject<T>(reason?: Error): Task<T> {
|
|
||||||
return new this((resolve, reject) => reject(reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a resolved task.
|
|
||||||
*
|
|
||||||
* @param value The value to resolve with
|
|
||||||
*
|
|
||||||
* @return A task
|
|
||||||
*/
|
|
||||||
public static resolve(): Task<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a resolved task.
|
|
||||||
*
|
|
||||||
* @param value The value to resolve with
|
|
||||||
*
|
|
||||||
* @return A task
|
|
||||||
*/
|
|
||||||
public static resolve<T>(value: T | Thenable<T>): Task<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a resolved task.
|
|
||||||
*
|
|
||||||
* @param value The value to resolve with
|
|
||||||
*
|
|
||||||
* @return A task
|
|
||||||
*/
|
|
||||||
public static resolve<T>(value?: any): Task<T> {
|
|
||||||
return new this<T>((resolve, reject) => resolve(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: DictionaryOfPromises<T>): Task<{ [key: string]: T }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: (T | Thenable<T>)[]): Task<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: T | Thenable<T>): Task<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: ListOfPromises<T>): Task<T[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a ExtensiblePromise that resolves when all of the passed in objects have resolved. When used with a key/value
|
|
||||||
* pair, the returned promise's argument is a key/value pair of the original keys with their resolved values.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ExtensiblePromise.all({ one: 1, two: 2 }).then(results => console.log(results));
|
|
||||||
* // { one: 1, two: 2 }
|
|
||||||
*
|
|
||||||
* @param iterable An iterable of values to resolve, or a key/value pair of values to resolve. These can be Promises, ExtensiblePromises, or other objects
|
|
||||||
* @returns An extensible promise
|
|
||||||
*/
|
|
||||||
static all<T>(iterable: DictionaryOfPromises<T> | ListOfPromises<T>): Task<any> {
|
|
||||||
return new Task(
|
|
||||||
(resolve, reject) => {
|
|
||||||
super.all(iterable).then(resolve, reject);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (isArrayLike(iterable)) {
|
|
||||||
for (let i = 0; i < iterable.length; i++) {
|
|
||||||
const promiseLike = iterable[i];
|
|
||||||
|
|
||||||
if (isTask(promiseLike)) {
|
|
||||||
promiseLike.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isIterable(iterable)) {
|
|
||||||
for (const promiseLike of iterable) {
|
|
||||||
if (isTask(promiseLike)) {
|
|
||||||
promiseLike.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Object.keys(iterable).forEach((key: any) => {
|
|
||||||
const promiseLike = iterable[key];
|
|
||||||
|
|
||||||
if (isTask(promiseLike)) {
|
|
||||||
promiseLike.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cancelation handler that will be called if this task is canceled.
|
|
||||||
*/
|
|
||||||
private canceler: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Children of this Task (i.e., Tasks that were created from this Task with `then` or `catch`).
|
|
||||||
*/
|
|
||||||
private readonly children: Task<any>[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The finally callback for this Task (if it was created by a call to `finally`).
|
|
||||||
*/
|
|
||||||
private _finally: undefined | (() => void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The state of the task
|
|
||||||
*/
|
|
||||||
protected _state: State;
|
|
||||||
|
|
||||||
get state() {
|
|
||||||
return this._state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* Create a new task. Executor is run immediately. The canceler will be called when the task is canceled.
|
|
||||||
*
|
|
||||||
* @param executor Method that initiates some task
|
|
||||||
* @param canceler Method to call when the task is canceled
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
constructor(executor: Executor<T>, canceler?: () => void) {
|
|
||||||
// we have to initialize these to avoid a compiler error of using them before they are initialized
|
|
||||||
let superResolve: (value?: T | Thenable<T> | undefined) => void = () => {};
|
|
||||||
let superReject: (reason?: any) => void = () => {};
|
|
||||||
|
|
||||||
super((resolve, reject) => {
|
|
||||||
superResolve = resolve;
|
|
||||||
superReject = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._state = State.Pending;
|
|
||||||
|
|
||||||
this.children = [];
|
|
||||||
this.canceler = () => {
|
|
||||||
if (canceler) {
|
|
||||||
canceler();
|
|
||||||
}
|
|
||||||
this._cancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't let the Task resolve if it's been canceled
|
|
||||||
try {
|
|
||||||
executor(
|
|
||||||
(value) => {
|
|
||||||
if (this._state === State.Canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._state = State.Fulfilled;
|
|
||||||
superResolve(value);
|
|
||||||
},
|
|
||||||
(reason) => {
|
|
||||||
if (this._state === State.Canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._state = State.Rejected;
|
|
||||||
superReject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (reason) {
|
|
||||||
this._state = State.Rejected;
|
|
||||||
superReject(reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Propagates cancellation down through a Task tree. The Task's state is immediately set to canceled. If a Thenable
|
|
||||||
* finally task was passed in, it is resolved before calling this Task's finally callback; otherwise, this Task's
|
|
||||||
* finally callback is immediately executed. `_cancel` is called for each child Task, passing in the value returned
|
|
||||||
* by this Task's finally callback or a Promise chain that will eventually resolve to that value.
|
|
||||||
*/
|
|
||||||
private _cancel(finallyTask?: void | Thenable<any>): void {
|
|
||||||
this._state = State.Canceled;
|
|
||||||
|
|
||||||
const runFinally = () => {
|
|
||||||
try {
|
|
||||||
return this._finally && this._finally();
|
|
||||||
} catch (error) {
|
|
||||||
// Any errors in a `finally` callback are completely ignored during cancelation
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this._finally) {
|
|
||||||
if (isThenable(finallyTask)) {
|
|
||||||
finallyTask = (<Thenable<any>>finallyTask).then(runFinally, runFinally);
|
|
||||||
} else {
|
|
||||||
finallyTask = runFinally();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.children.forEach(function(child) {
|
|
||||||
child._cancel(finallyTask);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immediately cancels this task if it has not already resolved. This Task and any descendants are synchronously set
|
|
||||||
* to the Canceled state and any `finally` added downstream from the canceled Task are invoked.
|
|
||||||
*/
|
|
||||||
cancel(): void {
|
|
||||||
if (this._state === State.Pending) {
|
|
||||||
this.canceler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
catch<TResult = never>(
|
|
||||||
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined
|
|
||||||
): Task<T | TResult> {
|
|
||||||
return this.then(undefined, onRejected) as Task<T | TResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows for cleanup actions to be performed after resolution of a Promise.
|
|
||||||
*/
|
|
||||||
finally(callback: () => void): Task<T> {
|
|
||||||
// if this task is already canceled, call the task
|
|
||||||
if (this._state === State.Canceled) {
|
|
||||||
callback();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = this.then<any>(
|
|
||||||
(value) => Task.resolve(callback()).then(() => value),
|
|
||||||
(reason) =>
|
|
||||||
Task.resolve(callback()).then(() => {
|
|
||||||
throw reason;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Keep a reference to the callback; it will be called if the Task is canceled
|
|
||||||
task._finally = callback;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a callback to be invoked when the Task resolves or is rejected.
|
|
||||||
*
|
|
||||||
* @param onFulfilled A function to call to handle the resolution. The paramter to the function will be the resolved value, if any.
|
|
||||||
* @param onRejected A function to call to handle the error. The parameter to the function will be the caught error.
|
|
||||||
*
|
|
||||||
* @returns A task
|
|
||||||
*/
|
|
||||||
then<TResult1 = T, TResult2 = never>(
|
|
||||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
||||||
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
|
||||||
): Task<TResult1 | TResult2> {
|
|
||||||
// FIXME
|
|
||||||
// tslint:disable-next-line:no-var-keyword
|
|
||||||
var task = super.then(
|
|
||||||
// Don't call the onFulfilled or onRejected handlers if this Task is canceled
|
|
||||||
function(value) {
|
|
||||||
if (task._state === State.Canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (onFulfilled) {
|
|
||||||
return onFulfilled(value);
|
|
||||||
}
|
|
||||||
return <any>value;
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
if (task._state === State.Canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (onRejected) {
|
|
||||||
return onRejected(error);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
) as Task<TResult1 | TResult2>;
|
|
||||||
|
|
||||||
task.canceler = () => {
|
|
||||||
// If task's parent (this) hasn't been resolved, cancel it; downward propagation will start at the first
|
|
||||||
// unresolved parent
|
|
||||||
if (this._state === State.Pending) {
|
|
||||||
this.cancel();
|
|
||||||
} else {
|
|
||||||
// If task's parent has been resolved, propagate cancelation to the task's descendants
|
|
||||||
task._cancel();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep track of child Tasks for propogating cancelation back down the chain
|
|
||||||
this.children.push(task);
|
|
||||||
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Task;
|
|
||||||
@ -1,295 +0,0 @@
|
|||||||
import * as array from '@dojo/shim/array';
|
|
||||||
import { isArrayLike, Iterable } from '@dojo/shim/iterator';
|
|
||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import { Thenable } from '@dojo/shim/interfaces';
|
|
||||||
|
|
||||||
function isThenable<T>(value: any): value is Thenable<T> {
|
|
||||||
return value && typeof value.then === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValuesAndResults<T, U> = { values: T[] | undefined; results: U[] | undefined };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes all items and then applies the callback to each item and eventually returns an object containing the
|
|
||||||
* processed values and callback results
|
|
||||||
* @param items a list of synchronous/asynchronous values to process
|
|
||||||
* @param callback a callback that maps values to synchronous/asynchronous results
|
|
||||||
* @return a list of objects holding the synchronous values and synchronous results.
|
|
||||||
*/
|
|
||||||
function processValuesAndCallback<T, U>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Thenable<T>)[],
|
|
||||||
callback: Mapper<T, U>
|
|
||||||
): Promise<ValuesAndResults<T, U>> {
|
|
||||||
return Promise.all(items).then(function(results) {
|
|
||||||
const pass: (U | Promise<U>)[] = Array.prototype.map.call(results, callback);
|
|
||||||
return Promise.all(pass).then(function(pass) {
|
|
||||||
return { values: results, results: pass };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the index of the next value in a sparse array-like object
|
|
||||||
* @param list the sparse array-like object
|
|
||||||
* @param offset the starting offset
|
|
||||||
* @return the offset of the next index with a value; or -1 if not found
|
|
||||||
*/
|
|
||||||
function findNextValueIndex<T>(list: ArrayLike<T>, offset: number = -1): number {
|
|
||||||
offset++;
|
|
||||||
for (let length = list.length; offset < length; offset++) {
|
|
||||||
if (offset in list) {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findLastValueIndex(list: ArrayLike<any>, offset?: number): number {
|
|
||||||
offset = (offset === undefined ? list.length : offset) - 1;
|
|
||||||
for (; offset >= 0; offset--) {
|
|
||||||
if (offset in list) {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generalReduce<T, U>(
|
|
||||||
findNextIndex: (list: ArrayLike<any>, offset?: number) => number,
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Reducer<T, U>,
|
|
||||||
initialValue?: U
|
|
||||||
): Promise<U> {
|
|
||||||
const hasInitialValue = arguments.length > 3;
|
|
||||||
return Promise.all(items).then(function(results) {
|
|
||||||
return new Promise<U>(function(resolve, reject) {
|
|
||||||
// As iterators do not have indices like `ArrayLike` objects, the results array
|
|
||||||
// is used to determine the next value.
|
|
||||||
const list = isArrayLike(items) ? items : results;
|
|
||||||
let i: number;
|
|
||||||
function next(currentValue: U | undefined): void {
|
|
||||||
i = findNextIndex(list, i);
|
|
||||||
if (i >= 0) {
|
|
||||||
if (results) {
|
|
||||||
if (currentValue) {
|
|
||||||
const result = callback(currentValue, results[i], i, results);
|
|
||||||
|
|
||||||
if (isThenable(result)) {
|
|
||||||
result.then(next, reject);
|
|
||||||
} else {
|
|
||||||
next(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let value: U | undefined;
|
|
||||||
if (hasInitialValue) {
|
|
||||||
value = initialValue;
|
|
||||||
} else {
|
|
||||||
i = findNextIndex(list);
|
|
||||||
|
|
||||||
if (i < 0) {
|
|
||||||
throw new Error('reduce array with no initial value');
|
|
||||||
}
|
|
||||||
if (results) {
|
|
||||||
value = <any>results[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next(value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAndHaltOnCondition<T>(
|
|
||||||
condition: boolean,
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Filterer<T>
|
|
||||||
): Promise<boolean> {
|
|
||||||
return Promise.all(items).then(function(results) {
|
|
||||||
return new Promise<boolean>(function(resolve) {
|
|
||||||
let result: boolean | Thenable<boolean>;
|
|
||||||
let pendingCount = 0;
|
|
||||||
if (results) {
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
|
||||||
result = callback(results[i], i, results);
|
|
||||||
if (result === condition) {
|
|
||||||
return resolve(result);
|
|
||||||
} else if (isThenable(result)) {
|
|
||||||
pendingCount++;
|
|
||||||
result.then(function(result) {
|
|
||||||
if (result === condition) {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
pendingCount--;
|
|
||||||
if (pendingCount === 0) {
|
|
||||||
resolve(!condition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pendingCount === 0) {
|
|
||||||
resolve(!condition);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether all elements in the array pass the provided callback
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous test
|
|
||||||
* @return eventually returns true if all values pass; otherwise false
|
|
||||||
*/
|
|
||||||
export function every<T>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Filterer<T>
|
|
||||||
): Promise<boolean> {
|
|
||||||
return testAndHaltOnCondition(false, items, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of elements which pass the provided callback
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous test
|
|
||||||
* @return eventually returns a new array with only values that have passed
|
|
||||||
*/
|
|
||||||
export function filter<T>(items: Iterable<T | Promise<T>> | (T | Promise<T>)[], callback: Filterer<T>): Promise<T[]> {
|
|
||||||
return processValuesAndCallback(items, callback).then(function(result) {
|
|
||||||
let arr: T[] = [];
|
|
||||||
if (result && result.results && result.values) {
|
|
||||||
for (let i = 0; i < result.results.length; i++) {
|
|
||||||
result.results[i] && arr.push(result.values[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first value matching a filter function
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous test
|
|
||||||
* @return a promise eventually containing the item or undefined if a match is not found
|
|
||||||
*/
|
|
||||||
export function find<T>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Filterer<T>
|
|
||||||
): Promise<T | undefined> {
|
|
||||||
const list = isArrayLike(items) ? items : array.from(items);
|
|
||||||
return findIndex(list, callback).then<T | undefined>(function(i) {
|
|
||||||
return i !== undefined && i >= 0 ? list[i] : undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the first index with a value matching the filter function
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous test
|
|
||||||
* @return a promise eventually containing the index of the matching item or -1 if a match is not found
|
|
||||||
*/
|
|
||||||
export function findIndex<T>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Thenable<T>)[],
|
|
||||||
callback: Filterer<T>
|
|
||||||
): Promise<number> {
|
|
||||||
// TODO we can improve this by returning immediately
|
|
||||||
return processValuesAndCallback(items, callback).then(function(result: ValuesAndResults<T, boolean>) {
|
|
||||||
if (result && result.results) {
|
|
||||||
for (let i = 0; i < result.results.length; i++) {
|
|
||||||
if (result.results[i]) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* transform a list of items using a mapper function
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous transform function
|
|
||||||
* @return a promise eventually containing a collection of each transformed value
|
|
||||||
*/
|
|
||||||
export function map<T, U>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Mapper<T, U>
|
|
||||||
): Promise<U[] | null | undefined> {
|
|
||||||
return processValuesAndCallback(items, callback).then(function(result) {
|
|
||||||
return result ? result.results : null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reduce a list of items down to a single value
|
|
||||||
* @param items a collection of synchronous/asynchronous values
|
|
||||||
* @param callback a synchronous/asynchronous reducer function
|
|
||||||
* @param [initialValue] the first value to pass to the callback
|
|
||||||
* @return a promise eventually containing a value that is the result of the reduction
|
|
||||||
*/
|
|
||||||
export function reduce<T, U>(
|
|
||||||
this: any,
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Reducer<T, U>,
|
|
||||||
initialValue?: U
|
|
||||||
): Promise<U> {
|
|
||||||
const args: any[] = <any[]>array.from(arguments);
|
|
||||||
args.unshift(findNextValueIndex);
|
|
||||||
return generalReduce.apply(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reduceRight<T, U>(
|
|
||||||
this: any,
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
callback: Reducer<T, U>,
|
|
||||||
initialValue?: U
|
|
||||||
): Promise<U> {
|
|
||||||
const args: any[] = <any[]>array.from(arguments);
|
|
||||||
args.unshift(findLastValueIndex);
|
|
||||||
return generalReduce.apply(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function series<T, U>(
|
|
||||||
items: Iterable<T | Promise<T>> | (T | Promise<T>)[],
|
|
||||||
operation: Mapper<T, U>
|
|
||||||
): Promise<U[]> {
|
|
||||||
return generalReduce(
|
|
||||||
findNextValueIndex,
|
|
||||||
items,
|
|
||||||
function(previousValue, currentValue: T, index: number, array: T[]) {
|
|
||||||
const result = operation(currentValue, index, array);
|
|
||||||
|
|
||||||
if (isThenable(result)) {
|
|
||||||
return result.then(function(value) {
|
|
||||||
previousValue.push(value);
|
|
||||||
return previousValue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
previousValue.push(result);
|
|
||||||
return previousValue;
|
|
||||||
},
|
|
||||||
[] as U[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function some<T>(
|
|
||||||
items: Iterable<T | Promise<T>> | Array<T | Promise<T>>,
|
|
||||||
callback: Filterer<T>
|
|
||||||
): Promise<boolean> {
|
|
||||||
return testAndHaltOnCondition(true, items, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Filterer<T> extends Mapper<T, boolean> {}
|
|
||||||
|
|
||||||
export interface Mapper<T, U> {
|
|
||||||
(value: T, index: number, array: T[]): U | Thenable<U>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Reducer<T, U> {
|
|
||||||
(previousValue: U, currentValue: T, index: number, array: T[]): U | Thenable<U>;
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import Promise from './ExtensiblePromise';
|
|
||||||
import { Thenable } from '@dojo/shim/interfaces';
|
|
||||||
|
|
||||||
export type IdentityValue<T> = T | (() => T | Thenable<T>);
|
|
||||||
export interface Identity<T> {
|
|
||||||
(value?: IdentityValue<T>): Promise<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for delaying a Promise chain for a specific number of milliseconds.
|
|
||||||
*
|
|
||||||
* @param milliseconds the number of milliseconds to delay
|
|
||||||
* @return {function (value: T | (() => T | Thenable<T>)): Promise<T>} a function producing a promise that eventually returns the value or executes the value function passed to it; usable with Thenable.then()
|
|
||||||
*/
|
|
||||||
export function delay<T>(milliseconds: number): Identity<T> {
|
|
||||||
return function(value?: IdentityValue<T>): Promise<T> {
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
setTimeout(function() {
|
|
||||||
resolve(typeof value === 'function' ? value() : value);
|
|
||||||
}, milliseconds);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reject a promise chain if a result hasn't been found before the timeout
|
|
||||||
*
|
|
||||||
* @param milliseconds after this number of milliseconds a rejection will be returned
|
|
||||||
* @param reason The reason for the rejection
|
|
||||||
* @return {function(T): Promise<T>} a function that produces a promise that is rejected or resolved based on your timeout
|
|
||||||
*/
|
|
||||||
export function timeout<T>(milliseconds: number, reason: Error): Identity<T> {
|
|
||||||
const start = Date.now();
|
|
||||||
return function(value?: IdentityValue<T>): Promise<T> {
|
|
||||||
if (Date.now() - milliseconds > start) {
|
|
||||||
return Promise.reject<T>(reason);
|
|
||||||
}
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
return Promise.resolve(value());
|
|
||||||
}
|
|
||||||
return Promise.resolve(value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Promise that will reject itself automatically after a time.
|
|
||||||
* Useful for combining with other promises in Promise.race.
|
|
||||||
*/
|
|
||||||
export class DelayedRejection extends Promise<any> {
|
|
||||||
/**
|
|
||||||
* @param milliseconds the number of milliseconds to wait before triggering a rejection
|
|
||||||
* @param reason the reason for the rejection
|
|
||||||
*/
|
|
||||||
constructor(milliseconds: number, reason?: Error) {
|
|
||||||
super(() => {});
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(reason);
|
|
||||||
}, milliseconds);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import global from '@dojo/shim/global';
|
|
||||||
import has, { add as hasAdd } from '@dojo/has/has';
|
|
||||||
|
|
||||||
hasAdd('btoa', 'btoa' in global, true);
|
|
||||||
hasAdd('atob', 'atob' in global, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a string encoded in base64 and decode it
|
|
||||||
* @param encodedString The base64 encoded string
|
|
||||||
*/
|
|
||||||
export const decode: (encodedString: string) => string = has('atob')
|
|
||||||
? function(encodedString: string) {
|
|
||||||
/* this allows for utf8 characters to be decoded properly */
|
|
||||||
return decodeURIComponent(
|
|
||||||
Array.prototype.map
|
|
||||||
.call(
|
|
||||||
atob(encodedString),
|
|
||||||
(char: string) => '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)
|
|
||||||
)
|
|
||||||
.join('')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: function(encodedString: string): string {
|
|
||||||
return new Buffer(encodedString.toString(), 'base64').toString('utf8');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a string and encode it to base64
|
|
||||||
* @param rawString The string to encode
|
|
||||||
*/
|
|
||||||
export const encode: (rawString: string) => string = has('btoa')
|
|
||||||
? function(decodedString: string) {
|
|
||||||
/* this allows for utf8 characters to be encoded properly */
|
|
||||||
return btoa(
|
|
||||||
encodeURIComponent(decodedString).replace(/%([0-9A-F]{2})/g, (match, code: string) =>
|
|
||||||
String.fromCharCode(Number('0x' + code))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: function(rawString: string): string {
|
|
||||||
return new Buffer(rawString.toString(), 'utf8').toString('base64');
|
|
||||||
};
|
|
||||||
@ -1,754 +0,0 @@
|
|||||||
import { assign } from '@dojo/shim/object';
|
|
||||||
import { keys } from '@dojo/shim/object';
|
|
||||||
import Set from '@dojo/shim/Set';
|
|
||||||
|
|
||||||
/* Assigning to local variables to improve minification and readability */
|
|
||||||
|
|
||||||
const objectCreate = Object.create;
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
||||||
const defineProperty = Object.defineProperty;
|
|
||||||
const isArray = Array.isArray;
|
|
||||||
const isFrozen = Object.isFrozen;
|
|
||||||
const isSealed = Object.isSealed;
|
|
||||||
|
|
||||||
export type IgnorePropertyFunction = (name: string, a: any, b: any) => boolean;
|
|
||||||
|
|
||||||
export interface DiffOptions {
|
|
||||||
/**
|
|
||||||
* Allow functions to be values. Values will be considered equal if the `typeof` both values are `function`.
|
|
||||||
* When adding or updating the property, the value of the property of `a` will be used in the record, which
|
|
||||||
* will be a reference to the function.
|
|
||||||
*/
|
|
||||||
allowFunctionValues?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of strings or regular expressions which flag certain properties to be ignored. Alternatively
|
|
||||||
* a function, which returns `true` to have the property ignored or `false` to diff the property.
|
|
||||||
*/
|
|
||||||
ignoreProperties?: (string | RegExp)[] | IgnorePropertyFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of strings or regular expressions which flag certain values to be ignored. For flagged properties,
|
|
||||||
* if the property is present in both `a` and `b` the value will be ignored. If adding the property,
|
|
||||||
* whatever the value of the property of `a` will be used, which could be a reference.
|
|
||||||
*/
|
|
||||||
ignorePropertyValues?: (string | RegExp)[] | IgnorePropertyFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a generic constructor function
|
|
||||||
*/
|
|
||||||
export interface Constructor {
|
|
||||||
new (...args: any[]): object;
|
|
||||||
prototype: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A partial property descriptor that provides the property descriptor flags supported by the
|
|
||||||
* complex property construction of `patch()`
|
|
||||||
*
|
|
||||||
* All properties are value properties, with the value being supplied by the `ConstructRecord`
|
|
||||||
*/
|
|
||||||
export interface ConstructDescriptor {
|
|
||||||
/**
|
|
||||||
* Is the property configurable?
|
|
||||||
*/
|
|
||||||
configurable?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the property enumerable?
|
|
||||||
*/
|
|
||||||
enumerable?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the property configurable?
|
|
||||||
*/
|
|
||||||
writable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A record that describes a constructor function and arguments necessary to create an instance of
|
|
||||||
* an object
|
|
||||||
*/
|
|
||||||
export interface AnonymousConstructRecord {
|
|
||||||
/**
|
|
||||||
* Any arguments to pass to the constructor function
|
|
||||||
*/
|
|
||||||
args?: any[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constructor function to use to create the instance
|
|
||||||
*/
|
|
||||||
Ctor: Constructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The partial descriptor that is used to set the value of the instance
|
|
||||||
*/
|
|
||||||
descriptor?: ConstructDescriptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any patches to properties that need to occur on the instance
|
|
||||||
*/
|
|
||||||
propertyRecords?: (ConstructRecord | PatchRecord)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConstructRecord extends AnonymousConstructRecord {
|
|
||||||
/**
|
|
||||||
* The name of the property on the Object
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A record that describes the mutations necessary to a property of an object to make that property look
|
|
||||||
* like another
|
|
||||||
*/
|
|
||||||
export type PatchRecord =
|
|
||||||
| {
|
|
||||||
/**
|
|
||||||
* The name of the property on the Object
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the patch
|
|
||||||
*/
|
|
||||||
type: 'delete';
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
/**
|
|
||||||
* A property descriptor that describes the property in `name`
|
|
||||||
*/
|
|
||||||
descriptor: PropertyDescriptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the property on the Object
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of the patch
|
|
||||||
*/
|
|
||||||
type: 'add' | 'update';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional patch records which describe the value of the property
|
|
||||||
*/
|
|
||||||
valueRecords?: (ConstructRecord | PatchRecord | SpliceRecord)[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The different types of patch records supported
|
|
||||||
*/
|
|
||||||
export type PatchTypes = 'add' | 'update' | 'delete';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A record that describes a splice operation to perform on an array to make the array look like another array
|
|
||||||
*/
|
|
||||||
export interface SpliceRecord {
|
|
||||||
/**
|
|
||||||
* Any items that are being added to the array
|
|
||||||
*/
|
|
||||||
add?: any[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of items in the array to delete
|
|
||||||
*/
|
|
||||||
deleteCount: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type, set to `splice`
|
|
||||||
*/
|
|
||||||
type: 'splice';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The index of where to start the splice
|
|
||||||
*/
|
|
||||||
start: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A record that describes how to instantiate a new object via a constructor function
|
|
||||||
* @param Ctor The constructor function
|
|
||||||
* @param args Any arguments to be passed to the constructor function
|
|
||||||
*/
|
|
||||||
/* tslint:disable:variable-name */
|
|
||||||
export function createConstructRecord(
|
|
||||||
Ctor: Constructor,
|
|
||||||
args?: any[],
|
|
||||||
descriptor?: ConstructDescriptor
|
|
||||||
): AnonymousConstructRecord {
|
|
||||||
const record: AnonymousConstructRecord = assign(objectCreate(null), { Ctor });
|
|
||||||
if (args) {
|
|
||||||
record.args = args;
|
|
||||||
}
|
|
||||||
if (descriptor) {
|
|
||||||
record.descriptor = descriptor;
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
/* tslint:enable:variable-name */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that returns a new patch record
|
|
||||||
*
|
|
||||||
* @param type The type of patch record
|
|
||||||
* @param name The property name the record refers to
|
|
||||||
* @param descriptor The property descriptor to be installed on the object
|
|
||||||
* @param valueRecords Any subsequenet patch recrds to be applied to the value of the descriptor
|
|
||||||
*/
|
|
||||||
function createPatchRecord(
|
|
||||||
type: PatchTypes,
|
|
||||||
name: string,
|
|
||||||
descriptor?: PropertyDescriptor,
|
|
||||||
valueRecords?: (ConstructRecord | PatchRecord | SpliceRecord)[]
|
|
||||||
): PatchRecord {
|
|
||||||
const patchRecord = assign(objectCreate(null), {
|
|
||||||
type,
|
|
||||||
name
|
|
||||||
});
|
|
||||||
|
|
||||||
if (descriptor) {
|
|
||||||
patchRecord.descriptor = descriptor;
|
|
||||||
}
|
|
||||||
if (valueRecords) {
|
|
||||||
patchRecord.valueRecords = valueRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
return patchRecord as PatchRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that returns a new splice record
|
|
||||||
*
|
|
||||||
* @param start Where in the array to start the splice
|
|
||||||
* @param deleteCount The number of elements to delete from the array
|
|
||||||
* @param add Elements to be added to the target
|
|
||||||
*/
|
|
||||||
function createSpliceRecord(start: number, deleteCount: number, add?: any[]): SpliceRecord {
|
|
||||||
const spliceRecord: SpliceRecord = assign(objectCreate(null), {
|
|
||||||
type: 'splice',
|
|
||||||
start,
|
|
||||||
deleteCount
|
|
||||||
});
|
|
||||||
|
|
||||||
if (add && add.length) {
|
|
||||||
spliceRecord.add = add;
|
|
||||||
}
|
|
||||||
|
|
||||||
return spliceRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that produces a value property descriptor, which assumes that properties are enumerable, writable and configurable
|
|
||||||
* unless specified
|
|
||||||
*
|
|
||||||
* @param value The value for the descriptor
|
|
||||||
* @param writable Defaults to `true` if not specified
|
|
||||||
* @param enumerable Defaults to `true` if not specified
|
|
||||||
* @param configurable Defaults to `true` if not specified
|
|
||||||
*/
|
|
||||||
function createValuePropertyDescriptor(
|
|
||||||
value: any,
|
|
||||||
writable: boolean = true,
|
|
||||||
enumerable: boolean = true,
|
|
||||||
configurable: boolean = true
|
|
||||||
): PropertyDescriptor {
|
|
||||||
return assign(objectCreate(null), {
|
|
||||||
value,
|
|
||||||
writable,
|
|
||||||
enumerable,
|
|
||||||
configurable
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that returns a constructor record or `undefined` when diffing a value
|
|
||||||
*/
|
|
||||||
export type CustomDiffFunction<T> = (
|
|
||||||
value: T,
|
|
||||||
nameOrIndex: string | number,
|
|
||||||
parent: object
|
|
||||||
) => AnonymousConstructRecord | void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class which is used when making a custom comparison of a non-plain object or array
|
|
||||||
*/
|
|
||||||
export class CustomDiff<T> {
|
|
||||||
private _differ: CustomDiffFunction<T>;
|
|
||||||
|
|
||||||
constructor(diff: CustomDiffFunction<T>) {
|
|
||||||
this._differ = diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the difference of the `value`
|
|
||||||
* @param value The value to diff
|
|
||||||
* @param nameOrIndex A `string` if comparing a property or a `number` if comparing an array element
|
|
||||||
* @param parent The outer parent that this value is part of
|
|
||||||
*/
|
|
||||||
diff(value: T, nameOrIndex: string | number, parent: object): ConstructRecord | void {
|
|
||||||
const record = this._differ(value, nameOrIndex, parent);
|
|
||||||
if (record && typeof nameOrIndex === 'string') {
|
|
||||||
return assign(record, { name: nameOrIndex });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that detects the differences between an array and another value and returns a set of splice records that
|
|
||||||
* describe the differences
|
|
||||||
*
|
|
||||||
* @param a The first array to compare to
|
|
||||||
* @param b The second value to compare to
|
|
||||||
* @param options An options bag that allows configuration of the behaviour of `diffArray()`
|
|
||||||
*/
|
|
||||||
function diffArray(a: any[], b: any, options: DiffOptions): SpliceRecord[] {
|
|
||||||
/* This function takes an overly simplistic approach to calculating splice records. There are many situations where
|
|
||||||
* in complicated array mutations, the splice records can be more optimised.
|
|
||||||
*
|
|
||||||
* TODO: Raise an issue for this when it is finally merged and put into core
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { allowFunctionValues = false } = options;
|
|
||||||
|
|
||||||
const arrayA = a;
|
|
||||||
const lengthA = arrayA.length;
|
|
||||||
const arrayB = isArray(b) ? b : [];
|
|
||||||
const lengthB = arrayB.length;
|
|
||||||
const patchRecords: SpliceRecord[] = [];
|
|
||||||
|
|
||||||
if (!lengthA && lengthB) {
|
|
||||||
/* empty array */
|
|
||||||
patchRecords.push(createSpliceRecord(0, lengthB));
|
|
||||||
return patchRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
let add: any[] = [];
|
|
||||||
let start = 0;
|
|
||||||
let deleteCount = 0;
|
|
||||||
let last = -1;
|
|
||||||
|
|
||||||
function flushSpliceRecord() {
|
|
||||||
if (deleteCount || add.length) {
|
|
||||||
patchRecords.push(
|
|
||||||
createSpliceRecord(start, start + deleteCount > lengthB ? lengthB - start : deleteCount, add)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDifference(index: number, adding: boolean, value?: any) {
|
|
||||||
if (index > last + 1) {
|
|
||||||
/* flush the splice */
|
|
||||||
flushSpliceRecord();
|
|
||||||
start = index;
|
|
||||||
deleteCount = 0;
|
|
||||||
if (add.length) {
|
|
||||||
add = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adding) {
|
|
||||||
add.push(value);
|
|
||||||
}
|
|
||||||
deleteCount++;
|
|
||||||
last = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayA.forEach((valueA, index) => {
|
|
||||||
const valueB = arrayB[index];
|
|
||||||
|
|
||||||
if (
|
|
||||||
index in arrayB &&
|
|
||||||
(valueA === valueB || (allowFunctionValues && typeof valueA === 'function' && typeof valueB === 'function'))
|
|
||||||
) {
|
|
||||||
return; /* not different */
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValueAArray = isArray(valueA);
|
|
||||||
const isValueAPlainObject = isPlainObject(valueA);
|
|
||||||
|
|
||||||
if (isValueAArray || isValueAPlainObject) {
|
|
||||||
const value = isValueAArray
|
|
||||||
? isArray(valueB) ? valueB : []
|
|
||||||
: isPlainObject(valueB) ? valueB : Object.create(null);
|
|
||||||
const valueRecords = diff(valueA, value, options);
|
|
||||||
if (valueRecords.length) {
|
|
||||||
/* only add if there are changes */
|
|
||||||
addDifference(index, true, diff(valueA, value, options));
|
|
||||||
}
|
|
||||||
} else if (isPrimitive(valueA)) {
|
|
||||||
addDifference(index, true, valueA);
|
|
||||||
} else if (allowFunctionValues && typeof valueA === 'function') {
|
|
||||||
addDifference(index, true, valueA);
|
|
||||||
} else {
|
|
||||||
throw new TypeError(
|
|
||||||
`Value of array element "${index}" from first argument is not a primative, plain Object, or Array.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (lengthB > lengthA) {
|
|
||||||
for (let index = lengthA; index < lengthB; index++) {
|
|
||||||
addDifference(index, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* flush any deletes */
|
|
||||||
flushSpliceRecord();
|
|
||||||
|
|
||||||
return patchRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal function that detects the differences between plain objects and returns a set of patch records that
|
|
||||||
* describe the differences
|
|
||||||
*
|
|
||||||
* @param a The first plain object to compare to
|
|
||||||
* @param b The second plain bject to compare to
|
|
||||||
* @param options An options bag that allows configuration of the behaviour of `diffPlainObject()`
|
|
||||||
*/
|
|
||||||
function diffPlainObject(a: any, b: any, options: DiffOptions): (ConstructRecord | PatchRecord)[] {
|
|
||||||
const { allowFunctionValues = false, ignorePropertyValues = [] } = options;
|
|
||||||
const patchRecords: (ConstructRecord | PatchRecord)[] = [];
|
|
||||||
const { comparableA, comparableB } = getComparableObjects(a, b, options);
|
|
||||||
|
|
||||||
/* look for keys in a that are different from b */
|
|
||||||
keys(comparableA).reduce((patchRecords, name) => {
|
|
||||||
const valueA = a[name];
|
|
||||||
const valueB = b[name];
|
|
||||||
const bHasOwnProperty = hasOwnProperty.call(comparableB, name);
|
|
||||||
|
|
||||||
if (
|
|
||||||
bHasOwnProperty &&
|
|
||||||
(valueA === valueB || (allowFunctionValues && typeof valueA === 'function' && typeof valueB === 'function'))
|
|
||||||
) {
|
|
||||||
/* not different */
|
|
||||||
/* when `allowFunctionValues` is true, functions are simply considered to be equal by `typeof` */
|
|
||||||
return patchRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = bHasOwnProperty ? 'update' : 'add';
|
|
||||||
|
|
||||||
const isValueAArray = isArray(valueA);
|
|
||||||
const isValueAPlainObject = isPlainObject(valueA);
|
|
||||||
|
|
||||||
if (isValueAArray || isValueAPlainObject) {
|
|
||||||
/* non-primitive values we can diff */
|
|
||||||
/* this is a bit complicated, but essentially if valueA and valueB are both arrays or plain objects, then
|
|
||||||
* we can diff those two values, if not, then we need to use an empty array or an empty object and diff
|
|
||||||
* the valueA with that */
|
|
||||||
const value =
|
|
||||||
(isValueAArray && isArray(valueB)) || (isValueAPlainObject && isPlainObject(valueB))
|
|
||||||
? valueB
|
|
||||||
: isValueAArray ? [] : objectCreate(null);
|
|
||||||
const valueRecords = diff(valueA, value, options);
|
|
||||||
if (valueRecords.length) {
|
|
||||||
/* only add if there are changes */
|
|
||||||
patchRecords.push(
|
|
||||||
createPatchRecord(type, name, createValuePropertyDescriptor(value), diff(valueA, value, options))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (isCustomDiff(valueA) && !isCustomDiff(valueB)) {
|
|
||||||
/* complex diff left hand */
|
|
||||||
const result = valueA.diff(valueB, name, b);
|
|
||||||
if (result) {
|
|
||||||
patchRecords.push(result);
|
|
||||||
}
|
|
||||||
} else if (isCustomDiff(valueB)) {
|
|
||||||
/* complex diff right hand */
|
|
||||||
const result = valueB.diff(valueA, name, a);
|
|
||||||
if (result) {
|
|
||||||
patchRecords.push(result);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
isPrimitive(valueA) ||
|
|
||||||
(allowFunctionValues && typeof valueA === 'function') ||
|
|
||||||
isIgnoredPropertyValue(name, a, b, ignorePropertyValues)
|
|
||||||
) {
|
|
||||||
/* primitive values, functions values if allowed, or ignored property values can just be copied */
|
|
||||||
patchRecords.push(createPatchRecord(type, name, createValuePropertyDescriptor(valueA)));
|
|
||||||
} else {
|
|
||||||
throw new TypeError(
|
|
||||||
`Value of property named "${name}" from first argument is not a primative, plain Object, or Array.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return patchRecords;
|
|
||||||
}, patchRecords);
|
|
||||||
|
|
||||||
/* look for keys in b that are not in a */
|
|
||||||
keys(comparableB).reduce((patchRecords, name) => {
|
|
||||||
if (!hasOwnProperty.call(comparableA, name)) {
|
|
||||||
patchRecords.push(createPatchRecord('delete', name));
|
|
||||||
}
|
|
||||||
return patchRecords;
|
|
||||||
}, patchRecords);
|
|
||||||
|
|
||||||
return patchRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes two plain objects to be compared, as well as options customizing the behavior of the comparison, and returns
|
|
||||||
* two new objects that contain only those properties that should be compared. If a property is ignored
|
|
||||||
* it will not be included in either returned object. If a property's value should be ignored it will be excluded
|
|
||||||
* if it is present in both objects.
|
|
||||||
* @param a The first object to compare
|
|
||||||
* @param b The second object to compare
|
|
||||||
* @param options An options bag indicating which properties should be ignored or have their values ignored, if any.
|
|
||||||
*/
|
|
||||||
export function getComparableObjects(a: any, b: any, options: DiffOptions) {
|
|
||||||
const { ignoreProperties = [], ignorePropertyValues = [] } = options;
|
|
||||||
const ignore = new Set<string>();
|
|
||||||
const keep = new Set<string>();
|
|
||||||
|
|
||||||
const isIgnoredProperty = Array.isArray(ignoreProperties)
|
|
||||||
? (name: string) => {
|
|
||||||
return ignoreProperties.some(
|
|
||||||
(value) => (typeof value === 'string' ? name === value : value.test(name))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: (name: string) => ignoreProperties(name, a, b);
|
|
||||||
|
|
||||||
const comparableA = keys(a).reduce(
|
|
||||||
(obj, name) => {
|
|
||||||
if (
|
|
||||||
isIgnoredProperty(name) ||
|
|
||||||
(hasOwnProperty.call(b, name) && isIgnoredPropertyValue(name, a, b, ignorePropertyValues))
|
|
||||||
) {
|
|
||||||
ignore.add(name);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
keep.add(name);
|
|
||||||
obj[name] = a[name];
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: any }
|
|
||||||
);
|
|
||||||
|
|
||||||
const comparableB = keys(b).reduce(
|
|
||||||
(obj, name) => {
|
|
||||||
if (ignore.has(name) || (!keep.has(name) && isIgnoredProperty(name))) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[name] = b[name];
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: any }
|
|
||||||
);
|
|
||||||
|
|
||||||
return { comparableA, comparableB, ignore };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a `ConstructRecord`
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isConstructRecord(value: any): value is ConstructRecord {
|
|
||||||
return Boolean(value && typeof value === 'object' && value !== null && value.Ctor && value.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIgnoredPropertyValue(
|
|
||||||
name: string,
|
|
||||||
a: any,
|
|
||||||
b: any,
|
|
||||||
ignoredPropertyValues: (string | RegExp)[] | IgnorePropertyFunction
|
|
||||||
) {
|
|
||||||
return Array.isArray(ignoredPropertyValues)
|
|
||||||
? ignoredPropertyValues.some((value) => {
|
|
||||||
return typeof value === 'string' ? name === value : value.test(name);
|
|
||||||
})
|
|
||||||
: ignoredPropertyValues(name, a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a `PatchRecord`
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isPatchRecord(value: any): value is PatchRecord {
|
|
||||||
return Boolean(value && value.type && value.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is an array of `PatchRecord`s
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isPatchRecordArray(value: any): value is PatchRecord[] {
|
|
||||||
return Boolean(isArray(value) && value.length && isPatchRecord(value[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a plain object. A plain object is an object that has
|
|
||||||
* either no constructor (e.g. `Object.create(null)`) or has Object as its constructor.
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isPlainObject(value: any): value is Object {
|
|
||||||
return Boolean(
|
|
||||||
value && typeof value === 'object' && (value.constructor === Object || value.constructor === undefined)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a primitive (including `null`), as these values are
|
|
||||||
* fine to just copy.
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isPrimitive(value: any): value is string | number | boolean | undefined | null {
|
|
||||||
const typeofValue = typeof value;
|
|
||||||
return (
|
|
||||||
value === null ||
|
|
||||||
typeofValue === 'undefined' ||
|
|
||||||
typeofValue === 'string' ||
|
|
||||||
typeofValue === 'number' ||
|
|
||||||
typeofValue === 'boolean'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a `CustomDiff`
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isCustomDiff<T>(value: any): value is CustomDiff<T> {
|
|
||||||
return typeof value === 'object' && value instanceof CustomDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is a `SpliceRecord`
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isSpliceRecord(value: any): value is SpliceRecord {
|
|
||||||
return value && value.type === 'splice' && 'start' in value && 'deleteCount' in value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A guard that determines if the value is an array of `SpliceRecord`s
|
|
||||||
*
|
|
||||||
* @param value The value to check
|
|
||||||
*/
|
|
||||||
function isSpliceRecordArray(value: any): value is SpliceRecord[] {
|
|
||||||
return Boolean(isArray(value) && value.length && isSpliceRecord(value[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that patches a target with a `SpliceRecord`
|
|
||||||
*/
|
|
||||||
function patchSplice(target: any[], { add, deleteCount, start }: SpliceRecord): any {
|
|
||||||
if (add && add.length) {
|
|
||||||
const deletedItems = deleteCount ? target.slice(start, start + deleteCount) : [];
|
|
||||||
add = add.map((value, index) => resolveTargetValue(value, deletedItems[index]));
|
|
||||||
target.splice(start, deleteCount, ...add);
|
|
||||||
} else {
|
|
||||||
target.splice(start, deleteCount);
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that patches a target with a `PatchRecord`
|
|
||||||
*/
|
|
||||||
function patchPatch(target: any, record: PatchRecord): any {
|
|
||||||
const { name } = record;
|
|
||||||
if (record.type === 'delete') {
|
|
||||||
delete target[name];
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
const { descriptor, valueRecords } = record;
|
|
||||||
if (valueRecords && valueRecords.length) {
|
|
||||||
descriptor.value = patch(descriptor.value, valueRecords);
|
|
||||||
}
|
|
||||||
defineProperty(target, name, descriptor);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultConstructDescriptor = {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
};
|
|
||||||
|
|
||||||
function patchConstruct(target: any, record: ConstructRecord): any {
|
|
||||||
const { args, descriptor = defaultConstructDescriptor, Ctor, name, propertyRecords } = record;
|
|
||||||
const value = new Ctor(...(args || []));
|
|
||||||
if (propertyRecords) {
|
|
||||||
propertyRecords.forEach(
|
|
||||||
(record) => (isConstructRecord(record) ? patchConstruct(value, record) : patchPatch(value, record))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
defineProperty(target, name, assign({ value }, descriptor));
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An internal function that takes a value from array being patched and the target value from the same
|
|
||||||
* index and determines the value that should actually be patched into the target array
|
|
||||||
*/
|
|
||||||
function resolveTargetValue(patchValue: any, targetValue: any): any {
|
|
||||||
const patchIsSpliceRecordArray = isSpliceRecordArray(patchValue);
|
|
||||||
return patchIsSpliceRecordArray || isPatchRecordArray(patchValue)
|
|
||||||
? patch(
|
|
||||||
patchIsSpliceRecordArray
|
|
||||||
? isArray(targetValue) ? targetValue : []
|
|
||||||
: isPlainObject(targetValue) ? targetValue : objectCreate(null),
|
|
||||||
patchValue
|
|
||||||
)
|
|
||||||
: patchValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two plain objects or arrays and return a set of records which describe the differences between the two
|
|
||||||
*
|
|
||||||
* The records describe what would need to be applied to the second argument to make it look like the first argument
|
|
||||||
*
|
|
||||||
* @param a The plain object or array to compare with
|
|
||||||
* @param b The plain object or array to compare to
|
|
||||||
* @param options An options bag that allows configuration of the behaviour of `diff()`
|
|
||||||
*/
|
|
||||||
export function diff(a: any, b: any, options: DiffOptions = {}): (ConstructRecord | PatchRecord | SpliceRecord)[] {
|
|
||||||
if (typeof a !== 'object' || typeof b !== 'object') {
|
|
||||||
throw new TypeError('Arguments are not of type object.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isArray(a)) {
|
|
||||||
return diffArray(a, b, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isArray(b)) {
|
|
||||||
b = objectCreate(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPlainObject(a) || !isPlainObject(b)) {
|
|
||||||
throw new TypeError('Arguments are not plain Objects or Arrays.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffPlainObject(a, b, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a set of patch records to a target.
|
|
||||||
*
|
|
||||||
* @param target The plain object or array that the patch records should be applied to
|
|
||||||
* @param records A set of patch records to be applied to the target
|
|
||||||
*/
|
|
||||||
export function patch(target: any, records: (ConstructRecord | PatchRecord | SpliceRecord)[]): any {
|
|
||||||
if (!isArray(target) && !isPlainObject(target)) {
|
|
||||||
throw new TypeError('A target for a patch must be either an array or a plain object.');
|
|
||||||
}
|
|
||||||
if (isFrozen(target) || isSealed(target)) {
|
|
||||||
throw new TypeError('Cannot patch sealed or frozen objects.');
|
|
||||||
}
|
|
||||||
|
|
||||||
records.forEach((record) => {
|
|
||||||
target = isSpliceRecord(record)
|
|
||||||
? patchSplice(isArray(target) ? target : [], record) /* patch arrays */
|
|
||||||
: isConstructRecord(record)
|
|
||||||
? patchConstruct(target, record) /* patch complex object */
|
|
||||||
: patchPatch(isPlainObject(target) ? target : {}, record); /* patch plain object */
|
|
||||||
});
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import globalObject from '@dojo/shim/global';
|
|
||||||
import { deprecated } from './instrument';
|
|
||||||
|
|
||||||
deprecated({
|
|
||||||
message: 'has been replaced with @dojo/shim/global',
|
|
||||||
name: '@dojo/core/global',
|
|
||||||
url: 'https://github.com/dojo/core/issues/302'
|
|
||||||
});
|
|
||||||
|
|
||||||
export default globalObject;
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import global from '@dojo/shim/global';
|
|
||||||
import has, { add } from '@dojo/shim/support/has';
|
|
||||||
|
|
||||||
export * from '@dojo/shim/support/has';
|
|
||||||
export default has;
|
|
||||||
|
|
||||||
add('object-assign', typeof global.Object.assign === 'function', true);
|
|
||||||
|
|
||||||
add('arraybuffer', typeof global.ArrayBuffer !== 'undefined', true);
|
|
||||||
add('formdata', typeof global.FormData !== 'undefined', true);
|
|
||||||
add('filereader', typeof global.FileReader !== 'undefined', true);
|
|
||||||
add('xhr', typeof global.XMLHttpRequest !== 'undefined', true);
|
|
||||||
add('xhr2', has('xhr') && 'responseType' in global.XMLHttpRequest.prototype, true);
|
|
||||||
add(
|
|
||||||
'blob',
|
|
||||||
function() {
|
|
||||||
if (!has('xhr2')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new global.XMLHttpRequest();
|
|
||||||
request.open('GET', global.location.protocol + '//www.google.com', true);
|
|
||||||
request.responseType = 'blob';
|
|
||||||
request.abort();
|
|
||||||
return request.responseType === 'blob';
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
add('node-buffer', 'Buffer' in global && typeof global.Buffer === 'function', true);
|
|
||||||
|
|
||||||
add('fetch', 'fetch' in global && typeof global.fetch === 'function', true);
|
|
||||||
|
|
||||||
add(
|
|
||||||
'web-worker-xhr-upload',
|
|
||||||
typeof global.Promise !== 'undefined' &&
|
|
||||||
new Promise((resolve) => {
|
|
||||||
try {
|
|
||||||
if (global.Worker !== undefined && global.URL && global.URL.createObjectURL) {
|
|
||||||
const blob = new Blob(
|
|
||||||
[
|
|
||||||
`(function () {
|
|
||||||
self.addEventListener('message', function () {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
try {
|
|
||||||
xhr.upload;
|
|
||||||
postMessage('true');
|
|
||||||
} catch (e) {
|
|
||||||
postMessage('false');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})()`
|
|
||||||
],
|
|
||||||
{ type: 'application/javascript' }
|
|
||||||
);
|
|
||||||
const worker = new Worker(URL.createObjectURL(blob));
|
|
||||||
worker.addEventListener('message', ({ data: result }) => {
|
|
||||||
resolve(result === 'true');
|
|
||||||
});
|
|
||||||
worker.postMessage({});
|
|
||||||
} else {
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// IE11 on Winodws 8.1 encounters a security error.
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
import has from './has';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default message to warn when no other is provided
|
|
||||||
*/
|
|
||||||
const DEFAULT_DEPRECATED_MESSAGE = 'This function will be removed in future versions.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When set, globalWarn will be used instead of `console.warn`
|
|
||||||
*/
|
|
||||||
let globalWarn: ((message?: any, ...optionalParams: any[]) => void) | undefined;
|
|
||||||
|
|
||||||
export interface DeprecatedOptions {
|
|
||||||
/**
|
|
||||||
* The message to use when warning
|
|
||||||
*/
|
|
||||||
message?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the method or function to use
|
|
||||||
*/
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An alternative function to log the warning to
|
|
||||||
*/
|
|
||||||
warn?: (...args: any[]) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference an URL for more information when warning
|
|
||||||
*/
|
|
||||||
url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that will console warn that a function has been deprecated
|
|
||||||
*
|
|
||||||
* @param options Provide options which change the display of the message
|
|
||||||
*/
|
|
||||||
export function deprecated({ message, name, warn, url }: DeprecatedOptions = {}): void {
|
|
||||||
/* istanbul ignore else: testing with debug off is difficult */
|
|
||||||
if (has('debug')) {
|
|
||||||
message = message || DEFAULT_DEPRECATED_MESSAGE;
|
|
||||||
let warning = `DEPRECATED: ${name ? name + ': ' : ''}${message}`;
|
|
||||||
if (url) {
|
|
||||||
warning += `\n\n See ${url} for more details.\n\n`;
|
|
||||||
}
|
|
||||||
if (warn) {
|
|
||||||
warn(warning);
|
|
||||||
} else if (globalWarn) {
|
|
||||||
globalWarn(warning);
|
|
||||||
} else {
|
|
||||||
console.warn(warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that generates before advice that can be used to warn when an API has been deprecated
|
|
||||||
*
|
|
||||||
* @param options Provide options which change the display of the message
|
|
||||||
*/
|
|
||||||
export function deprecatedAdvice(options?: DeprecatedOptions): (...args: any[]) => any[] {
|
|
||||||
return function(...args: any[]): any[] {
|
|
||||||
deprecated(options);
|
|
||||||
return args;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A method decorator that will console warn when a method if invoked that is deprecated
|
|
||||||
*
|
|
||||||
* @param options Provide options which change the display of the message
|
|
||||||
*/
|
|
||||||
export function deprecatedDecorator(options?: DeprecatedOptions): MethodDecorator {
|
|
||||||
return function(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
||||||
if (has('debug')) {
|
|
||||||
const { value: originalFn } = descriptor;
|
|
||||||
options = options || {};
|
|
||||||
propertyKey = String(propertyKey);
|
|
||||||
/* IE 10/11 don't have the name property on functions */
|
|
||||||
options.name = target.constructor.name ? `${target.constructor.name}#${propertyKey}` : propertyKey;
|
|
||||||
descriptor.value = function(...args: any[]) {
|
|
||||||
deprecated(options);
|
|
||||||
return originalFn.apply(target, args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return descriptor;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that will set the warn function that will be used instead of `console.warn` when
|
|
||||||
* logging warning messages
|
|
||||||
*
|
|
||||||
* @param warn The function (or `undefined`) to use instead of `console.warn`
|
|
||||||
*/
|
|
||||||
export function setWarn(warn?: ((message?: any, ...optionalParams: any[]) => void)): void {
|
|
||||||
globalWarn = warn;
|
|
||||||
}
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
export type EventType = string | symbol;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base event object, which provides a `type` property
|
|
||||||
*/
|
|
||||||
export interface EventObject<T = EventType> {
|
|
||||||
/**
|
|
||||||
* The type of the event
|
|
||||||
*/
|
|
||||||
readonly type: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventErrorObject<T = EventType> extends EventObject<T> {
|
|
||||||
/**
|
|
||||||
* The error that is the subject of this event
|
|
||||||
*/
|
|
||||||
readonly error: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for an object which provides a cancelable event API. By calling the
|
|
||||||
* `.preventDefault()` method on the object, the event should be cancelled and not
|
|
||||||
* proceed any further
|
|
||||||
*/
|
|
||||||
export interface EventCancelableObject<T = EventType> extends EventObject<T> {
|
|
||||||
/**
|
|
||||||
* Can the event be canceled?
|
|
||||||
*/
|
|
||||||
readonly cancelable: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Was the event canceled?
|
|
||||||
*/
|
|
||||||
readonly defaultPrevented: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel the event
|
|
||||||
*/
|
|
||||||
preventDefault(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used through the toolkit as a consistent API to manage how callers can "cleanup"
|
|
||||||
* when doing a function.
|
|
||||||
*/
|
|
||||||
export interface Handle {
|
|
||||||
/**
|
|
||||||
* Perform the destruction/cleanup logic associated with this handle
|
|
||||||
*/
|
|
||||||
destroy(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A general interface that can be used to renference a general index map of values of a particular type
|
|
||||||
*/
|
|
||||||
export interface Hash<T> {
|
|
||||||
[id: string]: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A base map of styles where each key is the name of the style attribute and the value is a string
|
|
||||||
* which represents the style
|
|
||||||
*/
|
|
||||||
export interface StylesMap {
|
|
||||||
[style: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The interfaces to the `@dojo/loader` AMD loader
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface AmdConfig {
|
|
||||||
/**
|
|
||||||
* The base URL that the loader will use to resolve modules
|
|
||||||
*/
|
|
||||||
baseUrl?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of module identifiers and their replacement meta data
|
|
||||||
*/
|
|
||||||
map?: AmdModuleMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of packages that the loader should use when resolving a module ID
|
|
||||||
*/
|
|
||||||
packages?: AmdPackage[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of paths to use when resolving modules names
|
|
||||||
*/
|
|
||||||
paths?: { [path: string]: string };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of packages that the loader should use when resolving a module ID
|
|
||||||
*/
|
|
||||||
pkgs?: { [path: string]: AmdPackage };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdDefine {
|
|
||||||
/**
|
|
||||||
* Define a module
|
|
||||||
*
|
|
||||||
* @param moduleId the MID to use for the module
|
|
||||||
* @param dependencies an array of MIDs this module depends upon
|
|
||||||
* @param factory the factory function that will return the module
|
|
||||||
*/
|
|
||||||
(moduleId: string, dependencies: string[], factory: AmdFactory): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a module
|
|
||||||
*
|
|
||||||
* @param dependencies an array of MIDs this module depends upon
|
|
||||||
* @param factory the factory function that will return the module
|
|
||||||
*/
|
|
||||||
(dependencies: string[], factory: AmdFactory): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a module
|
|
||||||
*
|
|
||||||
* @param factory the factory function that will return the module
|
|
||||||
*/
|
|
||||||
(factory: AmdFactory): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a module
|
|
||||||
*
|
|
||||||
* @param value the value for the module
|
|
||||||
*/
|
|
||||||
(value: any): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Meta data about this particular AMD loader
|
|
||||||
*/
|
|
||||||
amd: { [prop: string]: string | number | boolean };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdFactory {
|
|
||||||
/**
|
|
||||||
* The module factory
|
|
||||||
*
|
|
||||||
* @param modules The arguments that represent the resolved versions of the module dependencies
|
|
||||||
*/
|
|
||||||
(...modules: any[]): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdHas {
|
|
||||||
/**
|
|
||||||
* Determine if a feature is present
|
|
||||||
*
|
|
||||||
* @param name the feature name to check
|
|
||||||
*/
|
|
||||||
(name: string): any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a feature test
|
|
||||||
*
|
|
||||||
* @param name The name of the feature to register
|
|
||||||
* @param value The test for the feature
|
|
||||||
* @param now If `true` the test will be executed immediatly, if not, it will be lazily executed
|
|
||||||
* @param force If `true` the test value will be overwritten if already registered
|
|
||||||
*/
|
|
||||||
add(
|
|
||||||
name: string,
|
|
||||||
value: (global: Window, document?: HTMLDocument, element?: HTMLDivElement) => any,
|
|
||||||
now?: boolean,
|
|
||||||
force?: boolean
|
|
||||||
): void;
|
|
||||||
add(name: string, value: any, now?: boolean, force?: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdModuleMap extends AmdModuleMapItem {
|
|
||||||
[sourceMid: string]: AmdModuleMapReplacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdModuleMapItem {
|
|
||||||
[mid: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdModuleMapReplacement extends AmdModuleMapItem {
|
|
||||||
[findMid: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeRequire {
|
|
||||||
(moduleId: string): any;
|
|
||||||
resolve(moduleId: string): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdPackage {
|
|
||||||
/**
|
|
||||||
* The path to the root of the package
|
|
||||||
*/
|
|
||||||
location?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main module of the package (defaults to `main.js`)
|
|
||||||
*/
|
|
||||||
main?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The package name
|
|
||||||
*/
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdRequire {
|
|
||||||
/**
|
|
||||||
* Resolve a list of module dependencies and pass them to the callback
|
|
||||||
*
|
|
||||||
* @param dependencies The array of MIDs to resolve
|
|
||||||
* @param callback The function to invoke with the resolved dependencies
|
|
||||||
*/
|
|
||||||
(dependencies: string[], callback: AmdRequireCallback): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve and return a single module (compatability with CommonJS `require`)
|
|
||||||
*
|
|
||||||
* @param moduleId The module ID to resolve and return
|
|
||||||
*/
|
|
||||||
<ModuleType>(moduleId: string): ModuleType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If running in the node environment, a reference to the original NodeJS `require`
|
|
||||||
*/
|
|
||||||
nodeRequire?: NodeRequire;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a relative MID and return an absolute MID
|
|
||||||
*
|
|
||||||
* @param moduleId The relative module ID to resolve
|
|
||||||
*/
|
|
||||||
toAbsMid(moduleId: string): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a path and resolve the full URL for the path
|
|
||||||
*
|
|
||||||
* @param path The path to resolve and return as a URL
|
|
||||||
*/
|
|
||||||
toUrl(path: string): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdRequireCallback {
|
|
||||||
/**
|
|
||||||
* The `require` callback
|
|
||||||
*
|
|
||||||
* @param modules The arguments that represent the resolved versions of dependencies
|
|
||||||
*/
|
|
||||||
(...modules: any[]): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AmdRootRequire extends AmdRequire {
|
|
||||||
/**
|
|
||||||
* The minimalist `has` API integrated with the `@dojo/loader`
|
|
||||||
*/
|
|
||||||
has: AmdHas;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register an event listener
|
|
||||||
*
|
|
||||||
* @param type The event type to listen for
|
|
||||||
* @param listener The listener to call when the event is emitted
|
|
||||||
*/
|
|
||||||
on(type: AmdRequireOnSignalType, listener: any): { remove: () => void };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure the loader
|
|
||||||
*
|
|
||||||
* @param config The configuration to apply to the loader
|
|
||||||
*/
|
|
||||||
config(config: AmdConfig): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return internal values of loader for debug purposes
|
|
||||||
*
|
|
||||||
* @param name The name of the internal label
|
|
||||||
*/
|
|
||||||
inspect?(name: string): any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undefine a module, based on absolute MID that should be removed from the loader cache
|
|
||||||
*/
|
|
||||||
undef(moduleId: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The signal type for the `require.on` API
|
|
||||||
*/
|
|
||||||
export type AmdRequireOnSignalType = 'error';
|
|
||||||
@ -1,390 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
import { assign } from '@dojo/shim/object';
|
|
||||||
|
|
||||||
export { assign } from '@dojo/shim/object';
|
|
||||||
|
|
||||||
const slice = Array.prototype.slice;
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type guard that ensures that the value can be coerced to Object
|
|
||||||
* to weed out host objects that do not derive from Object.
|
|
||||||
* This function is used to check if we want to deep copy an object or not.
|
|
||||||
* Note: In ES6 it is possible to modify an object's Symbol.toStringTag property, which will
|
|
||||||
* change the value returned by `toString`. This is a rare edge case that is difficult to handle,
|
|
||||||
* so it is not handled here.
|
|
||||||
* @param value The value to check
|
|
||||||
* @return If the value is coercible into an Object
|
|
||||||
*/
|
|
||||||
function shouldDeepCopyObject(value: any): value is Object {
|
|
||||||
return Object.prototype.toString.call(value) === '[object Object]';
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyArray<T>(array: T[], inherited: boolean): T[] {
|
|
||||||
return array.map(function(item: T): T {
|
|
||||||
if (Array.isArray(item)) {
|
|
||||||
return <any>copyArray(<any>item, inherited);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !shouldDeepCopyObject(item)
|
|
||||||
? item
|
|
||||||
: _mixin({
|
|
||||||
deep: true,
|
|
||||||
inherited: inherited,
|
|
||||||
sources: <Array<T>>[item],
|
|
||||||
target: <T>{}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MixinArgs<T extends {}, U extends {}> {
|
|
||||||
deep: boolean;
|
|
||||||
inherited: boolean;
|
|
||||||
sources: (U | null | undefined)[];
|
|
||||||
target: T;
|
|
||||||
copied?: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function _mixin<T extends {}, U extends {}>(kwArgs: MixinArgs<T, U>): T & U {
|
|
||||||
const deep = kwArgs.deep;
|
|
||||||
const inherited = kwArgs.inherited;
|
|
||||||
const target: any = kwArgs.target;
|
|
||||||
const copied = kwArgs.copied || [];
|
|
||||||
const copiedClone = [...copied];
|
|
||||||
|
|
||||||
for (let i = 0; i < kwArgs.sources.length; i++) {
|
|
||||||
const source = kwArgs.sources[i];
|
|
||||||
|
|
||||||
if (source === null || source === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let key in source) {
|
|
||||||
if (inherited || hasOwnProperty.call(source, key)) {
|
|
||||||
let value: any = source[key];
|
|
||||||
|
|
||||||
if (copiedClone.indexOf(value) !== -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deep) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
value = copyArray(value, inherited);
|
|
||||||
} else if (shouldDeepCopyObject(value)) {
|
|
||||||
const targetValue: any = target[key] || {};
|
|
||||||
copied.push(source);
|
|
||||||
value = _mixin({
|
|
||||||
deep: true,
|
|
||||||
inherited: inherited,
|
|
||||||
sources: [value],
|
|
||||||
target: targetValue,
|
|
||||||
copied
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <T & U>target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new object from the given prototype, and copies all enumerable own properties of one or more
|
|
||||||
* source objects to the newly created target object.
|
|
||||||
*
|
|
||||||
* @param prototype The prototype to create a new object from
|
|
||||||
* @param mixins Any number of objects whose enumerable own properties will be copied to the created object
|
|
||||||
* @return The new object
|
|
||||||
*/
|
|
||||||
export function create<
|
|
||||||
T extends {},
|
|
||||||
U extends {},
|
|
||||||
V extends {},
|
|
||||||
W extends {},
|
|
||||||
X extends {},
|
|
||||||
Y extends {},
|
|
||||||
Z extends {}
|
|
||||||
>(prototype: T, mixin1: U, mixin2: V, mixin3: W, mixin4: X, mixin5: Y, mixin6: Z): T & U & V & W & X & Y & Z;
|
|
||||||
export function create<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}, Y extends {}>(
|
|
||||||
prototype: T,
|
|
||||||
mixin1: U,
|
|
||||||
mixin2: V,
|
|
||||||
mixin3: W,
|
|
||||||
mixin4: X,
|
|
||||||
mixin5: Y
|
|
||||||
): T & U & V & W & X & Y;
|
|
||||||
export function create<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}>(
|
|
||||||
prototype: T,
|
|
||||||
mixin1: U,
|
|
||||||
mixin2: V,
|
|
||||||
mixin3: W,
|
|
||||||
mixin4: X
|
|
||||||
): T & U & V & W & X;
|
|
||||||
export function create<T extends {}, U extends {}, V extends {}, W extends {}>(
|
|
||||||
prototype: T,
|
|
||||||
mixin1: U,
|
|
||||||
mixin2: V,
|
|
||||||
mixin3: W
|
|
||||||
): T & U & V & W;
|
|
||||||
export function create<T extends {}, U extends {}, V extends {}>(prototype: T, mixin1: U, mixin2: V): T & U & V;
|
|
||||||
export function create<T extends {}, U extends {}>(prototype: T, mixin: U): T & U;
|
|
||||||
export function create<T extends {}>(prototype: T): T;
|
|
||||||
export function create(prototype: any, ...mixins: any[]): any {
|
|
||||||
if (!mixins.length) {
|
|
||||||
throw new RangeError('lang.create requires at least one mixin object.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = mixins.slice();
|
|
||||||
args.unshift(Object.create(prototype));
|
|
||||||
|
|
||||||
return assign.apply(null, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the values of all enumerable own properties of one or more source objects to the target object,
|
|
||||||
* recursively copying all nested objects and arrays as well.
|
|
||||||
*
|
|
||||||
* @param target The target object to receive values from source objects
|
|
||||||
* @param sources Any number of objects whose enumerable own properties will be copied to the target object
|
|
||||||
* @return The modified target object
|
|
||||||
*/
|
|
||||||
export function deepAssign<
|
|
||||||
T extends {},
|
|
||||||
U extends {},
|
|
||||||
V extends {},
|
|
||||||
W extends {},
|
|
||||||
X extends {},
|
|
||||||
Y extends {},
|
|
||||||
Z extends {}
|
|
||||||
>(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z): T & U & V & W & X & Y & Z;
|
|
||||||
export function deepAssign<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}, Y extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X,
|
|
||||||
source5: Y
|
|
||||||
): T & U & V & W & X & Y;
|
|
||||||
export function deepAssign<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X
|
|
||||||
): T & U & V & W & X;
|
|
||||||
export function deepAssign<T extends {}, U extends {}, V extends {}, W extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W
|
|
||||||
): T & U & V & W;
|
|
||||||
export function deepAssign<T extends {}, U extends {}, V extends {}>(target: T, source1: U, source2: V): T & U & V;
|
|
||||||
export function deepAssign<T extends {}, U extends {}>(target: T, source: U): T & U;
|
|
||||||
export function deepAssign(target: any, ...sources: any[]): any {
|
|
||||||
return _mixin({
|
|
||||||
deep: true,
|
|
||||||
inherited: false,
|
|
||||||
sources: sources,
|
|
||||||
target: target
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the values of all enumerable (own or inherited) properties of one or more source objects to the
|
|
||||||
* target object, recursively copying all nested objects and arrays as well.
|
|
||||||
*
|
|
||||||
* @param target The target object to receive values from source objects
|
|
||||||
* @param sources Any number of objects whose enumerable properties will be copied to the target object
|
|
||||||
* @return The modified target object
|
|
||||||
*/
|
|
||||||
export function deepMixin<
|
|
||||||
T extends {},
|
|
||||||
U extends {},
|
|
||||||
V extends {},
|
|
||||||
W extends {},
|
|
||||||
X extends {},
|
|
||||||
Y extends {},
|
|
||||||
Z extends {}
|
|
||||||
>(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z): T & U & V & W & X & Y & Z;
|
|
||||||
export function deepMixin<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}, Y extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X,
|
|
||||||
source5: Y
|
|
||||||
): T & U & V & W & X & Y;
|
|
||||||
export function deepMixin<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X
|
|
||||||
): T & U & V & W & X;
|
|
||||||
export function deepMixin<T extends {}, U extends {}, V extends {}, W extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W
|
|
||||||
): T & U & V & W;
|
|
||||||
export function deepMixin<T extends {}, U extends {}, V extends {}>(target: T, source1: U, source2: V): T & U & V;
|
|
||||||
export function deepMixin<T extends {}, U extends {}>(target: T, source: U): T & U;
|
|
||||||
export function deepMixin(target: any, ...sources: any[]): any {
|
|
||||||
return _mixin({
|
|
||||||
deep: true,
|
|
||||||
inherited: true,
|
|
||||||
sources: sources,
|
|
||||||
target: target
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new object using the provided source's prototype as the prototype for the new object, and then
|
|
||||||
* deep copies the provided source's values into the new target.
|
|
||||||
*
|
|
||||||
* @param source The object to duplicate
|
|
||||||
* @return The new object
|
|
||||||
*/
|
|
||||||
export function duplicate<T extends {}>(source: T): T {
|
|
||||||
const target = Object.create(Object.getPrototypeOf(source));
|
|
||||||
|
|
||||||
return deepMixin(target, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether two values are the same value.
|
|
||||||
*
|
|
||||||
* @param a First value to compare
|
|
||||||
* @param b Second value to compare
|
|
||||||
* @return true if the values are the same; false otherwise
|
|
||||||
*/
|
|
||||||
export function isIdentical(a: any, b: any): boolean {
|
|
||||||
return (
|
|
||||||
a === b ||
|
|
||||||
/* both values are NaN */
|
|
||||||
(a !== a && b !== b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function that binds a method to the specified object at runtime. This is similar to
|
|
||||||
* `Function.prototype.bind`, but instead of a function it takes the name of a method on an object.
|
|
||||||
* As a result, the function returned by `lateBind` will always call the function currently assigned to
|
|
||||||
* the specified property on the object as of the moment the function it returns is called.
|
|
||||||
*
|
|
||||||
* @param instance The context object
|
|
||||||
* @param method The name of the method on the context object to bind to itself
|
|
||||||
* @param suppliedArgs An optional array of values to prepend to the `instance[method]` arguments list
|
|
||||||
* @return The bound function
|
|
||||||
*/
|
|
||||||
export function lateBind(instance: {}, method: string, ...suppliedArgs: any[]): (...args: any[]) => any {
|
|
||||||
return suppliedArgs.length
|
|
||||||
? function() {
|
|
||||||
const args: any[] = arguments.length ? suppliedArgs.concat(slice.call(arguments)) : suppliedArgs;
|
|
||||||
|
|
||||||
// TS7017
|
|
||||||
return (<any>instance)[method].apply(instance, args);
|
|
||||||
}
|
|
||||||
: function() {
|
|
||||||
// TS7017
|
|
||||||
return (<any>instance)[method].apply(instance, arguments);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies the values of all enumerable (own or inherited) properties of one or more source objects to the
|
|
||||||
* target object.
|
|
||||||
*
|
|
||||||
* @return The modified target object
|
|
||||||
*/
|
|
||||||
export function mixin<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}, Y extends {}, Z extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X,
|
|
||||||
source5: Y,
|
|
||||||
source6: Z
|
|
||||||
): T & U & V & W & X & Y & Z;
|
|
||||||
export function mixin<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}, Y extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X,
|
|
||||||
source5: Y
|
|
||||||
): T & U & V & W & X & Y;
|
|
||||||
export function mixin<T extends {}, U extends {}, V extends {}, W extends {}, X extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W,
|
|
||||||
source4: X
|
|
||||||
): T & U & V & W & X;
|
|
||||||
export function mixin<T extends {}, U extends {}, V extends {}, W extends {}>(
|
|
||||||
target: T,
|
|
||||||
source1: U,
|
|
||||||
source2: V,
|
|
||||||
source3: W
|
|
||||||
): T & U & V & W;
|
|
||||||
export function mixin<T extends {}, U extends {}, V extends {}>(target: T, source1: U, source2: V): T & U & V;
|
|
||||||
export function mixin<T extends {}, U extends {}>(target: T, source: U): T & U;
|
|
||||||
export function mixin(target: any, ...sources: any[]): any {
|
|
||||||
return _mixin({
|
|
||||||
deep: false,
|
|
||||||
inherited: true,
|
|
||||||
sources: sources,
|
|
||||||
target: target
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function which invokes the given function with the given arguments prepended to its argument list.
|
|
||||||
* Like `Function.prototype.bind`, but does not alter execution context.
|
|
||||||
*
|
|
||||||
* @param targetFunction The function that needs to be bound
|
|
||||||
* @param suppliedArgs An optional array of arguments to prepend to the `targetFunction` arguments list
|
|
||||||
* @return The bound function
|
|
||||||
*/
|
|
||||||
export function partial(targetFunction: (...args: any[]) => any, ...suppliedArgs: any[]): (...args: any[]) => any {
|
|
||||||
return function(this: any) {
|
|
||||||
const args: any[] = arguments.length ? suppliedArgs.concat(slice.call(arguments)) : suppliedArgs;
|
|
||||||
|
|
||||||
return targetFunction.apply(this, args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object with a destroy method that, when called, calls the passed-in destructor.
|
|
||||||
* This is intended to provide a unified interface for creating "remove" / "destroy" handlers for
|
|
||||||
* event listeners, timers, etc.
|
|
||||||
*
|
|
||||||
* @param destructor A function that will be called when the handle's `destroy` method is invoked
|
|
||||||
* @return The handle object
|
|
||||||
*/
|
|
||||||
export function createHandle(destructor: () => void): Handle {
|
|
||||||
let called = false;
|
|
||||||
return {
|
|
||||||
destroy: function(this: Handle) {
|
|
||||||
if (!called) {
|
|
||||||
called = true;
|
|
||||||
destructor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a single handle that can be used to destroy multiple handles simultaneously.
|
|
||||||
*
|
|
||||||
* @param handles An array of handles with `destroy` methods
|
|
||||||
* @return The handle object
|
|
||||||
*/
|
|
||||||
export function createCompositeHandle(...handles: Handle[]): Handle {
|
|
||||||
return createHandle(function() {
|
|
||||||
for (let i = 0; i < handles.length; i++) {
|
|
||||||
handles[i].destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import { AmdRequire, AmdDefine, NodeRequire } from './interfaces';
|
|
||||||
import { isPlugin, useDefault } from './load/util';
|
|
||||||
|
|
||||||
export type Require = AmdRequire | NodeRequire;
|
|
||||||
|
|
||||||
export interface Load {
|
|
||||||
(require: Require, ...moduleIds: string[]): Promise<any[]>;
|
|
||||||
(...moduleIds: string[]): Promise<any[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const require: Require;
|
|
||||||
|
|
||||||
declare const define: AmdDefine;
|
|
||||||
|
|
||||||
export function isAmdRequire(object: any): object is AmdRequire {
|
|
||||||
return typeof object.toUrl === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNodeRequire(object: any): object is NodeRequire {
|
|
||||||
return typeof object.resolve === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
const load: Load = (function(): Load {
|
|
||||||
const resolver = isAmdRequire(require)
|
|
||||||
? require.toUrl
|
|
||||||
: isNodeRequire(require) ? require.resolve : (resourceId: string) => resourceId;
|
|
||||||
|
|
||||||
function pluginLoad(moduleIds: string[], load: Load, loader: (modulesIds: string[]) => Promise<any>) {
|
|
||||||
const pluginResourceIds: string[] = [];
|
|
||||||
moduleIds = moduleIds.map((id: string, i: number) => {
|
|
||||||
const parts = id.split('!');
|
|
||||||
pluginResourceIds[i] = parts[1];
|
|
||||||
return parts[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
return loader(moduleIds).then((modules: any[]) => {
|
|
||||||
pluginResourceIds.forEach((resourceId: string, i: number) => {
|
|
||||||
if (typeof resourceId === 'string') {
|
|
||||||
const module = modules[i];
|
|
||||||
const defaultExport = module['default'] || module;
|
|
||||||
|
|
||||||
if (isPlugin(defaultExport)) {
|
|
||||||
resourceId =
|
|
||||||
typeof defaultExport.normalize === 'function'
|
|
||||||
? defaultExport.normalize(resourceId, resolver)
|
|
||||||
: resolver(resourceId);
|
|
||||||
|
|
||||||
modules[i] = defaultExport.load(resourceId, load);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(modules);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof module === 'object' && typeof module.exports === 'object') {
|
|
||||||
return function load(contextualRequire: any, ...moduleIds: string[]): Promise<any[]> {
|
|
||||||
if (typeof contextualRequire === 'string') {
|
|
||||||
moduleIds.unshift(contextualRequire);
|
|
||||||
contextualRequire = require;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginLoad(moduleIds, load, (moduleIds: string[]) => {
|
|
||||||
try {
|
|
||||||
return Promise.resolve(
|
|
||||||
moduleIds.map(function(moduleId): any {
|
|
||||||
return contextualRequire(moduleId.split('!')[0]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else if (typeof define === 'function' && define.amd) {
|
|
||||||
return function load(contextualRequire: any, ...moduleIds: string[]): Promise<any[]> {
|
|
||||||
if (typeof contextualRequire === 'string') {
|
|
||||||
moduleIds.unshift(contextualRequire);
|
|
||||||
contextualRequire = require;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginLoad(moduleIds, load, (moduleIds: string[]) => {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
let errorHandle: { remove: () => void };
|
|
||||||
|
|
||||||
if (typeof contextualRequire.on === 'function') {
|
|
||||||
errorHandle = contextualRequire.on('error', (error: Error) => {
|
|
||||||
errorHandle.remove();
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
contextualRequire(moduleIds, function(...modules: any[]) {
|
|
||||||
errorHandle && errorHandle.remove();
|
|
||||||
resolve(modules);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return function() {
|
|
||||||
return Promise.reject(new Error('Unknown loader'));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
export default load;
|
|
||||||
|
|
||||||
export { isPlugin, useDefault };
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { isArrayLike, isIterable } from '@dojo/shim/iterator';
|
|
||||||
import { Load } from '../load';
|
|
||||||
|
|
||||||
export interface LoadPlugin<T> {
|
|
||||||
/**
|
|
||||||
* An optional method that normmalizes a resource id.
|
|
||||||
*
|
|
||||||
* @param resourceId
|
|
||||||
* The raw resource id.
|
|
||||||
*
|
|
||||||
* @param resolver
|
|
||||||
* A method that can resolve an id to an absolute path. Depending on the environment, this will
|
|
||||||
* usually be either `require.toUrl` or `require.resolve`.
|
|
||||||
*/
|
|
||||||
normalize?: (resourceId: string, resolver: (resourceId: string) => string) => string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A method that loads the specified resource.
|
|
||||||
*
|
|
||||||
* @param resourceId
|
|
||||||
* The id of the resource to load.
|
|
||||||
*
|
|
||||||
* @param load
|
|
||||||
* The `load` method that was used to load and execute the plugin.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A promise that resolves to the loaded resource.
|
|
||||||
*/
|
|
||||||
load(resourceId: string, load: Load): Promise<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isPlugin(value: any): value is LoadPlugin<any> {
|
|
||||||
return Boolean(value) && typeof value.load === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDefault(modules: any[]): any[];
|
|
||||||
export function useDefault(module: any): any;
|
|
||||||
export function useDefault(modules: any | any[]): any[] | any {
|
|
||||||
if (isArrayLike(modules)) {
|
|
||||||
let processedModules: any[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < modules.length; i++) {
|
|
||||||
const module = modules[i];
|
|
||||||
processedModules.push(module.__esModule && module.default ? module.default : module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedModules;
|
|
||||||
} else if (isIterable(modules)) {
|
|
||||||
let processedModules: any[] = [];
|
|
||||||
|
|
||||||
for (const module of modules) {
|
|
||||||
processedModules.push(module.__esModule && module.default ? module.default : module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedModules;
|
|
||||||
} else {
|
|
||||||
return modules.__esModule && modules.default ? modules.default : modules;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import { isPlugin, useDefault } from './util';
|
|
||||||
|
|
||||||
interface ModuleIdMap {
|
|
||||||
[path: string]: { id: number; lazy: boolean };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BundleLoaderCallback<T> {
|
|
||||||
(value: T): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WebpackRequire<T> {
|
|
||||||
(id: number): T | BundleLoaderCallback<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A global map (set by the build) of resolved module paths to webpack-specific module data.
|
|
||||||
*/
|
|
||||||
declare const __modules__: ModuleIdMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The webpack-specific require function, set globally by webpack.
|
|
||||||
*/
|
|
||||||
declare const __webpack_require__: WebpackRequire<any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* Resolves an absolute path from an absolute base path and relative module ID.
|
|
||||||
*
|
|
||||||
* @param base
|
|
||||||
* The absolute base path.
|
|
||||||
*
|
|
||||||
* @param mid
|
|
||||||
* The relative module ID
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The resolved absolute module path.
|
|
||||||
*/
|
|
||||||
function resolveRelative(base: string, mid: string): string {
|
|
||||||
const isRelative = mid.match(/^\.+\//);
|
|
||||||
let result = base;
|
|
||||||
|
|
||||||
if (isRelative) {
|
|
||||||
if (mid.match(/^\.\//)) {
|
|
||||||
mid = mid.replace(/\.\//, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
const up = mid.match(/\.\.\//g);
|
|
||||||
if (up) {
|
|
||||||
const chunks = base.split('/');
|
|
||||||
|
|
||||||
if (up.length > chunks.length) {
|
|
||||||
throw new Error('Path cannot go beyond root directory.');
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks.splice(chunks.length - up.length);
|
|
||||||
result = chunks.join('/');
|
|
||||||
mid = mid.replace(/\.\.\//g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
mid = result + '/' + mid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* Returns the parent directory for the specified module ID.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* A function that returns the context module ID.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The parent directory of the path returned by the context function.
|
|
||||||
*/
|
|
||||||
function getBasePath(context: () => string): string {
|
|
||||||
return context()
|
|
||||||
.split('/')
|
|
||||||
.slice(0, -1)
|
|
||||||
.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A webpack-specific function that replaces `@dojo/core/load` in its builds. In order for a module to be loaded,
|
|
||||||
* it must first be included in a webpack chunk, whether that chunk is included in the main build, or lazy-loaded.
|
|
||||||
* Note that this module is not intended for direct use, but rather is intended for use by a webpack plugin
|
|
||||||
* that sets the module ID map used to translate resolved module paths to webpack module IDs.
|
|
||||||
*
|
|
||||||
* @param contextRequire
|
|
||||||
* An optional function that returns the base path to use when resolving relative module IDs.
|
|
||||||
*
|
|
||||||
* @param ...mids
|
|
||||||
* One or more IDs for modules to load.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A promise to the loaded module values.
|
|
||||||
*/
|
|
||||||
export default function load(contextRequire: () => string, ...mids: string[]): Promise<any[]>;
|
|
||||||
export default function load(...mids: string[]): Promise<any[]>;
|
|
||||||
export default function load(...args: any[]): Promise<any[]> {
|
|
||||||
const req = __webpack_require__;
|
|
||||||
const context =
|
|
||||||
typeof args[0] === 'function'
|
|
||||||
? args[0]
|
|
||||||
: function() {
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const modules = __modules__ || {};
|
|
||||||
const base = getBasePath(context);
|
|
||||||
|
|
||||||
const results = args
|
|
||||||
.filter((mid: string | Function) => typeof mid === 'string')
|
|
||||||
.map((mid: string) => resolveRelative(base, mid))
|
|
||||||
.map((mid: string) => {
|
|
||||||
let [moduleId, pluginResourceId] = mid.split('!');
|
|
||||||
const moduleMeta = modules[mid] || modules[moduleId];
|
|
||||||
|
|
||||||
if (!moduleMeta) {
|
|
||||||
return Promise.reject(new Error(`Missing module: ${mid}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleMeta.lazy) {
|
|
||||||
return new Promise((resolve) => req(moduleMeta.id)(resolve));
|
|
||||||
}
|
|
||||||
|
|
||||||
const module = req(moduleMeta.id);
|
|
||||||
const defaultExport = module['default'] || module;
|
|
||||||
|
|
||||||
if (isPlugin(defaultExport)) {
|
|
||||||
pluginResourceId =
|
|
||||||
typeof defaultExport.normalize === 'function'
|
|
||||||
? defaultExport.normalize(pluginResourceId, (mid: string) => resolveRelative(base, mid))
|
|
||||||
: resolveRelative(base, pluginResourceId);
|
|
||||||
|
|
||||||
return Promise.resolve(defaultExport.load(pluginResourceId, <any>load));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(module);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { isPlugin, useDefault };
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import * as aspect from './aspect';
|
|
||||||
import DateObject from './DateObject';
|
|
||||||
import Evented from './Evented';
|
|
||||||
import IdentityRegistry from './IdentityRegistry';
|
|
||||||
import * as lang from './lang';
|
|
||||||
import * as base64 from './base64';
|
|
||||||
import load from './load';
|
|
||||||
import MatchRegistry from './MatchRegistry';
|
|
||||||
import on, { emit } from './on';
|
|
||||||
import * as queue from './queue';
|
|
||||||
import request from './request';
|
|
||||||
import Scheduler from './Scheduler';
|
|
||||||
import * as stringExtras from './stringExtras';
|
|
||||||
import * as text from './text';
|
|
||||||
import UrlSearchParams from './UrlSearchParams';
|
|
||||||
import * as util from './util';
|
|
||||||
|
|
||||||
import * as iteration from './async/iteration';
|
|
||||||
import Task from './async/Task';
|
|
||||||
import * as timing from './async/timing';
|
|
||||||
|
|
||||||
const async = {
|
|
||||||
iteration,
|
|
||||||
Task,
|
|
||||||
timing
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
aspect,
|
|
||||||
async,
|
|
||||||
base64,
|
|
||||||
DateObject,
|
|
||||||
emit,
|
|
||||||
Evented,
|
|
||||||
IdentityRegistry,
|
|
||||||
lang,
|
|
||||||
load,
|
|
||||||
MatchRegistry,
|
|
||||||
on,
|
|
||||||
queue,
|
|
||||||
request,
|
|
||||||
Scheduler,
|
|
||||||
stringExtras,
|
|
||||||
text,
|
|
||||||
UrlSearchParams,
|
|
||||||
util
|
|
||||||
};
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
import { Handle, EventObject } from './interfaces';
|
|
||||||
import { createHandle, createCompositeHandle } from './lang';
|
|
||||||
import Evented, { CustomEventTypes } from './Evented';
|
|
||||||
|
|
||||||
export interface EventCallback<O = EventObject<string>> {
|
|
||||||
(event: O): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventEmitter {
|
|
||||||
on(event: string, listener: EventCallback): EventEmitter;
|
|
||||||
removeListener(event: string, listener: EventCallback): EventEmitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMEventObject extends EventObject {
|
|
||||||
bubbles: boolean;
|
|
||||||
cancelable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a normalized mechanism for dispatching events for event emitters, Evented objects, or DOM nodes.
|
|
||||||
* @param target The target to emit the event from
|
|
||||||
* @param event The event object to emit
|
|
||||||
* @return Boolean indicating if preventDefault was called on the event object (only relevant for DOM events;
|
|
||||||
* always false for other event emitters)
|
|
||||||
*/
|
|
||||||
export function emit<
|
|
||||||
M extends CustomEventTypes,
|
|
||||||
T,
|
|
||||||
O extends EventObject<T> = EventObject<T>,
|
|
||||||
K extends keyof M = keyof M
|
|
||||||
>(target: Evented<M, T, O>, event: M[K]): boolean;
|
|
||||||
export function emit<T, O extends EventObject<T> = EventObject<T>>(target: Evented<any, T, O>, event: O): boolean;
|
|
||||||
export function emit<O extends EventObject<string> = EventObject<string>>(
|
|
||||||
target: EventTarget | EventEmitter,
|
|
||||||
event: O
|
|
||||||
): boolean;
|
|
||||||
export function emit(target: any, event: EventObject<any>): boolean {
|
|
||||||
if (
|
|
||||||
target.dispatchEvent /* includes window and document */ &&
|
|
||||||
((target.ownerDocument && target.ownerDocument.createEvent) /* matches nodes */ ||
|
|
||||||
(target.document && target.document.createEvent) /* matches window */ ||
|
|
||||||
target.createEvent) /* matches document */
|
|
||||||
) {
|
|
||||||
const nativeEvent = (target.ownerDocument || target.document || target).createEvent('HTMLEvents');
|
|
||||||
nativeEvent.initEvent(
|
|
||||||
event.type,
|
|
||||||
Boolean((<DOMEventObject>event).bubbles),
|
|
||||||
Boolean((<DOMEventObject>event).cancelable)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let key in event) {
|
|
||||||
if (!(key in nativeEvent)) {
|
|
||||||
nativeEvent[key] = (<any>event)[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return target.dispatchEvent(nativeEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.emit) {
|
|
||||||
if (target.removeListener) {
|
|
||||||
// Node.js EventEmitter
|
|
||||||
target.emit(event.type, event);
|
|
||||||
return false;
|
|
||||||
} else if (target.on) {
|
|
||||||
// Dojo Evented or similar
|
|
||||||
target.emit(event);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Target must be an event emitter');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a normalized mechanism for listening to events from event emitters, Evented objects, or DOM nodes.
|
|
||||||
* @param target Target to listen for event on
|
|
||||||
* @param type Event event type(s) to listen for; may a string or an array of strings
|
|
||||||
* @param listener Callback to handle the event when it fires
|
|
||||||
* @param capture Whether the listener should be registered in the capture phase (DOM events only)
|
|
||||||
* @return A handle which will remove the listener when destroy is called
|
|
||||||
*/
|
|
||||||
export default function on<
|
|
||||||
M extends CustomEventTypes,
|
|
||||||
T,
|
|
||||||
K extends keyof M = keyof M,
|
|
||||||
O extends EventObject<T> = EventObject<T>
|
|
||||||
>(target: Evented<M, T, O>, type: K | K[], listener: EventCallback<M[K]>): Handle;
|
|
||||||
export default function on<T, O extends EventObject<T> = EventObject<T>>(
|
|
||||||
target: Evented<any, T, O>,
|
|
||||||
type: T | T[],
|
|
||||||
listener: EventCallback<O>
|
|
||||||
): Handle;
|
|
||||||
export default function on(target: EventEmitter, type: string | string[], listener: EventCallback): Handle;
|
|
||||||
export default function on(
|
|
||||||
target: EventTarget,
|
|
||||||
type: string | string[],
|
|
||||||
listener: EventCallback,
|
|
||||||
capture?: boolean
|
|
||||||
): Handle;
|
|
||||||
export default function on(target: any, type: any, listener: any, capture?: boolean): Handle {
|
|
||||||
if (Array.isArray(type)) {
|
|
||||||
let handles: Handle[] = type.map(function(type: string): Handle {
|
|
||||||
return on(target, type, listener, capture);
|
|
||||||
});
|
|
||||||
|
|
||||||
return createCompositeHandle(...handles);
|
|
||||||
}
|
|
||||||
|
|
||||||
const callback = function(this: any) {
|
|
||||||
listener.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
// DOM EventTarget
|
|
||||||
if (target.addEventListener && target.removeEventListener) {
|
|
||||||
target.addEventListener(type, callback, capture);
|
|
||||||
return createHandle(function() {
|
|
||||||
target.removeEventListener(type, callback, capture);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.on) {
|
|
||||||
// EventEmitter
|
|
||||||
if (target.removeListener) {
|
|
||||||
target.on(type, callback);
|
|
||||||
return createHandle(function() {
|
|
||||||
target.removeListener(type, callback);
|
|
||||||
});
|
|
||||||
} else if (target.emit) {
|
|
||||||
// Evented
|
|
||||||
return target.on(type, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeError('Unknown event emitter object');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a mechanism for listening to the next occurrence of an event from event
|
|
||||||
* emitters, Evented objects, or DOM nodes.
|
|
||||||
* @param target Target to listen for event on
|
|
||||||
* @param type Event event type(s) to listen for; may be a string or an array of strings
|
|
||||||
* @param listener Callback to handle the event when it fires
|
|
||||||
* @param capture Whether the listener should be registered in the capture phase (DOM events only)
|
|
||||||
* @return A handle which will remove the listener when destroy is called
|
|
||||||
*/
|
|
||||||
export function once<
|
|
||||||
M extends CustomEventTypes,
|
|
||||||
T,
|
|
||||||
K extends keyof M = keyof M,
|
|
||||||
O extends EventObject<T> = EventObject<T>
|
|
||||||
>(target: Evented<M, T, O>, type: K | K[], listener: EventCallback<M[K]>): Handle;
|
|
||||||
export function once<T, O extends EventObject<T> = EventObject<T>>(
|
|
||||||
target: Evented<any, T, O>,
|
|
||||||
type: T | T[],
|
|
||||||
listener: EventCallback<O>
|
|
||||||
): Handle;
|
|
||||||
export function once(target: EventTarget, type: string | string[], listener: EventCallback, capture?: boolean): Handle;
|
|
||||||
export function once(target: EventEmitter, type: string | string[], listener: EventCallback): Handle;
|
|
||||||
export function once(target: any, type: any, listener: any, capture?: boolean): Handle {
|
|
||||||
// FIXME
|
|
||||||
// tslint:disable-next-line:no-var-keyword
|
|
||||||
var handle = on(
|
|
||||||
target,
|
|
||||||
type,
|
|
||||||
function(this: any) {
|
|
||||||
handle.destroy();
|
|
||||||
return listener.apply(this, arguments);
|
|
||||||
},
|
|
||||||
capture
|
|
||||||
);
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PausableHandle extends Handle {
|
|
||||||
pause(): void;
|
|
||||||
resume(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a mechanism for creating pausable listeners for events from event emitters, Evented objects, or DOM nodes.
|
|
||||||
* @param target Target to listen for event on
|
|
||||||
* @param type Event event type(s) to listen for; may a string or an array of strings
|
|
||||||
* @param listener Callback to handle the event when it fires
|
|
||||||
* @param capture Whether the listener should be registered in the capture phase (DOM events only)
|
|
||||||
* @return A handle with additional pause and resume methods; the listener will never fire when paused
|
|
||||||
*/
|
|
||||||
export function pausable<
|
|
||||||
M extends CustomEventTypes,
|
|
||||||
T,
|
|
||||||
K extends keyof M = keyof M,
|
|
||||||
O extends EventObject<T> = EventObject<T>
|
|
||||||
>(target: Evented<M, T, O>, type: K | K[], listener: EventCallback<M[K]>): PausableHandle;
|
|
||||||
export function pausable<T, O extends EventObject<T> = EventObject<T>>(
|
|
||||||
target: Evented<any, T, O>,
|
|
||||||
type: T | T[],
|
|
||||||
listener: EventCallback<O>
|
|
||||||
): PausableHandle;
|
|
||||||
export function pausable(
|
|
||||||
target: EventTarget,
|
|
||||||
type: string | string[],
|
|
||||||
listener: EventCallback,
|
|
||||||
capture?: boolean
|
|
||||||
): PausableHandle;
|
|
||||||
export function pausable(target: EventEmitter, type: string | string[], listener: EventCallback): PausableHandle;
|
|
||||||
export function pausable(target: any, type: any, listener: any, capture?: boolean): PausableHandle {
|
|
||||||
let paused: boolean;
|
|
||||||
|
|
||||||
const handle = <PausableHandle>on(
|
|
||||||
target,
|
|
||||||
type,
|
|
||||||
function(this: any) {
|
|
||||||
if (!paused) {
|
|
||||||
return listener.apply(this, arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
capture
|
|
||||||
);
|
|
||||||
|
|
||||||
handle.pause = function() {
|
|
||||||
paused = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
handle.resume = function() {
|
|
||||||
paused = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from '@dojo/shim/support/queue';
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import has from '@dojo/has/has';
|
|
||||||
import Task from './async/Task';
|
|
||||||
import { RequestOptions, Response, Provider, UploadObservableTask } from './request/interfaces';
|
|
||||||
import ProviderRegistry from './request/ProviderRegistry';
|
|
||||||
import xhr from './request/providers/xhr';
|
|
||||||
|
|
||||||
export const providerRegistry = new ProviderRegistry();
|
|
||||||
|
|
||||||
const request: {
|
|
||||||
(url: string, options?: RequestOptions): Task<Response>;
|
|
||||||
delete(url: string, options?: RequestOptions): Task<Response>;
|
|
||||||
get(url: string, options?: RequestOptions): Task<Response>;
|
|
||||||
head(url: string, options?: RequestOptions): Task<Response>;
|
|
||||||
options(url: string, options?: RequestOptions): Task<Response>;
|
|
||||||
post(url: string, options?: RequestOptions): UploadObservableTask<Response>;
|
|
||||||
put(url: string, options?: RequestOptions): UploadObservableTask<Response>;
|
|
||||||
|
|
||||||
setDefaultProvider(provider: Provider): void;
|
|
||||||
} = <any>function request(url: string, options: RequestOptions = {}): Task<Response> {
|
|
||||||
try {
|
|
||||||
return providerRegistry.match(url, options)(url, options);
|
|
||||||
} catch (error) {
|
|
||||||
return Task.reject<Response>(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
['DELETE', 'GET', 'HEAD', 'OPTIONS'].forEach((method) => {
|
|
||||||
Object.defineProperty(request, method.toLowerCase(), {
|
|
||||||
value(url: string, options: RequestOptions = {}): Task<Response> {
|
|
||||||
options = Object.create(options);
|
|
||||||
options.method = method;
|
|
||||||
return request(url, options);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
['POST', 'PUT'].forEach((method) => {
|
|
||||||
Object.defineProperty(request, method.toLowerCase(), {
|
|
||||||
value(url: string, options: RequestOptions = {}): UploadObservableTask<Response> {
|
|
||||||
options = Object.create(options);
|
|
||||||
options.method = method;
|
|
||||||
return <UploadObservableTask<Response>>request(url, options);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(request, 'setDefaultProvider', {
|
|
||||||
value(provider: Provider) {
|
|
||||||
providerRegistry.setDefaultProvider(provider);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
providerRegistry.setDefaultProvider(xhr);
|
|
||||||
|
|
||||||
if (has('host-node')) {
|
|
||||||
// tslint:disable-next-line
|
|
||||||
import('./request/providers/node').then((node) => {
|
|
||||||
providerRegistry.setDefaultProvider(node.default);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default request;
|
|
||||||
export * from './request/interfaces';
|
|
||||||
export { default as Headers } from './request/Headers';
|
|
||||||
export { default as TimeoutError } from './request/TimeoutError';
|
|
||||||
export { ResponseData } from './request/Response';
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
import { Headers as HeadersInterface } from './interfaces';
|
|
||||||
import { IterableIterator, ShimIterator } from '@dojo/shim/iterator';
|
|
||||||
import Map from '@dojo/shim/Map';
|
|
||||||
|
|
||||||
function isHeadersLike(object: any): object is HeadersInterface {
|
|
||||||
return (
|
|
||||||
typeof object.append === 'function' &&
|
|
||||||
typeof object.entries === 'function' &&
|
|
||||||
typeof object[Symbol.iterator] === 'function'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Headers implements HeadersInterface {
|
|
||||||
protected map = new Map<string, string[]>();
|
|
||||||
|
|
||||||
constructor(headers?: { [key: string]: string } | HeadersInterface) {
|
|
||||||
if (headers) {
|
|
||||||
if (headers instanceof Headers) {
|
|
||||||
this.map = new Map(headers.map);
|
|
||||||
} else if (isHeadersLike(headers)) {
|
|
||||||
for (const [key, value] of headers) {
|
|
||||||
this.append(key, value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let key in headers) {
|
|
||||||
this.set(key, headers[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
append(name: string, value: string) {
|
|
||||||
const values = this.map.get(name.toLowerCase());
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
values.push(value);
|
|
||||||
} else {
|
|
||||||
this.set(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(name: string) {
|
|
||||||
this.map.delete(name.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
entries(): IterableIterator<[string, string]> {
|
|
||||||
const entries: [string, string][] = [];
|
|
||||||
for (const [key, values] of this.map.entries()) {
|
|
||||||
values.forEach((value) => {
|
|
||||||
entries.push([key, value]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new ShimIterator(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(name: string): string | null {
|
|
||||||
const values = this.map.get(name.toLowerCase());
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
return values[0];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAll(name: string): string[] {
|
|
||||||
const values = this.map.get(name.toLowerCase());
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
return values.slice(0);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
has(name: string): boolean {
|
|
||||||
return this.map.has(name.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
keys(): IterableIterator<string> {
|
|
||||||
return this.map.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
set(name: string, value: string) {
|
|
||||||
this.map.set(name.toLowerCase(), [value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
values(): IterableIterator<string> {
|
|
||||||
const values: string[] = [];
|
|
||||||
for (const value of this.map.values()) {
|
|
||||||
values.push(...value);
|
|
||||||
}
|
|
||||||
return new ShimIterator(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this.entries();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Headers;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { Provider, ProviderTest } from './interfaces';
|
|
||||||
import MatchRegistry, { Test } from '../MatchRegistry';
|
|
||||||
import { Handle } from '../interfaces';
|
|
||||||
|
|
||||||
export class ProviderRegistry extends MatchRegistry<Provider> {
|
|
||||||
setDefaultProvider(provider: Provider) {
|
|
||||||
this._defaultValue = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(test: string | RegExp | ProviderTest | null, value: Provider, first?: boolean): Handle {
|
|
||||||
let entryTest: Test | null;
|
|
||||||
|
|
||||||
if (typeof test === 'string') {
|
|
||||||
entryTest = (url, options) => test === url;
|
|
||||||
} else if (test instanceof RegExp) {
|
|
||||||
entryTest = (url, options) => {
|
|
||||||
return test ? test.test(url) : null;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
entryTest = test;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.register(entryTest, value, first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProviderRegistry;
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
## Features
|
|
||||||
|
|
||||||
This module allows the consumer to make HTTP requests using Node, XMLHttpRequest, or the Fetch API. The details of
|
|
||||||
these implementations are exposed through a common interface.
|
|
||||||
|
|
||||||
With this module you can,
|
|
||||||
|
|
||||||
* Easily make simple HTTP requests
|
|
||||||
* Convert response bodies to common formats like text, json, or html
|
|
||||||
* Access response headers of a request before the body is downloaded
|
|
||||||
* Monitor progress of a request
|
|
||||||
* Stream response data
|
|
||||||
|
|
||||||
## How do I use this package?
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
To make simple GET requests, you must register your provider (node, or XHR) then make the request. The overall
|
|
||||||
format of the API resembles the [Fetch Standard](https://fetch.spec.whatwg.org/).
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import request from 'dojo-request';
|
|
||||||
import node from 'dojo-request/providers/node';
|
|
||||||
|
|
||||||
request.setDefaultProvider(node);
|
|
||||||
|
|
||||||
request.get('http://www.example.com').then(response => {
|
|
||||||
return response.text();
|
|
||||||
}).then(html => {
|
|
||||||
console.log(html);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Responses can be returned as an `ArrayBuffer`, `Blob`, XML document, JSON object, or text string.
|
|
||||||
|
|
||||||
You can also easily send request data,
|
|
||||||
|
|
||||||
```ts
|
|
||||||
request.post('http://www.example.com', {
|
|
||||||
body: 'request data here'
|
|
||||||
}).then(response => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Reading Response Headers
|
|
||||||
|
|
||||||
This approach allows for processing of response headers _before_ the response body is available.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
request.get('http://www.example.com').then(response => {
|
|
||||||
const expectedSize = Number(response.headers.get('content-length') || 0);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response Events
|
|
||||||
|
|
||||||
`Response` objects also emit `start`, `end`, `progress`, and `data` events. You can use these events to monitor download progress
|
|
||||||
or stream a response directly to a file.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
request.get('http://www.example.com').then(response => {
|
|
||||||
response.on('progress', (event: ProgressEvent) => {
|
|
||||||
console.log(`Total downloaded: ${event.totalBytesDownloaded}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.blob();
|
|
||||||
}).then(blob => {
|
|
||||||
// do something with the data
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that there are some caveats when using these events. XHR cannot stream data (a final `data` event is sent at the end however).
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import Task from '../async/Task';
|
|
||||||
import { Headers, Response as ResponseInterface, RequestOptions } from './interfaces';
|
|
||||||
import Observable from '../Observable';
|
|
||||||
|
|
||||||
export interface ResponseData {
|
|
||||||
task: Task<any>;
|
|
||||||
used: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Response implements ResponseInterface {
|
|
||||||
abstract readonly headers: Headers;
|
|
||||||
abstract readonly ok: boolean;
|
|
||||||
abstract readonly status: number;
|
|
||||||
abstract readonly statusText: string;
|
|
||||||
abstract readonly url: string;
|
|
||||||
abstract readonly bodyUsed: boolean;
|
|
||||||
abstract readonly requestOptions: RequestOptions;
|
|
||||||
|
|
||||||
abstract readonly download: Observable<number>;
|
|
||||||
abstract readonly data: Observable<any>;
|
|
||||||
|
|
||||||
json<T>(): Task<T> {
|
|
||||||
return <any>this.text().then(JSON.parse);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract arrayBuffer(): Task<ArrayBuffer>;
|
|
||||||
abstract blob(): Task<Blob>;
|
|
||||||
abstract formData(): Task<FormData>;
|
|
||||||
abstract text(): Task<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Response;
|
|
||||||
|
|
||||||
export function getFileReaderPromise<T>(reader: FileReader): Promise<T> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
reader.onload = function() {
|
|
||||||
resolve(reader.result);
|
|
||||||
};
|
|
||||||
reader.onerror = function() {
|
|
||||||
reject(reader.error);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTextFromBlob(blob: Blob) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
const promise = getFileReaderPromise<string>(reader);
|
|
||||||
reader.readAsText(blob);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getArrayBufferFromBlob(blob: Blob) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
const promise = getFileReaderPromise<ArrayBuffer>(reader);
|
|
||||||
reader.readAsArrayBuffer(blob);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTextFromArrayBuffer(buffer: ArrayBuffer) {
|
|
||||||
const view = new Uint8Array(buffer);
|
|
||||||
const chars: string[] = [];
|
|
||||||
|
|
||||||
view.forEach((charCode, index) => {
|
|
||||||
chars[index] = String.fromCharCode(charCode);
|
|
||||||
});
|
|
||||||
|
|
||||||
return chars.join('');
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { SubscriptionObserver } from '@dojo/shim/Observable';
|
|
||||||
|
|
||||||
export class SubscriptionPool<T> {
|
|
||||||
private _observers: SubscriptionObserver<T>[] = [];
|
|
||||||
private _queue: T[] = [];
|
|
||||||
private _queueMaxLength: number;
|
|
||||||
|
|
||||||
constructor(maxLength = 10) {
|
|
||||||
this._queueMaxLength = maxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(subscription: SubscriptionObserver<T>) {
|
|
||||||
this._observers.push(subscription);
|
|
||||||
|
|
||||||
while (this._queue.length > 0) {
|
|
||||||
this.next(this._queue.shift()!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this._observers.splice(this._observers.indexOf(subscription), 1);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
next(value: T) {
|
|
||||||
if (this._observers.length === 0) {
|
|
||||||
this._queue.push(value);
|
|
||||||
|
|
||||||
// when the queue is full, get rid of the first ones
|
|
||||||
while (this._queue.length > this._queueMaxLength) {
|
|
||||||
this._queue.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._observers.forEach((observer) => {
|
|
||||||
observer.next(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
complete() {
|
|
||||||
this._observers.forEach((observer) => {
|
|
||||||
observer.complete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SubscriptionPool;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
export class TimeoutError implements Error {
|
|
||||||
readonly message: string;
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return 'TimeoutError';
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(message?: string) {
|
|
||||||
message = message || 'The request timed out';
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimeoutError;
|
|
||||||
84
src/lib/core/src/request/interfaces.d.ts
vendored
84
src/lib/core/src/request/interfaces.d.ts
vendored
@ -1,84 +0,0 @@
|
|||||||
import { IterableIterator } from '@dojo/shim/iterator';
|
|
||||||
import Task from '../async/Task';
|
|
||||||
import UrlSearchParams, { ParamList } from '../UrlSearchParams';
|
|
||||||
import Observable from '../Observable';
|
|
||||||
|
|
||||||
export interface Body {
|
|
||||||
readonly bodyUsed: boolean;
|
|
||||||
|
|
||||||
arrayBuffer(): Task<ArrayBuffer>;
|
|
||||||
blob(): Task<Blob>;
|
|
||||||
formData(): Task<FormData>;
|
|
||||||
json<T>(): Task<T>;
|
|
||||||
text(): Task<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Headers {
|
|
||||||
append(name: string, value: string): void;
|
|
||||||
delete(name: string): void;
|
|
||||||
entries(): IterableIterator<[string, string]>;
|
|
||||||
get(name: string): string | null;
|
|
||||||
getAll(name: string): string[];
|
|
||||||
has(name: string): boolean;
|
|
||||||
keys(): IterableIterator<string>;
|
|
||||||
set(name: string, value: string): void;
|
|
||||||
values(): IterableIterator<string>;
|
|
||||||
[Symbol.iterator](): IterableIterator<[string, string]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadObservableTask<T> extends Task<T> {
|
|
||||||
upload: Observable<number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Provider = (url: string, options?: RequestOptions) => UploadObservableTask<Response>;
|
|
||||||
|
|
||||||
export type ProviderTest = (url: string, options?: RequestOptions) => boolean | null;
|
|
||||||
|
|
||||||
export interface RequestOptions {
|
|
||||||
/**
|
|
||||||
* Enable cache busting (default false). Cache busting will make a new URL by appending a parameter to the
|
|
||||||
* requested URL
|
|
||||||
*/
|
|
||||||
cacheBust?: boolean;
|
|
||||||
credentials?: 'omit' | 'same-origin' | 'include';
|
|
||||||
/**
|
|
||||||
* Body to send along with the http request
|
|
||||||
*/
|
|
||||||
body?: Blob | BufferSource | FormData | UrlSearchParams | string;
|
|
||||||
/**
|
|
||||||
* Headers to send along with the http request
|
|
||||||
*/
|
|
||||||
headers?: Headers | { [key: string]: string };
|
|
||||||
/**
|
|
||||||
* HTTP method
|
|
||||||
*/
|
|
||||||
method?: string;
|
|
||||||
/**
|
|
||||||
* Password for HTTP authentication
|
|
||||||
*/
|
|
||||||
password?: string;
|
|
||||||
/**
|
|
||||||
* Number of milliseconds before the request times out and is canceled
|
|
||||||
*/
|
|
||||||
timeout?: number;
|
|
||||||
/**
|
|
||||||
* User for HTTP authentication
|
|
||||||
*/
|
|
||||||
user?: string;
|
|
||||||
/**
|
|
||||||
* Optional query parameter(s) for the URL. The requested url will have these query parameters appended.
|
|
||||||
*/
|
|
||||||
query?: string | ParamList;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response extends Body {
|
|
||||||
readonly headers: Headers;
|
|
||||||
readonly ok: boolean;
|
|
||||||
readonly status: number;
|
|
||||||
readonly statusText: string;
|
|
||||||
readonly url: string;
|
|
||||||
readonly requestOptions: RequestOptions;
|
|
||||||
|
|
||||||
readonly download: Observable<number>;
|
|
||||||
readonly data: Observable<any>;
|
|
||||||
}
|
|
||||||
@ -1,677 +0,0 @@
|
|||||||
import { Handle } from '../../interfaces';
|
|
||||||
import Set from '@dojo/shim/Set';
|
|
||||||
import WeakMap from '@dojo/shim/WeakMap';
|
|
||||||
import * as http from 'http';
|
|
||||||
import * as https from 'https';
|
|
||||||
import * as urlUtil from 'url';
|
|
||||||
import * as zlib from 'zlib';
|
|
||||||
import Task from '../../async/Task';
|
|
||||||
import { deepAssign } from '../../lang';
|
|
||||||
import { queueTask } from '../../queue';
|
|
||||||
import { createTimer } from '../../util';
|
|
||||||
import Headers from '../Headers';
|
|
||||||
import { RequestOptions, UploadObservableTask } from '../interfaces';
|
|
||||||
import Response from '../Response';
|
|
||||||
import TimeoutError from '../TimeoutError';
|
|
||||||
import { Readable } from 'stream';
|
|
||||||
import Observable from '../../Observable';
|
|
||||||
import SubscriptionPool from '../SubscriptionPool';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request options specific to a node request. For HTTPS options, see
|
|
||||||
* https://nodejs.org/api/tls.html#tls_tls_connect_options_callback for more details.
|
|
||||||
*/
|
|
||||||
export interface NodeRequestOptions extends RequestOptions {
|
|
||||||
/**
|
|
||||||
* User-agent header
|
|
||||||
*/
|
|
||||||
agent?: any;
|
|
||||||
/**
|
|
||||||
* If specified, the request body is read from the stream specified here, rather than from the `body` field.
|
|
||||||
*/
|
|
||||||
bodyStream?: Readable;
|
|
||||||
/**
|
|
||||||
* HTTPS optionally override the trusted CA certificates
|
|
||||||
*/
|
|
||||||
ca?: any;
|
|
||||||
/**
|
|
||||||
* HTTPS optional cert chains in PEM format. One cert chain should be provided per private key.
|
|
||||||
*/
|
|
||||||
cert?: string;
|
|
||||||
/**
|
|
||||||
* HTTPS optional cipher suite specification
|
|
||||||
*/
|
|
||||||
ciphers?: string;
|
|
||||||
dataEncoding?: string;
|
|
||||||
/**
|
|
||||||
* Whether or not to automatically follow redirects (default true)
|
|
||||||
*/
|
|
||||||
followRedirects?: boolean;
|
|
||||||
/**
|
|
||||||
* HTTPS optional private key in PEM format.
|
|
||||||
*/
|
|
||||||
key?: string;
|
|
||||||
/**
|
|
||||||
* Local interface to bind for network connections.
|
|
||||||
*/
|
|
||||||
localAddress?: string;
|
|
||||||
/**
|
|
||||||
* HTTPS optional shared passphrase used for a single private key and/or a PFX.
|
|
||||||
*/
|
|
||||||
passphrase?: string;
|
|
||||||
/**
|
|
||||||
* HTTPS optional PFX or PKCS12 encoded private key and certificate chain.
|
|
||||||
*/
|
|
||||||
pfx?: any;
|
|
||||||
/**
|
|
||||||
* Optional proxy address. If specified, requests will be sent through this url.
|
|
||||||
*/
|
|
||||||
proxy?: string;
|
|
||||||
/**
|
|
||||||
* HTTPS If not false the server will reject any connection which is not authorized with the list of supplied CAs
|
|
||||||
*/
|
|
||||||
rejectUnauthorized?: boolean;
|
|
||||||
/**
|
|
||||||
* HTTPS optional SSL method to use, default is "SSLv23_method"
|
|
||||||
*/
|
|
||||||
secureProtocol?: string;
|
|
||||||
/**
|
|
||||||
* Unix Domain Socket (use one of host:port or socketPath)
|
|
||||||
*/
|
|
||||||
socketPath?: string;
|
|
||||||
/**
|
|
||||||
* Whether or not to add the gzip and deflate accept headers (default true)
|
|
||||||
*/
|
|
||||||
acceptCompression?: boolean;
|
|
||||||
/**
|
|
||||||
* A set of options to set on the HTTP request
|
|
||||||
*/
|
|
||||||
socketOptions?: {
|
|
||||||
/**
|
|
||||||
* Enable/disable keep-alive functionality, and optionally set the initial delay before the first keepalive probe is sent on an idle socket.
|
|
||||||
*/
|
|
||||||
keepAlive?: number;
|
|
||||||
/**
|
|
||||||
* Disables the Nagle algorithm. By default TCP connections use the Nagle algorithm, they buffer data before sending it off.
|
|
||||||
*/
|
|
||||||
noDelay?: boolean;
|
|
||||||
/**
|
|
||||||
* Number of milliseconds before the HTTP request times out
|
|
||||||
*/
|
|
||||||
timeout?: number;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Stream encoding on incoming HTTP response
|
|
||||||
*/
|
|
||||||
streamEncoding?: string;
|
|
||||||
/**
|
|
||||||
* Options to control redirect follow logic
|
|
||||||
*/
|
|
||||||
redirectOptions?: {
|
|
||||||
/**
|
|
||||||
* The limit to the number of redirects that will be followed (default 15). This is used to prevent infinite
|
|
||||||
* redirect loops.
|
|
||||||
*/
|
|
||||||
limit?: number;
|
|
||||||
count?: number;
|
|
||||||
/**
|
|
||||||
* Whether or not to keep the original HTTP method during 301 redirects (default false).
|
|
||||||
*/
|
|
||||||
keepOriginalMethod?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This should be read from the package and not hard coded!
|
|
||||||
let version = '2.0.0-pre';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If not overridden, redirects will only be processed this many times before aborting (per request).
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
const DEFAULT_REDIRECT_LIMIT = 15;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options to be passed to node's request
|
|
||||||
*/
|
|
||||||
interface Options {
|
|
||||||
agent?: any;
|
|
||||||
auth?: string;
|
|
||||||
headers?: { [name: string]: string };
|
|
||||||
host?: string;
|
|
||||||
hostname?: string;
|
|
||||||
localAddress?: string;
|
|
||||||
method?: string;
|
|
||||||
path?: string;
|
|
||||||
port?: number;
|
|
||||||
socketPath?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTPS specific options for node
|
|
||||||
*/
|
|
||||||
interface HttpsOptions extends Options {
|
|
||||||
ca?: any;
|
|
||||||
cert?: string;
|
|
||||||
ciphers?: string;
|
|
||||||
key?: string;
|
|
||||||
passphrase?: string;
|
|
||||||
pfx?: any;
|
|
||||||
rejectUnauthorized?: boolean;
|
|
||||||
secureProtocol?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestData {
|
|
||||||
task: Task<http.IncomingMessage>;
|
|
||||||
buffer: any[];
|
|
||||||
data: Buffer;
|
|
||||||
size: number;
|
|
||||||
used: boolean;
|
|
||||||
nativeResponse: http.IncomingMessage;
|
|
||||||
requestOptions: NodeRequestOptions;
|
|
||||||
url: string;
|
|
||||||
downloadObservable: Observable<number>;
|
|
||||||
dataObservable: Observable<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataMap = new WeakMap<NodeResponse, RequestData>();
|
|
||||||
const discardedDuplicates = new Set<string>([
|
|
||||||
'age',
|
|
||||||
'authorization',
|
|
||||||
'content-length',
|
|
||||||
'content-type',
|
|
||||||
'etag',
|
|
||||||
'expires',
|
|
||||||
'from',
|
|
||||||
'host',
|
|
||||||
'if-modified-since',
|
|
||||||
'if-unmodified-since',
|
|
||||||
'last-modified',
|
|
||||||
'location',
|
|
||||||
'max-forwards',
|
|
||||||
'proxy-authorization',
|
|
||||||
'referer',
|
|
||||||
'retry-after',
|
|
||||||
'user-agent'
|
|
||||||
]);
|
|
||||||
|
|
||||||
function getDataTask(response: NodeResponse): Task<RequestData> {
|
|
||||||
const data = dataMap.get(response)!;
|
|
||||||
|
|
||||||
if (data.used) {
|
|
||||||
return Task.reject<any>(new TypeError('Body already read'));
|
|
||||||
}
|
|
||||||
|
|
||||||
data.used = true;
|
|
||||||
|
|
||||||
return <Task<RequestData>>data.task.then((_) => data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn a node native response object into something that resembles the fetch api
|
|
||||||
*/
|
|
||||||
export class NodeResponse extends Response {
|
|
||||||
readonly headers: Headers;
|
|
||||||
readonly ok: boolean;
|
|
||||||
readonly status: number;
|
|
||||||
readonly statusText: string;
|
|
||||||
|
|
||||||
downloadBody = true;
|
|
||||||
|
|
||||||
get bodyUsed(): boolean {
|
|
||||||
return dataMap.get(this)!.used;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nativeResponse(): http.IncomingMessage {
|
|
||||||
return dataMap.get(this)!.nativeResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
get requestOptions(): NodeRequestOptions {
|
|
||||||
return dataMap.get(this)!.requestOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url(): string {
|
|
||||||
return dataMap.get(this)!.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
get download(): Observable<number> {
|
|
||||||
return dataMap.get(this)!.downloadObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
get data(): Observable<any> {
|
|
||||||
return dataMap.get(this)!.dataObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(response: http.IncomingMessage) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
const headers = (this.headers = new Headers());
|
|
||||||
for (let key in response.headers) {
|
|
||||||
const value = response.headers[key];
|
|
||||||
if (value) {
|
|
||||||
if (discardedDuplicates.has(key) && !Array.isArray(value)) {
|
|
||||||
headers.append(key, value);
|
|
||||||
}
|
|
||||||
(Array.isArray(value) ? value : value.split(/\s*,\s*/)).forEach((v) => {
|
|
||||||
headers.append(key, v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.status = response.statusCode || 0;
|
|
||||||
this.ok = this.status >= 200 && this.status < 300;
|
|
||||||
this.statusText = response.statusMessage || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayBuffer(): Task<ArrayBuffer> {
|
|
||||||
return <any>getDataTask(this).then((data) => {
|
|
||||||
if (data) {
|
|
||||||
return data.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Buffer([]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
blob(): Task<Blob> {
|
|
||||||
// Node doesn't support Blobs
|
|
||||||
return Task.reject<Blob>(new Error('Blob not supported'));
|
|
||||||
}
|
|
||||||
|
|
||||||
formData(): Task<FormData> {
|
|
||||||
return Task.reject<FormData>(new Error('FormData not supported'));
|
|
||||||
}
|
|
||||||
|
|
||||||
text(): Task<string> {
|
|
||||||
return <any>getDataTask(this).then((data) => {
|
|
||||||
return String(data ? data.data : '');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function redirect(
|
|
||||||
resolve: (p?: any) => void,
|
|
||||||
reject: (_?: Error) => void,
|
|
||||||
originalUrl: string,
|
|
||||||
redirectUrl: string | null,
|
|
||||||
options: NodeRequestOptions
|
|
||||||
): boolean {
|
|
||||||
if (!options.redirectOptions) {
|
|
||||||
options.redirectOptions = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { limit: redirectLimit = DEFAULT_REDIRECT_LIMIT, count: redirectCount = 0 } = options.redirectOptions;
|
|
||||||
const { followRedirects = true } = options;
|
|
||||||
|
|
||||||
if (!followRedirects) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we only check for undefined here because empty string redirects are now allowed
|
|
||||||
// (they'll resolve to the current url)
|
|
||||||
if (redirectUrl === undefined || redirectUrl === null) {
|
|
||||||
reject(new Error('asked to redirect but no location header was found'));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirectCount > redirectLimit) {
|
|
||||||
reject(new Error(`too many redirects, limit reached at ${redirectLimit}`));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.redirectOptions.count = redirectCount + 1;
|
|
||||||
|
|
||||||
// we wrap the url in a call to node's URL.resolve which will handle relative and partial
|
|
||||||
// redirects (like "/another-page" on the same domain).
|
|
||||||
resolve(node(urlUtil.resolve(originalUrl, redirectUrl), options));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAuth(proxyAuth: string | undefined, options: NodeRequestOptions): string | undefined {
|
|
||||||
if (proxyAuth) {
|
|
||||||
return proxyAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.user || options.password) {
|
|
||||||
return `${options.user || ''}:${options.password || ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function node(url: string, options: NodeRequestOptions = {}): UploadObservableTask<NodeResponse> {
|
|
||||||
const parsedUrl = urlUtil.parse(options.proxy || url);
|
|
||||||
|
|
||||||
const requestOptions: HttpsOptions = {
|
|
||||||
agent: options.agent,
|
|
||||||
auth: getAuth(parsedUrl.auth, options),
|
|
||||||
ca: options.ca,
|
|
||||||
cert: options.cert,
|
|
||||||
ciphers: options.ciphers,
|
|
||||||
host: parsedUrl.host,
|
|
||||||
hostname: parsedUrl.hostname,
|
|
||||||
key: options.key,
|
|
||||||
localAddress: options.localAddress,
|
|
||||||
method: options.method ? options.method.toUpperCase() : 'GET',
|
|
||||||
passphrase: options.passphrase,
|
|
||||||
path: parsedUrl.path,
|
|
||||||
pfx: options.pfx,
|
|
||||||
port: Number(parsedUrl.port),
|
|
||||||
rejectUnauthorized: options.rejectUnauthorized,
|
|
||||||
secureProtocol: options.secureProtocol,
|
|
||||||
socketPath: options.socketPath
|
|
||||||
};
|
|
||||||
|
|
||||||
requestOptions.headers = <{ [key: string]: string }>options.headers || {};
|
|
||||||
|
|
||||||
if (
|
|
||||||
!Object.keys(requestOptions.headers)
|
|
||||||
.map((headerName) => headerName.toLowerCase())
|
|
||||||
.some((headerName) => headerName === 'user-agent')
|
|
||||||
) {
|
|
||||||
requestOptions.headers['user-agent'] = 'dojo/' + version + ' Node.js/' + process.version.replace(/^v/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.proxy) {
|
|
||||||
requestOptions.path = url;
|
|
||||||
if (parsedUrl.auth) {
|
|
||||||
requestOptions.headers['proxy-authorization'] = 'Basic ' + new Buffer(parsedUrl.auth).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedProxyUrl = urlUtil.parse(url);
|
|
||||||
if (parsedProxyUrl.host) {
|
|
||||||
requestOptions.headers['host'] = parsedProxyUrl.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedProxyUrl.auth) {
|
|
||||||
requestOptions.auth = parsedProxyUrl.auth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { acceptCompression = true } = options;
|
|
||||||
if (acceptCompression) {
|
|
||||||
requestOptions.headers['Accept-Encoding'] = 'gzip, deflate';
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = parsedUrl.protocol === 'https:' ? https.request(requestOptions) : http.request(requestOptions);
|
|
||||||
|
|
||||||
const uploadObserverPool = new SubscriptionPool<number>();
|
|
||||||
|
|
||||||
const requestTask = <UploadObservableTask<NodeResponse>>new Task<NodeResponse>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
let timeoutHandle: Handle;
|
|
||||||
let timeoutReject: Function = reject;
|
|
||||||
|
|
||||||
if (options.socketOptions) {
|
|
||||||
if (options.socketOptions.timeout) {
|
|
||||||
request.setTimeout(options.socketOptions.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('noDelay' in options.socketOptions) {
|
|
||||||
request.setNoDelay(options.socketOptions.noDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('keepAlive' in options.socketOptions) {
|
|
||||||
const initialDelay: number | undefined = options.socketOptions.keepAlive;
|
|
||||||
if (initialDelay !== undefined) {
|
|
||||||
request.setSocketKeepAlive(initialDelay >= 0, initialDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.once('response', (message: http.IncomingMessage) => {
|
|
||||||
const response = new NodeResponse(message);
|
|
||||||
|
|
||||||
// Redirection handling defaults to true in order to harmonise with the XHR provider, which will always
|
|
||||||
// follow redirects
|
|
||||||
if (response.status >= 300 && response.status < 400) {
|
|
||||||
const redirectOptions = options.redirectOptions || {};
|
|
||||||
const newOptions = deepAssign({}, options);
|
|
||||||
|
|
||||||
switch (response.status) {
|
|
||||||
case 300:
|
|
||||||
/**
|
|
||||||
* Note about 300 redirects. RFC 2616 doesn't specify what to do with them, it is up to the client to "pick
|
|
||||||
* the right one". We're picking like Chrome does, just don't pick any.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 301:
|
|
||||||
case 302:
|
|
||||||
/**
|
|
||||||
* RFC 2616 says,
|
|
||||||
*
|
|
||||||
* If the 301 status code is received in response to a request other
|
|
||||||
* than GET or HEAD, the user agent MUST NOT automatically redirect the
|
|
||||||
* request unless it can be confirmed by the user, since this might
|
|
||||||
* change the conditions under which the request was issued.
|
|
||||||
*
|
|
||||||
* Note: When automatically redirecting a POST request after
|
|
||||||
* receiving a 301 status code, some existing HTTP/1.0 user agents
|
|
||||||
* will erroneously change it into a GET request.
|
|
||||||
*
|
|
||||||
* We're going to be one of those erroneous agents, to prevent the request from failing..
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
requestOptions.method !== 'GET' &&
|
|
||||||
requestOptions.method !== 'HEAD' &&
|
|
||||||
!redirectOptions.keepOriginalMethod
|
|
||||||
) {
|
|
||||||
newOptions.method = 'GET';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirect(resolve, reject, url, response.headers.get('location'), newOptions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 303:
|
|
||||||
/**
|
|
||||||
* The response to the request can be found under a different URI and
|
|
||||||
* SHOULD be retrieved using a GET method on that resource.
|
|
||||||
*/
|
|
||||||
if (requestOptions.method !== 'GET') {
|
|
||||||
newOptions.method = 'GET';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirect(resolve, reject, url, response.headers.get('location'), newOptions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 304:
|
|
||||||
// do nothing so this can fall through and return the response as normal. Nothing more can
|
|
||||||
// be done for 304
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 305:
|
|
||||||
if (!response.headers.get('location')) {
|
|
||||||
reject(new Error('expected Location header to contain a proxy url'));
|
|
||||||
} else {
|
|
||||||
newOptions.proxy = response.headers.get('location') || '';
|
|
||||||
if (redirect(resolve, reject, url, '', newOptions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 307:
|
|
||||||
/**
|
|
||||||
* If the 307 status code is received in response to a request other
|
|
||||||
* than GET or HEAD, the user agent MUST NOT automatically redirect the
|
|
||||||
* request unless it can be confirmed by the user, since this might
|
|
||||||
* change the conditions under which the request was issued.
|
|
||||||
*/
|
|
||||||
if (redirect(resolve, reject, url, response.headers.get('location'), newOptions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
reject(new Error('unhandled redirect status ' + response.status));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.streamEncoding && message.setEncoding(options.streamEncoding);
|
|
||||||
|
|
||||||
const downloadSubscriptionPool = new SubscriptionPool<number>();
|
|
||||||
const dataSubscriptionPool = new SubscriptionPool<any>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
[RFC 2616](https://tools.ietf.org/html/rfc2616#page-118) says that content-encoding can have multiple
|
|
||||||
values, so we split them here and put them in a list to process later.
|
|
||||||
*/
|
|
||||||
const contentEncodings = response.headers.getAll('content-encoding');
|
|
||||||
|
|
||||||
const task = new Task<http.IncomingMessage>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
timeoutReject = reject;
|
|
||||||
|
|
||||||
// we queue this up for later to allow listeners to register themselves before we start receiving data
|
|
||||||
queueTask(() => {
|
|
||||||
/*
|
|
||||||
* Note that this is the raw data, if your input stream is zipped, and you are piecing
|
|
||||||
* together the downloaded data, you'll have to decompress it yourself
|
|
||||||
*/
|
|
||||||
message.on('data', (chunk: any) => {
|
|
||||||
dataSubscriptionPool.next(chunk);
|
|
||||||
|
|
||||||
if (response.downloadBody) {
|
|
||||||
data.buffer.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.size +=
|
|
||||||
typeof chunk === 'string'
|
|
||||||
? Buffer.byteLength(chunk, options.streamEncoding)
|
|
||||||
: chunk.length;
|
|
||||||
|
|
||||||
downloadSubscriptionPool.next(data.size);
|
|
||||||
});
|
|
||||||
|
|
||||||
message.once('end', () => {
|
|
||||||
timeoutHandle && timeoutHandle.destroy();
|
|
||||||
|
|
||||||
let dataAsBuffer = options.streamEncoding
|
|
||||||
? new Buffer(data.buffer.join(''), 'utf8')
|
|
||||||
: Buffer.concat(data.buffer, data.size);
|
|
||||||
|
|
||||||
const handleEncoding = function() {
|
|
||||||
/*
|
|
||||||
Content encoding is ordered by the order in which they were applied to the
|
|
||||||
content, so do undo the encoding we have to start at the end and work backwards.
|
|
||||||
*/
|
|
||||||
if (contentEncodings.length) {
|
|
||||||
const encoding = contentEncodings.pop()!.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (encoding === '' || encoding === 'none' || encoding === 'identity') {
|
|
||||||
// do nothing, response stream is as-is
|
|
||||||
handleEncoding();
|
|
||||||
} else if (encoding === 'gzip') {
|
|
||||||
zlib.gunzip(dataAsBuffer, function(err: Error | null, result: Buffer) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataAsBuffer = result;
|
|
||||||
handleEncoding();
|
|
||||||
});
|
|
||||||
} else if (encoding === 'deflate') {
|
|
||||||
zlib.inflate(dataAsBuffer, function(err: Error | null, result: Buffer) {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataAsBuffer = result;
|
|
||||||
handleEncoding();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(new Error('Unsupported content encoding, ' + encoding));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.data = dataAsBuffer;
|
|
||||||
|
|
||||||
resolve(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEncoding();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
request.abort();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data: RequestData = {
|
|
||||||
task,
|
|
||||||
buffer: [],
|
|
||||||
data: Buffer.alloc(0),
|
|
||||||
size: 0,
|
|
||||||
used: false,
|
|
||||||
url: url,
|
|
||||||
requestOptions: options,
|
|
||||||
nativeResponse: message,
|
|
||||||
downloadObservable: new Observable<number>((observer) => downloadSubscriptionPool.add(observer)),
|
|
||||||
dataObservable: new Observable<any>((observer) => dataSubscriptionPool.add(observer))
|
|
||||||
};
|
|
||||||
|
|
||||||
dataMap.set(response, data);
|
|
||||||
|
|
||||||
resolve(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.once('error', reject);
|
|
||||||
|
|
||||||
if (options.bodyStream) {
|
|
||||||
options.bodyStream.pipe(request);
|
|
||||||
let uploadedSize = 0;
|
|
||||||
|
|
||||||
options.bodyStream.on('data', (chunk: any) => {
|
|
||||||
uploadedSize += chunk.length;
|
|
||||||
uploadObserverPool.next(uploadedSize);
|
|
||||||
});
|
|
||||||
|
|
||||||
options.bodyStream.on('end', () => {
|
|
||||||
uploadObserverPool.complete();
|
|
||||||
request.end();
|
|
||||||
});
|
|
||||||
} else if (options.body) {
|
|
||||||
const body = options.body.toString();
|
|
||||||
|
|
||||||
request.on('response', () => {
|
|
||||||
uploadObserverPool.next(body.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.end(body);
|
|
||||||
} else {
|
|
||||||
request.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.timeout && options.timeout > 0 && options.timeout !== Infinity) {
|
|
||||||
timeoutHandle = createTimer(() => {
|
|
||||||
timeoutReject && timeoutReject(new TimeoutError('The request timed out'));
|
|
||||||
}, options.timeout);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
request.abort();
|
|
||||||
}
|
|
||||||
).catch(function(error: Error): any {
|
|
||||||
const parsedUrl = urlUtil.parse(url);
|
|
||||||
|
|
||||||
if (parsedUrl.auth) {
|
|
||||||
parsedUrl.auth = '(redacted)';
|
|
||||||
}
|
|
||||||
|
|
||||||
const sanitizedUrl = urlUtil.format(parsedUrl);
|
|
||||||
|
|
||||||
error.message = '[' + requestOptions.method + ' ' + sanitizedUrl + '] ' + error.message;
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
requestTask.upload = new Observable<number>((observer) => uploadObserverPool.add(observer));
|
|
||||||
|
|
||||||
return requestTask;
|
|
||||||
}
|
|
||||||
@ -1,333 +0,0 @@
|
|||||||
import { Handle } from '../../interfaces';
|
|
||||||
import global from '@dojo/shim/global';
|
|
||||||
import WeakMap from '@dojo/shim/WeakMap';
|
|
||||||
import Task, { State } from '../../async/Task';
|
|
||||||
import has from '../../has';
|
|
||||||
import Observable from '../../Observable';
|
|
||||||
import { createTimer } from '../../util';
|
|
||||||
import Headers from '../Headers';
|
|
||||||
import { RequestOptions, UploadObservableTask } from '../interfaces';
|
|
||||||
import Response, { getArrayBufferFromBlob, getTextFromBlob } from '../Response';
|
|
||||||
import SubscriptionPool from '../SubscriptionPool';
|
|
||||||
import TimeoutError from '../TimeoutError';
|
|
||||||
import { generateRequestUrl } from '../util';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request options specific to an XHR request
|
|
||||||
*/
|
|
||||||
export interface XhrRequestOptions extends RequestOptions {
|
|
||||||
/**
|
|
||||||
* Controls whether or not the request is synchronous (blocks the main thread) or asynchronous (default).
|
|
||||||
*/
|
|
||||||
blockMainThread?: boolean;
|
|
||||||
/**
|
|
||||||
* Controls whether or not the X-Requested-With header is added to the request (default true). Set to false to not
|
|
||||||
* include the header.
|
|
||||||
*/
|
|
||||||
includeRequestedWithHeader?: boolean;
|
|
||||||
/**
|
|
||||||
* Controls whether or not to subscribe to events on `XMLHttpRequest.upload`, if available. This causes all requests
|
|
||||||
* to be preflighted (https://xhr.spec.whatwg.org/#request)
|
|
||||||
*/
|
|
||||||
includeUploadProgress?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RequestData {
|
|
||||||
task: Task<XMLHttpRequest>;
|
|
||||||
used: boolean;
|
|
||||||
requestOptions: XhrRequestOptions;
|
|
||||||
nativeResponse: XMLHttpRequest;
|
|
||||||
url: string;
|
|
||||||
downloadObservable: Observable<number>;
|
|
||||||
dataObservable: Observable<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataMap = new WeakMap<XhrResponse, RequestData>();
|
|
||||||
|
|
||||||
function getDataTask(response: XhrResponse): Task<XMLHttpRequest> {
|
|
||||||
const data = dataMap.get(response)!;
|
|
||||||
|
|
||||||
if (data.used) {
|
|
||||||
return Task.reject<any>(new TypeError('Body already read'));
|
|
||||||
}
|
|
||||||
|
|
||||||
data.used = true;
|
|
||||||
|
|
||||||
return data.task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps an XHR request in a response that mimics the fetch API
|
|
||||||
*/
|
|
||||||
export class XhrResponse extends Response {
|
|
||||||
readonly headers: Headers;
|
|
||||||
readonly ok: boolean;
|
|
||||||
readonly status: number;
|
|
||||||
readonly statusText: string;
|
|
||||||
|
|
||||||
get bodyUsed(): boolean {
|
|
||||||
return dataMap.get(this)!.used;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nativeResponse(): XMLHttpRequest {
|
|
||||||
return dataMap.get(this)!.nativeResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
get requestOptions(): XhrRequestOptions {
|
|
||||||
return dataMap.get(this)!.requestOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
get url(): string {
|
|
||||||
return dataMap.get(this)!.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
get download(): Observable<number> {
|
|
||||||
return dataMap.get(this)!.downloadObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
get data(): Observable<any> {
|
|
||||||
return dataMap.get(this)!.dataObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(request: XMLHttpRequest) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
const headers = (this.headers = new Headers());
|
|
||||||
|
|
||||||
const responseHeaders = request.getAllResponseHeaders();
|
|
||||||
if (responseHeaders) {
|
|
||||||
const lines = responseHeaders.split(/\r\n/g);
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const match = lines[i].match(/^(.*?): (.*)$/);
|
|
||||||
if (match) {
|
|
||||||
headers.append(match[1], match[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.status = request.status;
|
|
||||||
this.ok = this.status >= 200 && this.status < 300;
|
|
||||||
this.statusText = request.statusText || 'OK';
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayBuffer(): Task<ArrayBuffer> {
|
|
||||||
return Task.reject<ArrayBuffer>(new Error('ArrayBuffer not supported'));
|
|
||||||
}
|
|
||||||
|
|
||||||
blob(): Task<Blob> {
|
|
||||||
return Task.reject<Blob>(new Error('Blob not supported'));
|
|
||||||
}
|
|
||||||
|
|
||||||
formData(): Task<FormData> {
|
|
||||||
return Task.reject<FormData>(new Error('FormData not supported'));
|
|
||||||
}
|
|
||||||
|
|
||||||
text(): Task<string> {
|
|
||||||
return <any>getDataTask(this).then((request: XMLHttpRequest) => {
|
|
||||||
return String(request.responseText);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
xml(): Task<Document> {
|
|
||||||
return <any>this.text().then((text: string) => {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
return parser.parseFromString(text, this.headers.get('content-type') || 'text/html');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has('blob')) {
|
|
||||||
XhrResponse.prototype.blob = function(this: XhrResponse): Task<Blob> {
|
|
||||||
return <any>getDataTask(this).then((request: XMLHttpRequest) => request.response);
|
|
||||||
};
|
|
||||||
|
|
||||||
XhrResponse.prototype.text = function(this: XhrResponse): Task<string> {
|
|
||||||
return <any>this.blob().then(getTextFromBlob);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (has('arraybuffer')) {
|
|
||||||
XhrResponse.prototype.arrayBuffer = function(this: XhrResponse): Task<ArrayBuffer> {
|
|
||||||
return <any>this.blob().then(getArrayBufferFromBlob);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has('formdata')) {
|
|
||||||
XhrResponse.prototype.formData = function(this: XhrResponse): Task<FormData> {
|
|
||||||
return <any>this.text().then((text: string) => {
|
|
||||||
const data = new FormData();
|
|
||||||
|
|
||||||
text
|
|
||||||
.trim()
|
|
||||||
.split('&')
|
|
||||||
.forEach((keyValues) => {
|
|
||||||
if (keyValues) {
|
|
||||||
const pairs = keyValues.split('=');
|
|
||||||
const name = (pairs.shift() || '').replace(/\+/, ' ');
|
|
||||||
const value = pairs.join('=').replace(/\+/, ' ');
|
|
||||||
|
|
||||||
data.append(decodeURIComponent(name), decodeURIComponent(value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function noop() {}
|
|
||||||
|
|
||||||
function setOnError(request: XMLHttpRequest, reject: Function) {
|
|
||||||
request.addEventListener('error', function(event) {
|
|
||||||
reject(new TypeError(event.error || 'Network request failed'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function xhr(url: string, options: XhrRequestOptions = {}): UploadObservableTask<XhrResponse> {
|
|
||||||
const request = new XMLHttpRequest();
|
|
||||||
const requestUrl = generateRequestUrl(url, options);
|
|
||||||
|
|
||||||
options = Object.create(options);
|
|
||||||
|
|
||||||
if (!options.method) {
|
|
||||||
options.method = 'GET';
|
|
||||||
}
|
|
||||||
|
|
||||||
let isAborted = false;
|
|
||||||
|
|
||||||
function abort() {
|
|
||||||
isAborted = true;
|
|
||||||
if (request) {
|
|
||||||
request.abort();
|
|
||||||
request.onreadystatechange = noop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeoutHandle: Handle;
|
|
||||||
let timeoutReject: Function;
|
|
||||||
|
|
||||||
const task = <UploadObservableTask<XhrResponse>>new Task<XhrResponse>((resolve, reject) => {
|
|
||||||
timeoutReject = reject;
|
|
||||||
|
|
||||||
request.onreadystatechange = function() {
|
|
||||||
if (isAborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.readyState === 2) {
|
|
||||||
const response = new XhrResponse(request);
|
|
||||||
|
|
||||||
const downloadSubscriptionPool = new SubscriptionPool<number>();
|
|
||||||
const dataSubscriptionPool = new SubscriptionPool<any>();
|
|
||||||
|
|
||||||
const task = new Task<XMLHttpRequest>((resolve, reject) => {
|
|
||||||
timeoutReject = reject;
|
|
||||||
|
|
||||||
request.onprogress = function(event: any) {
|
|
||||||
if (isAborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadSubscriptionPool.next(event.loaded);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onreadystatechange = function() {
|
|
||||||
if (isAborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.readyState === 4) {
|
|
||||||
request.onreadystatechange = noop;
|
|
||||||
timeoutHandle && timeoutHandle.destroy();
|
|
||||||
|
|
||||||
dataSubscriptionPool.next(request.response);
|
|
||||||
dataSubscriptionPool.complete();
|
|
||||||
|
|
||||||
resolve(request);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setOnError(request, reject);
|
|
||||||
}, abort);
|
|
||||||
|
|
||||||
dataMap.set(response, {
|
|
||||||
task,
|
|
||||||
used: false,
|
|
||||||
nativeResponse: request,
|
|
||||||
requestOptions: options,
|
|
||||||
url: requestUrl,
|
|
||||||
downloadObservable: new Observable<number>((observer) => downloadSubscriptionPool.add(observer)),
|
|
||||||
dataObservable: new Observable<any>((observer) => dataSubscriptionPool.add(observer))
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setOnError(request, reject);
|
|
||||||
}, abort);
|
|
||||||
|
|
||||||
request.open(options.method, requestUrl, !options.blockMainThread, options.user, options.password);
|
|
||||||
|
|
||||||
if (has('filereader') && has('blob')) {
|
|
||||||
request.responseType = 'blob';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.timeout && options.timeout > 0 && options.timeout !== Infinity) {
|
|
||||||
timeoutHandle = createTimer(() => {
|
|
||||||
// Reject first, since aborting will also fire onreadystatechange which would reject with a
|
|
||||||
// less specific error. (This is also why we set up our own timeout rather than using
|
|
||||||
// native timeout and ontimeout, because that aborts and fires onreadystatechange before ontimeout.)
|
|
||||||
timeoutReject && timeoutReject(new TimeoutError('The XMLHttpRequest request timed out'));
|
|
||||||
abort();
|
|
||||||
}, options.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasContentTypeHeader = false;
|
|
||||||
let hasRequestedWithHeader = false;
|
|
||||||
const { includeRequestedWithHeader = true, includeUploadProgress = true } = options;
|
|
||||||
|
|
||||||
if (options.headers) {
|
|
||||||
const requestHeaders = new Headers(options.headers);
|
|
||||||
|
|
||||||
hasRequestedWithHeader = requestHeaders.has('x-requested-with');
|
|
||||||
hasContentTypeHeader = requestHeaders.has('content-type');
|
|
||||||
|
|
||||||
for (const [key, value] of requestHeaders) {
|
|
||||||
request.setRequestHeader(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasRequestedWithHeader && includeRequestedWithHeader) {
|
|
||||||
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasContentTypeHeader && has('formdata') && options.body instanceof global.FormData) {
|
|
||||||
// Assume that most forms do not contain large binary files. If that is not the case,
|
|
||||||
// then "multipart/form-data" should be manually specified as the "Content-Type" header.
|
|
||||||
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
||||||
}
|
|
||||||
|
|
||||||
task.finally(() => {
|
|
||||||
if (task.state !== State.Fulfilled) {
|
|
||||||
request.onreadystatechange = noop;
|
|
||||||
timeoutHandle && timeoutHandle.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (includeUploadProgress) {
|
|
||||||
const uploadObserverPool = new SubscriptionPool<number>();
|
|
||||||
task.upload = new Observable<number>((observer) => uploadObserverPool.add(observer));
|
|
||||||
|
|
||||||
if (has('host-browser') || has('web-worker-xhr-upload')) {
|
|
||||||
request.upload.addEventListener('progress', (event) => {
|
|
||||||
uploadObserverPool.next(event.loaded);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.send(options.body || null);
|
|
||||||
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import { RequestOptions } from './interfaces';
|
|
||||||
import UrlSearchParams from '../UrlSearchParams';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a URL formatted with optional query string and cache-busting segments.
|
|
||||||
*
|
|
||||||
* @param url The base URL.
|
|
||||||
* @param options The RequestOptions used to generate the query string or cacheBust.
|
|
||||||
*/
|
|
||||||
export function generateRequestUrl(url: string, options: RequestOptions = {}): string {
|
|
||||||
let query = new UrlSearchParams(options.query).toString();
|
|
||||||
if (options.cacheBust) {
|
|
||||||
const bustString = String(Date.now());
|
|
||||||
query += query ? `&${bustString}` : bustString;
|
|
||||||
}
|
|
||||||
const separator = url.indexOf('?') > -1 ? '&' : '?';
|
|
||||||
return query ? `${url}${separator}${query}` : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStringFromFormData(formData: any): string {
|
|
||||||
const fields: string[] = [];
|
|
||||||
|
|
||||||
for (const key of formData.keys()) {
|
|
||||||
fields.push(encodeURIComponent(key) + '=' + encodeURIComponent(formData.get(key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields.join('&');
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import { Hash } from './interfaces';
|
|
||||||
|
|
||||||
const escapeRegExpPattern = /[[\]{}()|\/\\^$.*+?]/g;
|
|
||||||
const escapeXmlPattern = /[&<]/g;
|
|
||||||
const escapeXmlForPattern = /[&<>'"]/g;
|
|
||||||
const escapeXmlMap: Hash<string> = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": '''
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes a string so that it can safely be passed to the RegExp constructor.
|
|
||||||
* @param text The string to be escaped
|
|
||||||
* @return The escaped string
|
|
||||||
*/
|
|
||||||
export function escapeRegExp(text: string): string {
|
|
||||||
return !text ? text : text.replace(escapeRegExpPattern, '\\$&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes a string to protect against tag injection.
|
|
||||||
* @param xml The string to be escaped
|
|
||||||
* @param forAttribute Whether to also escape ', ", and > in addition to < and &
|
|
||||||
* @return The escaped string
|
|
||||||
*/
|
|
||||||
export function escapeXml(xml: string, forAttribute: boolean = true): string {
|
|
||||||
if (!xml) {
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pattern = forAttribute ? escapeXmlForPattern : escapeXmlPattern;
|
|
||||||
|
|
||||||
return xml.replace(pattern, function(character: string): string {
|
|
||||||
return escapeXmlMap[character];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
import Promise from '@dojo/shim/Promise';
|
|
||||||
import has from './has';
|
|
||||||
import request from './request';
|
|
||||||
import { NodeRequire, AmdRequire, AmdConfig } from './interfaces';
|
|
||||||
import { Require, isAmdRequire } from './load';
|
|
||||||
|
|
||||||
declare const require: Require;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Strips <?xml ...?> declarations so that external SVG and XML
|
|
||||||
* documents can be added to a document without worry. Also, if the string
|
|
||||||
* is an HTML document, only the part inside the body tag is returned.
|
|
||||||
*/
|
|
||||||
function strip(text: string): string {
|
|
||||||
if (!text) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.replace(/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, '');
|
|
||||||
let matches = text.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
|
|
||||||
text = matches ? matches[1] : text;
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Host-specific method to retrieve text
|
|
||||||
*/
|
|
||||||
let getText: (url: string, callback: (value: string | null) => void) => void;
|
|
||||||
|
|
||||||
if (has('host-browser')) {
|
|
||||||
getText = function(url: string, callback: (value: string | null) => void): void {
|
|
||||||
request(url).then((response) => {
|
|
||||||
response.text().then((data) => {
|
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else if (has('host-node')) {
|
|
||||||
let fs = isAmdRequire(require) && require.nodeRequire ? require.nodeRequire('fs') : (<NodeRequire>require)('fs');
|
|
||||||
getText = function(url: string, callback: (value: string) => void): void {
|
|
||||||
fs.readFile(url, { encoding: 'utf8' }, function(error: Error, data: string): void {
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
getText = function(): void {
|
|
||||||
throw new Error('dojo/text not supported on this platform');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Cache of previously-loaded text resources
|
|
||||||
*/
|
|
||||||
let textCache: { [key: string]: any } = {};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Cache of pending text resources
|
|
||||||
*/
|
|
||||||
let pending: { [key: string]: any } = {};
|
|
||||||
|
|
||||||
export function get(url: string): Promise<string | null> {
|
|
||||||
let promise = new Promise<string | null>(function(resolve, reject) {
|
|
||||||
getText(url, function(text) {
|
|
||||||
resolve(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalize(id: string, toAbsMid: (moduleId: string) => string): string {
|
|
||||||
let parts = id.split('!');
|
|
||||||
let url = parts[0];
|
|
||||||
|
|
||||||
return (/^\./.test(url) ? toAbsMid(url) : url) + (parts[1] ? '!' + parts[1] : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function load(id: string, require: AmdRequire, load: (value?: any) => void, config?: AmdConfig): void {
|
|
||||||
let parts = id.split('!');
|
|
||||||
let stripFlag = parts.length > 1;
|
|
||||||
let mid = parts[0];
|
|
||||||
let url = require.toUrl(mid);
|
|
||||||
let text: string | undefined;
|
|
||||||
|
|
||||||
function finish(text: string): void {
|
|
||||||
load(stripFlag ? strip(text) : text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mid in textCache) {
|
|
||||||
text = textCache[mid];
|
|
||||||
} else if (url in textCache) {
|
|
||||||
text = textCache[url];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
if (pending[url]) {
|
|
||||||
pending[url].push(finish);
|
|
||||||
} else {
|
|
||||||
let pendingList = (pending[url] = [finish]);
|
|
||||||
getText(url, function(value) {
|
|
||||||
textCache[mid] = textCache[url] = value;
|
|
||||||
for (let i = 0; i < pendingList.length; ) {
|
|
||||||
pendingList[i++](value || '');
|
|
||||||
}
|
|
||||||
delete pending[url];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
finish(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
import { Handle } from './interfaces';
|
|
||||||
import { createHandle } from './lang';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a setTimeout call in a handle, allowing the timeout to be cleared by calling destroy.
|
|
||||||
*
|
|
||||||
* @param callback Callback to be called when the timeout elapses
|
|
||||||
* @param delay Number of milliseconds to wait before calling the callback
|
|
||||||
* @return Handle which can be destroyed to clear the timeout
|
|
||||||
*/
|
|
||||||
export function createTimer(callback: (...args: any[]) => void, delay?: number): Handle {
|
|
||||||
let timerId: number | null = setTimeout(callback, delay);
|
|
||||||
|
|
||||||
return createHandle(function() {
|
|
||||||
if (timerId) {
|
|
||||||
clearTimeout(timerId);
|
|
||||||
timerId = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a callback, returning a function which fires after no further calls are received over a set interval.
|
|
||||||
*
|
|
||||||
* @param callback Callback to wrap
|
|
||||||
* @param delay Number of milliseconds to wait after any invocations before calling the original callback
|
|
||||||
* @return Debounced function
|
|
||||||
*/
|
|
||||||
export function debounce<T extends (this: any, ...args: any[]) => void>(callback: T, delay: number): T {
|
|
||||||
// node.d.ts clobbers setTimeout/clearTimeout with versions that return/receive NodeJS.Timer,
|
|
||||||
// but browsers return/receive a number
|
|
||||||
let timer: Handle | null;
|
|
||||||
|
|
||||||
return <T>function() {
|
|
||||||
timer && timer.destroy();
|
|
||||||
|
|
||||||
let context = this;
|
|
||||||
let args: IArguments | null = arguments;
|
|
||||||
|
|
||||||
timer = guaranteeMinimumTimeout(function() {
|
|
||||||
callback.apply(context, args);
|
|
||||||
args = context = timer = null;
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a callback, returning a function which fires at most once per set interval.
|
|
||||||
*
|
|
||||||
* @param callback Callback to wrap
|
|
||||||
* @param delay Number of milliseconds to wait before allowing the original callback to be called again
|
|
||||||
* @return Throttled function
|
|
||||||
*/
|
|
||||||
export function throttle<T extends (this: any, ...args: any[]) => void>(callback: T, delay: number): T {
|
|
||||||
let ran: boolean | null;
|
|
||||||
|
|
||||||
return <T>function() {
|
|
||||||
if (ran) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ran = true;
|
|
||||||
|
|
||||||
callback.apply(this, arguments);
|
|
||||||
guaranteeMinimumTimeout(function() {
|
|
||||||
ran = null;
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like throttle, but calls the callback at the end of each interval rather than the beginning.
|
|
||||||
* Useful for e.g. resize or scroll events, when debounce would appear unresponsive.
|
|
||||||
*
|
|
||||||
* @param callback Callback to wrap
|
|
||||||
* @param delay Number of milliseconds to wait before calling the original callback and allowing it to be called again
|
|
||||||
* @return Throttled function
|
|
||||||
*/
|
|
||||||
export function throttleAfter<T extends (this: any, ...args: any[]) => void>(callback: T, delay: number): T {
|
|
||||||
let ran: boolean | null;
|
|
||||||
|
|
||||||
return <T>function() {
|
|
||||||
if (ran) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ran = true;
|
|
||||||
|
|
||||||
let context = this;
|
|
||||||
let args: IArguments | null = arguments;
|
|
||||||
|
|
||||||
guaranteeMinimumTimeout(function() {
|
|
||||||
callback.apply(context, args);
|
|
||||||
args = context = ran = null;
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function guaranteeMinimumTimeout(callback: (...args: any[]) => void, delay?: number): Handle {
|
|
||||||
const startTime = Date.now();
|
|
||||||
let timerId: number | null;
|
|
||||||
|
|
||||||
function timeoutHandler() {
|
|
||||||
const delta = Date.now() - startTime;
|
|
||||||
if (delay == null || delta >= delay) {
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
// Cast setTimeout return value to fix TypeScript parsing bug. Without it,
|
|
||||||
// it thinks we are using the Node version of setTimeout.
|
|
||||||
// Revisit this with the next TypeScript update.
|
|
||||||
// Set another timer for the mount of time that we came up short.
|
|
||||||
timerId = <any>setTimeout(timeoutHandler, delay - delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timerId = setTimeout(timeoutHandler, delay);
|
|
||||||
return createHandle(() => {
|
|
||||||
if (timerId != null) {
|
|
||||||
clearTimeout(timerId);
|
|
||||||
timerId = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Returns a v4 compliant UUID.
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export default function uuid(): string {
|
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
||||||
const r = (Math.random() * 16) | 0,
|
|
||||||
v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
||||||
return v.toString(16);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import './lang';
|
|
||||||
import './stringExtras';
|
|
||||||
@ -1,269 +0,0 @@
|
|||||||
import Benchmark = require('benchmark');
|
|
||||||
import lang = require('../../src/lang');
|
|
||||||
|
|
||||||
function onComplete(this: any) {
|
|
||||||
console.log(this.name + ': ' + this.hz + ' with a margin of error of ' + this.stats.moe);
|
|
||||||
}
|
|
||||||
|
|
||||||
const simpleSource = { a: 1, b: 'Lorem ipsum', c: 4 };
|
|
||||||
const simpleSourceWithArray = {
|
|
||||||
a: 1,
|
|
||||||
b: 'Dolor sit amet.',
|
|
||||||
c: [1, 2, 3],
|
|
||||||
d: 5
|
|
||||||
};
|
|
||||||
const sourceFromConstructor = (function() {
|
|
||||||
function Answers(this: any, kwArgs: { [key: string]: any }) {
|
|
||||||
Object.keys(kwArgs).forEach(function(this: any, key: string): void {
|
|
||||||
(<any>this)[key] = kwArgs[key];
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new (<any>Answers)({
|
|
||||||
universe: 42
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
const sourceWithInherited = Object.create(
|
|
||||||
Object.create(null, {
|
|
||||||
x: {
|
|
||||||
value: '1234567890'.split('')
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
enumerable: true,
|
|
||||||
value: /\s/
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
a: {
|
|
||||||
value: 1,
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
b: {
|
|
||||||
value: 'Lorem ipsum',
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
value: [],
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
d: {
|
|
||||||
value: 4,
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let benchmarks: Benchmark[] = [];
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.assign (single source)',
|
|
||||||
function() {
|
|
||||||
lang.assign(Object.create(null), simpleSource);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.assign (multiple sources)',
|
|
||||||
function() {
|
|
||||||
lang.assign(
|
|
||||||
Object.create(null),
|
|
||||||
simpleSource,
|
|
||||||
simpleSourceWithArray,
|
|
||||||
sourceWithInherited,
|
|
||||||
sourceFromConstructor
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.deepAssign (single source)',
|
|
||||||
function() {
|
|
||||||
lang.deepAssign(Object.create(null), simpleSource);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.deepAssign (multiple sources)',
|
|
||||||
function() {
|
|
||||||
lang.deepAssign(
|
|
||||||
Object.create(null),
|
|
||||||
simpleSource,
|
|
||||||
simpleSourceWithArray,
|
|
||||||
sourceWithInherited,
|
|
||||||
sourceFromConstructor
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.mixin (single source)',
|
|
||||||
function() {
|
|
||||||
lang.mixin(Object.create(null), simpleSource);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.mixin (multiple sources)',
|
|
||||||
function() {
|
|
||||||
lang.mixin(
|
|
||||||
Object.create(null),
|
|
||||||
simpleSource,
|
|
||||||
simpleSourceWithArray,
|
|
||||||
sourceWithInherited,
|
|
||||||
sourceFromConstructor
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.deepMixin (single source)',
|
|
||||||
function() {
|
|
||||||
lang.deepMixin(Object.create(null), simpleSource);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.deepMixin (multiple sources)',
|
|
||||||
function() {
|
|
||||||
lang.deepMixin(
|
|
||||||
Object.create(null),
|
|
||||||
simpleSource,
|
|
||||||
simpleSourceWithArray,
|
|
||||||
sourceWithInherited,
|
|
||||||
sourceFromConstructor
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.create',
|
|
||||||
function() {
|
|
||||||
lang.create(simpleSource, simpleSource, simpleSource, simpleSource);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.isIdentical',
|
|
||||||
(function() {
|
|
||||||
let a = Number('asdfx{}');
|
|
||||||
let b = Number('xkcd');
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
lang.isIdentical(a, b);
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.lateBind',
|
|
||||||
(function() {
|
|
||||||
let object: any = {
|
|
||||||
method: function() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
lang.lateBind(object, 'method');
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.lateBind (partial application)',
|
|
||||||
(function() {
|
|
||||||
let object: any = {
|
|
||||||
method: function() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
lang.lateBind(object, 'method', 1, 2, 3);
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.push(
|
|
||||||
new Benchmark(
|
|
||||||
'lang.partial',
|
|
||||||
(function() {
|
|
||||||
function f() {}
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
lang.partial(f, 1, 2, 3);
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
onComplete: onComplete
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
benchmarks.forEach(function(benchmark: Benchmark): void {
|
|
||||||
benchmark.run();
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import Benchmark = require('benchmark');
|
|
||||||
|
|
||||||
let benchmarks: any[] = [];
|
|
||||||
|
|
||||||
benchmarks.forEach(function(benchmark: any): void {
|
|
||||||
new Benchmark(benchmark.name, benchmark.fn, {
|
|
||||||
onComplete: function(this: any) {
|
|
||||||
console.log(this.name + ': ' + this.hz + ' with a margin of error of ' + this.stats.moe);
|
|
||||||
}
|
|
||||||
}).run();
|
|
||||||
});
|
|
||||||
@ -1 +0,0 @@
|
|||||||
import './text/textPlugin';
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Text Plugin Test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="../../../../node_modules/@dojo/shim/util/amd.js"></script>
|
|
||||||
<script src="../../../../node_modules/@dojo/loader/loader.js"></script>
|
|
||||||
<script>
|
|
||||||
require.config(shimAmdDependencies({
|
|
||||||
baseUrl: '../../../../',
|
|
||||||
packages: [
|
|
||||||
{ name: 'src', location: '_build/src' },
|
|
||||||
{ name: 'tests', location: '_build/tests' }
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
require(['@dojo/shim/main'], function () {
|
|
||||||
require([
|
|
||||||
'src/text!./_build/tests/support/data/correctText.txt'
|
|
||||||
], function (result) {
|
|
||||||
window.loaderTestResults = {
|
|
||||||
text: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
const { registerSuite } = intern.getInterface('object');
|
|
||||||
const { assert } = intern.getPlugin('chai');
|
|
||||||
import Test from 'intern/lib/Test';
|
|
||||||
import pollUntil from '@theintern/leadfoot/helpers/pollUntil';
|
|
||||||
|
|
||||||
async function executeTest(test: Test, htmlTestPath: string, timeout = 10000) {
|
|
||||||
try {
|
|
||||||
return await test.remote.get(htmlTestPath).then(
|
|
||||||
pollUntil<{ text: string }>(
|
|
||||||
function() {
|
|
||||||
return (<any>window).loaderTestResults || null;
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
timeout
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('loaderTestResult was not set.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = 'abc';
|
|
||||||
|
|
||||||
registerSuite('text plugin', {
|
|
||||||
async 'correct text'() {
|
|
||||||
const results = await executeTest(this, `${__dirname}/textPlugin.html`);
|
|
||||||
assert.strictEqual(results.text, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
async 'strips XML'(this: any) {
|
|
||||||
const results = await executeTest(this, `${__dirname}/textPluginXML.html`);
|
|
||||||
assert.strictEqual(results.text, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
async 'strips HTML'(this: any) {
|
|
||||||
const results = await executeTest(this, `${__dirname}/textPluginHTML.html`);
|
|
||||||
assert.strictEqual(results.text, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
async 'strips empty file'(this: any) {
|
|
||||||
const results = await executeTest(this, `${__dirname}/textPluginEmpty.html`);
|
|
||||||
assert.strictEqual(results.text, '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Text Plugin Test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="../../../../node_modules/@dojo/shim/util/amd.js"></script>
|
|
||||||
<script src="../../../../node_modules/@dojo/loader/loader.js"></script>
|
|
||||||
<script>
|
|
||||||
require.config(shimAmdDependencies({
|
|
||||||
baseUrl: '../../../../',
|
|
||||||
packages: [
|
|
||||||
{ name: 'src', location: '_build/src' },
|
|
||||||
{ name: 'tests', location: '_build/tests' }
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
require(['@dojo/shim/main'], function () {
|
|
||||||
require([
|
|
||||||
'src/text!./_build/tests/support/data/stripEmpty.xml!strip'
|
|
||||||
], function (result) {
|
|
||||||
window.loaderTestResults = {
|
|
||||||
text: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Text Plugin Test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="../../../../node_modules/@dojo/shim/util/amd.js"></script>
|
|
||||||
<script src="../../../../node_modules/@dojo/loader/loader.js"></script>
|
|
||||||
<script>
|
|
||||||
require.config(shimAmdDependencies({
|
|
||||||
baseUrl: '../../../../',
|
|
||||||
packages: [
|
|
||||||
{ name: 'src', location: '_build/src' },
|
|
||||||
{ name: 'tests', location: '_build/tests' }
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
require(['@dojo/shim/main'], function () {
|
|
||||||
require([
|
|
||||||
'src/text!./_build/tests/support/data/strip.html!strip'
|
|
||||||
], function (result) {
|
|
||||||
window.loaderTestResults = {
|
|
||||||
text: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Text Plugin Test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="../../../../node_modules/@dojo/shim/util/amd.js"></script>
|
|
||||||
<script src="../../../../node_modules/@dojo/loader/loader.js"></script>
|
|
||||||
<script>
|
|
||||||
require.config(shimAmdDependencies({
|
|
||||||
baseUrl: '../../../../',
|
|
||||||
packages: [
|
|
||||||
{ name: 'src', location: '_build/src' },
|
|
||||||
{ name: 'tests', location: '_build/tests' }
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
require(['@dojo/shim/main'], function () {
|
|
||||||
require([
|
|
||||||
'src/text!./_build/tests/support/data/strip.xml!strip'
|
|
||||||
], function (result) {
|
|
||||||
window.loaderTestResults = {
|
|
||||||
text: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
import * as express from 'express';
|
|
||||||
import * as multer from 'multer';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
intern.registerPlugin('echo-service', () => {
|
|
||||||
intern.on('serverStart', (server) => {
|
|
||||||
const app: express.Express = (<any>server)._app;
|
|
||||||
const echo = express();
|
|
||||||
const upload = multer();
|
|
||||||
|
|
||||||
echo.use((request, _, next) => {
|
|
||||||
if (request.query.delay) {
|
|
||||||
setTimeout(() => next(), Number(request.query.delay));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
echo.post('/post', upload.any());
|
|
||||||
|
|
||||||
echo.use((request, response, next) => {
|
|
||||||
const responseType: string | undefined = request.query.responseType;
|
|
||||||
if (!responseType || responseType.localeCompare('json') === 0) {
|
|
||||||
response
|
|
||||||
.status(200)
|
|
||||||
.type('json')
|
|
||||||
.send({
|
|
||||||
method: request.method,
|
|
||||||
query: request.query,
|
|
||||||
headers: request.headers,
|
|
||||||
payload: request.files || request.body
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (responseType.localeCompare('xml') === 0) {
|
|
||||||
response.sendFile(path.resolve(__dirname, '..', 'support', 'data', 'foo.xml'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}, express.static(path.resolve(__dirname, '..', 'support', 'data'), { fallthrough: false }));
|
|
||||||
|
|
||||||
// A hack until Intern allows custom middleware
|
|
||||||
const stack: any[] = (<any>app)._router.stack;
|
|
||||||
const layers = stack.splice(stack.length - 3);
|
|
||||||
|
|
||||||
app.use('/__echo', echo);
|
|
||||||
|
|
||||||
stack.push(...layers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Intern suite</title>
|
|
||||||
<meta http-equiv="refresh" content="0;url=../node_modules/intern/client.html?config=_build/tests/intern">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
Redirecting to Intern client
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 B |
@ -1 +0,0 @@
|
|||||||
abc
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Foo</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Bar</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{ "foo": "bar" }
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<response>
|
|
||||||
<foo value="bar" />
|
|
||||||
</response>
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head lang="en">
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>HTML Strip Test</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>abc</body>
|
|
||||||
</html>
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0"?>abc
|
|
||||||
@ -1 +0,0 @@
|
|||||||
test
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export const one = 1;
|
|
||||||
export const two = 2;
|
|
||||||
|
|
||||||
const a = 'A';
|
|
||||||
export default a;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export const three = 3;
|
|
||||||
export const four = 4;
|
|
||||||
|
|
||||||
const b = 'B';
|
|
||||||
export default b;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export const five = 5;
|
|
||||||
export const six = 6;
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import load from '../../../src/load';
|
|
||||||
import { useDefault } from '../../../src/load/util';
|
|
||||||
import { Require } from '../../../src/load';
|
|
||||||
|
|
||||||
declare const require: Require;
|
|
||||||
|
|
||||||
export const succeed = load.bind(null, require, './a', './b');
|
|
||||||
export const succeedDefault = () => {
|
|
||||||
return load(require, './a', './b').then(useDefault);
|
|
||||||
};
|
|
||||||
export const fail = load.bind(null, require, './a', './nonexistent');
|
|
||||||
|
|
||||||
export const globalSucceed = load.bind(null, 'fs', 'path');
|
|
||||||
export const globalFail = load.bind(null, 'fs', './a');
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user