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",
|
||||
"version": "1.0.15",
|
||||
"version": "1.0.16",
|
||||
"description": "",
|
||||
"types": "./index.d.ts",
|
||||
"publishConfig": {
|
||||
@ -29,14 +29,10 @@
|
||||
"@plastichub/fs": "^0.13.25",
|
||||
"chalk": "^2.4.1",
|
||||
"cli-spinners": "^2.6.0",
|
||||
"defaults": "^1.0.3",
|
||||
"fast-glob": "^3.1.1",
|
||||
"ora": "^2.1.0",
|
||||
"portable-fetch": "^3.0.0",
|
||||
"readline": "^1.3.0",
|
||||
"typescript": "^3.9.10",
|
||||
"yargs": "^15.0.2",
|
||||
"yarn": "^1.22.10"
|
||||
"node-fetch": "^3.1.0",
|
||||
"ora": "^6.0.1",
|
||||
"typescript": "^3.9.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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';
|
||||
@ -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 debug from '@plastichub/core/debug';
|
||||
import { homedir } from 'os';
|
||||
import { sync as read } from '@plastichub/fs/read';
|
||||
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