# HG changeset patch # User cin # Date 2018-12-28 23:01:31 # Node ID c981b10db77f8da23992b2cd6c71c3a7e290c066 # Parent 2357b5d2b895ace7f882347caa6b0857af9398da # Parent 224ffacdbef2aeeec087ffca2679ff946a146a0e Merge with di-typescript diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -2,3 +2,4 @@ syntax: glob .gradle/ build/ node_modules/ +src/typings/ diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -1,1 +1,2 @@ 9b7927c5bafc1c80e589d9feb807e428075ef513 v1.1.1 +43a2828f8abeb9f2f9bfaf9e6d0e0b370c8a6456 v1.2.0-rc diff --git a/.project b/.project new file mode 100644 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + core + Project core created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "java.configuration.updateBuildConfiguration": "disabled", + "tslint.enable": true, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "/build": true + }, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/**": true, + "/build": true + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -2,15 +2,41 @@ if (release != 'rtm') { version += "-$release" } -println "version: $version" +if(!npmName) + npmName = name; + +if(!["amd", "cjs"].contains(platform)) + throw new Exception("Invalid platform specified: $platform"); + +def moduleTypes = [ + "amd": "amd", + "cjs": "commonjs" +] + +ext.packageName="$npmScope/$npmName-$platform"; -def distDir = "$buildDir/dist" -def testDir = "$buildDir/test" +def srcDir = "$projectDir/src" +def typingsDir = "$srcDir/typings" +def distDir = "$buildDir/dist/$platform" +def testDir = "$buildDir/test/$platform" +def moduleType = moduleTypes[platform] + +def sourceSets = ["main", "amd", "cjs", "test"]; + +task printVersion { + doLast { + println "version: $version" + println "packageName: $packageName" + println "platform: $platform" + println "module: $moduleType" + } +} task clean { doLast { delete buildDir - delete 'node_modules/@implab' + delete "node_modules/$packageName" + delete typingsDir } } @@ -30,65 +56,79 @@ task _npmInstall() { } } -task _legacyJs(type:Copy) { - from 'src/js/' - into distDir +sourceSets.each { + def setName = it.capitalize(); + + def destDir = "$buildDir/compile/$it" + def declDir = "$typingsDir/$it" + def setDir = "$projectDir/src/$it" + + task "_copyJs$setName"(type:Copy) { + from "$setDir/js" + into distDir + } + + task "_compileTs$setName"(dependsOn: _npmInstall, type:Exec) { + inputs.dir("$setDir/ts") + inputs.file("$srcDir/tsconfig.json") + inputs.file("$setDir/tsconfig.json") + outputs.dir(destDir) + outputs.dir(declDir) + + commandLine 'node_modules/.bin/tsc', + '-p', "$setDir/tsconfig.json", + '-m', moduleType, + '--outDir', destDir, + '--declarationDir', declDir + } + + task "_buildTs$setName"(dependsOn: "_compileTs$setName", type:Copy) { + from tasks.getByPath("_compileTs$setName"); + into distDir + } } -task _buildTs(dependsOn: _npmInstall, type:Exec) { - inputs.dir('src/ts') - inputs.file('tsc.json') - outputs.dir(distDir) +_compileTsAmd { + dependsOn _buildTsMain +} - commandLine 'node_modules/.bin/tsc', '-p', 'tsc.json' +_buildTsTest { + into testDir +} + +_copyJsTest { + into testDir } task _packageMeta(type: Copy) { inputs.property("version", version) from('.') { - include 'package.json', '.npmignore', 'readme.md', 'license', 'history.md' + include '.npmignore', 'readme.md', 'license', 'history.md' + } + from("$srcDir/package.template.json") { + expand project.properties + rename { "package.json" } } into distDir - doLast { - exec { - workingDir distDir - commandLine 'npm', 'version', version - } - } } -task build(dependsOn: [_npmInstall, _buildTs, _legacyJs, _packageMeta]) { +task build(dependsOn: [_copyJsMain, _copyJsAmd, _npmInstall, _buildTsMain, _buildTsAmd, _packageMeta]) { } -task _localInstall(dependsOn: build, type: Exec) { - inputs.file("$distDir/package.json") - outputs.upToDateWhen { - new File("$projectDir/node_modules/@implab/core").exists() - } - - commandLine 'npm', 'install', '--no-save', '--force', distDir +_compileTsTest { + dependsOn build } -task copyJsTests(type: Copy) { - from 'test/js' - into testDir +task buildTests(dependsOn: [_copyJsTest, _buildTsTest]) { } -task buildTests(dependsOn: _localInstall, type: Exec) { - inputs.dir('test/ts') - inputs.file('tsc.test.json') - outputs.dir(testDir) - - commandLine 'node_modules/.bin/tsc', '-p', 'tsc.test.json' -} - -task test(dependsOn: [copyJsTests, buildTests], type: Exec) { - commandLine 'node', 'run-amd-tests.js' +task test(dependsOn: buildTests, type: Exec) { + commandLine 'node', "$testDir/run-amd-tests.js" } task pack(dependsOn: build, type: Exec) { - workingDir = distDir + workingDir distDir commandLine 'npm', 'pack' } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,9 @@ -version=1.1.1 -release=rtm \ No newline at end of file +version=1.2.0 +release=rc +author=Implab team +platform=amd +description=Dependency injection, logging, simple and fast text template engine +license=BSD-2-Clause +repository=https://bitbucket.org/implab/implabjs +npmScope=@implab +npmName=core \ No newline at end of file diff --git a/license b/license --- a/license +++ b/license @@ -1,4 +1,4 @@ -Copyright 2017-2018 Implab team +Copyright 2017-2019 Implab team Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/package-lock.json b/package-lock.json --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,15 @@ "requires": true, "dependencies": { "@types/node": { - "version": "10.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.1.tgz", - "integrity": "sha512-AFLl1IALIuyt6oK4AYZsgWVJ/5rnyzQWud7IebaZWWV3YmgtPZkQmYio9R5Ze/2pdd7XfqF5bP+hWS11mAKoOQ==", + "version": "10.12.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", + "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==", + "dev": true + }, + "@types/requirejs": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.31.tgz", + "integrity": "sha512-b2soeyuU76rMbcRJ4e0hEl0tbMhFwZeTC0VZnfuWlfGlk6BwWNsev6kFu/twKABPX29wkX84wU2o+cEJoXsiTw==", "dev": true }, "@types/tape": { @@ -16,7 +22,7 @@ "integrity": "sha512-xil0KO5wkPoixdBWGIGolPv9dekf6dVkjjJLAFYchfKcd4DICou67rgGCIO7wAh3i5Ff/6j9IDgZz+GU9cMaqQ==", "dev": true, "requires": { - "@types/node": "10.5.1" + "@types/node": "*" } }, "balanced-match": { @@ -31,7 +37,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -48,35 +54,43 @@ "dev": true }, "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=", "dev": true }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.12" + "object-keys": "^1.0.12" + }, + "dependencies": { + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + } } }, "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", "dev": true }, "dojo": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.13.0.tgz", - "integrity": "sha512-mGoGvsXAbPkUrBnxCoO7m6CFH8jvWq7rAL7fP7jrhJEOyswA/bZwWdXwEH0ovs68t8S0+xOpV/3V7addYbaiAA==" + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.14.2.tgz", + "integrity": "sha512-TI+Ytgfh/VfmHWERp45Jte6NFMdoJTPsvUP/uzJUvAXET8FP2h442LePWWJ/q/xZ4V0V8OtdJhx8It/GB+Zbxg==", + "dev": true }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -86,22 +100,22 @@ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.3", - "is-callable": "1.1.3", - "is-regex": "1.0.4" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "faucet": { @@ -111,44 +125,26 @@ "dev": true, "requires": { "defined": "0.0.0", - "duplexer": "0.1.1", + "duplexer": "~0.1.1", "minimist": "0.0.5", - "sprintf": "0.1.5", - "tap-parser": "0.4.3", - "tape": "2.3.3", - "through2": "0.2.3" + "sprintf": "~0.1.3", + "tap-parser": "~0.4.0", + "tape": "~2.3.2", + "through2": "~0.2.3" }, "dependencies": { - "deep-equal": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", - "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=", - "dev": true - }, - "defined": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", - "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", - "dev": true - }, - "minimist": { - "version": "0.0.5", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", - "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=", - "dev": true - }, "tape": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/tape/-/tape-2.3.3.tgz", "integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=", "dev": true, "requires": { - "deep-equal": "0.1.2", - "defined": "0.0.0", - "inherits": "2.0.3", - "jsonify": "0.0.0", - "resumer": "0.0.0", - "through": "2.3.8" + "deep-equal": "~0.1.0", + "defined": "~0.0.0", + "inherits": "~2.0.1", + "jsonify": "~0.0.0", + "resumer": "~0.0.0", + "through": "~2.3.4" } } } @@ -159,15 +155,9 @@ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "requires": { - "is-callable": "1.1.3" + "is-callable": "^1.1.3" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -181,17 +171,17 @@ "dev": true }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has": { @@ -200,17 +190,23 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -220,9 +216,9 @@ "dev": true }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-date-object": { @@ -237,14 +233,17 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.1" } }, "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } }, "isarray": { "version": "0.0.1", @@ -264,13 +263,13 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "0.0.5", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=", "dev": true }, "object-inspect": { @@ -280,9 +279,9 @@ "dev": true }, "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", "dev": true }, "once": { @@ -291,31 +290,31 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "requirejs": { @@ -326,11 +325,11 @@ }, "resolve": { "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resumer": { @@ -339,7 +338,7 @@ "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", "dev": true, "requires": { - "through": "2.3.8" + "through": "~2.3.4" } }, "sprintf": { @@ -354,14 +353,14 @@ "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.12.0", - "function-bind": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" } }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, @@ -371,8 +370,8 @@ "integrity": "sha1-pOrhkMENdsehEZIf84u+TVjwnuo=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "1.1.14" + "inherits": "~2.0.1", + "readable-stream": "~1.1.11" } }, "tape": { @@ -381,41 +380,67 @@ "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", "dev": true, "requires": { - "deep-equal": "1.0.1", - "defined": "1.0.0", - "for-each": "0.3.3", - "function-bind": "1.1.1", - "glob": "7.1.2", - "has": "1.0.3", - "inherits": "2.0.3", - "minimist": "1.2.0", - "object-inspect": "1.6.0", - "resolve": "1.7.1", - "resumer": "0.0.0", - "string.prototype.trim": "1.1.2", - "through": "2.3.8" + "deep-equal": "~1.0.1", + "defined": "~1.0.0", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.2", + "has": "~1.0.3", + "inherits": "~2.0.3", + "minimist": "~1.2.0", + "object-inspect": "~1.6.0", + "resolve": "~1.7.1", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.1.2", + "through": "~2.3.8" + }, + "dependencies": { + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } } }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, "through2": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "resolved": "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz", "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", "dev": true, "requires": { - "readable-stream": "1.1.14", - "xtend": "2.1.2" + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" } }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", - "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", "dev": true }, "wrappy": { @@ -430,15 +455,7 @@ "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", "dev": true, "requires": { - "object-keys": "0.4.0" - }, - "dependencies": { - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - } + "object-keys": "~0.4.0" } } } diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -16,15 +16,20 @@ "publishConfig": { "access": "public" }, - "dependencies": { - "dojo": "^1.10.0" + "peerDependencies": { + "dojo": "^1.10.0", + "tslib": "latest" }, "devDependencies": { "typescript": "latest", "tape": "latest", "@types/tape": "latest", + "@types/requirejs": "latest", + "@types/node": "latest", "requirejs": "latest", - "faucet": "latest" + "faucet": "latest", + "dojo": "^1.10.0", + "tslib": "latest" }, "types": "main.d.ts" } diff --git a/settings.gradle b/settings.gradle --- a/settings.gradle +++ b/settings.gradle @@ -12,4 +12,4 @@ //include 'sub-project-name' -rootProject.name = 'implab-core' \ No newline at end of file +rootProject.name = 'core' \ No newline at end of file diff --git a/src/js/Uri.js b/src/amd/js/Uri.js rename from src/js/Uri.js rename to src/amd/js/Uri.js diff --git a/src/js/data/DataContext.js b/src/amd/js/data/DataContext.js rename from src/js/data/DataContext.js rename to src/amd/js/data/DataContext.js diff --git a/src/js/data/MapSchema.js b/src/amd/js/data/MapSchema.js rename from src/js/data/MapSchema.js rename to src/amd/js/data/MapSchema.js diff --git a/src/js/data/ObjectStore.js b/src/amd/js/data/ObjectStore.js rename from src/js/data/ObjectStore.js rename to src/amd/js/data/ObjectStore.js diff --git a/src/js/data/RestStore.js b/src/amd/js/data/RestStore.js rename from src/js/data/RestStore.js rename to src/amd/js/data/RestStore.js diff --git a/src/js/data/StatefullStoreAdapter.js b/src/amd/js/data/StatefullStoreAdapter.js rename from src/js/data/StatefullStoreAdapter.js rename to src/amd/js/data/StatefullStoreAdapter.js diff --git a/src/js/data/StoreAdapter.js b/src/amd/js/data/StoreAdapter.js rename from src/js/data/StoreAdapter.js rename to src/amd/js/data/StoreAdapter.js diff --git a/src/js/data/_ModelBase.js b/src/amd/js/data/_ModelBase.js rename from src/js/data/_ModelBase.js rename to src/amd/js/data/_ModelBase.js diff --git a/src/js/data/_StatefulModelMixin.js b/src/amd/js/data/_StatefulModelMixin.js rename from src/js/data/_StatefulModelMixin.js rename to src/amd/js/data/_StatefulModelMixin.js diff --git a/src/js/data/declare-model.js b/src/amd/js/data/declare-model.js rename from src/js/data/declare-model.js rename to src/amd/js/data/declare-model.js diff --git a/src/js/declare/_load.js b/src/amd/js/declare/_load.js rename from src/js/declare/_load.js rename to src/amd/js/declare/_load.js diff --git a/src/js/declare/override.js b/src/amd/js/declare/override.js rename from src/js/declare/override.js rename to src/amd/js/declare/override.js diff --git a/src/js/log/trace.js b/src/amd/js/log/trace.js rename from src/js/log/trace.js rename to src/amd/js/log/trace.js diff --git a/src/js/messaging/Client.js b/src/amd/js/messaging/Client.js rename from src/js/messaging/Client.js rename to src/amd/js/messaging/Client.js diff --git a/src/js/messaging/Destination.js b/src/amd/js/messaging/Destination.js rename from src/js/messaging/Destination.js rename to src/amd/js/messaging/Destination.js diff --git a/src/js/messaging/Listener.js b/src/amd/js/messaging/Listener.js rename from src/js/messaging/Listener.js rename to src/amd/js/messaging/Listener.js diff --git a/src/js/messaging/Session.js b/src/amd/js/messaging/Session.js rename from src/js/messaging/Session.js rename to src/amd/js/messaging/Session.js diff --git a/src/amd/ts/di/RequireJsHelper.ts b/src/amd/ts/di/RequireJsHelper.ts new file mode 100644 --- /dev/null +++ b/src/amd/ts/di/RequireJsHelper.ts @@ -0,0 +1,41 @@ +import { Uuid } from "../Uuid"; +import { argumentNotEmptyString, argumentNotNull } from "../safe"; +import { TraceSource } from "../log/TraceSource"; +import m = require("module"); + +const trace = TraceSource.get(m.id); + +export async function createContextRequire(moduleName: string): Promise { + argumentNotEmptyString(moduleName, "moduleName"); + + const parts = moduleName.split("/"); + if (parts[0] === ".") + throw new Error("An absolute module path is required"); + + if (parts.length > 1) + parts.splice(-1, 1, Uuid()); + else + parts.push(Uuid()); + + const shim = parts.join("/"); + + trace.debug(`define shim ${shim}`); + + return new Promise(cb => { + define(shim, ["require"], r => { + trace.debug("shim resolved"); + return r; + }); + require([shim], cb); + }); +} + +export function makeResolver(req: Require) { + argumentNotNull(req, "req"); + + return (name: string) => { + return new Promise((cb, eb) => { + req([name], cb, eb); + }); + }; +} diff --git a/src/amd/ts/text/format-compile.ts b/src/amd/ts/text/format-compile.ts new file mode 100644 --- /dev/null +++ b/src/amd/ts/text/format-compile.ts @@ -0,0 +1,8 @@ +import * as module from "module"; +import { TraceSource } from "../log/TraceSource"; + +const logger = TraceSource.get(module.id); + +logger.warn("The module is deprecated, use StringFormat.compile() method directly"); + +export { compile } from "./StringFormat"; diff --git a/src/amd/ts/text/format.ts b/src/amd/ts/text/format.ts new file mode 100644 --- /dev/null +++ b/src/amd/ts/text/format.ts @@ -0,0 +1,47 @@ +import { format as dojoFormatNumber } from "dojo/number"; +import { format as dojoFormatDate } from "dojo/date/locale"; +import { Formatter } from "./StringFormat"; + +import { isNumber } from "../safe"; + +interface NumberFormatOptions { + round?: number; + pattern?: string; +} + +function convertNumber(value: any, pattern: string) { + if (isNumber(value)) { + const nopt = {} as NumberFormatOptions; + if (pattern.indexOf("!") === 0) { + nopt.round = -1; + pattern = pattern.substr(1); + } + nopt.pattern = pattern; + + return dojoFormatNumber(value, nopt); + } +} + +function convertDate(value: any, pattern: string) { + if (value instanceof Date) { + const m = pattern.match(/^(\w+)-(\w+)$/); + if (m) + return dojoFormatDate(value, { + selector: m[2], + formatLength: m[1] + }); + else if (pattern === "iso") + return value.toISOString(); + else + return dojoFormatDate(value, { + selector: "date", + datePattern: pattern + }); + } +} + +const _formatter = new Formatter([convertNumber, convertDate]); + +export = function format(msg: string, ...args: any[]) { + return _formatter.format.apply(msg, ...args); +}; diff --git a/src/amd/ts/text/template-compile.ts b/src/amd/ts/text/template-compile.ts new file mode 100644 --- /dev/null +++ b/src/amd/ts/text/template-compile.ts @@ -0,0 +1,49 @@ +import request = require("dojo/request"); +import m = require("module"); +import { TraceSource } from "../log/TraceSource"; +import { TemplateCompiler } from "./TemplateCompiler"; +import { TemplateParser } from "./TemplateParser"; +import { isNullOrEmptyString } from "../safe"; +import { MapOf } from "../interfaces"; + +type TemplateFn = (obj: object) => string; + +const trace = TraceSource.get(m.id); + +function compile(str: string) { + if (isNullOrEmptyString(str)) + return () => ""; + + const parser = new TemplateParser(str); + const compiler = new TemplateCompiler(); + + return compiler.compile(parser); +} + +const cache: MapOf = {}; + +interface OnLoadFn { + (res: T): void; + error(e: any): void; +} + +compile.load = (id: string, require: Require, callback: OnLoadFn) => { + const url = require.toUrl(id); + if (url in cache) { + trace.debug("{0} -> {1}: cached", id, url); + callback(cache[url]); + } else { + trace.debug("{0} -> {1}: load", id, url); + request(url).then(compile).then((tc: TemplateFn) => { + trace.debug("{0}: compiled", url); + callback(cache[url] = tc); + }, (err: any) => { + callback.error({ + inner: err, + src: "@implab/core/text/template-compile" + }); + }); + } +}; + +export = compile; diff --git a/src/amd/tsconfig.json b/src/amd/tsconfig.json new file mode 100644 --- /dev/null +++ b/src/amd/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "types": [ + "requirejs" + ], + "rootDir": "ts", + "rootDirs": [ + "ts", + "../typings/main" + ] + }, + "include": [ + "ts/**/*.ts" + ] +} \ No newline at end of file diff --git a/src/cjs/ts/di/CommonJsHelper.ts b/src/cjs/ts/di/CommonJsHelper.ts new file mode 100644 --- /dev/null +++ b/src/cjs/ts/di/CommonJsHelper.ts @@ -0,0 +1,3 @@ +export function createContextResolver(moduleName: string) { + return (m: string) => { }; +} diff --git a/src/cjs/tsconfig.json b/src/cjs/tsconfig.json new file mode 100644 --- /dev/null +++ b/src/cjs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "types": [ + "@types/node" + ] + }, + "include": [ + "ts/**/*.ts" + ] +} \ No newline at end of file diff --git a/src/js/Deferred.js b/src/js/Deferred.js deleted file mode 100644 --- a/src/js/Deferred.js +++ /dev/null @@ -1,3 +0,0 @@ -define(["dojo/Deferred"], function(Deferred) { - return Deferred; -}); \ No newline at end of file diff --git a/src/js/declare.js b/src/js/declare.js deleted file mode 100644 --- a/src/js/declare.js +++ /dev/null @@ -1,6 +0,0 @@ -define([ - './declare/_load!' -], function(declare) { - 'use strict'; - return declare; -}); \ No newline at end of file diff --git a/src/js/di/ActivationContext.js b/src/js/di/ActivationContext.js deleted file mode 100644 --- a/src/js/di/ActivationContext.js +++ /dev/null @@ -1,138 +0,0 @@ -define([ - "../declare", - "../safe", - "./Descriptor", - "./ValueDescriptor", - "../log/trace!" -], function (declare, safe, Descriptor, Value, trace) { - var Context = declare(null, { - - _cache: null, - - _services: null, - - _stack: null, - - _visited: null, - - container: null, - - _trace: true, - - constructor: function (container, services, cache, visited) { - safe.argumentNotNull(container, "container"); - safe.argumentNotNull(services, "services"); - - this._visited = visited || {}; - this._stack = []; - this._cache = cache || {}; - this._services = services; - this.container = container; - }, - - getService: function (name, def) { - var d = this._services[name]; - - if (!d) - if (arguments.length > 1) - return def; - else - throw new Error("Service '" + name + "' not found"); - - return d.activate(this, name); - }, - - /** - * registers services local to the the activation context - * - * @name{string} the name of the service - * @service{string} the service descriptor to register - */ - register: function (name, service) { - safe.argumentNotEmptyString(name, "name"); - - if (!(service instanceof Descriptor)) - service = new Value(service, true); - this._services[name] = service; - }, - - clone: function () { - return new Context( - this.container, - Object.create(this._services), - this._cache, - this._visited - ); - - }, - - has: function (id) { - return id in this._cache; - }, - - get: function (id) { - return this._cache[id]; - }, - - store: function (id, value) { - return (this._cache[id] = value); - }, - - parse: function (data, name) { - var me = this; - if (safe.isPrimitive(data)) - return data; - - if (data instanceof Descriptor) { - return data.activate(this, name); - } else if (data instanceof Array) { - me.enter(name); - var v = data.map(function (x, i) { - return me.parse(x, "." + i); - }); - me.leave(); - return v; - } else { - me.enter(name); - var result = {}; - for (var p in data) - result[p] = me.parse(data[p], "." + p); - me.leave(); - return result; - } - }, - - visit: function (id) { - var count = this._visited[id] || 0; - this._visited[id] = count + 1; - return count; - }, - - getStack: function () { - return this._stack.slice().reverse(); - }, - - enter: function (name, d, localize) { - if (this._trace) - trace.log("enter " + name + " " + (d || "") + - (localize ? " localize" : "")); - this._stack.push({ - name: name, - service: d, - scope: this._services - }); - if (localize) - this._services = Object.create(this._services); - }, - - leave: function () { - var ctx = this._stack.pop(); - this._services = ctx.scope; - - if (this._trace) - trace.log("leave " + ctx.name + " " + (ctx.service || "")); - } - }); - - return Context; -}); \ No newline at end of file diff --git a/src/js/di/ActivationError.js b/src/js/di/ActivationError.js deleted file mode 100644 --- a/src/js/di/ActivationError.js +++ /dev/null @@ -1,39 +0,0 @@ -define([ - "../declare" -], function (declare) { - return declare(null, { - activationStack: null, - - service: null, - - innerException: null, - - message: null, - - constructor: function (service, activationStack, innerException) { - this.message = "Failed to activate the service"; - this.activationStack = activationStack; - this.service = service; - this.innerException = innerException; - }, - - toString: function () { - var parts = [this.message]; - if (this.service) - parts.push("when activating: " + this.service.toString()); - - if (this.innerException) - parts.push("caused by: " + this.innerException.toString()); - - if (this.activationStack) { - parts.push("at"); - this.activationStack.forEach(function (x) { - parts.push(" " + x.name + " " + - (x.service ? x.service.toString() : "")); - }); - } - - return parts.join("\n"); - } - }); -}); \ No newline at end of file diff --git a/src/js/di/Container.js b/src/js/di/Container.js deleted file mode 100644 --- a/src/js/di/Container.js +++ /dev/null @@ -1,299 +0,0 @@ -define([ - "../declare", - "../safe", - "../Uuid", - "../Deferred", - "./ActivationContext", - "./Descriptor", - "./ValueDescriptor", - "./ReferenceDescriptor", - "./ServiceDescriptor", - "./ActivationError" -], function ( - declare, - safe, - Uuid, - Deferred, - ActivationContext, - Descriptor, - Value, - Reference, - Service, - ActivationError) { - var Container = declare(null, { - _services: null, - _cache: null, - _cleanup: null, - _root: null, - _parent: null, - - constructor: function (parent) { - this._parent = parent; - this._services = parent ? Object.create(parent._services) : {}; - this._cache = {}; - this._cleanup = []; - this._root = parent ? parent.getRootContainer() : this; - this._services.container = new Value(this, true); - }, - - getRootContainer: function () { - return this._root; - }, - - getParent: function () { - return this._parent; - }, - - /** - * - */ - getService: function (name, def) { - var d = this._services[name]; - if (!d) - if (arguments.length > 1) - return def; - else - throw new Error("Service '" + name + "' isn't found"); - if (d.isInstanceCreated()) - return d.getInstance(); - - var context = new ActivationContext(this, this._services); - - try { - return d.activate(context, name); - } catch (error) { - throw new ActivationError(name, context.getStack(), error); - } - }, - - register: function (name, service) { - if (arguments.length == 1) { - var data = name; - for (name in data) - this.register(name, data[name]); - } else { - if (!(service instanceof Descriptor)) - service = new Value(service, true); - this._services[name] = service; - } - return this; - }, - - onDispose: function (callback) { - if (!(callback instanceof Function)) - throw new Error("The callback must be a function"); - this._cleanup.push(callback); - }, - - dispose: function () { - if (this._cleanup) { - for (var i = 0; i < this._cleanup.length; i++) - this._cleanup[i].call(null); - this._cleanup = null; - } - }, - - /** - * @param{String|Object} config - * The configuration of the contaier. Can be either a string or an object, - * if the configuration is an object it's treated as a collection of - * services which will be registed in the contaier. - * - * @param{Function} opts.contextRequire - * The function which will be used to load a configuration or types for services. - * - */ - configure: function (config, opts) { - var p, me = this, - contextRequire = (opts && opts.contextRequire); - - if (typeof (config) === "string") { - p = new Deferred(); - if (!contextRequire) { - var shim = [config, Uuid()].join(config.indexOf("/") != -1 ? "-" : "/"); - define(shim, ["require", config], function (ctx, data) { - p.resolve([data, { - contextRequire: ctx - }]); - }); - require([shim]); - } else { - // TODO how to get correct contextRequire for the relative config module? - contextRequire([config], function (data) { - p.resolve([data, { - contextRequire: contextRequire - }]); - }); - } - - return p.then(function (args) { - return me._configure.apply(me, args); - }); - } else { - return me._configure(config, opts); - } - }, - - createChildContainer: function () { - return new Container(this); - }, - - has: function (id) { - return id in this._cache; - }, - - get: function (id) { - return this._cache[id]; - }, - - store: function (id, value) { - return (this._cache[id] = value); - }, - - _configure: function (data, opts) { - var typemap = {}, - d = new Deferred(), - me = this, - p, - contextRequire = (opts && opts.contextRequire) || require; - - var services = {}; - - for (p in data) { - var service = me._parse(data[p], typemap); - if (!(service instanceof Descriptor)) - service = new Value(service, false); - services[p] = service; - } - - me.register(services); - - var names = []; - - for (p in typemap) - names.push(p); - - if (names.length) { - contextRequire(names, function () { - for (var i = 0; i < names.length; i++) - typemap[names[i]] = arguments[i]; - d.resolve(me); - }); - } else { - d.resolve(me); - } - return d.promise; - }, - - _parse: function (data, typemap) { - if (safe.isPrimitive(data) || data instanceof Descriptor) - return data; - if (data.$dependency) - return new Reference( - data.$dependency, - data.lazy, - data.optional, - data["default"], - data.services && this._parseObject(data.services, typemap)); - if (data.$value) { - var raw = !data.parse; - return new Value(raw ? data.$value : this._parse( - data.$value, - typemap), raw); - } - if (data.$type || data.$factory) - return this._parseService(data, typemap); - if (data instanceof Array) - return this._parseArray(data, typemap); - - return this._parseObject(data, typemap); - }, - - _parseService: function (data, typemap) { - var me = this, - opts = { - owner: this - }; - if (data.$type) { - - opts.type = data.$type; - - if (typeof (data.$type) === "string") { - typemap[data.$type] = null; - opts.typeMap = typemap; - } - } - - if (data.$factory) - opts.factory = data.$factory; - - if (data.services) - opts.services = me._parseObject(data.services, typemap); - if (data.inject) - opts.inject = data.inject instanceof Array ? data.inject.map(function (x) { - return me._parseObject(x, typemap); - }) : me._parseObject(data.inject, typemap); - if (data.params) - opts.params = me._parse(data.params, typemap); - - if (data.activation) { - if (typeof (data.activation) === "string") { - switch (data.activation.toLowerCase()) { - case "singleton": - opts.activation = Service.SINGLETON; - break; - case "container": - opts.activation = Service.CONTAINER; - break; - case "hierarchy": - opts.activation = Service.HIERARCHY; - break; - case "context": - opts.activation = Service.CONTEXT; - break; - case "call": - opts.activation = Service.CALL; - break; - default: - throw new Error("Unknown activation type: " + - data.activation); - } - } else { - opts.activation = Number(data.activation); - } - } - - if (data.cleanup) - opts.cleanup = data.cleanup; - - return new Service(opts); - }, - - _parseObject: function (data, typemap) { - if (data.constructor && - data.constructor.prototype !== Object.prototype) - return new Value(data, true); - - var o = {}; - - for (var p in data) - o[p] = this._parse(data[p], typemap); - - return o; - }, - - _parseArray: function (data, typemap) { - if (data.constructor && - data.constructor.prototype !== Array.prototype) - return new Value(data, true); - - var me = this; - return data.map(function (x) { - return me._parse(x, typemap); - }); - } - - }); - - return Container; -}); \ No newline at end of file diff --git a/src/js/di/Descriptor.js b/src/js/di/Descriptor.js deleted file mode 100644 --- a/src/js/di/Descriptor.js +++ /dev/null @@ -1,4 +0,0 @@ -define([], function() { - // abstract base type for descriptros - return function() {}; -}); \ No newline at end of file diff --git a/src/js/di/ReferenceDescriptor.js b/src/js/di/ReferenceDescriptor.js deleted file mode 100644 --- a/src/js/di/ReferenceDescriptor.js +++ /dev/null @@ -1,90 +0,0 @@ -define([ - "../declare", "../safe", "./Descriptor", "./ActivationError", "./ValueDescriptor" -], - -function(declare, safe, Descriptor, ActivationError, Value) { - return declare(Descriptor, { - _name : null, - _lazy : false, - _optional : false, - _default : undefined, - - constructor : function(name, lazy, optional, def, services) { - safe.argumentNotEmptyString(name, "name"); - this._name = name; - this._lazy = Boolean(lazy); - this._optional = Boolean(optional); - this._default = def; - this._services = services; - }, - - activate : function(context, name) { - var me = this; - - context.enter(name, this, true); - - // добавляем сервисы - if (me._services) { - for ( var p in me._services) { - var sv = me._services[p]; - context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); - } - } - - if (me._lazy) { - // сохраняем контекст активации - context = context.clone(); - return function(cfg) { - // защищаем контекст на случай исключения в процессе - // активации - var ct = context.clone(); - try { - if (cfg) - safe.each(cfg, function(v, k) { - ct.register(k, v instanceof Descriptor ? v : new Value(v, false)); - }); - return me._optional ? ct.getService(me._name, me._default) : ct - .getService(me._name); - } catch (error) { - throw new ActivationError(me._name, ct.getStack(), error); - } - }; - } - - var v = me._optional ? context.getService(me._name, me._default) : context - .getService(me._name); - context.leave(me); - return v; - }, - - isInstanceCreated : function() { - return false; - }, - - toString : function() { - var opts = []; - if (this._optional) - opts.push("optional"); - if (this._lazy) - opts.push("lazy"); - - var parts = [ - "@ref " - ]; - if (opts.length) { - parts.push("{"); - parts.push(opts.join()); - parts.push("} "); - } - - parts.push(this._name); - - if (!safe.isNull(this._default)) { - parts.push(" = "); - parts.push(this._default); - } - - return parts.join(""); - } - }); -}); \ No newline at end of file diff --git a/src/js/di/ServiceDescriptor.js b/src/js/di/ServiceDescriptor.js deleted file mode 100644 --- a/src/js/di/ServiceDescriptor.js +++ /dev/null @@ -1,289 +0,0 @@ -define( - [ - "../declare", - "../safe", - "./Descriptor", - "./ValueDescriptor" - ], - - function (declare, safe, Descriptor, Value) { - var SINGLETON_ACTIVATION = 1, - CONTAINER_ACTIVATION = 2, - CONTEXT_ACTIVATION = 3, - CALL_ACTIVATION = 4, - HIERARCHY_ACTIVATION = 5; - - var injectMethod = function (target, method, context, args) { - var m = target[method]; - if (!m) - throw new Error("Method '" + method + "' not found"); - - if (args instanceof Array) - m.apply(target, context.parse(args, "." + method)); - else - m.call(target, context.parse(args, "." + method)); - }; - - var makeClenupCallback = function (target, method) { - if (typeof (method) === "string") { - return function () { - target[method](); - }; - } else { - return function () { - method(target); - }; - } - }; - - var cacheId = 0; - - var cls = declare( - Descriptor, { - _instance: null, - _hasInstance: false, - _activationType: CALL_ACTIVATION, - _services: null, - _type: null, - _typeMap: null, - _factory: null, - _params: undefined, - _inject: null, - _cleanup: null, - _cacheId: null, - _owner: null, - - constructor: function (opts) { - safe.argumentNotNull(opts, "opts"); - safe.argumentNotNull(opts.owner, "opts.owner"); - - this._owner = opts.owner; - - if (!(opts.type || opts.factory)) - throw new Error( - "Either a type or a factory must be specified"); - - if (typeof (opts.type) === "string" && !opts.typeMap) - throw new Error( - "The typeMap is required when the type is specified by its name"); - - if (opts.activation) - this._activationType = opts.activation; - if (opts.type) - this._type = opts.type; - if (opts.params) - this._params = opts.params; - if (opts.inject) - this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject]; - if (opts.services) - this._services = opts.services; - if (opts.factory) - this._factory = opts.factory; - if (opts.typeMap) - this._typeMap = opts.typeMap; - if (opts.cleanup) { - if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) - throw new Error( - "The cleanup parameter must be either a function or a function name"); - - this._cleanup = opts.cleanup; - } - - this._cacheId = ++cacheId; - }, - - activate: function (context, name) { - - // if we have a local service records, register them first - - var instance; - - switch (this._activationType) { - case 1: // SINGLETON - // if the value is cached return it - if (this._hasInstance) - return this._instance; - - var tof = this._type || this._factory; - - // create the persistent cache identifier for the type - if (safe.isPrimitive(tof)) - this._cacheId = this._type; - else - this._cacheId = safe.oid(tof); - - // singletons are bound to the root container - var container = context.container.getRootContainer(); - - if (container.has(this._cacheId)) { - instance = container.get(this._cacheId); - } else { - instance = this._create(context, name); - container.store(this._cacheId, instance); - if (this._cleanup) - container.onDispose( - makeClenupCallback(instance, this._cleanup)); - } - - this._hasInstance = true; - return (this._instance = instance); - - case 2: // CONTAINER - //return a cached value - if (this._hasInstance) - return this._instance; - - // create an instance - instance = this._create(context, name); - - // the instance is bound to the container - if (this._cleanup) - this._owner.onDispose( - makeClenupCallback(instance, this._cleanup)); - - // cache and return the instance - this._hasInstance = true; - return (this._instance = instance); - case 3: // CONTEXT - //return a cached value if one exists - if (context.has(this._cacheId)) - return context.get(this._cacheId); - // context context activated instances are controlled by callers - return context.store(this._cacheId, this._create( - context, - name)); - case 4: // CALL - // per-call created instances are controlled by callers - return this._create(context, name); - case 5: // HIERARCHY - // hierarchy activated instances are behave much like container activated - // except they are created and bound to the child container - - // return a cached value - if (context.container.has(this._cacheId)) - return context.container.get(this._cacheId); - - instance = this._create(context, name); - - if (this._cleanup) - context.container.onDispose(makeClenupCallback( - instance, - this._cleanup)); - - return context.container.store(this._cacheId, instance); - default: - throw "Invalid activation type: " + this._activationType; - } - }, - - isInstanceCreated: function () { - return this._hasInstance; - }, - - getInstance: function () { - return this._instance; - }, - - _create: function (context, name) { - context.enter(name, this, Boolean(this._services)); - - if (this._activationType != CALL_ACTIVATION && - context.visit(this._cacheId) > 0) - throw new Error("Recursion detected"); - - if (this._services) { - for (var p in this._services) { - var sv = this._services[p]; - context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); - } - } - - var instance; - - if (!this._factory) { - var ctor, type = this._type; - - if (typeof (type) === "string") { - ctor = this._typeMap[type]; - if (!ctor) - throw new Error("Failed to resolve the type '" + - type + "'"); - } else { - ctor = type; - } - - if (this._params === undefined) { - this._factory = function () { - return new ctor(); - }; - } else if (this._params instanceof Array) { - this._factory = function () { - var inst = Object.create(ctor.prototype); - var ret = ctor.apply(inst, arguments); - return typeof (ret) === "object" ? ret : inst; - }; - } else { - this._factory = function (param) { - return new ctor(param); - }; - } - } - - if (this._params === undefined) { - instance = this._factory(); - } else if (this._params instanceof Array) { - instance = this._factory.apply(this, context.parse( - this._params, - ".params")); - } else { - instance = this._factory(context.parse( - this._params, - ".params")); - } - - if (this._inject) { - this._inject.forEach(function (spec) { - for (var m in spec) - injectMethod(instance, m, context, spec[m]); - }); - } - - context.leave(); - - return instance; - }, - - // @constructor {singleton} foo/bar/Baz - // @factory {singleton} - toString: function () { - var parts = []; - - parts.push(this._type ? "@constructor" : "@factory"); - - parts.push(activationNames[this._activationType]); - - if (typeof (this._type) === "string") - parts.push(this._type); - - return parts.join(" "); - } - - }); - - cls.SINGLETON = SINGLETON_ACTIVATION; - cls.CONTAINER = CONTAINER_ACTIVATION; - cls.CONTEXT = CONTEXT_ACTIVATION; - cls.CALL = CALL_ACTIVATION; - cls.HIERARCHY = HIERARCHY_ACTIVATION; - - var activationNames = [ - "", - "{singleton}", - "{container}", - "{context}", - "{call}", - "{hierarchy}" - ]; - - return cls; - }); \ No newline at end of file diff --git a/src/js/di/ValueDescriptor.js b/src/js/di/ValueDescriptor.js deleted file mode 100644 --- a/src/js/di/ValueDescriptor.js +++ /dev/null @@ -1,38 +0,0 @@ -define([ "../declare", "./Descriptor", "../safe" ], - -function(declare, Descriptor, safe) { - return declare(Descriptor, { - _value : undefined, - _raw : false, - constructor : function(value, raw) { - this._value = value; - this._raw = Boolean(raw); - }, - - activate : function(context, name) { - context.enter(name, this); - var v = this._raw ? this._value : context.parse( - this._value, - ".params"); - context.leave(this); - return v; - }, - - isInstanceCreated : function() { - return this._raw; - }, - - getInstance : function() { - if (!this._raw) - throw new Error("The instance isn't constructed"); - return this._value; - }, - - toString : function() { - if (this._raw) - return "@value {raw}"; - else - return safe.isNull(this._value) ? "@value " : "@value"; - } - }); -}); \ No newline at end of file diff --git a/src/js/text/format-compile.js b/src/js/text/format-compile.js deleted file mode 100644 --- a/src/js/text/format-compile.js +++ /dev/null @@ -1,101 +0,0 @@ -define( - [], - function() { - var map = { - "\\{" : "&curlopen;", - "\\}" : "&curlclose;", - "&" : "&", - "\\:" : ":" - }; - - var rev = { - curlopen : "{", - curlclose : "}", - amp : "&", - colon : ":" - }; - - var espaceString = function(s) { - if (!s) - return s; - return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n","\\n") + "'"; - }; - - var encode = function(s) { - if (!s) - return s; - return s.replace(/\\{|\\}|&|\\:|\n/g, function(m) { - return map[m] || m; - }); - }; - - var decode = function(s) { - if (!s) - return s; - return s.replace(/&(\w+);/g, function(m, $1) { - return rev[$1] || m; - }); - }; - - var subst = function(s) { - var i = s.indexOf(":"), name, pattern; - if (i >= 0) { - name = s.substr(0, i); - pattern = s.substr(i + 1); - } else { - name = s; - } - - if (pattern) - return [ - espaceString(decode(name)), - espaceString(decode(pattern)) ]; - else - return [ espaceString(decode(name)) ]; - }; - - var compile = function(str) { - if (!str) - return function() {}; - - var chunks = encode(str).split("{"), chunk; - - var code = [ "var result=[];" ]; - - for (var i = 0; i < chunks.length; i++) { - chunk = chunks[i]; - - if (i === 0) { - if (chunk) - code.push("result.push(" + espaceString(decode(chunk)) + - ");"); - } else { - var len = chunk.indexOf("}"); - if (len < 0) - throw new Error("Unbalanced substitution #" + i); - - code.push("result.push(subst(" + - subst(chunk.substr(0, len)).join(",") + "));"); - if (chunk.length > len + 1) - code.push("result.push(" + - espaceString(decode(chunk.substr(len + 1))) + ");"); - } - } - - code.push("return result.join('');"); - - /* jshint -W054 */ - return new Function("subst", code.join("\n")); - }; - - var cache = {}; - - return function(template) { - var compiled = cache[template]; - if (!compiled) { - compiled = compile(template); - cache[template] = compiled; - } - return compiled; - }; - }); \ No newline at end of file diff --git a/src/js/text/format.js b/src/js/text/format.js deleted file mode 100644 --- a/src/js/text/format.js +++ /dev/null @@ -1,87 +0,0 @@ -define([ - "../safe", - "./format-compile", - "dojo/number", - "dojo/date/locale", - "dojo/_base/array" ], function(safe, compile, number, date, array) { - - // {short,medium,full,long}-{date,time} - var convert = function(value, pattern) { - if (!pattern) - return value.toString(); - - if (pattern.toLocaleLowerCase() == "json") { - var cache = []; - return JSON.stringify(value, function(k, v) { - if (!safe.isPrimitive(v)) { - var id = array.indexOf(cache, v); - if (id >= 0) - return "@ref-" + id; - else - return v; - } else { - return v; - } - },2); - } - - if (safe.isNumber(value)) { - var nopt = {}; - if (pattern.indexOf("!") === 0) { - nopt.round = -1; - pattern = pattern.substr(1); - } - nopt.pattern = pattern; - return number.format(value, nopt); - } else if (value instanceof Date) { - var m = pattern.match(/^(\w+)-(\w+)$/); - if (m) - return date.format(value, { - selector : m[2], - formatLength : m[1] - }); - else if (pattern == "iso") - return value.toISOString(); - else - return date.format(value, { - selector : "date", - datePattern : pattern - }); - } else { - return value.toString(pattern); - } - }; - - function formatter(format) { - var data; - - if (arguments.length <= 1) - return format; - - data = Array.prototype.slice.call(arguments, 1); - - var template = compile(format); - - return template(function(name, pattern) { - var value = data[name]; - return !safe.isNull(value) ? convert(value, pattern) : ""; - }); - } - - formatter.compile = function(format) { - var template = compile(format); - - return function() { - var data = arguments; - - return template(function(name, pattern) { - var value = data[name]; - return !safe.isNull(value) ? convert(value, pattern) : ""; - }); - }; - }; - - formatter.convert = convert; - - return formatter; -}); \ No newline at end of file diff --git a/src/js/text/template-compile.js b/src/js/text/template-compile.js deleted file mode 100644 --- a/src/js/text/template-compile.js +++ /dev/null @@ -1,134 +0,0 @@ -define( - ["dojo/request", "./format", "../log/trace!"], - function (request, format, trace) { - - // разбивает строку шаблона на токены, возвращает контекст для - // дальнейшей обработки в visitTemplate - var parseTemplate = function (str) { - var tokens = str.split(/(<%=|\[%=|<%|\[%|%\]|%>)/); - var pos = -1; - var data = [], - code = []; - - return { - next: function () { - pos++; - return pos < tokens.length; - }, - token: function () { - return tokens[pos]; - }, - pushData: function () { - var i = data.length; - data.push.apply(data, arguments); - return i; - }, - pushCode : function() { - var i = code.length; - code.push.apply(code, arguments); - return i; - }, - compile: function () { - var text = "var $p = [];\n" + - "var print = function(){\n" + - " $p.push(format.apply(null,arguments));\n" + - "};\n" + - // Introduce the data as local variables using with(){} - "with(obj){\n" + - code.join("\n") + - "}\n" + - "return $p.join('');"; - - try { - var compiled = new Function("obj, format, $data", text); - /** - * Функция форматирования по шаблону - * - * @type{Function} - * @param{Object} obj объект с параметрами для подстановки - */ - return function (obj) { - return compiled(obj || {}, format, data); - }; - } catch (e) { - trace.error([e]); - trace.log([text, data]); - throw e; - } - } - } - }; - - function visitTemplate(context) { - while (context.next()) { - switch (context.token()) { - case "<%": - case "[%": - visitCode(context); - break; - case "<%=": - case "[%=": - visitInline(context); - break; - default: - visitTextFragment(context); - break; - } - } - } - - function visitInline(context) { - var code = ["$p.push("]; - while (context.next()) { - if (context.token() == "%>" || context.token() == "%]") - break; - code.push(context.token()); - } - code.push(");"); - context.pushCode(code.join('')); - } - - function visitCode(context) { - var code = []; - while (context.next()) { - if (context.token() == "%>" || context.token() == "%]") - break; - code.push(context.token()); - } - context.pushCode(code.join('')); - } - - function visitTextFragment(context) { - var i = context.pushData(context.token()); - context.pushCode("$p.push($data["+i+"]);"); - } - - var compile = function (str) { - if (!str) - return function() { return "";}; - - var ctx = parseTemplate(str); - visitTemplate(ctx); - return ctx.compile(); - }; - - var cache = {}; - - compile.load = function (id, require, callback) { - var url = require.toUrl(id); - if (url in cache) { - callback(cache[url]); - } else { - request(url).then(compile).then(function (tc) { - callback(cache[url] = tc); - }, function (err) { - require.signal("error", [{ - error: err, - src: 'implab/text/template-compile' - }]); - }); - } - }; - - return compile; - }); \ No newline at end of file diff --git a/src/ts/Cancellation.ts b/src/main/ts/Cancellation.ts rename from src/ts/Cancellation.ts rename to src/main/ts/Cancellation.ts --- a/src/ts/Cancellation.ts +++ b/src/main/ts/Cancellation.ts @@ -40,21 +40,21 @@ export class Cancellation implements ICa else this._cbs.push(cb); - let me = this; + const me = this; return { - destroy() { - me._unregister(cb); - } + destroy() { + me._unregister(cb); + } }; } } - + private _unregister(cb) { - if(this._cbs) { - let i = this._cbs.indexOf(cb); - if ( i>=0 ) - this._cbs.splice(i,1); - } + if (this._cbs) { + const i = this._cbs.indexOf(cb); + if (i >= 0) + this._cbs.splice(i, 1); + } } private _cancel(reason) { @@ -63,7 +63,6 @@ export class Cancellation implements ICa this._reason = (reason = reason || new Error("Operation cancelled")); - if (this._cbs) { this._cbs.forEach(cb => cb(reason)); this._cbs = null; @@ -83,7 +82,7 @@ export class Cancellation implements ICa }, register(_cb: (e: any) => void): IDestroyable { - return destroyed; + return destroyed; } }; -} \ No newline at end of file +} diff --git a/src/ts/Observable.ts b/src/main/ts/Observable.ts rename from src/ts/Observable.ts rename to src/main/ts/Observable.ts --- a/src/ts/Observable.ts +++ b/src/main/ts/Observable.ts @@ -1,36 +1,30 @@ -import { IObservable, IDestroyable, ICancellation } from './interfaces'; -import { Cancellation } from './Cancellation' -import { argumentNotNull } from './safe'; - +import { IObservable, IDestroyable, ICancellation } from "./interfaces"; +import { Cancellation } from "./Cancellation"; +import { argumentNotNull } from "./safe"; -interface Handler { - (x: T): void -} +type Handler = (x: T) => void; -interface Initializer { - (notify: Handler, error?: (e: any) => void, complete?: () => void): void; -} +type Initializer = (notify: Handler, error?: (e: any) => void, complete?: () => void) => void; // TODO: think about to move this interfaces.ts and make it public interface IObserver { - next(event: T): void + next(event: T): void; - error(e: any): void + error(e: any): void; - complete(): void + complete(): void; } -const noop = () => {}; +const noop = () => { }; export class Observable implements IObservable { private _once = new Array>(); private _observers = new Array>(); + private _complete: boolean; - private _complete: boolean - - private _error: any + private _error: any; constructor(func?: Initializer) { if (func) @@ -43,21 +37,21 @@ export class Observable implements IO /** * Registers handlers for the current observable object. - * + * * @param next the handler for events * @param error the handler for a error * @param complete the handler for a completion * @returns {IDestroyable} the handler for the current subscription, this * handler can be used to unsubscribe from events. - * + * */ on(next: Handler, error?: Handler, complete?: () => void): IDestroyable { argumentNotNull(next, "next"); - let me = this; + const me = this; - let observer: IObserver & IDestroyable = { - next: next, + const observer: IObserver & IDestroyable = { + next, error: error ? error.bind(null) : noop, complete: complete ? complete.bind(null) : noop, @@ -68,7 +62,6 @@ export class Observable implements IO this._addObserver(observer); - return observer; } @@ -90,19 +83,19 @@ export class Observable implements IO /** * Waits for the next event. This method can't be used to read messages * as a sequence since it can skip some messages between calls. - * + * * @param ct a cancellation token */ next(ct: ICancellation = Cancellation.none): Promise { return new Promise((resolve, reject) => { - let observer: IObserver = { + const observer: IObserver = { next: resolve, error: reject, complete: () => reject("No more events are available") }; if (this._addOnce(observer) && ct.isSupported()) { - ct.register((e) => { + ct.register(e => { this._removeOnce(observer); reject(e); }); @@ -131,48 +124,44 @@ export class Observable implements IO } private _removeOnce(d: IObserver) { - let i = this._once.indexOf(d); + const i = this._once.indexOf(d); if (i >= 0) this._once.splice(i, 1); } private _removeObserver(d: IObserver) { - let i = this._observers.indexOf(d); + const i = this._observers.indexOf(d); if (i >= 0) this._observers.splice(i, 1); } private _notify(guard: (observer: IObserver) => void) { - if (this._once.length) { - for (let i = 0; i < this._once.length; i++) - guard(this._once[i]); - this._once = []; - } + this._once.forEach(guard); + this._once = []; - for (let i = 0; i < this._observers.length; i++) - guard(this._observers[i]); + this._observers.forEach(guard); } protected _notifyNext(evt: T) { - let guard = (observer: IObserver) => { + const guard = (observer: IObserver) => { try { observer.next(evt); } catch (e) { this.onObserverException(e); } - } + }; this._notify(guard); } protected _notifyError(e: any) { - let guard = (observer: IObserver) => { + const guard = (observer: IObserver) => { try { observer.error(e); } catch (e) { this.onObserverException(e); } - } + }; this._notify(guard); this._observers = []; @@ -180,16 +169,16 @@ export class Observable implements IO } protected _notifyCompleted() { - let guard = (observer: IObserver) => { + const guard = (observer: IObserver) => { try { observer.complete(); } catch (e) { this.onObserverException(e); } - } + }; this._notify(guard); this._observers = []; this._complete = true; } -} \ No newline at end of file +} diff --git a/src/ts/Uuid.ts b/src/main/ts/Uuid.ts rename from src/ts/Uuid.ts rename to src/main/ts/Uuid.ts --- a/src/ts/Uuid.ts +++ b/src/main/ts/Uuid.ts @@ -6,9 +6,11 @@ // Copyright (c) 2010-2012 Robert Kieffer // MIT License - http://opensource.org/licenses/mit-license.php -declare var window: any; +declare const window: any; +declare const require; +declare const Buffer; -let _window : any = 'undefined' !== typeof window ? window : null; +const _window: any = "undefined" !== typeof window ? window : null; // Unique ID creation requires a high quality random # generator. We // feature @@ -19,14 +21,14 @@ let _rng; function setupBrowser() { // Allow for MSIE11 msCrypto - let _crypto = _window.crypto || _window.msCrypto; + const _crypto = _window.crypto || _window.msCrypto; if (!_rng && _crypto && _crypto.getRandomValues) { // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto // // Moderately fast, high quality try { - let _rnds8 = new Uint8Array(16); + const _rnds8 = new Uint8Array(16); _rng = function whatwgRNG() { _crypto.getRandomValues(_rnds8); return _rnds8; @@ -41,9 +43,9 @@ function setupBrowser() { // If all else fails, use Math.random(). It's fast, but is of // unspecified // quality. - let _rnds = new Array(16); - _rng = function () { - for (var i = 0, r; i < 16; i++) { + const _rnds = new Array(16); + _rng = () => { + for (let i = 0, r; i < 16; i++) { if ((i & 0x03) === 0) { r = Math.random() * 0x100000000; } @@ -52,7 +54,7 @@ function setupBrowser() { return _rnds; }; - if ('undefined' !== typeof console && console.warn) { + if ("undefined" !== typeof console && console.warn) { console.warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()"); } } @@ -63,12 +65,10 @@ function setupNode() { // http://nodejs.org/docs/v0.6.2/api/crypto.html // // Moderately fast, high quality - if ('function' === typeof require) { + if ("function" === typeof require) { try { - let _rb = require('crypto').randomBytes; - _rng = _rb && function () { - return _rb(16); - }; + const _rb = require("crypto").randomBytes; + _rng = _rb && (() => _rb(16)); _rng(); } catch (e) { /**/ } } @@ -81,22 +81,22 @@ if (_window) { } // Buffer class to use -let BufferClass = ('function' === typeof Buffer) ? Buffer : Array; +const BufferClass = ("function" === typeof Buffer) ? Buffer : Array; // Maps for number <-> hex string conversion -let _byteToHex = []; -let _hexToByte = {}; +const _byteToHex = []; +const _hexToByte = {}; for (let i = 0; i < 256; i++) { _byteToHex[i] = (i + 0x100).toString(16).substr(1); _hexToByte[_byteToHex[i]] = i; } // **`parse()` - Parse a UUID into it's component bytes** -function parse(s, buf?, offset?) : Array { - let i = (buf && offset) || 0, ii = 0; +export function _parse(s, buf?, offset?): Array { + const i = (buf && offset) || 0; let ii = 0; buf = buf || []; - s.toLowerCase().replace(/[0-9a-f]{2}/g, function (oct) { + s.toLowerCase().replace(/[0-9a-f]{2}/g, oct => { if (ii < 16) { // Don't overflow! buf[i + ii++] = _hexToByte[oct]; } @@ -111,12 +111,12 @@ function parse(s, buf?, offset?) : Array } // **`unparse()` - Convert UUID byte array (ala parse()) into a string** -function unparse(buf, offset?) : string { - let i = offset || 0, bth = _byteToHex; +function _unparse(buf, offset?): string { + let i = offset || 0; const bth = _byteToHex; return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + - bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' + - bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + - bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + "-" + + bth[buf[i++]] + bth[buf[i++]] + "-" + bth[buf[i++]] + + bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]]; } @@ -126,11 +126,11 @@ function unparse(buf, offset?) : string // and http://docs.python.org/library/uuid.html // random #'s we need to init node and clockseq -let _seedBytes = _rng(); +const _seedBytes = _rng(); // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = // 1) -let _nodeId = [ +const _nodeId = [ _seedBytes[0] | 0x01, _seedBytes[1], _seedBytes[2], @@ -143,12 +143,12 @@ let _nodeId = [ let _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff; // Previous uuid creation time -let _lastMSecs = 0, _lastNSecs = 0; +let _lastMSecs = 0; let _lastNSecs = 0; // See https://github.com/broofa/node-uuid for API details -function v1(options?, buf?, offset?) : string { +export function _v1(options?, buf?, offset?): string { let i = buf && offset || 0; - let b = buf || []; + const b = buf || []; options = options || {}; @@ -170,7 +170,7 @@ function v1(options?, buf?, offset?) : s let nsecs = (options.nsecs != null) ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs) - let dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000; + const dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression if (dt < 0 && options.clockseq == null) { @@ -187,7 +187,7 @@ function v1(options?, buf?, offset?) : s // Per 4.2.1.2 Throw error if too many uuids are requested if (nsecs >= 10000) { throw new Error( - 'uuid.v1(): Can\'t create more than 10M uuids/sec'); + "uuid.v1(): Can't create more than 10M uuids/sec"); } _lastMSecs = msecs; @@ -198,14 +198,14 @@ function v1(options?, buf?, offset?) : s msecs += 12219292800000; // `time_low` - let tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; b[i++] = tl >>> 24 & 0xff; b[i++] = tl >>> 16 & 0xff; b[i++] = tl >>> 8 & 0xff; b[i++] = tl & 0xff; // `time_mid` - let tmh = (msecs / 0x100000000 * 10000) & 0xfffffff; + const tmh = (msecs / 0x100000000 * 10000) & 0xfffffff; b[i++] = tmh >>> 8 & 0xff; b[i++] = tmh & 0xff; @@ -220,28 +220,28 @@ function v1(options?, buf?, offset?) : s b[i++] = clockseq & 0xff; // `node` - let node = options.node || _nodeId; + const node = options.node || _nodeId; for (let n = 0; n < 6; n++) { b[i + n] = node[n]; } - return buf ? buf : unparse(b); + return buf ? buf : _unparse(b); } // **`v4()` - Generate random UUID** // See https://github.com/broofa/node-uuid for API details -function v4(options?, buf?, offset?) : string { +export function _v4(options?, buf?, offset?): string { // Deprecated - 'format' argument, as supported in v1.2 - let i = buf && offset || 0; + const i = buf && offset || 0; - if (typeof (options) === 'string') { - buf = (options === 'binary') ? new BufferClass(16) : null; + if (typeof (options) === "string") { + buf = (options === "binary") ? new BufferClass(16) : null; options = null; } options = options || {}; - let rnds = options.random || (options.rng || _rng)(); + const rnds = options.random || (options.rng || _rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = (rnds[6] & 0x0f) | 0x40; @@ -254,29 +254,16 @@ function v4(options?, buf?, offset?) : s } } - return buf || unparse(rnds); + return buf || _unparse(rnds); +} + +export function Uuid() { + return _v4(); } -// Export public API -const empty = "00000000-0000-0000-0000-000000000000"; - -interface uuid { - (options?, buf?, offset?) : string; - v1(options?, buf?, offset?) : string; - v4(options?, buf?, offset?) : string; - readonly empty: string; - parse(s, buf?, offset?) : Array; - unparse(buf, offset?) : string; +export namespace Uuid { + export const v4 = _v4; + export const v1 = _v1; + export const empty = "00000000-0000-0000-0000-000000000000"; + export const parse = _parse; } - -export = (() =>{ - var f : any = function(options?, buf?, offset?) : string { - return v4(options, buf, offset); - }; - f.v1 = v1; - f.v4 = v4; - f.empty = empty; - f.parse = parse; - f.unparse = unparse; - return f; -})(); \ No newline at end of file diff --git a/src/ts/components/ActivatableMixin.ts b/src/main/ts/components/ActivatableMixin.ts rename from src/ts/components/ActivatableMixin.ts rename to src/main/ts/components/ActivatableMixin.ts --- a/src/ts/components/ActivatableMixin.ts +++ b/src/main/ts/components/ActivatableMixin.ts @@ -1,11 +1,11 @@ -import { IActivationController, IActivatable, ICancellation } from '../interfaces'; -import { AsyncComponent } from './AsyncComponent'; -import { Cancellation } from '../Cancellation'; -import { TraceSource } from '../log/TraceSource'; +import { IActivationController, IActivatable, ICancellation } from "../interfaces"; +import { AsyncComponent } from "./AsyncComponent"; +import { Cancellation } from "../Cancellation"; +import { TraceSource } from "../log/TraceSource"; type Constructor = new (...args: any[]) => T; -const log = TraceSource.get('@implab/core/components/ActivatableMixin'); +const log = TraceSource.get("@implab/core/components/ActivatableMixin"); export function ActivatableMixin>(Base: TBase) { return class extends Base implements IActivatable { @@ -77,7 +77,7 @@ export function ActivatableMixin = Promise.resolve(); - getCompletion() { return this._completion }; + getCompletion() { return this._completion; } runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { // create inner cancellation bound to the passed cancellation token let h: IDestroyable; - let inner = new Cancellation(cancel => { + const inner = new Cancellation(cancel => { this._cancel = cancel; h = ct.register(cancel); }); // TODO create cancellation source here - let guard = async () => { + const guard = async () => { try { await op(inner); } finally { @@ -28,7 +28,7 @@ export class AsyncComponent implements I destroy(h); this._cancel = null; } - } + }; return this._completion = guard(); } @@ -37,4 +37,4 @@ export class AsyncComponent implements I if (this._cancel) this._cancel(reason); } -} \ No newline at end of file +} diff --git a/src/main/ts/di.ts b/src/main/ts/di.ts new file mode 100644 diff --git a/src/main/ts/di/ActivationContext.ts b/src/main/ts/di/ActivationContext.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ActivationContext.ts @@ -0,0 +1,132 @@ +import { TraceSource } from "../log/TraceSource"; +import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; +import { Descriptor, ServiceMap } from "./interfaces"; +import { Container } from "./Container"; + +const trace = TraceSource.get("@implab/core/di/ActivationContext"); + +export interface ActivationContextInfo { + name: string; + + service: string; + + scope: ServiceMap; +} + +export class ActivationContext { + _cache: object; + + _services: ServiceMap; + + _stack: ActivationContextInfo[]; + + _visited: object; + + _name: string; + + _localized: boolean; + + container: Container; + + constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) { + argumentNotNull(container, "container"); + argumentNotNull(services, "services"); + + this._name = name; + this._visited = visited || {}; + this._stack = []; + this._cache = cache || {}; + this._services = services; + this.container = container; + } + + getName() { + return this._name; + } + + resolve(name, def?): any { + const d = this._services[name]; + + if (!d) + if (arguments.length > 1) + return def; + else + throw new Error(`Service ${name} not found`); + + return this.activate(d, name); + } + + /** + * registers services local to the the activation context + * + * @name{string} the name of the service + * @service{string} the service descriptor to register + */ + register(name: string, service: Descriptor) { + argumentNotEmptyString(name, "name"); + + this._services[name] = service; + } + + clone() { + return new ActivationContext( + this.container, + this._services, + this._name, + this._cache, + this._visited + ); + } + + has(id: string) { + return id in this._cache; + } + + get(id: string) { + return this._cache[id]; + } + + store(id: string, value) { + return (this._cache[id] = value); + } + + activate(d: Descriptor, name: string) { + if (trace.isLogEnabled()) + trace.log(`enter ${name} ${d}`); + + this.enter(name, d.toString()); + const v = d.activate(this); + this.leave(); + + if (trace.isLogEnabled()) + trace.log(`leave ${name}`); + + return v; + } + + visit(id: string) { + const count = this._visited[id] || 0; + this._visited[id] = count + 1; + return count; + } + + getStack() { + return this._stack.slice().reverse(); + } + + private enter(name: string, service: string) { + this._stack.push({ + name, + service, + scope: this._services + }); + this._name = name; + this._services = Object.create(this._services); + } + + private leave() { + const ctx = this._stack.pop(); + this._services = ctx.scope; + this._name = ctx.name; + } +} diff --git a/src/main/ts/di/ActivationError.ts b/src/main/ts/di/ActivationError.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ActivationError.ts @@ -0,0 +1,36 @@ +import { ActivationContextInfo } from "./ActivationContext"; + +export class ActivationError { + activationStack: ActivationContextInfo[]; + + service: string; + + innerException: any; + + message: string; + + constructor(service: string, activationStack: ActivationContextInfo[], innerException) { + this.message = "Failed to activate the service"; + this.activationStack = activationStack; + this.service = service; + this.innerException = innerException; + } + + toString() { + const parts = [this.message]; + if (this.service) + parts.push("when activating: " + this.service.toString()); + + if (this.innerException) + parts.push("caused by: " + this.innerException.toString()); + + if (this.activationStack) { + parts.push("at"); + this.activationStack + .forEach(x => parts.push(` ${x.name} ${x.service}`)); + + } + + return parts.join("\n"); + } +} diff --git a/src/main/ts/di/AggregateDescriptor.ts b/src/main/ts/di/AggregateDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/AggregateDescriptor.ts @@ -0,0 +1,37 @@ +import { Descriptor, isDescriptor } from "./interfaces"; +import { ActivationContext } from "./ActivationContext"; +import { isPrimitive } from "../safe"; + +export class AggregateDescriptor implements Descriptor { + _value: object; + + constructor(value: object) { + this._value = value; + } + + activate(context: ActivationContext) { + return this._parse(this._value, context, "$value"); + } + + // TODO: make async + _parse(value, context: ActivationContext, path: string) { + if (isPrimitive(value)) + return value; + + if (isDescriptor(value)) + return context.activate(value, path); + + if (value instanceof Array) + return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)); + + const t = {}; + for (const p of Object.keys(value)) + t[p] = this._parse(value[p], context, `${path}.${p}`); + return t; + + } + + toString() { + return "@walk"; + } +} diff --git a/src/main/ts/di/ConfigError.ts b/src/main/ts/di/ConfigError.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ConfigError.ts @@ -0,0 +1,12 @@ +export class ConfigError extends Error { + inner; + + path: string; + + configName: string; + + constructor(message: string, inner?) { + super(message); + this.inner = inner; + } +} diff --git a/src/main/ts/di/Configuration.ts b/src/main/ts/di/Configuration.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/Configuration.ts @@ -0,0 +1,348 @@ +import { + ServiceRegistration, + TypeRegistration, + FactoryRegistration, + ServiceMap, + isDescriptor, + isDependencyRegistration, + DependencyRegistration, + ValueRegistration, + ActivationType, + isValueRegistration, + isTypeRegistration, + isFactoryRegistration +} from "./interfaces"; + +import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe"; +import { AggregateDescriptor } from "./AggregateDescriptor"; +import { ValueDescriptor } from "./ValueDescriptor"; +import { Container } from "./Container"; +import { ReferenceDescriptor } from "./ReferenceDescriptor"; +import { TypeServiceDescriptor } from "./TypeServiceDescriptor"; +import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor"; +import { TraceSource } from "../log/TraceSource"; +import { ConfigError } from "./ConfigError"; +import { Cancellation } from "../Cancellation"; + +const trace = TraceSource.get("@implab/core/di/Configuration"); + +declare const define; +declare const require; + +function hasAmdLoader() { + return (typeof define === "function" && define.amd); +} + +async function mapAll(data: object | any[], map?: (v, k) => any): Promise { + if (data instanceof Array) { + return Promise.all(map ? data.map(map) : data); + } else { + const keys = Object.keys(data); + + const o: any = {}; + + await Promise.all(keys.map(async k => { + const v = map ? map(data[k], k) : data[k]; + o[k] = isPromise(v) ? await v : v; + })); + + return o; + } +} + +type Resolver = (qname: string) => any; + +type _key = string | number; + +export class Configuration { + + _hasInnerDescriptors = false; + + _container: Container; + + _path: Array<_key>; + + _configName: string; + + _require: Resolver; + + constructor(container: Container) { + argumentNotNull(container, container); + this._container = container; + this._path = []; + } + + async loadConfiguration(moduleName: string, ct = Cancellation.none) { + argumentNotEmptyString(moduleName, "moduleName"); + // TODO remove the code below somewehere else + if (hasAmdLoader()) { + // if we have a requirejs loader, use it directly + // don't rely on typescript 'import' function + const m = await new Promise(cb => require(["./RequireJsHelper"], cb)); + const r = m.makeResolver(require); + const config = await r(moduleName); + + return this.applyConfiguration( + config, + m.makeResolver(await m.createContextRequire(moduleName)) + ); + } else { + throw new Error("This feature is supported only with the amd loader"); + } + } + + async applyConfiguration(data: object, resolver?: Resolver, ct = Cancellation.none) { + argumentNotNull(data, "data"); + + trace.log("applyConfiguration"); + + this._configName = "$"; + + if (resolver) + this._require = resolver; + + let services: ServiceMap; + + try { + services = await this._visitRegistrations(data, "$"); + } catch (e) { + throw this._makeError(e); + } + + this._container.register(services); + } + + _makeError(inner) { + const e = new ConfigError("Failed to load configuration", inner); + e.configName = this._configName; + e.path = this._makePath(); + return e; + } + + _makePath() { + return this._path + .reduce( + (prev, cur) => typeof cur === "number" ? + `${prev}[${cur}]` : + `${prev}.${cur}` + ) + .toString(); + } + + async _resolveType(moduleName: string, localName: string) { + trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName); + try { + const m = await this._loadModule(moduleName); + return localName ? get(localName, m) : m; + } catch (e) { + trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName); + throw e; + } + } + + async _loadModule(moduleName: string) { + trace.debug("loadModule {0}", moduleName); + + const m = await this._require(moduleName); + + return m; + } + + async _visitRegistrations(data, name: _key) { + this._enter(name); + + if (data.constructor && + data.constructor.prototype !== Object.prototype) + throw new Error("Configuration must be a simple object"); + + const o: ServiceMap = {}; + const keys = Object.keys(data); + + const services = await mapAll(data, async (v, k) => { + const d = await this._visit(v, k); + return isDescriptor(d) ? d : new AggregateDescriptor(d); + }) as ServiceMap; + + this._leave(); + + return services; + } + + _enter(name: _key) { + this._path.push(name); + trace.debug(">{0}", name); + } + + _leave() { + const name = this._path.pop(); + trace.debug("<{0}", name); + } + + async _visit(data, name: string): Promise { + if (isPrimitive(data) || isDescriptor(data)) + return data; + + if (isDependencyRegistration(data)) { + return this._visitDependencyRegistration(data, name); + } else if (isValueRegistration(data)) { + return this._visitValueRegistration(data, name); + } else if (isTypeRegistration(data)) { + return this._visitTypeRegistration(data, name); + } else if (isFactoryRegistration(data)) { + return this._visitFactoryRegistration(data, name); + } else if (data instanceof Array) { + return this._visitArray(data, name); + } + + return this._visitObject(data, name); + } + + async _visitObject(data: object, name: _key) { + if (data.constructor && + data.constructor.prototype !== Object.prototype) + return new ValueDescriptor(data); + + this._enter(name); + + const v = await mapAll(data, delegate(this, "_visit")); + + // TODO: handle inline descriptors properly + // const ex = { + // activate(ctx) { + // const value = ctx.activate(this.prop, "prop"); + // // some code + // }, + // // will be turned to ReferenceDescriptor + // prop: { $dependency: "depName" } + // }; + + this._leave(); + return v; + } + + async _visitArray(data: any[], name: _key) { + if (data.constructor && + data.constructor.prototype !== Array.prototype) + return new ValueDescriptor(data); + + this._enter(name); + + const v = await mapAll(data, delegate(this, "_visit")); + this._leave(); + + return v; + } + + _makeServiceParams(data: ServiceRegistration) { + const opts: any = { + owner: this._container + }; + if (data.services) + opts.services = this._visitRegistrations(data.services, "services"); + + if (data.inject) { + this._path.push("inject"); + opts.inject = mapAll( + data.inject instanceof Array ? + data.inject : + [data.inject], + delegate(this, "_visitObject") + ); + this._leave(); + } + + if ("params" in data) + opts.params = data.params instanceof Array ? + this._visitArray(data.params, "params") : + this._visit(data.params, "params"); + + if (data.activation) { + if (typeof (data.activation) === "string") { + switch (data.activation.toLowerCase()) { + case "singleton": + opts.activation = ActivationType.Singleton; + break; + case "container": + opts.activation = ActivationType.Container; + break; + case "hierarchy": + opts.activation = ActivationType.Hierarchy; + break; + case "context": + opts.activation = ActivationType.Context; + break; + case "call": + opts.activation = ActivationType.Call; + break; + default: + throw new Error("Unknown activation type: " + + data.activation); + } + } else { + opts.activation = Number(data.activation); + } + } + + if (data.cleanup) + opts.cleanup = data.cleanup; + + return opts; + } + + async _visitValueRegistration(data: ValueRegistration, name: _key) { + this._enter(name); + const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value); + this._leave(); + return d; + } + + async _visitDependencyRegistration(data: DependencyRegistration, name: _key) { + argumentNotEmptyString(data && data.$dependency, "data.$dependency"); + this._enter(name); + const d = new ReferenceDescriptor({ + name: data.$dependency, + lazy: data.lazy, + optional: data.optional, + default: data.default, + services: data.services && await this._visitRegistrations(data.services, "services") + }); + this._leave(); + return d; + } + + async _visitTypeRegistration(data: TypeRegistration, name: _key) { + argumentNotNull(data.$type, "data.$type"); + this._enter(name); + + const opts = this._makeServiceParams(data); + if (data.$type instanceof Function) { + opts.type = data.$type; + } else { + const [moduleName, typeName] = data.$type.split(":", 2); + opts.type = this._resolveType(moduleName, typeName); + } + + const d = new TypeServiceDescriptor( + await mapAll(opts) + ); + + this._leave(); + + return d; + } + + async _visitFactoryRegistration(data: FactoryRegistration, name: _key) { + argumentOfType(data.$factory, Function, "data.$type"); + this._enter(name); + + const opts = this._makeServiceParams(data); + opts.factory = opts.$factory; + + const d = new FactoryServiceDescriptor( + await mapAll(opts) + ); + + this._leave(); + return d; + } +} diff --git a/src/main/ts/di/Container.ts b/src/main/ts/di/Container.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/Container.ts @@ -0,0 +1,128 @@ +import { ActivationContext } from "./ActivationContext"; +import { ValueDescriptor } from "./ValueDescriptor"; +import { ActivationError } from "./ActivationError"; +import { isDescriptor, ServiceMap } from "./interfaces"; +import { TraceSource } from "../log/TraceSource"; +import { Configuration } from "./Configuration"; +import { Cancellation } from "../Cancellation"; + +const trace = TraceSource.get("@implab/core/di/ActivationContext"); + +export class Container { + _services: ServiceMap; + + _cache: object; + + _cleanup: (() => void)[]; + + _root: Container; + + _parent: Container; + + constructor(parent?: Container) { + this._parent = parent; + this._services = parent ? Object.create(parent._services) : {}; + this._cache = {}; + this._cleanup = []; + this._root = parent ? parent.getRootContainer() : this; + this._services.container = new ValueDescriptor(this); + } + + getRootContainer() { + return this._root; + } + + getParent() { + return this._parent; + } + + resolve(name: string, def?) { + trace.debug("resolve {0}", name); + const d = this._services[name]; + if (d === undefined) { + if (arguments.length > 1) + return def; + else + throw new Error("Service '" + name + "' isn't found"); + } + + const context = new ActivationContext(this, this._services); + try { + return context.activate(d, name); + } catch (error) { + throw new ActivationError(name, context.getStack(), error); + } + } + + /** + * @deprecated use resolve() method + */ + getService() { + return this.resolve.apply(this, arguments); + } + + register(nameOrCollection, service?) { + if (arguments.length === 1) { + const data = nameOrCollection; + for (const name in data) + this.register(name, data[name]); + } else { + if (!isDescriptor(service)) + throw new Error("The service parameter must be a descriptor"); + + this._services[nameOrCollection] = service; + } + return this; + } + + onDispose(callback) { + if (!(callback instanceof Function)) + throw new Error("The callback must be a function"); + this._cleanup.push(callback); + } + + dispose() { + if (this._cleanup) { + for (const f of this._cleanup) + f(); + this._cleanup = null; + } + } + + /** + * @param{String|Object} config + * The configuration of the contaier. Can be either a string or an object, + * if the configuration is an object it's treated as a collection of + * services which will be registed in the contaier. + * + * @param{Function} opts.contextRequire + * The function which will be used to load a configuration or types for services. + * + */ + async configure(config: string | object, opts?, ct = Cancellation.none) { + const c = new Configuration(this); + + if (typeof (config) === "string") { + return c.loadConfiguration(config, ct); + } else { + return c.applyConfiguration(config, opts && opts.contextRequire, ct); + } + } + + createChildContainer() { + return new Container(this); + } + + has(id) { + return id in this._cache; + } + + get(id) { + return this._cache[id]; + } + + store(id, value) { + return (this._cache[id] = value); + } + +} diff --git a/src/main/ts/di/FactoryServiceDescriptor.ts b/src/main/ts/di/FactoryServiceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/FactoryServiceDescriptor.ts @@ -0,0 +1,23 @@ +import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; +import { Factory } from "../interfaces"; +import { argumentNotNull, oid } from "../safe"; +import { ActivationType } from "./interfaces"; + +export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { + factory: Factory; +} + +export class FactoryServiceDescriptor extends ServiceDescriptor { + constructor(opts: FactoryServiceDescriptorParams) { + super(opts); + + argumentNotNull(opts && opts.factory, "opts.factory"); + + // bind to null + this._factory = () => opts.factory(); + + if (opts.activation === ActivationType.Singleton) { + this._cacheId = oid(opts.factory); + } + } +} diff --git a/src/main/ts/di/ReferenceDescriptor.ts b/src/main/ts/di/ReferenceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ReferenceDescriptor.ts @@ -0,0 +1,100 @@ +import { isNull, argumentNotEmptyString, each } from "../safe"; +import { ActivationContext } from "./ActivationContext"; +import { ServiceMap, Descriptor } from "./interfaces"; +import { ActivationError } from "./ActivationError"; + +export interface ReferenceDescriptorParams { + name: string; + lazy?: boolean; + optional?: boolean; + default?; + services?: ServiceMap; +} + +export class ReferenceDescriptor implements Descriptor { + _name: string; + + _lazy = false; + + _optional = false; + + _default: any; + + _services: ServiceMap; + + constructor(opts: ReferenceDescriptorParams) { + argumentNotEmptyString(opts && opts.name, "opts.name"); + this._name = opts.name; + this._lazy = !!opts.lazy; + this._optional = !!opts.optional; + this._default = opts.default; + this._services = opts.services; + } + + activate(context: ActivationContext, name: string) { + // добавляем сервисы + if (this._services) { + for (const p of Object.keys(this._services)) + context.register(p, this._services[p]); + } + + if (this._lazy) { + const saved = context.clone(); + + return (cfg: ServiceMap) => { + // защищаем контекст на случай исключения в процессе + // активации + const ct = saved.clone(); + try { + if (cfg) { + for (const k in cfg) + ct.register(k, cfg[k]); + } + + return this._optional ? ct.resolve(this._name, this._default) : ct + .resolve(this._name); + } catch (error) { + throw new ActivationError(this._name, ct.getStack(), error); + } + }; + } else { + // добавляем сервисы + if (this._services) { + for (const p of Object.keys(this._services)) + context.register(p, this._services[p]); + } + + const v = this._optional ? + context.resolve(this._name, this._default) : + context.resolve(this._name); + + return v; + } + } + + toString() { + const opts = []; + if (this._optional) + opts.push("optional"); + if (this._lazy) + opts.push("lazy"); + + const parts = [ + "@ref " + ]; + if (opts.length) { + parts.push("{"); + parts.push(opts.join()); + parts.push("} "); + } + + parts.push(this._name); + + if (!isNull(this._default)) { + parts.push(" = "); + parts.push(this._default); + } + + return parts.join(""); + } +} diff --git a/src/main/ts/di/ServiceDescriptor.ts b/src/main/ts/di/ServiceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ServiceDescriptor.ts @@ -0,0 +1,234 @@ +import { ActivationContext } from "./ActivationContext"; +import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces"; +import { Container } from "./Container"; +import { argumentNotNull, isPrimitive } from "../safe"; +import { TraceSource } from "../log/TraceSource"; + +let cacheId = 0; + +const trace = TraceSource.get("@implab/core/di/ActivationContext"); + +function injectMethod(target, method, context, args) { + const m = target[method]; + if (!m) + throw new Error("Method '" + method + "' not found"); + + if (args instanceof Array) + return m.apply(target, context.parse(args, "." + method)); + else + return m.call(target, context.parse(args, "." + method)); +} + +function makeClenupCallback(target, method: ((instance) => void) | string) { + if (typeof (method) === "string") { + return () => { + target[method](); + }; + } else { + return () => { + method(target); + }; + } +} + +// TODO: make async +function _parse(value, context: ActivationContext, path: string) { + if (isPrimitive(value)) + return value; + + trace.debug("parse {0}", path); + + if (isDescriptor(value)) + return context.activate(value, path); + + if (value instanceof Array) + return value.map((x, i) => _parse(x, context, `${path}[${i}]`)); + + const t = {}; + for (const p of Object.keys(value)) + t[p] = _parse(value[p], context, `${path}.${p}`); + + return t; +} + +export interface ServiceDescriptorParams { + activation?: ActivationType; + + owner: Container; + + params?; + + inject?: object[]; + + services?: ServiceMap; + + cleanup?: ((x) => void) | string; +} + +export class ServiceDescriptor implements Descriptor { + _instance; + + _hasInstance = false; + + _activationType = ActivationType.Call; + + _services: ServiceMap; + + _params; + + _inject: object[]; + + _cleanup: ((x) => void) | string; + + _cacheId: any; + + _owner: Container; + + constructor(opts: ServiceDescriptorParams) { + argumentNotNull(opts, "opts"); + argumentNotNull(opts.owner, "owner"); + + this._owner = opts.owner; + + if (opts.activation) + this._activationType = opts.activation; + + if (opts.params) + this._params = opts.params; + + if (opts.inject) + this._inject = opts.inject; + + if (opts.services) + this._services = opts.services; + + if (opts.cleanup) { + if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) + throw new Error( + "The cleanup parameter must be either a function or a function name"); + + this._cleanup = opts.cleanup; + } + } + + activate(context: ActivationContext) { + // if we have a local service records, register them first + let instance; + + // ensure we have a cache id + if (!this._cacheId) + this._cacheId = ++cacheId; + + switch (this._activationType) { + case ActivationType.Singleton: // SINGLETON + // if the value is cached return it + if (this._hasInstance) + return this._instance; + + // singletons are bound to the root container + const container = context.container.getRootContainer(); + + if (container.has(this._cacheId)) { + instance = container.get(this._cacheId); + } else { + instance = this._create(context); + container.store(this._cacheId, instance); + if (this._cleanup) + container.onDispose( + makeClenupCallback(instance, this._cleanup)); + } + + this._hasInstance = true; + return (this._instance = instance); + + case ActivationType.Container: // CONTAINER + // return a cached value + + if (this._hasInstance) + return this._instance; + + // create an instance + instance = this._create(context); + + // the instance is bound to the container + if (this._cleanup) + this._owner.onDispose( + makeClenupCallback(instance, this._cleanup)); + + // cache and return the instance + this._hasInstance = true; + return (this._instance = instance); + case ActivationType.Context: // CONTEXT + // return a cached value if one exists + + if (context.has(this._cacheId)) + return context.get(this._cacheId); + // context context activated instances are controlled by callers + return context.store(this._cacheId, this._create(context)); + case ActivationType.Call: // CALL + // per-call created instances are controlled by callers + return this._create(context); + case ActivationType.Hierarchy: // HIERARCHY + // hierarchy activated instances are behave much like container activated + // except they are created and bound to the child container + + // return a cached value + if (context.container.has(this._cacheId)) + return context.container.get(this._cacheId); + + instance = this._create(context); + + if (this._cleanup) + context.container.onDispose(makeClenupCallback( + instance, + this._cleanup)); + + return context.container.store(this._cacheId, instance); + default: + throw new Error("Invalid activation type: " + this._activationType); + } + } + + isInstanceCreated() { + return this._hasInstance; + } + + getInstance() { + return this._instance; + } + + _factory(...params: any[]): any { + throw Error("Not implemented"); + } + + _create(context: ActivationContext) { + trace.debug(`constructing ${context._name}`); + + if (this._activationType !== ActivationType.Call && + context.visit(this._cacheId) > 0) + throw new Error("Recursion detected"); + + if (this._services) { + for (const p in this._services) + context.register(p, this._services[p]); + } + + let instance; + + if (this._params === undefined) { + instance = this._factory(); + } else if (this._params instanceof Array) { + instance = this._factory.apply(this, _parse(this._params, context, "args")); + } else { + instance = this._factory(_parse(this._params, context, "args")); + } + + if (this._inject) { + this._inject.forEach(spec => { + for (const m in spec) + injectMethod(instance, m, context, spec[m]); + }); + } + return instance; + } +} diff --git a/src/main/ts/di/TypeServiceDescriptor.ts b/src/main/ts/di/TypeServiceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/TypeServiceDescriptor.ts @@ -0,0 +1,42 @@ +import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; +import { Constructor, Factory } from "../interfaces"; +import { argumentNotNull, isPrimitive } from "../safe"; + +export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { + type: Constructor; +} + +export class TypeServiceDescriptor extends ServiceDescriptor { + _type: Constructor; + + constructor(opts: TypeServiceDescriptorParams) { + super(opts); + argumentNotNull(opts && opts.type, "opts.type"); + + const ctor = this._type = opts.type; + + if (this._params) { + if (this._params.length) { + this._factory = (...args) => { + const t = Object.create(ctor.prototype); + const inst = ctor.apply(t, args); + return isPrimitive(inst) ? t : inst; + }; + } else { + this._factory = arg => { + return new ctor(arg); + }; + } + } else { + this._factory = () => { + return new ctor(); + }; + } + + } + + toString() { + // @constructor {singleton} foo/bar/Baz + return ``; + } +} diff --git a/src/main/ts/di/ValueDescriptor.ts b/src/main/ts/di/ValueDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/ValueDescriptor.ts @@ -0,0 +1,17 @@ +import { Descriptor } from "./interfaces"; + +export class ValueDescriptor implements Descriptor { + _value; + + constructor(value) { + this._value = value; + } + + activate() { + return this._value; + } + + toString() { + return `@type=${typeof this._value}`; + } +} diff --git a/src/main/ts/di/interfaces.ts b/src/main/ts/di/interfaces.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/interfaces.ts @@ -0,0 +1,75 @@ +import { isNull, isPrimitive } from "../safe"; +import { ActivationContext } from "./ActivationContext"; +import { Constructor, Factory } from "../interfaces"; + +export interface Descriptor { + activate(context: ActivationContext, name?: string); +} + +export function isDescriptor(x): x is Descriptor { + return (!isPrimitive(x)) && + (x.activate instanceof Function); +} + +export interface ServiceMap { + [s: string]: Descriptor; +} + +export enum ActivationType { + Singleton, + Container, + Hierarchy, + Context, + Call +} + +export interface RegistrationWithServices { + services?: object; +} + +export interface ServiceRegistration extends RegistrationWithServices { + + activation?: "singleton" | "container" | "hierarchy" | "context" | "call"; + + params?; + + inject?: object | object[]; + + cleanup?: (instance) => void | string; +} + +export interface TypeRegistration extends ServiceRegistration { + $type: string | Constructor; +} + +export interface FactoryRegistration extends ServiceRegistration { + $factory: string | Factory; +} + +export interface ValueRegistration { + $value; + parse?: boolean; +} + +export interface DependencyRegistration extends RegistrationWithServices { + $dependency: string; + lazy?: boolean; + optional?: boolean; + default?; +} + +export function isTypeRegistration(x): x is TypeRegistration { + return (!isPrimitive(x)) && ("$type" in x || "$factory" in x); +} + +export function isFactoryRegistration(x): x is FactoryRegistration { + return (!isPrimitive(x)) && ("$type" in x || "$factory" in x); +} + +export function isValueRegistration(x): x is ValueRegistration { + return (!isPrimitive(x)) && ("$value" in x); +} + +export function isDependencyRegistration(x): x is DependencyRegistration { + return (!isPrimitive(x)) && ("$dependency" in x); +} diff --git a/src/ts/interfaces.ts b/src/main/ts/interfaces.ts rename from src/ts/interfaces.ts rename to src/main/ts/interfaces.ts --- a/src/ts/interfaces.ts +++ b/src/main/ts/interfaces.ts @@ -1,3 +1,11 @@ +export type Constructor = new (...args: any[]) => T; + +export type Factory = (...args: any[]) => T; + +export interface MapOf { + [key: string]: T; +} + export interface IDestroyable { destroy(); } @@ -22,18 +30,18 @@ export interface IActivatable { * Starts the component activation * @param ct cancellation token for this operation */ - activate(ct?: ICancellation) : Promise; + activate(ct?: ICancellation): Promise; /** * Starts the component deactivation * @param ct cancellation token for this operation */ - deactivate(ct?: ICancellation) : Promise; + deactivate(ct?: ICancellation): Promise; /** * Sets the activation controller for this component * @param controller The activation controller - * + * * Activation controller checks whether this component * can be activated and manages the active state of the * component @@ -71,6 +79,6 @@ export interface ICancellable { } export interface IObservable { - on(next: (x:T) => void, error?: (e:any) => void, complete?:() => void): IDestroyable; - next(ct?: ICancellation) : Promise; -} \ No newline at end of file + on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable; + next(ct?: ICancellation): Promise; +} diff --git a/src/main/ts/log/Registry.ts b/src/main/ts/log/Registry.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/log/Registry.ts @@ -0,0 +1,55 @@ +import { TraceSource } from "./TraceSource"; +import { argumentNotNull } from "../safe"; +import { IDestroyable } from "../interfaces"; + +export class Registry { + static readonly instance = new Registry(); + + private _registry: object = new Object(); + private _listeners: object = new Object(); + private _nextCookie: number = 1; + + get(id: any): TraceSource { + argumentNotNull(id, "id"); + + if (this._registry[id]) + return this._registry[id]; + + const source = new TraceSource(id); + this._registry[id] = source; + this._onNewSource(source); + + return source; + } + + add(id: any, source: TraceSource) { + argumentNotNull(id, "id"); + argumentNotNull(source, "source"); + + this._registry[id] = source; + this._onNewSource(source); + } + + _onNewSource(source: TraceSource) { + for (const i in this._listeners) + this._listeners[i].call(null, source); + } + + on(handler: (source: TraceSource) => void): IDestroyable { + argumentNotNull(handler, "handler"); + const me = this; + + const cookie = this._nextCookie++; + + this._listeners[cookie] = handler; + + for (const i in this._registry) + handler(this._registry[i]); + + return { + destroy() { + delete me._listeners[cookie]; + } + }; + } +} diff --git a/src/ts/log/TraceSource.ts b/src/main/ts/log/TraceSource.ts rename from src/ts/log/TraceSource.ts rename to src/main/ts/log/TraceSource.ts --- a/src/ts/log/TraceSource.ts +++ b/src/main/ts/log/TraceSource.ts @@ -1,7 +1,6 @@ -import * as format from '../text/format' -import { argumentNotNull } from '../safe'; -import { Observable } from '../Observable' -import { IDestroyable } from '../interfaces'; +import { Observable } from "../Observable"; +import { Registry } from "./Registry"; +import { format } from "../text/StringFormat"; export const DebugLevel = 400; @@ -13,91 +12,33 @@ export const ErrorLevel = 100; export const SilentLevel = 0; -export class TraceEvent { +export interface TraceEvent { readonly source: TraceSource; - readonly level: Number; + readonly level: number; readonly arg: any; - - constructor(source: TraceSource, level: Number, arg: any) { - this.source = source; - this.level = level; - this.arg = arg; - } -} - -class Registry { - static readonly instance = new Registry(); - - private _registry: object = new Object(); - private _listeners: object = new Object(); - private _nextCookie: number = 1; - - get(id: any): TraceSource { - argumentNotNull(id, "id"); - - if (this._registry[id]) - return this._registry[id]; - - var source = new TraceSource(id); - this._registry[id] = source; - this._onNewSource(source); - - return source; - } - - add(id: any, source: TraceSource) { - argumentNotNull(id, "id"); - argumentNotNull(source, "source"); - - this._registry[id] = source; - this._onNewSource(source); - } - - _onNewSource(source: TraceSource) { - for (let i in this._listeners) - this._listeners[i].call(null, source); - } - - on(handler: (source: TraceSource) => void): IDestroyable { - argumentNotNull(handler, "handler"); - var me = this; - - var cookie = this._nextCookie++; - - this._listeners[cookie] = handler; - - for (let i in this._registry) - handler(this._registry[i]); - - return { - destroy() { - delete me._listeners[cookie]; - } - }; - } } export class TraceSource { - readonly id: any + readonly id: any; - level: number + level: number; - readonly events: Observable + readonly events: Observable; - _notifyNext: (arg: TraceEvent) => void + _notifyNext: (arg: TraceEvent) => void; constructor(id: any) { this.id = id || new Object(); - this.events = new Observable((next) => { + this.events = new Observable(next => { this._notifyNext = next; - }) + }); } protected emit(level: number, arg: any) { - this._notifyNext(new TraceEvent(this, level, arg)); + this._notifyNext({ source: this, level, arg }); } isDebugEnabled() { @@ -106,7 +47,7 @@ export class TraceSource { debug(msg: string, ...args: any[]) { if (this.isEnabled(DebugLevel)) - this.emit(DebugLevel, format.apply(null, arguments)); + this.emit(DebugLevel, format(msg, args)); } isLogEnabled() { @@ -115,7 +56,7 @@ export class TraceSource { log(msg: string, ...args: any[]) { if (this.isEnabled(LogLevel)) - this.emit(LogLevel, format.apply(null, arguments)); + this.emit(LogLevel, format(msg, args)); } isWarnEnabled() { @@ -124,7 +65,7 @@ export class TraceSource { warn(msg: string, ...args: any[]) { if (this.isEnabled(WarnLevel)) - this.emit(WarnLevel, format.apply(null, arguments)); + this.emit(WarnLevel, format(msg, args)); } /** @@ -136,7 +77,7 @@ export class TraceSource { /** * Traces a error. - * + * * @param msg the message. * @param args parameters which will be substituted in the message. */ @@ -148,7 +89,7 @@ export class TraceSource { /** * Checks whether the specified level is enabled for this * trace source. - * + * * @param level the trace level which should be checked. */ isEnabled(level: number) { @@ -157,7 +98,7 @@ export class TraceSource { /** * Traces a raw event, passing data as it is to the underlying listeners - * + * * @param level the level of the event * @param arg the data of the event, can be a simple string or any object. */ @@ -169,7 +110,7 @@ export class TraceSource { /** * Register the specified handler to be called for every new and already * created trace source. - * + * * @param handler the handler which will be called for each trace source */ static on(handler: (source: TraceSource) => void) { @@ -178,11 +119,10 @@ export class TraceSource { /** * Creates or returns already created trace source for the specified id. - * + * * @param id the id for the trace source */ static get(id: any) { return Registry.instance.get(id); } } - diff --git a/src/ts/log/writers/ConsoleWriter.ts b/src/main/ts/log/writers/ConsoleWriter.ts rename from src/ts/log/writers/ConsoleWriter.ts rename to src/main/ts/log/writers/ConsoleWriter.ts --- a/src/ts/log/writers/ConsoleWriter.ts +++ b/src/main/ts/log/writers/ConsoleWriter.ts @@ -1,12 +1,13 @@ import { IObservable, IDestroyable, ICancellation } from "../../interfaces"; +import { TraceEvent, LogLevel, WarnLevel, DebugLevel } from "../TraceSource"; import { Cancellation } from "../../Cancellation"; -import { TraceEvent, LogLevel, WarnLevel } from "../TraceSource"; +import { destroy } from "../../safe"; export class ConsoleWriter implements IDestroyable { readonly _subscriptions = new Array(); writeEvents(source: IObservable, ct: ICancellation = Cancellation.none) { - var subscription = source.on(this.writeEvent.bind(this)); + const subscription = source.on(this.writeEvent.bind(this)); if (ct.isSupported()) { ct.register(subscription.destroy.bind(subscription)); } @@ -14,16 +15,22 @@ export class ConsoleWriter implements ID } writeEvent(next: TraceEvent) { - if (next.level >= LogLevel) { + if (next.level >= DebugLevel) { + // tslint:disable-next-line + console.debug(next.source.id.toString(), next.arg); + } else if (next.level >= LogLevel) { + // tslint:disable-next-line console.log(next.source.id.toString(), next.arg); - } else if(next.level >= WarnLevel) { + } else if (next.level >= WarnLevel) { + // tslint:disable-next-line console.warn(next.source.id.toString(), next.arg); } else { + // tslint:disable-next-line console.error(next.source.id.toString(), next.arg); } } destroy() { - this._subscriptions.forEach(x => x.destroy()); + this._subscriptions.forEach(destroy); } -} \ No newline at end of file +} diff --git a/src/ts/main.ts b/src/main/ts/main.ts rename from src/ts/main.ts rename to src/main/ts/main.ts diff --git a/src/ts/safe.ts b/src/main/ts/safe.ts rename from src/ts/safe.ts rename to src/main/ts/safe.ts --- a/src/ts/safe.ts +++ b/src/main/ts/safe.ts @@ -1,3 +1,18 @@ +let _nextOid = 0; +const _oid = typeof Symbol === "function" ? + Symbol("__implab__oid__") : + "__implab__oid__"; + +export function oid(instance: object): string { + if (isNull(instance)) + return null; + + if (_oid in instance) + return instance[_oid]; + else + return (instance[_oid] = "oid_" + (++_nextOid)); +} + export function argumentNotNull(arg, name) { if (arg === null || arg === undefined) throw new Error("The argument " + name + " can't be null or undefined"); @@ -28,32 +43,53 @@ export function isPrimitive(arg) { } export function isInteger(arg) { - return parseInt(arg) == arg; + return parseInt(arg, 10) === arg; } export function isNumber(arg) { - return parseFloat(arg) == arg; + return parseFloat(arg) === arg; } export function isString(val) { - return typeof (val) == "string" || val instanceof String; + return typeof (val) === "string" || val instanceof String; +} + +export function isPromise(val): val is PromiseLike { + return "then" in val && val.then instanceof Function; } export function isNullOrEmptyString(str) { if (str === null || str === undefined || - ((typeof (str) == "string" || str instanceof String) && str.length === 0)) + ((typeof (str) === "string" || str instanceof String) && str.length === 0)) return true; } -export function isNotEmptyArray(arg) { +export function isNotEmptyArray(arg): arg is Array { return (arg instanceof Array && arg.length > 0); } +export function getGlobal() { + return this; +} + +export function get(member: string, context?: object) { + argumentNotEmptyString(member, "member"); + let that = context || getGlobal(); + const parts = member.split("."); + for (const m of parts) { + if (!m) + continue; + if (isNull(that = that[m])) + break; + } + return that; +} + /** * Выполняет метод для каждого элемента массива, останавливается, когда * либо достигнут конец массива, либо функция cb вернула * значение. - * + * * @param {Array | Object} obj массив элементов для просмотра * @param {Function} cb функция, вызываемая для каждого элемента * @param {Object} thisArg значение, которое будет передано в качестве @@ -61,43 +97,81 @@ export function isNotEmptyArray(arg) { * @returns Результат вызова функции cb, либо undefined * если достигнут конец массива. */ -export function each(obj, cb, thisArg) { +export function each(obj, cb, thisArg?) { argumentNotNull(cb, "cb"); - var i, x; if (obj instanceof Array) { - for (i = 0; i < obj.length; i++) { - x = cb.call(thisArg, obj[i], i); + for (let i = 0; i < obj.length; i++) { + const x = cb.call(thisArg, obj[i], i); if (x !== undefined) return x; } } else { - var keys = Object.keys(obj); - for (i = 0; i < keys.length; i++) { - var k = keys[i]; - x = cb.call(thisArg, obj[k], k); + const keys = Object.keys(obj); + for (const k of keys) { + const x = cb.call(thisArg, obj[k], k); if (x !== undefined) return x; } } } +/** Copies property values from a source object to the destination and returns + * the destination onject. + * + * @param dest The destination object into which properties from the source + * object will be copied. + * @param source The source of values which will be copied to the destination + * object. + * @param template An optional parameter specifies which properties should be + * copied from the source and how to map them to the destination. If the + * template is an array it contains the list of property names to copy from the + * source to the destination. In case of object the templates contains the map + * where keys are property names in the source and the values are property + * names in the destination object. If the template isn't specified then the + * own properties of the source are entirely copied to the destination. + * + */ +export function mixin(dest: T, source: S, template?: string[] | object): T & S { + argumentNotNull(dest, "to"); + const _res = dest as T & S; + + if (template instanceof Array) { + for (const p of template) { + if (p in source) + _res[p] = source[p]; + } + } else if (template) { + const keys = Object.keys(source); + for (const p of keys) { + if (p in template) + _res[template[p]] = source[p]; + } + } else { + const keys = Object.keys(source); + for (const p of keys) + _res[p] = source[p]; + } + + return _res; +} + /** Wraps the specified function to emulate an asynchronous execution. * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function. * @param{Function|String} fn [Required] Function wich will be wrapped. */ -export function async(_fn: (...args: any[]) => any, thisArg) : (...args: any[]) => PromiseLike { +export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike { let fn = _fn; - if (arguments.length == 2 && !(fn instanceof Function)) + if (arguments.length === 2 && !(fn instanceof Function)) fn = thisArg[fn]; if (fn == null) throw new Error("The function must be specified"); - function wrapresult(x, e?) : PromiseLike { + function wrapresult(x, e?): PromiseLike { if (e) { return { - then: function (cb, eb) { + then(cb, eb) { try { return eb ? wrapresult(eb(e)) : this; } catch (e2) { @@ -109,7 +183,7 @@ export function async(_fn: (...args: any if (x && x.then) return x; return { - then: function (cb) { + then(cb) { try { return cb ? wrapresult(cb(x)) : this; } catch (e2) { @@ -120,58 +194,58 @@ export function async(_fn: (...args: any } } - return function () { + return (...args) => { try { - return wrapresult(fn.apply(thisArg, arguments)); + return wrapresult(fn.apply(thisArg, args)); } catch (e) { return wrapresult(null, e); } }; } -export function delegate(target, _method: (string | Function)) { - let method : Function; +type _AnyFn = (...args) => any; + +export function delegate(target: T, _method: (K | _AnyFn)) { + let method; if (!(_method instanceof Function)) { argumentNotNull(target, "target"); method = target[_method]; + if (!(method instanceof Function)) + throw new Error("'method' argument must be a Function or a method name"); } else { method = _method; } - if (!(method instanceof Function)) - throw new Error("'method' argument must be a Function or a method name"); - - return function () { - return method.apply(target, arguments); + return (...args) => { + return method.apply(target, args); }; } /** * Для каждого элемента массива вызывает указанную функцию и сохраняет * возвращенное значение в массиве результатов. - * + * * @remarks cb может выполняться асинхронно, при этом одновременно будет * только одна операция. - * + * * @async */ export function pmap(items, cb) { argumentNotNull(cb, "cb"); - if (items && items.then instanceof Function) - return items.then(function (data) { - return pmap(data, cb); - }); + if (isPromise(items)) + return items.then(data => pmap(data, cb)); if (isNull(items) || !items.length) return items; - var i = 0, - result = []; + let i = 0; + const result = []; function next() { - var r, ri; + let r; + let ri; function chain(x) { result[ri] = x; @@ -182,7 +256,7 @@ export function pmap(items, cb) { r = cb(items[i], i); ri = i; i++; - if (r && r.then) { + if (isPromise(r)) { return r.then(chain); } else { result[ri] = r; @@ -197,22 +271,20 @@ export function pmap(items, cb) { /** * Выбирает первый элемент из последовательности, или обещания, если в * качестве параметра используется обещание, оно должно вернуть массив. - * + * * @param {Function} cb обработчик результата, ему будет передан первый * элемент последовательности в случае успеха * @param {Function} err обработчик исключения, если массив пустой, либо * не массив - * + * * @remarks Если не указаны ни cb ни err, тогда функция вернет либо * обещание, либо первый элемент. * @async */ -export function first(sequence: any, cb: Function, err: Function) { +export function first(sequence, cb: (x) => any, err: (x) => any) { if (sequence) { - if (sequence.then instanceof Function) { - return sequence.then(function (res) { - return first(res, cb, err); - }, err); + if (isPromise(sequence)) { + return sequence.then(res => first(res, cb, err)); } else if (sequence && "length" in sequence) { if (sequence.length === 0) { if (err) @@ -230,7 +302,7 @@ export function first(sequence: any, cb: throw new Error("The sequence is required"); } -export function destroy(d: any) { - if (d && 'destroy' in d) +export function destroy(d) { + if (d && "destroy" in d) d.destroy(); -} \ No newline at end of file +} diff --git a/src/main/ts/text/StringFormat.ts b/src/main/ts/text/StringFormat.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/text/StringFormat.ts @@ -0,0 +1,173 @@ +import { isPrimitive, isNull, each } from "../safe"; +import { MapOf } from "../interfaces"; + +type SubstFn = (name: string, format?: string) => string; +type TemplateFn = (subst: SubstFn) => string; +type ConvertFn = (value: any, format?: string) => string; + +const map = { + "\\{": "&curlopen;", + "\\}": "&curlclose;", + "&": "&", + "\\:": ":" +}; + +const rev = { + curlopen: "{", + curlclose: "}", + amp: "&", + colon: ":" +}; + +function espaceString(s: string) { + if (!s) + return s; + return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n", "\\n") + "'"; +} + +function encode(s: string) { + if (!s) + return s; + return s.replace(/\\{|\\}|&|\\:|\n/g, m => map[m] || m); +} + +function decode(s: string) { + if (!s) + return s; + return s.replace(/&(\w+);/g, (m, $1) => rev[$1] || m); +} + +function subst(s: string) { + const i = s.indexOf(":"); + let name: string; + let pattern: string; + if (i >= 0) { + name = s.substr(0, i); + pattern = s.substr(i + 1); + } else { + name = s; + } + + if (pattern) + return [ + espaceString(decode(name)), + espaceString(decode(pattern))]; + else + return [espaceString(decode(name))]; +} + +function _compile(str: string) { + if (!str) + return () => void 0; + + const chunks = encode(str).split("{"); + let chunk: string; + + const code = ["var result=[];"]; + + for (let i = 0; i < chunks.length; i++) { + chunk = chunks[i]; + + if (i === 0) { + if (chunk) + code.push("result.push(" + espaceString(decode(chunk)) + + ");"); + } else { + const len = chunk.indexOf("}"); + if (len < 0) + throw new Error("Unbalanced substitution #" + i); + + code.push("result.push(subst(" + + subst(chunk.substr(0, len)).join(",") + "));"); + if (chunk.length > len + 1) + code.push("result.push(" + + espaceString(decode(chunk.substr(len + 1))) + ");"); + } + } + + code.push("return result.join('');"); + + // the code for this function is generated from the template + // tslint:disable-next-line:function-constructor + return new Function("subst", code.join("\n")) as TemplateFn; +} + +const cache: MapOf = {}; + +export function compile(template: string) { + let compiled = cache[template]; + if (!compiled) { + compiled = _compile(template); + cache[template] = compiled; + } + return compiled; +} + +function defaultConverter(value: any, pattern: string) { + if (pattern && pattern.toLocaleLowerCase() === "json") { + const seen = []; + return JSON.stringify(value, (k, v) => { + if (!isPrimitive(v)) { + const id = seen.indexOf(v); + if (id >= 0) + return "@ref-" + id; + else { + seen.push(v); + return v; + } + } else { + return v; + } + }, 2); + } else if (isNull(value)) { + return ""; + } else if (value instanceof Date) { + return value.toISOString(); + } else { + return pattern ? value.toString(pattern) : value.toString(); + } +} + +export class Formatter { + _converters: ConvertFn[]; + + constructor(converters?: ConvertFn[]) { + this._converters = converters || []; + this._converters.push(defaultConverter); + } + + convert(value: any, pattern: string) { + for (const c of this._converters) { + const res = c(value, pattern); + if (!isNull(res)) + return res; + } + return ""; + } + + format(msg: string, ...args: any[]) { + const template = compile(msg); + + return template((name, pattern) => { + const value = args[name]; + return !isNull(value) ? this.convert(value, pattern) : ""; + }); + + } + + compile(msg: string) { + const template = compile(msg); + return (...args: any[]) => { + return template((name, pattern) => { + const value = args[name]; + return !isNull(value) ? this.convert(value, pattern) : ""; + }); + }; + } +} + +const _default = new Formatter(); + +export function format(msg: string, ...args: any[]) { + return _default.format(msg, ...args); +} diff --git a/src/main/ts/text/TemplateCompiler.ts b/src/main/ts/text/TemplateCompiler.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/text/TemplateCompiler.ts @@ -0,0 +1,102 @@ +import { format } from "./StringFormat"; +import { TraceSource, DebugLevel } from "../log/TraceSource"; +import { ITemplateParser, TokenType } from "./TemplateParser"; + +const trace = TraceSource.get("@implab/text/TemplateCompiler"); + +type TemplateFn = (obj: object) => string; + +export class TemplateCompiler { + + _data: string[]; + _code: string[]; + _wrapWith = true; + + constructor() { + this._code = []; + this._data = []; + } + + compile(parser: ITemplateParser): TemplateFn { + this.preamble(); + this.visitTemplate(parser); + this.postamble(); + + const text = this._code.join("\n"); + + try { + const compiled = new Function("obj, format, $data", text); + /** + * Функция форматирования по шаблону + * + * @type{Function} + * @param{Object} obj объект с параметрами для подстановки + */ + return (obj: object) => compiled(obj || {}, format, this._data); + } catch (e) { + trace.traceEvent(DebugLevel, [e, text, this._data]); + throw e; + } + } + + preamble() { + this._code.push( + "var $p = [];", + "var print = function(){", + " $p.push(format.apply(null,arguments));", + "};" + ); + + if (this._wrapWith) + this._code.push("with(obj){"); + } + + postamble() { + if (this._wrapWith) + this._code.push("}"); + + this._code.push("return $p.join('');"); + } + + visitTemplate(parser: ITemplateParser) { + while (parser.next()) { + switch (parser.token()) { + case TokenType.OpenBlock: + this.visitCode(parser); + break; + case TokenType.OpenInlineBlock: + this.visitInline(parser); + break; + default: + this.visitTextFragment(parser); + break; + } + } + } + + visitInline(parser: ITemplateParser) { + const code = ["$p.push("]; + while (parser.next()) { + if (parser.token() === TokenType.CloseBlock) + break; + code.push(parser.value()); + } + code.push(");"); + this._code.push(code.join("")); + } + + visitCode(parser: ITemplateParser) { + const code = []; + while (parser.next()) { + if (parser.token() === TokenType.CloseBlock) + break; + code.push(parser.value()); + } + this._code.push(code.join("")); + } + + visitTextFragment(parser: ITemplateParser) { + const i = this._data.push(parser.value()); + this._code.push("$p.push($data[" + i + "]);"); + } +} diff --git a/src/main/ts/text/TemplateParser.ts b/src/main/ts/text/TemplateParser.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/text/TemplateParser.ts @@ -0,0 +1,64 @@ +import { argumentNotEmptyString } from "../safe"; +import { MapOf } from "../interfaces"; + +const splitRx = /(<%=|\[%=|<%|\[%|%\]|%>)/; + +export enum TokenType { + None, + Text, + OpenInlineBlock, + OpenBlock, + CloseBlock +} + +const tokenMap: MapOf = { + "<%": TokenType.OpenBlock, + "[%": TokenType.OpenBlock, + "<%=": TokenType.OpenInlineBlock, + "[%=": TokenType.OpenInlineBlock, + "%>": TokenType.CloseBlock, + "%]": TokenType.CloseBlock +}; + +export interface ITemplateParser { + next(): boolean; + token(): TokenType; + value(): string; +} + +export class TemplateParser implements ITemplateParser { + + _tokens: string[]; + _pos = -1; + _type: TokenType; + _value: string; + + constructor(text: string) { + argumentNotEmptyString(text, "text"); + + this._tokens = text.split(splitRx); + this._type = TokenType.None; + } + + next() { + this._pos++; + if (this._pos < this._tokens.length) { + this._value = this._tokens[this._pos]; + this._type = tokenMap[this._value] || TokenType.Text; + return true; + } else { + this._type = TokenType.None; + this._value = undefined; + return false; + } + } + + token() { + return this._type; + } + + value() { + return this._value; + } + +} diff --git a/src/main/tsconfig.json b/src/main/tsconfig.json new file mode 100644 --- /dev/null +++ b/src/main/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "rootDir": "ts" + }, + "include": [ + "ts/**/*.ts" + ] +} \ No newline at end of file diff --git a/src/package.template.json b/src/package.template.json new file mode 100644 --- /dev/null +++ b/src/package.template.json @@ -0,0 +1,23 @@ +{ + "name": "${packageName}", + "version": "${version}", + "description": "${description}", + "main": "main.js", + "keywords": [ + "di", + "ioc", + "logging", + "template engine", + "dependency injection" + ], + "author": "${author}", + "license": "${license}", + "repository": "$repository", + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "dojo": "^1.10.0" + } + } + \ No newline at end of file diff --git a/test/js/example.js b/src/test/js/example.js rename from test/js/example.js rename to src/test/js/example.js diff --git a/src/test/js/mock/config1.js b/src/test/js/mock/config1.js new file mode 100644 --- /dev/null +++ b/src/test/js/mock/config1.js @@ -0,0 +1,20 @@ +define({ + foo: { + $type: "./Foo:Foo" + }, + + bar: { + $type: "./Bar:Bar", + params: { + db: { + provider: { + $dependency: "db" + } + }, + foo: { + $type: "./Foo:Foo" + } + } + }, + db: "db://localhost" +}); \ No newline at end of file diff --git a/test/js/plan.js b/src/test/js/plan.js rename from test/js/plan.js rename to src/test/js/plan.js --- a/test/js/plan.js +++ b/src/test/js/plan.js @@ -1,3 +1,8 @@ -//define(["./ActivatableTests", "./trace-test", "./TraceSourceTests", "./CancellationTests"]); -//define(["./CancellationTests"]); -define(["./ObservableTests"]); \ No newline at end of file +define([ + "./ActivatableTests", + "./trace-test", + "./TraceSourceTests", + "./CancellationTests", + "./ObservableTests", + "./ContainerTests" +]); \ No newline at end of file diff --git a/run-amd-tests.js b/src/test/js/run-amd-tests.js rename from run-amd-tests.js rename to src/test/js/run-amd-tests.js --- a/run-amd-tests.js +++ b/src/test/js/run-amd-tests.js @@ -2,18 +2,13 @@ var requirejs = require('requirejs'); requirejs.config({ baseUrl: '.', - map: { - "*": { - "@implab/core": "core" - } - }, packages: [{ - name: "core", - location: "build/dist" + name: "@implab/core", + location: "build/dist/amd" }, { name: "test", - location: "build/test" + location: "build/test/amd" }, { name: "dojo", diff --git a/test/js/trace-test.js b/src/test/js/trace-test.js rename from test/js/trace-test.js rename to src/test/js/trace-test.js --- a/test/js/trace-test.js +++ b/src/test/js/trace-test.js @@ -3,7 +3,7 @@ define(["tape"], function(tape) { var sourceId = '73a633f3-eab8-49b0-8601-07cae710f234'; var sourceId2 = '3ba9c7cd-ed77-437b-9a2f-1cbeb1226b5b'; tape('Load TraceSource for the module', function(t) { - require(["core/log/trace!" + sourceId, "core/log/TraceSource"], function(trace, TraceSource_1) { + require(["@implab/core/log/trace!" + sourceId, "@implab/core/log/TraceSource"], function(trace, TraceSource_1) { var TraceSource = TraceSource_1.TraceSource; t.equal(trace && trace.id, sourceId, "trace should be taken from the loader plugin parameter"); diff --git a/test/ts/ActivatableTests.ts b/src/test/ts/ActivatableTests.ts rename from test/ts/ActivatableTests.ts rename to src/test/ts/ActivatableTests.ts --- a/test/ts/ActivatableTests.ts +++ b/src/test/ts/ActivatableTests.ts @@ -1,73 +1,25 @@ -import * as tape from 'tape'; -import { ActivatableMixin} from '@implab/core/components/ActivatableMixin'; -import { AsyncComponent } from '@implab/core/components/AsyncComponent'; -import { IActivationController, IActivatable, ICancellation } from '@implab/core/interfaces'; -import { Cancellation } from '@implab/core/Cancellation'; - -class SimpleActivatable extends ActivatableMixin(AsyncComponent) { - -} - -class MockActivationController implements IActivationController { - - _active: IActivatable = null; - - - getActive() : IActivatable { - return this._active; - } +import * as tape from "tape"; +import { MockActivationController } from "./mock/MockActivationController"; +import { SimpleActivatable } from "./mock/SimpleActivatable"; - async deactivate() { - if (this._active) - await this._active.deactivate(); - this._active = null; - } - - async activate(component: IActivatable) { - if (!component || component.isActive()) - return; - component.setActivationController(this); - - await component.activate(); - } - - async activating(component: IActivatable, ct: ICancellation = Cancellation.none) { - if (component != this._active) - await this.deactivate(); - } +tape("simple activation", async t => { - async activated(component: IActivatable, ct: ICancellation = Cancellation.none) { - this._active = component; - } - - async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) { - - } + const a = new SimpleActivatable(); + t.false(a.isActive()); - async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) { - if (this._active == component) - this._active = null; - } -} - -tape('simple activation',async function(t){ - - let a = new SimpleActivatable(); - t.false(a.isActive()); - await a.activate(); t.true(a.isActive()); - + await a.deactivate(); t.false(a.isActive()); t.end(); }); -tape('controller activation', async function(t) { +tape("controller activation", async t => { - let a = new SimpleActivatable(); - let c = new MockActivationController(); + const a = new SimpleActivatable(); + const c = new MockActivationController(); t.false(a.isActive(), "the component is not active by default"); t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default"); @@ -89,11 +41,11 @@ tape('controller activation', async func t.end(); }); -tape('handle error in onActivating', async function(t) { - let a = new SimpleActivatable(); +tape("handle error in onActivating", async t => { + const a = new SimpleActivatable(); - a.onActivating = async function() { - throw "Should fail"; + a.onActivating = async () => { + throw new Error("Should fail"); }; try { @@ -105,4 +57,4 @@ tape('handle error in onActivating', asy t.false(a.isActive(), "the component should remain inactive"); t.end(); -}); \ No newline at end of file +}); diff --git a/test/ts/CancellationTests.ts b/src/test/ts/CancellationTests.ts rename from test/ts/CancellationTests.ts rename to src/test/ts/CancellationTests.ts --- a/test/ts/CancellationTests.ts +++ b/src/test/ts/CancellationTests.ts @@ -1,18 +1,18 @@ -import * as tape from 'tape'; -import { Cancellation } from '@implab/core/Cancellation'; -import { ICancellation } from '@implab/core/interfaces'; -import { delay } from './TestTraits'; +import * as tape from "tape"; +import { Cancellation } from "@implab/core/Cancellation"; +import { ICancellation } from "@implab/core/interfaces"; +import { delay } from "./TestTraits"; -tape('standalone cancellation', async t => { +tape("standalone cancellation", async t => { let doCancel: (e) => void; - let ct = new Cancellation(cancel => { + const ct = new Cancellation(cancel => { doCancel = cancel; }); let counter = 0; - let reason = "BILL"; + const reason = "BILL"; t.true(ct.isSupported(), "Cancellation must be supported"); t.false(ct.isRequested(), "Cancellation shouldn't be requested"); @@ -33,7 +33,7 @@ tape('standalone cancellation', async t t.equals(counter, 2, "The callback should be triggered immediately"); let msg; - ct.register((e) => msg = e); + ct.register(e => msg = e); t.equals(msg, reason, "The cancellation reason should be passed to callback"); try { @@ -48,9 +48,9 @@ tape('standalone cancellation', async t t.end(); }); -tape('async cancellation', async t => { +tape("async cancellation", async t => { - let ct = new Cancellation(cancel => { + const ct = new Cancellation(cancel => { cancel("STOP!"); }); @@ -64,10 +64,10 @@ tape('async cancellation', async t => { t.end(); }); -tape('cancel with external event', async t => { - let ct = new Cancellation((cancel) => { - setTimeout(x => cancel('STOP!'), 0); - }) +tape("cancel with external event", async t => { + const ct = new Cancellation(cancel => { + setTimeout(x => cancel("STOP!"), 0); + }); try { await delay(10000, ct); @@ -79,10 +79,10 @@ tape('cancel with external event', async t.end(); }); -tape('operation normal flow', async t => { +tape("operation normal flow", async t => { let htimeout; - let ct = new Cancellation((cancel) => { + const ct = new Cancellation(cancel => { htimeout = setTimeout(() => cancel("STOP!"), 1000); }); @@ -94,4 +94,4 @@ tape('operation normal flow', async t => } t.end(); -}); \ No newline at end of file +}); diff --git a/src/test/ts/ContainerTests.ts b/src/test/ts/ContainerTests.ts new file mode 100644 --- /dev/null +++ b/src/test/ts/ContainerTests.ts @@ -0,0 +1,94 @@ +import { test, TapeWriter } from "./TestTraits"; +import { Container } from "@implab/core/di/Container"; +import { ReferenceDescriptor } from "@implab/core/di/ReferenceDescriptor"; +import { AggregateDescriptor } from "@implab/core/di/AggregateDescriptor"; +import { ValueDescriptor } from "@implab/core/di/ValueDescriptor"; +import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; +import { Foo } from "./mock/Foo"; +import { Bar } from "./mock/Bar"; +import { isNull } from "@implab/core/safe"; + +test("Container register/resolve tests", async t => { + const container = new Container(); + + const connection1 = "db://localhost"; + + t.throws( + () => container.register("bla-bla", "bla-bla"), + "Do not allow to register anything other than descriptors" + ); + + t.doesNotThrow( + () => container.register("connection", new ValueDescriptor(connection1)), + "register ValueDescriptor" + ); + + t.equals(container.resolve("connection"), connection1, "resolve string value"); + + t.doesNotThrow( + () => container.register( + "dbParams", + new AggregateDescriptor({ + timeout: 10, + connection: new ReferenceDescriptor({ name: "connection" }) + }) + ), + "register AggregateDescriptor" + ); + + const dbParams = container.resolve("dbParams"); + t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); +}); + +test("Container configure/resolve tests", async t => { + + const container = new Container(); + + await container.configure({ + foo: { + $type: Foo + }, + + box: { + $type: Bar, + params: { + $dependency: "foo" + } + }, + + bar: { + $type: Bar, + params: { + db: { + provider: { + $dependency: "db" + } + } + } + } + }); + t.pass("should configure from js object"); + + const f1 = container.resolve("foo"); + + t.assert(!isNull(f1), "foo should be not null"); + + t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'"); + +}); + +test("Load configuration from module", async t => { + const container = new Container(); + + await container.configure("test/mock/config1"); + t.pass("The configuration should load"); + + const f1 = container.resolve("foo"); + + t.assert(!isNull(f1), "foo should be not null"); + + const b1 = container.resolve("bar") as Bar; + + t.assert(!isNull(b1), "bar should not be null"); + t.assert(!isNull(b1.foo), "bar.foo should not be null"); +}); diff --git a/test/ts/ObservableTests.ts b/src/test/ts/ObservableTests.ts rename from test/ts/ObservableTests.ts rename to src/test/ts/ObservableTests.ts --- a/test/ts/ObservableTests.ts +++ b/src/test/ts/ObservableTests.ts @@ -1,23 +1,22 @@ -import { TraceSource, DebugLevel } from '@implab/core/log/TraceSource' -import * as tape from 'tape'; -import { TapeWriter, delay } from './TestTraits'; -import { Observable } from '@implab/core/Observable'; -import { IObservable } from '@implab/core/interfaces'; - -let trace = TraceSource.get("ObservableTests"); +import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; +import * as tape from "tape"; +import { TapeWriter, delay } from "./TestTraits"; +import { Observable } from "@implab/core/Observable"; +import { IObservable } from "@implab/core/interfaces"; -tape('events sequence example', async t => { +const trace = TraceSource.get("ObservableTests"); +tape("events sequence example", async t => { - let events: IObservable + let events: IObservable; - let done = new Promise((resolve) => { - events = new Observable(async (notify, fail, complete) => { + const done = new Promise(resolve => { + events = new Observable(async (notify, fail, finish) => { for (let i = 0; i < 10; i++) { await delay(0); notify(i); } - complete(); + finish(); resolve(); }); }); @@ -26,7 +25,7 @@ tape('events sequence example', async t let complete = false; events.on(x => count = count + x, null, () => complete = true); - let first = await events.next(); + const first = await events.next(); t.equals(first, 0, "the first event"); t.false(complete, "the sequence is not complete"); @@ -39,10 +38,10 @@ tape('events sequence example', async t t.end(); }); -tape('event sequence termination', async t => { - let events: IObservable +tape("event sequence termination", async t => { + let events: IObservable; - let done = new Promise((resolve) => { + const done = new Promise(resolve => { events = new Observable(async (notify, fail, complete) => { await delay(0); notify(1); @@ -55,14 +54,14 @@ tape('event sequence termination', async }); let count = 0; - events.on(() => {}, (e) => count++, () => count++); + events.on(() => {}, e => count++, () => count++); - let first = await events.next(); + const first = await events.next(); t.equals(first, 1, "the first message"); try { await events.next(); t.fail("shoud throw an exception"); - } catch(e) { + } catch (e) { t.pass("the sequence is terminated"); } @@ -71,4 +70,4 @@ tape('event sequence termination', async t.equals(count, 1, "the sequence must be terminated once"); t.end(); -}); \ No newline at end of file +}); diff --git a/test/ts/TestTraits.ts b/src/test/ts/TestTraits.ts rename from test/ts/TestTraits.ts rename to src/test/ts/TestTraits.ts --- a/test/ts/TestTraits.ts +++ b/src/test/ts/TestTraits.ts @@ -1,21 +1,21 @@ import { IObservable, ICancellation, IDestroyable } from "@implab/core/interfaces"; import { Cancellation } from "@implab/core/Cancellation"; -import { TraceEvent, LogLevel, WarnLevel } from "@implab/core/log/TraceSource"; -import * as tape from 'tape'; +import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "@implab/core/log/TraceSource"; +import * as tape from "tape"; import { argumentNotNull } from "@implab/core/safe"; export class TapeWriter implements IDestroyable { - readonly _tape: tape.Test + readonly _tape: tape.Test; _subscriptions = new Array(); - constructor(tape: tape.Test) { - argumentNotNull(tape, "tape"); - this._tape = tape; + constructor(t: tape.Test) { + argumentNotNull(t, "tape"); + this._tape = t; } writeEvents(source: IObservable, ct: ICancellation = Cancellation.none) { - let subscription = source.on(this.writeEvent.bind(this)); + const subscription = source.on(this.writeEvent.bind(this)); if (ct.isSupported()) { ct.register(subscription.destroy.bind(subscription)); } @@ -23,12 +23,14 @@ export class TapeWriter implements IDest } writeEvent(next: TraceEvent) { - if (next.level >= LogLevel) { - this._tape.comment("LOG " + next.arg); + if (next.level >= DebugLevel) { + this._tape.comment(`DEBUG ${next.source.id} ${next.arg}`); + } else if (next.level >= LogLevel) { + this._tape.comment(`LOG ${next.source.id} ${next.arg}`); } else if (next.level >= WarnLevel) { - this._tape.comment("WARN " + next.arg); + this._tape.comment(`WARN ${next.source.id} ${next.arg}`); } else { - this._tape.comment("ERROR " + next.arg); + this._tape.comment(`ERROR ${next.source.id} ${next.arg}`); } } @@ -39,24 +41,49 @@ export class TapeWriter implements IDest export async function delay(timeout: number, ct: ICancellation = Cancellation.none) { let un: IDestroyable; - + try { - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { if (ct.isRequested()) { - un = ct.register(reject); - } else { - let ht = setTimeout(() => { - resolve(); - }, timeout); - - un = ct.register(e => { - clearTimeout(ht); - reject(e); - }); - } - }); + un = ct.register(reject); + } else { + const ht = setTimeout(() => { + resolve(); + }, timeout); + + un = ct.register(e => { + clearTimeout(ht); + reject(e); + }); + } + }); } finally { - if(un) - un.destroy(); - }; -} \ No newline at end of file + if (un) + un.destroy(); + } +} + +export function test(name: string, cb: (t: tape.Test) => any) { + tape(name, async t => { + const writer = new TapeWriter(t); + + TraceSource.on(ts => { + ts.level = DebugLevel; + writer.writeEvents(ts.events); + }); + + try { + await cb(t); + } catch (e) { + + // verbose error information + // tslint:disable-next-line + console.error(e); + t.fail(e); + + } finally { + t.end(); + writer.destroy(); + } + }); +} diff --git a/test/ts/TraceSourceTests.ts b/src/test/ts/TraceSourceTests.ts rename from test/ts/TraceSourceTests.ts rename to src/test/ts/TraceSourceTests.ts --- a/test/ts/TraceSourceTests.ts +++ b/src/test/ts/TraceSourceTests.ts @@ -1,15 +1,15 @@ -import { TraceSource, DebugLevel } from '@implab/core/log/TraceSource' -import * as tape from 'tape'; -import { TapeWriter } from './TestTraits'; +import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; +import * as tape from "tape"; +import { TapeWriter } from "./TestTraits"; -const sourceId = 'test/TraceSourceTests'; +const sourceId = "test/TraceSourceTests"; -tape('trace message', t => { - let trace = TraceSource.get(sourceId); +tape("trace message", t => { + const trace = TraceSource.get(sourceId); trace.level = DebugLevel; - let h = trace.events.on((ev) => { + const h = trace.events.on(ev => { t.equal(ev.source, trace, "sender should be the current trace source"); t.equal(ev.level, DebugLevel, "level should be debug level"); t.equal(ev.arg, "Hello, World!", "The message should be a formatted message"); @@ -22,16 +22,16 @@ tape('trace message', t => { h.destroy(); }); -tape('trace event', t => { - let trace = TraceSource.get(sourceId); +tape("trace event", t => { + const trace = TraceSource.get(sourceId); trace.level = DebugLevel; - let event = { + const event = { name: "custom event" }; - let h = trace.events.on((ev) => { + const h = trace.events.on(ev => { t.equal(ev.source, trace, "sender should be the current trace source"); t.equal(ev.level, DebugLevel, "level should be debug level"); t.equal(ev.arg, event, "The message should be the specified object"); @@ -44,17 +44,17 @@ tape('trace event', t => { h.destroy(); }); -tape('tape comment writer', async t => { - let writer = new TapeWriter(t); +tape("tape comment writer", async t => { + const writer = new TapeWriter(t); TraceSource.on(ts => { writer.writeEvents(ts.events); }); - let trace = TraceSource.get(sourceId); + const trace = TraceSource.get(sourceId); trace.level = DebugLevel; - trace.log("Hello, {0}!", 'World'); + trace.log("Hello, {0}!", "World"); trace.log("Multi\n line"); trace.warn("Look at me!"); trace.error("DIE!"); @@ -66,4 +66,4 @@ tape('tape comment writer', async t => { t.comment("DONE"); t.end(); -}); \ No newline at end of file +}); diff --git a/test/ts/dummy.ts b/src/test/ts/dummy.ts rename from test/ts/dummy.ts rename to src/test/ts/dummy.ts --- a/test/ts/dummy.ts +++ b/src/test/ts/dummy.ts @@ -1,13 +1,13 @@ -import * as tape from 'tape'; -import * as uuid from '@implab/core/Uuid'; +import * as tape from "tape"; +import { Uuid } from "@implab/core/Uuid"; -tape('simple', function(t){ +tape("simple", t => { t.pass("sync assert"); setTimeout(() => { t.pass("async assert"); - t.comment(uuid()); - t.ok(uuid() != uuid()); + t.comment(Uuid()); + t.ok(Uuid() !== Uuid()); // end should be called after the last assertion t.end(); }, 100); -}); \ No newline at end of file +}); diff --git a/src/test/ts/mock/Bar.ts b/src/test/ts/mock/Bar.ts new file mode 100644 --- /dev/null +++ b/src/test/ts/mock/Bar.ts @@ -0,0 +1,12 @@ +import { Foo } from "./Foo"; + +export class Bar { + name = "bar"; + + foo: Foo; + + constructor(_opts) { + if (_opts && _opts.foo) + this.foo = _opts.foo; + } +} diff --git a/src/test/ts/mock/Foo.ts b/src/test/ts/mock/Foo.ts new file mode 100644 --- /dev/null +++ b/src/test/ts/mock/Foo.ts @@ -0,0 +1,3 @@ +export class Foo { + name = "foo"; +} diff --git a/src/test/ts/mock/MockActivationController.ts b/src/test/ts/mock/MockActivationController.ts new file mode 100644 --- /dev/null +++ b/src/test/ts/mock/MockActivationController.ts @@ -0,0 +1,43 @@ +import { IActivatable, ICancellation, IActivationController } from "@implab/core/interfaces"; +import { Cancellation } from "@implab/core/Cancellation"; + +export class MockActivationController implements IActivationController { + + _active: IActivatable = null; + + getActive(): IActivatable { + return this._active; + } + + async deactivate() { + if (this._active) + await this._active.deactivate(); + this._active = null; + } + + async activate(component: IActivatable) { + if (!component || component.isActive()) + return; + component.setActivationController(this); + + await component.activate(); + } + + async activating(component: IActivatable, ct: ICancellation = Cancellation.none) { + if (component !== this._active) + await this.deactivate(); + } + + async activated(component: IActivatable, ct: ICancellation = Cancellation.none) { + this._active = component; + } + + async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) { + + } + + async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) { + if (this._active === component) + this._active = null; + } +} diff --git a/src/test/ts/mock/SimpleActivatable.ts b/src/test/ts/mock/SimpleActivatable.ts new file mode 100644 --- /dev/null +++ b/src/test/ts/mock/SimpleActivatable.ts @@ -0,0 +1,6 @@ +import { AsyncComponent } from "@implab/core/components/AsyncComponent"; +import { ActivatableMixin } from "@implab/core/components/ActivatableMixin"; + +export class SimpleActivatable extends ActivatableMixin(AsyncComponent) { + +} diff --git a/src/test/tsconfig.json b/src/test/tsconfig.json new file mode 100644 --- /dev/null +++ b/src/test/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "rootDir": "ts", + "baseUrl": ".", + "paths": { + "@implab/core/*": [ + "../../build/dist/amd/*" + ] + } + }, + "include" : [ + "ts/**/*.ts" + ] +} \ No newline at end of file diff --git a/src/ts/text/format.d.ts b/src/ts/text/format.d.ts deleted file mode 100644 --- a/src/ts/text/format.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare function format(format: string, ...args: any[]): string; - -declare namespace format { - -} - -export = format; \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es3", + "sourceMap": true, + "declaration": true, + "moduleResolution": "node", + "noEmitOnError": true, + "listFiles": true, + "lib": [ + "es5", + "es2015.promise", + "es2015.symbol", + "dom" + ], + "types": [] + }, + "files": [] +} \ No newline at end of file diff --git a/tsc.json b/tsc.json deleted file mode 100644 --- a/tsc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "amd", - "sourceMap": true, - "outDir" : "build/dist", - "declaration": true, - "lib": [ - "ES2015" - ] - }, - "include" : [ - "src/ts/**/*.ts" - ] -} \ No newline at end of file diff --git a/tsc.test.json b/tsc.test.json deleted file mode 100644 --- a/tsc.test.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "amd", - "sourceMap": true, - "outDir" : "build/test", - "moduleResolution": "node", - "lib": [ - "ES2015" - ] - }, - "include" : [ - "test/ts/**/*.ts" - ] -} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 --- /dev/null +++ b/tslint.json @@ -0,0 +1,40 @@ +{ + "extends": "tslint:recommended", + "rules": { + "align": [ + true, + "parameters", + "statements" + ], + "interface-name": [false], + "max-line-length": [ true, 185 ], + "member-access": false, + "member-ordering": [ + false, + "variables-before-functions" + ], + "no-bitwise": false, + "no-empty": false, + "no-namespace": false, + "no-string-literal": false, + "ordered-imports": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-whitespace" + ], + "object-literal-sort-keys": false, + "trailing-comma": [ + true, + { + "singleline": "never", + "multiline": "never" + } + ], + "variable-name": false, + "curly": false, + "array-type": false, + "arrow-parens": [true, "ban-single-arg-parens"] + } +} \ No newline at end of file