You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:11:17 UTC

[sling-org-apache-sling-scripting-sightly-js-provider] 01/24: SLING-4704 - Move the Sightly scripting engine artifacts from contrib to bundles

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.scripting.sightly.js.provider-1.0.10
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-js-provider.git

commit 1cfbc86a98e708f1cbc7a0f28b39bfc02611f3eb
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Tue May 12 15:39:47 2015 +0000

    SLING-4704 - Move the Sightly scripting engine artifacts from contrib to bundles
    
    * moved Sightly artifacts from contrib to bundles
    * updated SCM info
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider@1678979 13f79535-47bb-0310-9956-ffa450edef68
---
 README.md                                          |    8 +
 pom.xml                                            |  171 ++
 .../sling/scripting/sightly/js/impl/Console.java   |   49 +
 .../scripting/sightly/js/impl/JsEnvironment.java   |  205 +++
 .../scripting/sightly/js/impl/JsUseProvider.java   |  108 ++
 .../sling/scripting/sightly/js/impl/Utils.java     |   46 +
 .../sling/scripting/sightly/js/impl/Variables.java |   34 +
 .../sightly/js/impl/async/AsyncContainer.java      |   95 +
 .../sightly/js/impl/async/AsyncExtractor.java      |   95 +
 .../impl/async/TimingBindingsValuesProvider.java   |   41 +
 .../sightly/js/impl/async/TimingFunction.java      |   54 +
 .../sightly/js/impl/async/UnaryCallback.java       |   31 +
 .../sightly/js/impl/cjs/CommonJsModule.java        |   69 +
 .../sightly/js/impl/cjs/ExportsObject.java         |   46 +
 .../js/impl/jsapi/SlyBindingsValuesProvider.java   |  311 ++++
 .../scripting/sightly/js/impl/loop/EventLoop.java  |   78 +
 .../sightly/js/impl/loop/EventLoopInterop.java     |   53 +
 .../sling/scripting/sightly/js/impl/loop/Task.java |   43 +
 .../sightly/js/impl/rhino/HybridObject.java        |  163 ++
 .../scripting/sightly/js/impl/rhino/JsUtils.java   |   46 +
 .../sightly/js/impl/rhino/JsValueAdapter.java      |  164 ++
 .../sightly/js/impl/use/DependencyResolver.java    |   57 +
 .../scripting/sightly/js/impl/use/UseFunction.java |  140 ++
 .../SLING-INF/libs/sling/sightly/js/3rd-party/q.js | 1935 ++++++++++++++++++++
 .../libs/sling/sightly/js/internal/helper.js       |   43 +
 .../libs/sling/sightly/js/internal/promise.js      |   36 +
 .../libs/sling/sightly/js/internal/request.js      |  140 ++
 .../libs/sling/sightly/js/internal/resource.js     |  166 ++
 .../libs/sling/sightly/js/internal/sly.js          |   59 +
 29 files changed, 4486 insertions(+)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..173e8d3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+Apache Sling Scripting Sightly JavaScript Use Provider
+=====================================
+
+This bundle allows Sightly's USE API to access JS scripts. It also wraps Sling's JS engine in a simulated event loop.
+
+The bundle also contains a bindings values provider that adds an API layer accessible from Sightly & JS.
+
+The implementation of that API can be found in `src/main/resources/SLING-INF`.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..88d6c57
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <!-- ======================================================================= -->
+    <!-- P A R E N T   P R O J E C T                                             -->
+    <!-- ======================================================================= -->
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>22</version>
+        <relativePath />
+    </parent>
+
+    <!-- ======================================================================= -->
+    <!-- P R O J E C T                                                           -->
+    <!-- ======================================================================= -->
+    <artifactId>org.apache.sling.scripting.sightly.js.provider</artifactId>
+    <version>1.0.5-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Scripting Sightly JavaScript Use Provider</name>
+
+    <description>
+        The Apache Sling Sightly JavaScript Use Provider adds support for accessing JS scripts from Sightly's Use-API.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</developerConnection>
+        <url>http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</url>
+    </scm>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <!-- ======================================================================= -->
+    <!-- B U I L D                                                               -->
+    <!-- ======================================================================= -->
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Sling-Bundle-Resources>/libs/sling/sightly/js;path:=/SLING-INF/libs/sling/sightly/js</Sling-Bundle-Resources>
+                        <Require-Capability>io.sightly; filter:="(&amp;(version&gt;=1.0)(!(version&gt;=2.0)))"</Require-Capability>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!-- ======================================================================= -->
+    <!-- D E P E N D E N C I E S                                                 -->
+    <!-- ======================================================================= -->
+    <dependencies>
+        <!-- OSGI -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Sling -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.api</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.javascript</artifactId>
+            <version>2.0.12</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.jcr.resource</artifactId>
+                </exclusion>
+            </exclusions>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Sightly -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.scripting.sightly</artifactId>
+            <version>1.0.3-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Apache Commons -->
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+
+
+        <!-- Rhino -->
+        <dependency>
+            <groupId>org.mozilla</groupId>
+            <artifactId>rhino</artifactId>
+            <version>1.7R4</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java
new file mode 100644
index 0000000..355e253
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl;
+
+import org.slf4j.Logger;
+
+/**
+ * The console object, used for logging
+ */
+public class Console {
+
+    private final Logger logger;
+
+    public Console(Logger logger) {
+        this.logger = logger;
+    }
+
+    public void log(String msg) {
+        logger.info(msg);
+    }
+
+    public void warn(String msg) {
+        logger.warn(msg);
+    }
+
+    public void error(String msg) {
+        logger.error(msg);
+    }
+
+    public void debug(String msg) {
+        logger.debug(msg);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
new file mode 100644
index 0000000..b2d096a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+import javax.script.SimpleScriptContext;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.sightly.ResourceResolution;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+import org.apache.sling.scripting.sightly.js.impl.cjs.CommonJsModule;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoop;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.loop.Task;
+import org.apache.sling.scripting.sightly.js.impl.use.DependencyResolver;
+import org.apache.sling.scripting.sightly.js.impl.use.UseFunction;
+import org.mozilla.javascript.Context;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Environment for running JS scripts
+ */
+public class JsEnvironment {
+
+    private final ScriptEngine jsEngine;
+    private final Bindings engineBindings;
+    private EventLoop eventLoop;
+
+    public JsEnvironment(ScriptEngine jsEngine) {
+        this.jsEngine = jsEngine;
+        engineBindings = new SimpleBindings();
+        TimingBindingsValuesProvider.INSTANCE.addBindings(engineBindings);
+    }
+
+    public void initialize() {
+        Context context = Context.enter();
+        eventLoop = EventLoopInterop.obtainEventLoop(context);
+    }
+
+    public void cleanup() {
+        Context context = Context.getCurrentContext();
+        if (context == null) {
+            throw new IllegalStateException("No current context");
+        }
+        EventLoopInterop.cleanupEventLoop(context);
+        Context.exit();
+    }
+
+    /**
+     * Run a Js script at a given path
+     * @param caller the resource of the script that invokes the Js code
+     * @param path the path to the JS script
+     * @param globalBindings the global bindings for the script
+     * @param arguments the arguments from the use-plugin
+     * @param callback callback that will receive the result of the script
+     */
+    public void run(Resource caller, String path, Bindings globalBindings, Bindings arguments, UnaryCallback callback) {
+        Resource scriptResource = caller.getChild(path);
+        SlingScriptHelper scriptHelper = (SlingScriptHelper) globalBindings.get(SlingBindings.SLING);
+        Resource componentCaller = ResourceResolution.getResourceForRequest(caller.getResourceResolver(), scriptHelper.getRequest());
+        if (scriptResource == null) {
+            if (isResourceOverlay(caller, componentCaller)) {
+                scriptResource = ResourceResolution.getResourceFromSearchPath(componentCaller, path);
+            } else {
+                scriptResource = ResourceResolution.getResourceFromSearchPath(caller, path);
+            }
+        }
+        if (scriptResource == null) {
+            throw new SightlyException("Required script resource could not be located: " + path);
+        }
+        runResource(scriptResource, globalBindings, arguments, callback);
+    }
+
+    public void runResource(Resource scriptResource, Bindings globalBindings, Bindings arguments, UnaryCallback callback) {
+        ScriptContext scriptContext = new SimpleScriptContext();
+        CommonJsModule module = new CommonJsModule();
+        Bindings scriptBindings = buildBindings(scriptResource, globalBindings, arguments, module);
+        scriptContext.setBindings(scriptBindings, ScriptContext.ENGINE_SCOPE);
+        runScript(scriptResource, scriptContext, callback, module);
+    }
+
+    public AsyncContainer runResource(Resource scriptResource, Bindings globalBindings, Bindings arguments) {
+        AsyncContainer asyncContainer = new AsyncContainer();
+        runResource(scriptResource, globalBindings, arguments, asyncContainer.createCompletionCallback());
+        return asyncContainer;
+    }
+
+    /**
+     * Run a script at a given path
+     * @param caller the resource of the script that invokes the Js code
+     * @param path the path to the JS script
+     * @param globalBindings bindings for the JS script
+     * @return an asynchronous container for the result
+     * @throws UnsupportedOperationException if this method is run when the event loop is not empty
+     */
+    public AsyncContainer run(Resource caller, String path, Bindings globalBindings, Bindings arguments) {
+        AsyncContainer asyncContainer = new AsyncContainer();
+        run(caller, path, globalBindings, arguments, asyncContainer.createCompletionCallback());
+        return asyncContainer;
+    }
+
+    private Bindings buildBindings(Resource scriptResource, Bindings local, Bindings arguments, CommonJsModule commonJsModule) {
+        Bindings bindings = new SimpleBindings();
+        bindings.putAll(engineBindings);
+        DependencyResolver dependencyResolver = new DependencyResolver(scriptResource, this, local);
+        UseFunction useFunction = new UseFunction(dependencyResolver, arguments);
+        bindings.put(Variables.JS_USE, useFunction);
+        bindings.put(Variables.MODULE, commonJsModule);
+        bindings.put(Variables.EXPORTS, commonJsModule.getExports());
+        bindings.put(Variables.CONSOLE, new Console(LoggerFactory.getLogger(scriptResource.getName())));
+        bindings.putAll(local);
+        return bindings;
+    }
+
+    private void runScript(Resource scriptResource, ScriptContext scriptContext, UnaryCallback callback, CommonJsModule commonJsModule) {
+        eventLoop.schedule(scriptTask(scriptResource, scriptContext, callback, commonJsModule));
+    }
+
+    private Task scriptTask(final Resource scriptResource, final ScriptContext scriptContext,
+                            final UnaryCallback callback, final CommonJsModule commonJsModule) {
+        return new Task(new Runnable() {
+            @Override
+            public void run() {
+                Reader reader = null;
+                try {
+                    reader = new InputStreamReader(scriptResource.adaptTo(InputStream.class));
+                    Object result = jsEngine.eval(reader, scriptContext);
+                    if (commonJsModule.isModified()) {
+                        result = commonJsModule.getExports();
+                    }
+                    if (result instanceof AsyncContainer) {
+                        ((AsyncContainer) result).addListener(callback);
+                    } else {
+                        callback.invoke(result);
+                    }
+                } catch (ScriptException e) {
+                    throw new SightlyException(e);
+                } finally {
+                    IOUtils.closeQuietly(reader);
+                }
+            }
+        });
+    }
+
+    /**
+     * Using the inheritance chain created with the help of {@code sling:resourceSuperType} this method checks if {@code resourceB}
+     * inherits from {@code resourceA}. In case {@code resourceA} is a {@code nt:file}, its parent will be used for the inheritance check.
+     *
+     * @param resourceA the base resource
+     * @param resourceB the potentially overlaid resource
+     * @return {@code true} if {@code resourceB} overlays {@code resourceB}, {@code false} otherwise
+     */
+    private boolean isResourceOverlay(Resource resourceA, Resource resourceB) {
+        String resourceBSuperType = resourceB.getResourceSuperType();
+        if (StringUtils.isNotEmpty(resourceBSuperType)) {
+            ResourceResolver resolver = resourceA.getResourceResolver();
+            String parentResourceType = resourceA.getResourceType();
+            if ("nt:file".equals(parentResourceType)) {
+                parentResourceType = ResourceUtil.getParent(resourceA.getPath());
+            }
+            Resource parentB = resolver.getResource(resourceBSuperType);
+            while (parentB != null && !"/".equals(parentB.getPath()) && StringUtils.isNotEmpty(resourceBSuperType)) {
+                if (parentB.getPath().equals(parentResourceType)) {
+                    return true;
+                }
+                resourceBSuperType = parentB.getResourceSuperType();
+                parentB = resolver.getResource(resourceBSuperType);
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java
new file mode 100644
index 0000000..ac2c886
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingException;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+import org.apache.sling.scripting.sightly.js.impl.jsapi.SlyBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsValueAdapter;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+import org.apache.sling.scripting.sightly.use.ProviderOutcome;
+import org.apache.sling.scripting.sightly.use.UseProvider;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Use provider for JS scripts. Ensures proper integration between Sightly & JS code-behind.
+ */
+@Component(
+        metatype = true,
+        label = "Apache Sling Scripting Sightly JavaScript Use Provider",
+        description = "The JavaScript Use Provider is responsible for instantiating JavaScript Use-API objects."
+)
+@Service(UseProvider.class)
+@Properties({
+        @Property(
+                name = Constants.SERVICE_RANKING,
+                label = "Service Ranking",
+                description = "The Service Ranking value acts as the priority with which this Use Provider is queried to return an " +
+                        "Use-object. A higher value represents a higher priority.",
+                intValue = 80,
+                propertyPrivate = false
+        )
+})
+public class JsUseProvider implements UseProvider {
+
+    private static final String JS_ENGINE_NAME = "javascript";
+
+    private static final Logger log = LoggerFactory.getLogger(JsUseProvider.class);
+    private static final JsValueAdapter jsValueAdapter = new JsValueAdapter(new AsyncExtractor());
+
+    @Reference
+    private ScriptEngineManager scriptEngineManager = null;
+
+    @Reference
+    private SlyBindingsValuesProvider slyBindingsValuesProvider = null;
+
+    @Override
+    public ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments) {
+        Bindings globalBindings = renderContext.getBindings();
+        slyBindingsValuesProvider.processBindings(globalBindings);
+        if (!Utils.isJsScript(identifier)) {
+            return ProviderOutcome.failure();
+        }
+        ScriptEngine jsEngine = scriptEngineManager.getEngineByName(JS_ENGINE_NAME);
+        if (jsEngine == null) {
+            return ProviderOutcome.failure(new SightlyException("No JavaScript engine was defined."));
+        }
+        SlingScriptHelper scriptHelper = Utils.getHelper(globalBindings);
+        JsEnvironment environment = null;
+        try {
+            environment = new JsEnvironment(jsEngine);
+            environment.initialize();
+            String callerPath = scriptHelper.getScript().getScriptResource().getPath();
+            ResourceResolver adminResolver = renderContext.getScriptResourceResolver();
+            Resource caller = adminResolver.getResource(callerPath);
+            AsyncContainer asyncContainer = environment.run(caller, identifier, globalBindings, arguments);
+            return ProviderOutcome.success(jsValueAdapter.adapt(asyncContainer));
+        } finally {
+            if (environment != null) {
+                environment.cleanup();
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java
new file mode 100644
index 0000000..7d22159
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+import java.util.Collections;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+
+/**
+ * Utilities for script evaluation
+ */
+public class Utils {
+    private static final String EXTENSION = "js";
+
+    public static final Bindings EMPTY_BINDINGS = new SimpleBindings(Collections.<String, Object>emptyMap());
+
+    public static SlingScriptHelper getHelper(Bindings bindings) {
+        return (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+    }
+
+    public static boolean isJsScript(String identifier) {
+        String extension = StringUtils.substringAfterLast(identifier, ".");
+        return EXTENSION.equalsIgnoreCase(extension);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java
new file mode 100644
index 0000000..176e85f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl;
+
+/**
+ * Variables exposed to js scripts
+ */
+public final class Variables {
+
+    public static final String CONSOLE = "console";
+
+    public static final String JS_USE = "use";
+    public static final String SET_TIMEOUT = "setTimeout";
+    public static final String SET_IMMEDIATE = "setImmediate";
+
+    public static final String MODULE = "module";
+    public static final String EXPORTS = "exports";
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java
new file mode 100644
index 0000000..b9bbd5c
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.async;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple container for asynchronously provided values
+ */
+public class AsyncContainer {
+
+    private Object value;
+    private boolean completed;
+    private List<UnaryCallback> callbacks = new ArrayList<UnaryCallback>();
+
+    /**
+     * Add a listener that will receive the value in this container when it will
+     * be filled. If the container already has a value, the callback is called
+     * immediately.
+     * @param unaryCallback the callback that will receive the result
+     */
+    public void addListener(UnaryCallback unaryCallback) {
+        callbacks.add(unaryCallback);
+        if (completed) {
+            notifyListener(unaryCallback);
+        }
+    }
+
+    /**
+     * Get the result of this holder
+     * @return the holder result
+     * @throws java.util.NoSuchElementException if the result has not yet been set
+     */
+    public Object getResult() {
+        return value;
+    }
+
+    /**
+     * Check whether the container was completed with a value
+     * @return the completion status
+     */
+    public boolean isCompleted() {
+        return completed;
+    }
+
+    /**
+     * Complete this async container with a value
+     * @param value the result value
+     * @throws java.lang.IllegalStateException if the container has been previously filled
+     */
+    public void complete(Object value) {
+        if (completed) {
+            throw new IllegalStateException("Value was already completed");
+        }
+        completed = true;
+        this.value = value;
+        for (UnaryCallback callback : callbacks) {
+            notifyListener(callback);
+        }
+    }
+
+    /**
+     * Create a callback that will complete this container
+     * @return the completion callback
+     */
+    public UnaryCallback createCompletionCallback() {
+        return new UnaryCallback() {
+            @Override
+            public void invoke(Object arg) {
+                complete(arg);
+            }
+        };
+    }
+
+    private void notifyListener(UnaryCallback unaryCallback) {
+        unaryCallback.invoke(value);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java
new file mode 100644
index 0000000..098bd71
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.async;
+
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+
+/**
+ *
+ */
+public class AsyncExtractor {
+
+    public static final String THEN_METHOD = "then";
+
+    public void extract(Object jsObj, UnaryCallback unaryCallback) {
+        if (!isPromise(jsObj)) {
+            unaryCallback.invoke(jsObj);
+        }
+        if (jsObj instanceof AsyncContainer) {
+            ((AsyncContainer) jsObj).addListener(unaryCallback);
+        }
+        if (jsObj instanceof ScriptableObject) {
+            ScriptableObject scriptableObject = (ScriptableObject) jsObj;
+            decodeJSPromise(scriptableObject, unaryCallback);
+        }
+    }
+
+    private void decodeJSPromise(final Scriptable promise, final UnaryCallback callback) {
+        try {
+            Context context = Context.enter();
+            final AsyncContainer errorContainer = new AsyncContainer();
+            final Function errorHandler = createErrorHandler(errorContainer);
+            final Function successHandler = convertCallback(callback);
+            EventLoopInterop.schedule(context, new Runnable() {
+                @Override
+                public void run() {
+                    ScriptableObject.callMethod(promise, THEN_METHOD,
+                            new Object[] {successHandler, errorHandler});
+                }
+            });
+            if (errorContainer.isCompleted()) {
+                throw new SightlyException("Promise has completed with failure: " + Context.toString(errorContainer.getResult()));
+            }
+        } finally {
+            Context.exit();
+        }
+    }
+
+    private Function createErrorHandler(AsyncContainer asyncContainer) {
+        return convertCallback(asyncContainer.createCompletionCallback());
+    }
+
+    public boolean isPromise(Object jsObj) {
+        if (jsObj instanceof AsyncContainer) {
+            return true;
+        }
+        if (jsObj instanceof ScriptableObject) {
+            Scriptable scriptable = (Scriptable) jsObj;
+            return ScriptableObject.hasProperty(scriptable, THEN_METHOD);
+        }
+        return false;
+    }
+
+    private static Function convertCallback(final UnaryCallback unaryCallback) {
+        return new BaseFunction() {
+            @Override
+            public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+                Object arg = (args.length == 0) ? Context.getUndefinedValue() : args[0];
+                unaryCallback.invoke(arg);
+                return Context.getUndefinedValue();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java
new file mode 100644
index 0000000..623de69
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.async;
+
+import javax.script.Bindings;
+
+import org.apache.sling.scripting.api.BindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.Variables;
+
+/**
+ * Value provider for timing functions
+ */
+public final class TimingBindingsValuesProvider implements BindingsValuesProvider {
+
+    public static final TimingBindingsValuesProvider INSTANCE = new TimingBindingsValuesProvider();
+
+    private TimingBindingsValuesProvider() {
+    }
+
+    @Override
+    public void addBindings(Bindings bindings) {
+        bindings.put(Variables.SET_TIMEOUT, TimingFunction.INSTANCE);
+        bindings.put(Variables.SET_IMMEDIATE, TimingFunction.INSTANCE);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java
new file mode 100644
index 0000000..5cf43b7
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.async;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsUtils;
+
+/**
+ * Timing function for JS scripts that use async constructs
+ */
+public final class TimingFunction extends BaseFunction {
+
+    public static final TimingFunction INSTANCE = new TimingFunction();
+
+    private TimingFunction() {
+    }
+
+    @Override
+    public Object call(final Context cx, final Scriptable scope, Scriptable thisObj, Object[] args) {
+        if (args.length == 0) {
+            return Context.getUndefinedValue();
+        }
+        if (!(args[0] instanceof Function)) {
+            throw new IllegalArgumentException("Timing function must receive a function as the first argument");
+        }
+        final Function function = (Function) args[0];
+        return EventLoopInterop.schedule(cx, new Runnable() {
+            @Override
+            public void run() {
+                JsUtils.callFn(function, cx, scope, null, new Object[0]);
+            }
+        });
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java
new file mode 100644
index 0000000..0ec0e61
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.async;
+
+/**
+ * Unary callback function
+ */
+public interface UnaryCallback {
+
+    /**
+     * Call the callback with one argument
+     * @param arg the callback argument
+     */
+    void invoke(Object arg);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java
new file mode 100644
index 0000000..9b1c675
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.cjs;
+
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+/**
+ * CommonJS module implementation
+ */
+public class CommonJsModule extends ScriptableObject {
+
+    private static final String EXPORTS = "exports";
+
+    private Object exports = new ExportsObject();
+    private boolean modifiedModule;
+
+
+    @Override
+    public Object get(String name, Scriptable start) {
+        if (name.equals(EXPORTS)) {
+            return exports;
+        }
+        return super.get(name, start);
+    }
+
+    @Override
+    public void put(String name, Scriptable start, Object value) {
+        if (name.equals(EXPORTS)) {
+            setExports(value);
+        } else {
+            super.put(name, start, value);
+        }
+    }
+
+    public Object getExports() {
+        return exports;
+    }
+
+    public void setExports(Object exports) {
+        modifiedModule = true;
+        this.exports = exports;
+    }
+
+    public boolean isModified() {
+        return modifiedModule || ((ExportsObject) exports).isModified();
+    }
+
+    @Override
+    public String getClassName() {
+        return "Module";
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java
new file mode 100644
index 0000000..3822861
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.cjs;
+
+
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+/**
+ * The default exports object
+ */
+public class ExportsObject extends ScriptableObject {
+
+    private boolean modified;
+
+    @Override
+    public void put(String name, Scriptable start, Object value) {
+        modified = true;
+        super.put(name, start, value);
+    }
+
+    public boolean isModified() {
+        return modified;
+    }
+
+    @Override
+    public String getClassName() {
+        return "exports";
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
new file mode 100644
index 0000000..6731ed4
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.jsapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.SimpleBindings;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.scripting.sightly.js.impl.JsEnvironment;
+import org.apache.sling.scripting.sightly.js.impl.Variables;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingFunction;
+import org.apache.sling.scripting.sightly.js.impl.cjs.CommonJsModule;
+import org.apache.sling.scripting.sightly.js.impl.rhino.HybridObject;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsValueAdapter;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Script;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides the {@code sightly} namespace for usage in Sightly & JS scripts
+ * called from Sightly
+ */
+@Component(metatype = true, label = "Apache Sling Scripting Sightly JavaScript Bindings Provider",
+        description = "The Apache Sling Scripting Sightly JavaScript Bindings Provider loads the JS Use-API and makes it available in the" +
+                " bindings map.")
+@Service(SlyBindingsValuesProvider.class)
+@Properties({
+        @Property(
+                name = SlyBindingsValuesProvider.SCR_PROP_JS_BINDING_IMPLEMENTATIONS,
+                value = {
+                    "sightly:" + SlyBindingsValuesProvider.SLING_NS_PATH
+                },
+                unbounded = PropertyUnbounded.ARRAY,
+                label = "Script Factories",
+                description = "Script factories to load in the bindings map. The entries should be in the form " +
+                        "'namespace:/path/from/repository'."
+        )
+})
+@SuppressWarnings("unused")
+public class SlyBindingsValuesProvider {
+
+    public static final String SCR_PROP_JS_BINDING_IMPLEMENTATIONS = "org.apache.sling.scripting.sightly.js.bindings";
+
+    public static final String SLING_NS_PATH = "/libs/sling/sightly/js/internal/sly.js";
+    public static final String Q_PATH = "/libs/sling/sightly/js/3rd-party/q.js";
+
+    private static final String REQ_NS = SlyBindingsValuesProvider.class.getCanonicalName();
+
+    private static final Logger log = LoggerFactory.getLogger(SlyBindingsValuesProvider.class);
+
+    @Reference
+    private ScriptEngineManager scriptEngineManager;
+
+    @Reference
+    private ResourceResolverFactory rrf = null;
+
+    private final AsyncExtractor asyncExtractor = new AsyncExtractor();
+    private final JsValueAdapter jsValueAdapter = new JsValueAdapter(asyncExtractor);
+
+    private Map<String, String> scriptPaths = new HashMap<String, String>();
+    private Map<String, Function> factories = new HashMap<String, Function>();
+
+    private Script qScript;
+    private final ScriptableObject qScope = createQScope();
+
+    public void processBindings(Bindings bindings) {
+        if (needsInit()) {
+            init(bindings);
+        }
+        Context context = null;
+        try {
+            context = Context.enter();
+            Object qInstance = obtainQInstance(context, bindings);
+            if (qInstance == null) {
+                return;
+            }
+            for (Map.Entry<String, Function> entry : factories.entrySet()) {
+                addBinding(context, entry.getValue(), bindings, entry.getKey(), qInstance);
+            }
+        } finally {
+            if (context != null) {
+                Context.exit();
+            }
+        }
+    }
+
+    @Activate
+    protected void activate(ComponentContext componentContext) {
+        Dictionary properties = componentContext.getProperties();
+        String[] factories = PropertiesUtil.toStringArray(properties.get(SCR_PROP_JS_BINDING_IMPLEMENTATIONS), new String[]{SLING_NS_PATH});
+        scriptPaths = new HashMap<String, String>(factories.length);
+        for (String f : factories) {
+            String[] parts = f.split(":");
+            if (parts.length == 2) {
+                scriptPaths.put(parts[0], parts[1]);
+            }
+        }
+    }
+
+    @Deactivate
+    protected void deactivate(ComponentContext componentContext) {
+        if (scriptPaths != null) {
+            scriptPaths.clear();
+        }
+        if (factories != null) {
+            factories.clear();
+        }
+    }
+
+
+    private void addBinding(Context context, Function factory, Bindings bindings, String globalName, Object qInstance) {
+        if (factory == null) {
+            return;
+        }
+        Object result = factory.call(context, factory, factory, new Object[] {bindings, qInstance});
+        HybridObject global = new HybridObject((Scriptable) result, jsValueAdapter);
+        bindings.put(globalName, global);
+    }
+
+    private boolean needsInit() {
+        return factories == null || factories.isEmpty() || qScript == null;
+    }
+
+    private synchronized void init(Bindings bindings) {
+        if (needsInit()) {
+            ensureFactoriesLoaded(bindings);
+        }
+    }
+
+    private void ensureFactoriesLoaded(Bindings bindings) {
+        JsEnvironment jsEnvironment = null;
+        try {
+            ScriptEngine scriptEngine = obtainEngine();
+            if (scriptEngine == null) {
+                return;
+            }
+            jsEnvironment = new JsEnvironment(scriptEngine);
+            jsEnvironment.initialize();
+            factories = new HashMap<String, Function>(scriptPaths.size());
+            for (Map.Entry<String, String> entry : scriptPaths.entrySet()) {
+                factories.put(entry.getKey(), loadFactory(jsEnvironment, entry.getValue(), bindings));
+            }
+            qScript = loadQScript();
+        } finally {
+            if (jsEnvironment != null) {
+                jsEnvironment.cleanup();
+            }
+        }
+    }
+
+    private Function loadFactory(JsEnvironment jsEnvironment, String path, Bindings bindings) {
+        ResourceResolver resolver = null;
+        try {
+            resolver = rrf.getAdministrativeResourceResolver(null);
+            Resource resource = resolver.getResource(path);
+            if (resource == null) {
+                log.warn("Sly namespace loader could not find the following script: " + path);
+                return null;
+            }
+            AsyncContainer container = jsEnvironment.runResource(resource, createBindings(bindings), new SimpleBindings());
+            Object obj = container.getResult();
+            if (!(obj instanceof Function)) {
+                log.warn("Script was expected to return a function");
+                return null;
+            }
+            return (Function) obj;
+        } catch (LoginException e) {
+            log.error("Cannot evaluate script " + path, e);
+            return null;
+        } finally {
+            if (resolver != null) {
+                resolver.close();
+            }
+        }
+    }
+
+    private Bindings createBindings(Bindings global) {
+        Bindings bindings = new SimpleBindings();
+        bindings.putAll(global);
+        TimingBindingsValuesProvider.INSTANCE.addBindings(bindings);
+        return bindings;
+    }
+
+    private ScriptEngine obtainEngine() {
+        return scriptEngineManager.getEngineByName("javascript");
+    }
+
+    private Object obtainQInstance(Context context, Bindings bindings) {
+        if (qScript == null) {
+            return null;
+        }
+        HttpServletRequest request = (HttpServletRequest) bindings.get(SlingBindings.REQUEST);
+        Object qInstance = null;
+        if (request != null) {
+            qInstance = request.getAttribute(REQ_NS);
+        }
+        if (qInstance == null) {
+            qInstance = createQInstance(context, qScript);
+            if (request != null) {
+                request.setAttribute(REQ_NS, qInstance);
+            }
+        }
+        return qInstance;
+    }
+
+    private ScriptableObject createQScope() {
+        Context context = Context.enter();
+        try {
+            ScriptableObject scope = context.initStandardObjects();
+            ScriptableObject.putProperty(scope, Variables.SET_IMMEDIATE, TimingFunction.INSTANCE);
+            ScriptableObject.putProperty(scope, Variables.SET_TIMEOUT, TimingFunction.INSTANCE);
+            return scope;
+        } finally {
+            Context.exit();
+        }
+    }
+
+    private Object createQInstance(Context context, Script qScript) {
+        CommonJsModule module = new CommonJsModule();
+        Scriptable tempScope = context.newObject(qScope);
+        ScriptableObject.putProperty(tempScope, Variables.MODULE, module);
+        ScriptableObject.putProperty(tempScope, Variables.EXPORTS, module.getExports());
+        qScript.exec(context, tempScope);
+        return module.getExports();
+    }
+
+    private Script loadQScript() {
+        ResourceResolver resourceResolver = null;
+        Context context = Context.enter();
+        context.initStandardObjects();
+        context.setOptimizationLevel(9);
+        InputStream reader = null;
+        try {
+            resourceResolver = rrf.getAdministrativeResourceResolver(null);
+            Resource resource = resourceResolver.getResource(Q_PATH);
+            if (resource == null) {
+                log.warn("Could not load Q library at path: " + Q_PATH);
+                return null;
+            }
+            reader = resource.adaptTo(InputStream.class);
+            if (reader == null) {
+                log.warn("Could not read content of Q library");
+                return null;
+            }
+            return context.compileReader(new InputStreamReader(reader), Q_PATH, 0, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            Context.exit();
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    log.error("Error while closing reader", e);
+                }
+            }
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java
new file mode 100644
index 0000000..ed565ef
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.loop;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Simulates an event loop for the Rhino JS engine.
+ */
+public class EventLoop {
+
+    private static final Logger log = LoggerFactory.getLogger(EventLoop.class);
+
+    private Queue<Task> taskQueue = new LinkedList<Task>();
+    private boolean isRunning;
+
+    /**
+     * Add a task to the queue. If the queue is empty, start running tasks. If it
+     * isn't empty, continue running the available tasks
+     * @param task the task to be added
+     */
+    public void schedule(Task task) {
+        taskQueue.offer(task);
+        run();
+    }
+
+    private void run() {
+        if (isRunning) {
+            return;
+        }
+        isRunning = true;
+        try {
+            // Holds the first exception encountered. If there is such a first exception, it will be
+            // rethrown
+            Exception thrownException = null;
+            while (!taskQueue.isEmpty()) {
+                Task task = taskQueue.poll();
+                try {
+                    task.run();
+                } catch (Exception e) {
+                    if (thrownException == null) {
+                        thrownException = e; //first exception
+                    } else {
+                        log.error("Additional error occurred while running JS script: ", e);
+                    }
+                }
+            }
+            if (thrownException != null) {
+                throw new SightlyException(thrownException);
+            }
+        } finally {
+            isRunning = false;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java
new file mode 100644
index 0000000..af9b8d5
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.loop;
+
+import org.mozilla.javascript.Context;
+
+/**
+ * Event-loop utilities for interoperability with JS code
+ */
+public class EventLoopInterop {
+
+    public static final String EVENT_LOOP_KEY = "EventLoop";
+
+    public static EventLoop obtainEventLoop(Context context) {
+        EventLoop eventLoop = getEventLoop(context);
+        if (eventLoop == null) {
+            eventLoop = new EventLoop();
+            context.putThreadLocal(EVENT_LOOP_KEY, eventLoop);
+        }
+        return eventLoop;
+    }
+
+    public static void cleanupEventLoop(Context context) {
+        context.removeThreadLocal(EVENT_LOOP_KEY);
+    }
+
+    public static Task schedule(Context context, Runnable runnable) {
+        Task task = new Task(runnable);
+        obtainEventLoop(context).schedule(task);
+        return task;
+    }
+
+    private static EventLoop getEventLoop(Context context) {
+        return (EventLoop) context.getThreadLocal(EVENT_LOOP_KEY);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java
new file mode 100644
index 0000000..1a96a12
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.loop;
+
+/**
+ * Task in an event loop
+ */
+public class Task {
+
+    private final Runnable runnable;
+    private boolean active;
+
+    public Task(Runnable runnable) {
+        this.runnable = runnable;
+        this.active = true;
+    }
+
+    public void run() {
+        if (active) {
+            runnable.run();
+        }
+    }
+
+    public void deactivate() {
+        this.active = false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java
new file mode 100644
index 0000000..2d52120
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.rhino;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sling.scripting.sightly.Record;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+
+/**
+ * Instances of this class can be used in both Sightly & JS scripts
+ */
+public class HybridObject implements Scriptable, Record<Object> {
+
+    private final Scriptable scriptable;
+    private final JsValueAdapter jsValueAdapter;
+
+    public HybridObject(Scriptable scriptable, JsValueAdapter jsValueAdapter) {
+        this.scriptable = scriptable;
+        this.jsValueAdapter = jsValueAdapter;
+    }
+
+    // Record implementation
+
+    @Override
+    public Object getProperty(String name) {
+        if (name == null) {
+            return null;
+        }
+        Context.enter();
+        try {
+            return getAdapted(name);
+        } finally {
+            Context.exit();
+        }
+    }
+
+    @Override
+    public Set<String> getPropertyNames() {
+        Object[] properties = scriptable.getIds();
+        Set<String> keys = new HashSet<String>();
+        for (Object property: properties) {
+            if (property instanceof String) {
+                keys.add((String) property);
+            }
+        }
+        return keys;
+    }
+
+    private Object getAdapted(String key) {
+        Object obj = ScriptableObject.getProperty(scriptable, key);
+        if (obj == null) {
+            return null;
+        }
+        if (obj instanceof Function) {
+            return jsValueAdapter.adapt(JsUtils.callFn((Function) obj, null, scriptable, scriptable, new Object[0]));
+        }
+        return jsValueAdapter.adapt(obj);
+    }
+
+    // Scriptable implementation
+
+    @Override
+    public String getClassName() {
+        return scriptable.getClassName();
+    }
+
+    @Override
+    public Object get(String name, Scriptable start) {
+        return scriptable.get(name, start);
+    }
+
+    @Override
+    public Object get(int index, Scriptable start) {
+        return scriptable.get(index, start);
+    }
+
+    @Override
+    public boolean has(String name, Scriptable start) {
+        return scriptable.has(name, start);
+    }
+
+    @Override
+    public boolean has(int index, Scriptable start) {
+        return scriptable.has(index, start);
+    }
+
+    @Override
+    public void put(String name, Scriptable start, Object value) {
+        scriptable.put(name, start, value);
+    }
+
+    @Override
+    public void put(int index, Scriptable start, Object value) {
+        scriptable.put(index, start, value);
+    }
+
+    @Override
+    public void delete(String name) {
+        scriptable.delete(name);
+    }
+
+    @Override
+    public void delete(int index) {
+        scriptable.delete(index);
+    }
+
+    @Override
+    public Scriptable getPrototype() {
+        return scriptable.getPrototype();
+    }
+
+    @Override
+    public void setPrototype(Scriptable prototype) {
+        scriptable.setPrototype(prototype);
+    }
+
+    @Override
+    public Scriptable getParentScope() {
+        return scriptable.getParentScope();
+    }
+
+    @Override
+    public void setParentScope(Scriptable parent) {
+        scriptable.setParentScope(parent);
+    }
+
+    @Override
+    public Object[] getIds() {
+        return scriptable.getIds();
+    }
+
+    @Override
+    public Object getDefaultValue(Class hint) {
+        return scriptable.getDefaultValue(hint);
+    }
+
+    @Override
+    public boolean hasInstance(Scriptable instance) {
+        return scriptable.hasInstance(instance);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java
new file mode 100644
index 0000000..cfc6cad
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.rhino;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+
+/**
+ * Utilities when inter-operating with JS scripts
+ */
+public class JsUtils {
+
+    public static Object callFn(Function function, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+        boolean exitContext = false;
+        if (Context.getCurrentContext() == null) {
+            Context.enter();
+            exitContext = true;
+        }
+        Context context = (cx == null) ? Context.getCurrentContext() : cx;
+        Object result = function.call(context, scope, thisObj, args);
+        if (exitContext) {
+            Context.exit();
+        }
+        return result;
+    }
+
+
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java
new file mode 100644
index 0000000..81226c6
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.js.impl.rhino;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Wrapper;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+
+/**
+ * Converts JS objects to Java objects
+ */
+public class JsValueAdapter {
+
+    private static final Map<String, Class<?>> knownConversions = new HashMap<String, Class<?>>();
+
+    static {
+        knownConversions.put("String", String.class);
+        knownConversions.put("Date", Date.class);
+    }
+
+    private final AsyncExtractor asyncExtractor;
+
+    public JsValueAdapter(AsyncExtractor asyncExtractor) {
+        this.asyncExtractor = asyncExtractor;
+    }
+
+    /**
+     * Convert a given JS value to a Java object
+     * @param jsValue the original JS value
+     * @return the Java correspondent
+     */
+    @SuppressWarnings("unchecked")
+    public Object adapt(Object jsValue) {
+        if (jsValue == null || jsValue == Context.getUndefinedValue() || jsValue == ScriptableObject.NOT_FOUND) {
+            return null;
+        }
+        if (jsValue instanceof Wrapper) {
+            return adapt(((Wrapper) jsValue).unwrap());
+        }
+        if (asyncExtractor.isPromise(jsValue)) {
+            return adapt(forceAsync(jsValue));
+        }
+        if (jsValue instanceof ScriptableObject) {
+            return extractScriptable((ScriptableObject) jsValue);
+        }
+        if (jsValue instanceof CharSequence) {
+            //convert any string-like type to plain java strings
+            return jsValue.toString();
+        }
+        if (jsValue instanceof Map) {
+            return convertMap((Map) jsValue);
+        }
+        if (jsValue instanceof Iterable) {
+            return convertIterable((Iterable) jsValue);
+        }
+        if (jsValue instanceof Number) {
+            return convertNumber((Number) jsValue);
+        }
+        if (jsValue instanceof Object[]) {
+            return convertIterable(Arrays.asList((Object[]) jsValue));
+        }
+        return jsValue;
+    }
+
+    private Object convertNumber(Number numValue) {
+        if (numValue instanceof Double) {
+            if (isLong((Double) numValue)) {
+                return numValue.longValue();
+            }
+        }
+        if (numValue instanceof Float) {
+            if (isLong((Float) numValue)) {
+                return numValue.longValue();
+            }
+        }
+        return numValue;
+    }
+
+    private boolean isLong(double x) {
+        return x == Math.floor(x);
+    }
+
+    private Object forceAsync(Object jsValue) {
+        AsyncContainer asyncContainer = new AsyncContainer();
+        asyncExtractor.extract(jsValue, asyncContainer.createCompletionCallback());
+        return asyncContainer.getResult();
+    }
+
+    private Object extractScriptable(ScriptableObject scriptableObject) {
+        Object obj = tryKnownConversion(scriptableObject);
+        if (obj != null) { return obj; }
+        if (scriptableObject instanceof NativeArray) {
+            return convertNativeArray((NativeArray) scriptableObject);
+        }
+        if (scriptableObject instanceof Function) {
+            return callFunction((Function) scriptableObject);
+        }
+        return new HybridObject(scriptableObject, this);
+    }
+
+    private Object callFunction(Function function) {
+        Object result = JsUtils.callFn(function, null, function, function, new Object[0]);
+        return adapt(result);
+    }
+
+    private Object[] convertNativeArray(NativeArray nativeArray) {
+        int length = (int) nativeArray.getLength();
+        Object[] objects = new Object[length];
+        for (int i = 0; i < length; i++) {
+            Object jsItem = nativeArray.get(i, nativeArray);
+            objects[i] = adapt(jsItem);
+        }
+        return objects;
+    }
+
+    private Map<Object, Object> convertMap(Map<Object, Object> original) {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+        for (Map.Entry<Object, Object> entry : original.entrySet()) {
+            map.put(entry.getKey(), adapt(entry.getValue()));
+        }
+        return map;
+    }
+
+    private List<Object> convertIterable(Iterable<Object> iterable) {
+        List<Object> objects = new ArrayList<Object>();
+        for (Object obj : iterable) {
+            objects.add(adapt(obj));
+        }
+        return objects;
+    }
+
+    private static Object tryKnownConversion(ScriptableObject object) {
+        String className = object.getClassName();
+        Class<?> cls = knownConversions.get(className);
+        return (cls == null) ? null : Context.jsToJava(object, cls);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
new file mode 100644
index 0000000..ef24105
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+
+package org.apache.sling.scripting.sightly.js.impl.use;
+
+import javax.script.Bindings;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.js.impl.JsEnvironment;
+import org.apache.sling.scripting.sightly.js.impl.Utils;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+
+/**
+ * Resolves dependencies specified by the Use function
+ */
+public class DependencyResolver {
+
+    private final Resource caller;
+    private final JsEnvironment jsEnvironment;
+    private final Bindings globalBindings;
+
+    public DependencyResolver(Resource resource, JsEnvironment jsEnvironment, Bindings globalBindings) {
+        this.caller = resource;
+        this.jsEnvironment = jsEnvironment;
+        this.globalBindings = globalBindings;
+    }
+
+    /**
+     * Resolve a dependency
+     * @param dependency the dependency identifier
+     * @param callback the callback that will receive the resolved dependency
+     */
+    public void resolve(String dependency, UnaryCallback callback) {
+        if (!Utils.isJsScript(dependency)) {
+            throw new SightlyException("Only JS scripts are allowed as dependencies. Invalid dependency: " + dependency);
+        }
+        jsEnvironment.run(caller, dependency, globalBindings, Utils.EMPTY_BINDINGS, callback);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java
new file mode 100644
index 0000000..fbfe132
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+
+package org.apache.sling.scripting.sightly.js.impl.use;
+
+import javax.script.Bindings;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsUtils;
+
+/**
+ * The JavaScript {@code use} function
+ */
+public class UseFunction extends BaseFunction {
+
+    private final DependencyResolver dependencyResolver;
+    private final Scriptable thisObj;
+
+    public UseFunction(DependencyResolver dependencyResolver, Bindings arguments) {
+        this.dependencyResolver = dependencyResolver;
+        this.thisObj = createThisBinding(arguments);
+    }
+
+    @Override
+    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+        Function function;
+        List<String> depNames;
+        if (args.length == 0) {
+            throw new IllegalArgumentException("Not enough arguments for use");
+        } else if (args.length == 1) {
+            function = decodeCallback(args[0]);
+            depNames = Collections.emptyList();
+        } else {
+            function = decodeCallback(args[1]);
+            depNames = decodeDepNames(args[0]);
+        }
+        return use(depNames, function, cx, scope);
+    }
+
+    private Object use(List<String> depNames, final Function callback, final Context cx, final Scriptable scope) {
+        final AsyncContainer asyncContainer = new AsyncContainer();
+        if (depNames.isEmpty()) {
+            callImmediate(callback, asyncContainer, cx, scope);
+        } else {
+            final int[] counter = {depNames.size()};
+            final Object[] dependencies = new Object[depNames.size()];
+            for (int i = 0; i < depNames.size(); i++) {
+                final int dependencyPos = i;
+                dependencyResolver.resolve(depNames.get(i), new UnaryCallback() {
+                    @Override
+                    public void invoke(Object arg) {
+                        counter[0]--;
+                        dependencies[dependencyPos] = arg;
+                        if (counter[0] == 0) {
+                            Object result = JsUtils.callFn(callback, cx, scope, thisObj, dependencies);
+                            asyncContainer.complete(result);
+                        }
+                    }
+                });
+            }
+        }
+        return asyncContainer;
+    }
+
+    private void callImmediate(final Function callback, final AsyncContainer asyncContainer, final Context cx, final Scriptable scope) {
+        EventLoopInterop.schedule(cx, new Runnable() {
+            @Override
+            public void run() {
+                Object value = JsUtils.callFn(callback, cx, scope, thisObj, new Object[0]);
+                asyncContainer.complete(value);
+            }
+        });
+    }
+
+    private Function decodeCallback(Object obj) {
+        if (!(obj instanceof Function)) {
+            throw new IllegalArgumentException("No callback argument supplied");
+        }
+        return (Function) obj;
+    }
+
+    private List<String> decodeDepNames(Object obj) {
+        if (obj instanceof NativeArray) {
+            return decodeDepArray((NativeArray) obj);
+        }
+        return Collections.singletonList(jsToString(obj));
+    }
+
+    private List<String> decodeDepArray(NativeArray nativeArray) {
+        int depLength = (int) nativeArray.getLength();
+        List<String> depNames = new ArrayList<String>(depLength);
+        for (int i = 0; i < depLength; i++) {
+            String depName = jsToString(nativeArray.get(i, nativeArray));
+            depNames.add(depName);
+        }
+        return depNames;
+    }
+
+    private String jsToString(Object obj) {
+        return (String) Context.jsToJava(obj, String.class);
+    }
+
+    private Scriptable createThisBinding(Bindings arguments) {
+        NativeObject nativeObject = new NativeObject();
+        for (Map.Entry<String, Object> entry : arguments.entrySet()) {
+            ScriptableObject.putProperty(nativeObject, entry.getKey(), entry.getValue());
+        }
+        return nativeObject;
+    }
+}
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js
new file mode 100644
index 0000000..5ed1dba
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js
@@ -0,0 +1,1935 @@
+// vim:ts=4:sts=4:sw=4:
+/*!
+ *
+ * Copyright 2009-2012 Kris Kowal under the terms of the MIT
+ * license found at http://github.com/kriskowal/q/raw/master/LICENSE
+ *
+ * With parts by Tyler Close
+ * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
+ * at http://www.opensource.org/licenses/mit-license.html
+ * Forked at ref_send.js version: 2009-05-11
+ *
+ * With parts by Mark Miller
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+(function (definition) {
+    // Turn off strict mode for this function so we can assign to global.Q
+    /* jshint strict: false */
+
+    // This file will function properly as a <script> tag, or a module
+    // using CommonJS and NodeJS or RequireJS module formats.  In
+    // Common/Node/RequireJS, the module exports the Q API and when
+    // executed as a simple <script>, it creates a Q global instead.
+
+    // Montage Require
+    if (typeof bootstrap === "function") {
+        bootstrap("promise", definition);
+
+    // CommonJS
+    } else if (typeof exports === "object") {
+        module.exports = definition();
+
+    // RequireJS
+    } else if (typeof define === "function" && define.amd) {
+        define(definition);
+
+    // SES (Secure EcmaScript)
+    } else if (typeof ses !== "undefined") {
+        if (!ses.ok()) {
+            return;
+        } else {
+            ses.makeQ = definition;
+        }
+
+    // <script>
+    } else {
+        Q = definition();
+    }
+
+})(function () {
+"use strict";
+
+var hasStacks = false;
+try {
+    throw new Error();
+} catch (e) {
+    hasStacks = !!e.stack;
+}
+
+// All code after this point will be filtered from stack traces reported
+// by Q.
+var qStartingLine = captureLine();
+var qFileName;
+
+// shims
+
+// used for fallback in "allResolved"
+var noop = function () {};
+
+// Use the fastest possible means to execute a task in a future turn
+// of the event loop.
+var nextTick =(function () {
+    // linked list of tasks (single, with head node)
+    var head = {task: void 0, next: null};
+    var tail = head;
+    var flushing = false;
+    var requestTick = void 0;
+    var isNodeJS = false;
+
+    function flush() {
+        /* jshint loopfunc: true */
+
+        while (head.next) {
+            head = head.next;
+            var task = head.task;
+            head.task = void 0;
+            var domain = head.domain;
+
+            if (domain) {
+                head.domain = void 0;
+                domain.enter();
+            }
+
+            try {
+                task();
+
+            } catch (e) {
+                if (isNodeJS) {
+                    // In node, uncaught exceptions are considered fatal errors.
+                    // Re-throw them synchronously to interrupt flushing!
+
+                    // Ensure continuation if the uncaught exception is suppressed
+                    // listening "uncaughtException" events (as domains does).
+                    // Continue in next event to avoid tick recursion.
+                    if (domain) {
+                        domain.exit();
+                    }
+                    setTimeout(flush, 0);
+                    if (domain) {
+                        domain.enter();
+                    }
+
+                    throw e;
+
+                } else {
+                    // In browsers, uncaught exceptions are not fatal.
+                    // Re-throw them asynchronously to avoid slow-downs.
+                    setTimeout(function() {
+                       throw e;
+                    }, 0);
+                }
+            }
+
+            if (domain) {
+                domain.exit();
+            }
+        }
+
+        flushing = false;
+    }
+
+    nextTick = function (task) {
+        tail = tail.next = {
+            task: task,
+            domain: isNodeJS && process.domain,
+            next: null
+        };
+
+        if (!flushing) {
+            flushing = true;
+            requestTick();
+        }
+    };
+
+    if (typeof process !== "undefined" && process.nextTick) {
+        // Node.js before 0.9. Note that some fake-Node environments, like the
+        // Mocha test runner, introduce a `process` global without a `nextTick`.
+        isNodeJS = true;
+
+        requestTick = function () {
+            process.nextTick(flush);
+        };
+
+    } else if (typeof setImmediate === "function") {
+        // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
+        if (typeof window !== "undefined") {
+            requestTick = setImmediate.bind(window, flush);
+        } else {
+            requestTick = function () {
+                setImmediate(flush);
+            };
+        }
+
+    } else if (typeof MessageChannel !== "undefined") {
+        // modern browsers
+        // http://www.nonblocking.io/2011/06/windownexttick.html
+        var channel = new MessageChannel();
+        // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create
+        // working message ports the first time a page loads.
+        channel.port1.onmessage = function () {
+            requestTick = requestPortTick;
+            channel.port1.onmessage = flush;
+            flush();
+        };
+        var requestPortTick = function () {
+            // Opera requires us to provide a message payload, regardless of
+            // whether we use it.
+            channel.port2.postMessage(0);
+        };
+        requestTick = function () {
+            setTimeout(flush, 0);
+            requestPortTick();
+        };
+
+    } else {
+        // old browsers
+        requestTick = function () {
+            setTimeout(flush, 0);
+        };
+    }
+
+    return nextTick;
+})();
+
+// Attempt to make generics safe in the face of downstream
+// modifications.
+// There is no situation where this is necessary.
+// If you need a security guarantee, these primordials need to be
+// deeply frozen anyway, and if you don’t need a security guarantee,
+// this is just plain paranoid.
+// However, this **might** have the nice side-effect of reducing the size of
+// the minified code by reducing x.call() to merely x()
+// See Mark Miller’s explanation of what this does.
+// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
+var call = Function.call;
+function uncurryThis(f) {
+    return function () {
+        return call.apply(f, arguments);
+    };
+}
+// This is equivalent, but slower:
+// uncurryThis = Function_bind.bind(Function_bind.call);
+// http://jsperf.com/uncurrythis
+
+var array_slice = uncurryThis(Array.prototype.slice);
+
+var array_reduce = uncurryThis(
+    Array.prototype.reduce || function (callback, basis) {
+        var index = 0,
+            length = this.length;
+        // concerning the initial value, if one is not provided
+        if (arguments.length === 1) {
+            // seek to the first value in the array, accounting
+            // for the possibility that is is a sparse array
+            do {
+                if (index in this) {
+                    basis = this[index++];
+                    break;
+                }
+                if (++index >= length) {
+                    throw new TypeError();
+                }
+            } while (1);
+        }
+        // reduce
+        for (; index < length; index++) {
+            // account for the possibility that the array is sparse
+            if (index in this) {
+                basis = callback(basis, this[index], index);
+            }
+        }
+        return basis;
+    }
+);
+
+var array_indexOf = uncurryThis(
+    Array.prototype.indexOf || function (value) {
+        // not a very good shim, but good enough for our one use of it
+        for (var i = 0; i < this.length; i++) {
+            if (this[i] === value) {
+                return i;
+            }
+        }
+        return -1;
+    }
+);
+
+var array_map = uncurryThis(
+    Array.prototype.map || function (callback, thisp) {
+        var self = this;
+        var collect = [];
+        array_reduce(self, function (undefined, value, index) {
+            collect.push(callback.call(thisp, value, index, self));
+        }, void 0);
+        return collect;
+    }
+);
+
+var object_create = Object.create || function (prototype) {
+    function Type() { }
+    Type.prototype = prototype;
+    return new Type();
+};
+
+var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+
+var object_keys = Object.keys || function (object) {
+    var keys = [];
+    for (var key in object) {
+        if (object_hasOwnProperty(object, key)) {
+            keys.push(key);
+        }
+    }
+    return keys;
+};
+
+var object_toString = uncurryThis(Object.prototype.toString);
+
+function isObject(value) {
+    return value === Object(value);
+}
+
+// generator related shims
+
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
+function isStopIteration(exception) {
+    return (
+        object_toString(exception) === "[object StopIteration]" ||
+        exception instanceof QReturnValue
+    );
+}
+
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
+var QReturnValue;
+if (typeof ReturnValue !== "undefined") {
+    QReturnValue = ReturnValue;
+} else {
+    QReturnValue = function (value) {
+        this.value = value;
+    };
+}
+
+// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+// engine that has a deployed base of browsers that support generators.
+// However, SM's generators use the Python-inspired semantics of
+// outdated ES6 drafts.  We would like to support ES6, but we'd also
+// like to make it possible to use generators in deployed browsers, so
+// we also support Python-style generators.  At some point we can remove
+// this block.
+var hasES6Generators;
+try {
+    /* jshint evil: true, nonew: false */
+    new Function("(function* (){ yield 1; })");
+    hasES6Generators = true;
+} catch (e) {
+    hasES6Generators = false;
+}
+
+// long stack traces
+
+var STACK_JUMP_SEPARATOR = "From previous event:";
+
+function makeStackTraceLong(error, promise) {
+    // If possible, transform the error stack trace by removing Node and Q
+    // cruft, then concatenating with the stack trace of `promise`. See #57.
+    if (hasStacks &&
+        promise.stack &&
+        typeof error === "object" &&
+        error !== null &&
+        error.stack &&
+        error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
+    ) {
+        var stacks = [];
+        for (var p = promise; !!p; p = p.source) {
+            if (p.stack) {
+                stacks.unshift(p.stack);
+            }
+        }
+        stacks.unshift(error.stack);
+
+        var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
+        error.stack = filterStackString(concatedStacks);
+    }
+}
+
+function filterStackString(stackString) {
+    var lines = stackString.split("\n");
+    var desiredLines = [];
+    for (var i = 0; i < lines.length; ++i) {
+        var line = lines[i];
+
+        if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
+            desiredLines.push(line);
+        }
+    }
+    return desiredLines.join("\n");
+}
+
+function isNodeFrame(stackLine) {
+    return stackLine.indexOf("(module.js:") !== -1 ||
+           stackLine.indexOf("(node.js:") !== -1;
+}
+
+function getFileNameAndLineNumber(stackLine) {
+    // Named functions: "at functionName (filename:lineNumber:columnNumber)"
+    // In IE10 function name can have spaces ("Anonymous function") O_o
+    var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
+    if (attempt1) {
+        return [attempt1[1], Number(attempt1[2])];
+    }
+
+    // Anonymous functions: "at filename:lineNumber:columnNumber"
+    var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
+    if (attempt2) {
+        return [attempt2[1], Number(attempt2[2])];
+    }
+
+    // Firefox style: "function@filename:lineNumber or @filename:lineNumber"
+    var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
+    if (attempt3) {
+        return [attempt3[1], Number(attempt3[2])];
+    }
+}
+
+function isInternalFrame(stackLine) {
+    var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
+
+    if (!fileNameAndLineNumber) {
+        return false;
+    }
+
+    var fileName = fileNameAndLineNumber[0];
+    var lineNumber = fileNameAndLineNumber[1];
+
+    return fileName === qFileName &&
+        lineNumber >= qStartingLine &&
+        lineNumber <= qEndingLine;
+}
+
+// discover own file name and line number range for filtering stack
+// traces
+function captureLine() {
+    if (!hasStacks) {
+        return;
+    }
+
+    try {
+        throw new Error();
+    } catch (e) {
+        var lines = e.stack.split("\n");
+        var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
+        var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
+        if (!fileNameAndLineNumber) {
+            return;
+        }
+
+        qFileName = fileNameAndLineNumber[0];
+        return fileNameAndLineNumber[1];
+    }
+}
+
+function deprecate(callback, name, alternative) {
+    return function () {
+        if (typeof console !== "undefined" &&
+            typeof console.warn === "function") {
+            console.warn(name + " is deprecated, use " + alternative +
+                         " instead.", new Error("").stack);
+        }
+        return callback.apply(callback, arguments);
+    };
+}
+
+// end of shims
+// beginning of real work
+
+/**
+ * Constructs a promise for an immediate reference, passes promises through, or
+ * coerces promises from different systems.
+ * @param value immediate reference or promise
+ */
+function Q(value) {
+    // If the object is already a Promise, return it directly.  This enables
+    // the resolve function to both be used to created references from objects,
+    // but to tolerably coerce non-promises to promises.
+    if (isPromise(value)) {
+        return value;
+    }
+
+    // assimilate thenables
+    if (isPromiseAlike(value)) {
+        return coerce(value);
+    } else {
+        return fulfill(value);
+    }
+}
+Q.resolve = Q;
+
+/**
+ * Performs a task in a future turn of the event loop.
+ * @param {Function} task
+ */
+Q.nextTick = nextTick;
+
+/**
+ * Controls whether or not long stack traces will be on
+ */
+Q.longStackSupport = false;
+
+/**
+ * Constructs a {promise, resolve, reject} object.
+ *
+ * `resolve` is a callback to invoke with a more resolved value for the
+ * promise. To fulfill the promise, invoke `resolve` with any value that is
+ * not a thenable. To reject the promise, invoke `resolve` with a rejected
+ * thenable, or invoke `reject` with the reason directly. To resolve the
+ * promise to another thenable, thus putting it in the same state, invoke
+ * `resolve` with that other thenable.
+ */
+Q.defer = defer;
+function defer() {
+    // if "messages" is an "Array", that indicates that the promise has not yet
+    // been resolved.  If it is "undefined", it has been resolved.  Each
+    // element of the messages array is itself an array of complete arguments to
+    // forward to the resolved promise.  We coerce the resolution value to a
+    // promise using the `resolve` function because it handles both fully
+    // non-thenable values and other thenables gracefully.
+    var messages = [], progressListeners = [], resolvedPromise;
+
+    var deferred = object_create(defer.prototype);
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, operands) {
+        var args = array_slice(arguments);
+        if (messages) {
+            messages.push(args);
+            if (op === "when" && operands[1]) { // progress operand
+                progressListeners.push(operands[1]);
+            }
+        } else {
+            nextTick(function () {
+                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
+            });
+        }
+    };
+
+    // XXX deprecated
+    promise.valueOf = function () {
+        if (messages) {
+            return promise;
+        }
+        var nearerValue = nearer(resolvedPromise);
+        if (isPromise(nearerValue)) {
+            resolvedPromise = nearerValue; // shorten chain
+        }
+        return nearerValue;
+    };
+
+    promise.inspect = function () {
+        if (!resolvedPromise) {
+            return { state: "pending" };
+        }
+        return resolvedPromise.inspect();
+    };
+
+    if (Q.longStackSupport && hasStacks) {
+        try {
+            throw new Error();
+        } catch (e) {
+            // NOTE: don't try to use `Error.captureStackTrace` or transfer the
+            // accessor around; that causes memory leaks as per GH-111. Just
+            // reify the stack trace as a string ASAP.
+            //
+            // At the same time, cut off the first line; it's always just
+            // "[object Promise]\n", as per the `toString`.
+            promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
+        }
+    }
+
+    // NOTE: we do the checks for `resolvedPromise` in each method, instead of
+    // consolidating them into `become`, since otherwise we'd create new
+    // promises with the lines `become(whatever(value))`. See e.g. GH-252.
+
+    function become(newPromise) {
+        resolvedPromise = newPromise;
+        promise.source = newPromise;
+
+        array_reduce(messages, function (undefined, message) {
+            nextTick(function () {
+                newPromise.promiseDispatch.apply(newPromise, message);
+            });
+        }, void 0);
+
+        messages = void 0;
+        progressListeners = void 0;
+    }
+
+    deferred.promise = promise;
+    deferred.resolve = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(Q(value));
+    };
+
+    deferred.fulfill = function (value) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(fulfill(value));
+    };
+    deferred.reject = function (reason) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        become(reject(reason));
+    };
+    deferred.notify = function (progress) {
+        if (resolvedPromise) {
+            return;
+        }
+
+        array_reduce(progressListeners, function (undefined, progressListener) {
+            nextTick(function () {
+                progressListener(progress);
+            });
+        }, void 0);
+    };
+
+    return deferred;
+}
+
+/**
+ * Creates a Node-style callback that will resolve or reject the deferred
+ * promise.
+ * @returns a nodeback
+ */
+defer.prototype.makeNodeResolver = function () {
+    var self = this;
+    return function (error, value) {
+        if (error) {
+            self.reject(error);
+        } else if (arguments.length > 2) {
+            self.resolve(array_slice(arguments, 1));
+        } else {
+            self.resolve(value);
+        }
+    };
+};
+
+/**
+ * @param resolver {Function} a function that returns nothing and accepts
+ * the resolve, reject, and notify functions for a deferred.
+ * @returns a promise that may be resolved with the given resolve and reject
+ * functions, or rejected by a thrown exception in resolver
+ */
+Q.promise = promise;
+function promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("resolver must be a function.");
+    }
+    var deferred = defer();
+    try {
+        resolver(deferred.resolve, deferred.reject, deferred.notify);
+    } catch (reason) {
+        deferred.reject(reason);
+    }
+    return deferred.promise;
+}
+
+// XXX experimental.  This method is a way to denote that a local value is
+// serializable and should be immediately dispatched to a remote upon request,
+// instead of passing a reference.
+Q.passByCopy = function (object) {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return object;
+};
+
+Promise.prototype.passByCopy = function () {
+    //freeze(object);
+    //passByCopies.set(object, true);
+    return this;
+};
+
+/**
+ * If two promises eventually fulfill to the same value, promises that value,
+ * but otherwise rejects.
+ * @param x {Any*}
+ * @param y {Any*}
+ * @returns {Any*} a promise for x and y if they are the same, but a rejection
+ * otherwise.
+ *
+ */
+Q.join = function (x, y) {
+    return Q(x).join(y);
+};
+
+Promise.prototype.join = function (that) {
+    return Q([this, that]).spread(function (x, y) {
+        if (x === y) {
+            // TODO: "===" should be Object.is or equiv
+            return x;
+        } else {
+            throw new Error("Can't join: not the same: " + x + " " + y);
+        }
+    });
+};
+
+/**
+ * Returns a promise for the first of an array of promises to become fulfilled.
+ * @param answers {Array[Any*]} promises to race
+ * @returns {Any*} the first promise to be fulfilled
+ */
+Q.race = race;
+function race(answerPs) {
+    return promise(function(resolve, reject) {
+        // Switch to this once we can assume at least ES5
+        // answerPs.forEach(function(answerP) {
+        //     Q(answerP).then(resolve, reject);
+        // });
+        // Use this in the meantime
+        for (var i = 0, len = answerPs.length; i < len; i++) {
+            Q(answerPs[i]).then(resolve, reject);
+        }
+    });
+}
+
+Promise.prototype.race = function () {
+    return this.then(Q.race);
+};
+
+/**
+ * Constructs a Promise with a promise descriptor object and optional fallback
+ * function.  The descriptor contains methods like when(rejected), get(name),
+ * set(name, value), post(name, args), and delete(name), which all
+ * return either a value, a promise for a value, or a rejection.  The fallback
+ * accepts the operation name, a resolver, and any further arguments that would
+ * have been forwarded to the appropriate method above had a method been
+ * provided with the proper name.  The API makes no guarantees about the nature
+ * of the returned object, apart from that it is usable whereever promises are
+ * bought and sold.
+ */
+Q.makePromise = Promise;
+function Promise(descriptor, fallback, inspect) {
+    if (fallback === void 0) {
+        fallback = function (op) {
+            return reject(new Error(
+                "Promise does not support operation: " + op
+            ));
+        };
+    }
+    if (inspect === void 0) {
+        inspect = function () {
+            return {state: "unknown"};
+        };
+    }
+
+    var promise = object_create(Promise.prototype);
+
+    promise.promiseDispatch = function (resolve, op, args) {
+        var result;
+        try {
+            if (descriptor[op]) {
+                result = descriptor[op].apply(promise, args);
+            } else {
+                result = fallback.call(promise, op, args);
+            }
+        } catch (exception) {
+            result = reject(exception);
+        }
+        if (resolve) {
+            resolve(result);
+        }
+    };
+
+    promise.inspect = inspect;
+
+    // XXX deprecated `valueOf` and `exception` support
+    if (inspect) {
+        var inspected = inspect();
+        if (inspected.state === "rejected") {
+            promise.exception = inspected.reason;
+        }
+
+        promise.valueOf = function () {
+            var inspected = inspect();
+            if (inspected.state === "pending" ||
+                inspected.state === "rejected") {
+                return promise;
+            }
+            return inspected.value;
+        };
+    }
+
+    return promise;
+}
+
+Promise.prototype.toString = function () {
+    return "[object Promise]";
+};
+
+Promise.prototype.then = function (fulfilled, rejected, progressed) {
+    var self = this;
+    var deferred = defer();
+    var done = false;   // ensure the untrusted promise makes at most a
+                        // single call to one of the callbacks
+
+    function _fulfilled(value) {
+        try {
+            return typeof fulfilled === "function" ? fulfilled(value) : value;
+        } catch (exception) {
+            return reject(exception);
+        }
+    }
+
+    function _rejected(exception) {
+        if (typeof rejected === "function") {
+            makeStackTraceLong(exception, self);
+            try {
+                return rejected(exception);
+            } catch (newException) {
+                return reject(newException);
+            }
+        }
+        return reject(exception);
+    }
+
+    function _progressed(value) {
+        return typeof progressed === "function" ? progressed(value) : value;
+    }
+
+    nextTick(function () {
+        self.promiseDispatch(function (value) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_fulfilled(value));
+        }, "when", [function (exception) {
+            if (done) {
+                return;
+            }
+            done = true;
+
+            deferred.resolve(_rejected(exception));
+        }]);
+    });
+
+    // Progress propagator need to be attached in the current tick.
+    self.promiseDispatch(void 0, "when", [void 0, function (value) {
+        var newValue;
+        var threw = false;
+        try {
+            newValue = _progressed(value);
+        } catch (e) {
+            threw = true;
+            if (Q.onerror) {
+                Q.onerror(e);
+            } else {
+                throw e;
+            }
+        }
+
+        if (!threw) {
+            deferred.notify(newValue);
+        }
+    }]);
+
+    return deferred.promise;
+};
+
+/**
+ * Registers an observer on a promise.
+ *
+ * Guarantees:
+ *
+ * 1. that fulfilled and rejected will be called only once.
+ * 2. that either the fulfilled callback or the rejected callback will be
+ *    called, but not both.
+ * 3. that fulfilled and rejected will not be called in this turn.
+ *
+ * @param value      promise or immediate reference to observe
+ * @param fulfilled  function to be called with the fulfilled value
+ * @param rejected   function to be called with the rejection exception
+ * @param progressed function to be called on any progress notifications
+ * @return promise for the return value from the invoked callback
+ */
+Q.when = when;
+function when(value, fulfilled, rejected, progressed) {
+    return Q(value).then(fulfilled, rejected, progressed);
+}
+
+Promise.prototype.thenResolve = function (value) {
+    return this.then(function () { return value; });
+};
+
+Q.thenResolve = function (promise, value) {
+    return Q(promise).thenResolve(value);
+};
+
+Promise.prototype.thenReject = function (reason) {
+    return this.then(function () { throw reason; });
+};
+
+Q.thenReject = function (promise, reason) {
+    return Q(promise).thenReject(reason);
+};
+
+/**
+ * If an object is not a promise, it is as "near" as possible.
+ * If a promise is rejected, it is as "near" as possible too.
+ * If it’s a fulfilled promise, the fulfillment value is nearer.
+ * If it’s a deferred promise and the deferred has been resolved, the
+ * resolution is "nearer".
+ * @param object
+ * @returns most resolved (nearest) form of the object
+ */
+
+// XXX should we re-do this?
+Q.nearer = nearer;
+function nearer(value) {
+    if (isPromise(value)) {
+        var inspected = value.inspect();
+        if (inspected.state === "fulfilled") {
+            return inspected.value;
+        }
+    }
+    return value;
+}
+
+/**
+ * @returns whether the given object is a promise.
+ * Otherwise it is a fulfilled value.
+ */
+Q.isPromise = isPromise;
+function isPromise(object) {
+    return isObject(object) &&
+        typeof object.promiseDispatch === "function" &&
+        typeof object.inspect === "function";
+}
+
+Q.isPromiseAlike = isPromiseAlike;
+function isPromiseAlike(object) {
+    return isObject(object) && typeof object.then === "function";
+}
+
+/**
+ * @returns whether the given object is a pending promise, meaning not
+ * fulfilled or rejected.
+ */
+Q.isPending = isPending;
+function isPending(object) {
+    return isPromise(object) && object.inspect().state === "pending";
+}
+
+Promise.prototype.isPending = function () {
+    return this.inspect().state === "pending";
+};
+
+/**
+ * @returns whether the given object is a value or fulfilled
+ * promise.
+ */
+Q.isFulfilled = isFulfilled;
+function isFulfilled(object) {
+    return !isPromise(object) || object.inspect().state === "fulfilled";
+}
+
+Promise.prototype.isFulfilled = function () {
+    return this.inspect().state === "fulfilled";
+};
+
+/**
+ * @returns whether the given object is a rejected promise.
+ */
+Q.isRejected = isRejected;
+function isRejected(object) {
+    return isPromise(object) && object.inspect().state === "rejected";
+}
+
+Promise.prototype.isRejected = function () {
+    return this.inspect().state === "rejected";
+};
+
+//// BEGIN UNHANDLED REJECTION TRACKING
+
+// This promise library consumes exceptions thrown in handlers so they can be
+// handled by a subsequent promise.  The exceptions get added to this array when
+// they are created, and removed when they are handled.  Note that in ES6 or
+// shimmed environments, this would naturally be a `Set`.
+var unhandledReasons = [];
+var unhandledRejections = [];
+var unhandledReasonsDisplayed = false;
+var trackUnhandledRejections = true;
+function displayUnhandledReasons() {
+    if (
+        !unhandledReasonsDisplayed &&
+        typeof window !== "undefined" &&
+        window.console
+    ) {
+        console.warn("[Q] Unhandled rejection reasons (should be empty):",
+                     unhandledReasons);
+    }
+
+    unhandledReasonsDisplayed = true;
+}
+
+function logUnhandledReasons() {
+    for (var i = 0; i < unhandledReasons.length; i++) {
+        var reason = unhandledReasons[i];
+        console.warn("Unhandled rejection reason:", reason);
+    }
+}
+
+function resetUnhandledRejections() {
+    unhandledReasons.length = 0;
+    unhandledRejections.length = 0;
+    unhandledReasonsDisplayed = false;
+
+    if (!trackUnhandledRejections) {
+        trackUnhandledRejections = true;
+
+        // Show unhandled rejection reasons if Node exits without handling an
+        // outstanding rejection.  (Note that Browserify presently produces a
+        // `process` global without the `EventEmitter` `on` method.)
+        if (typeof process !== "undefined" && process.on) {
+            process.on("exit", logUnhandledReasons);
+        }
+    }
+}
+
+function trackRejection(promise, reason) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    unhandledRejections.push(promise);
+    if (reason && typeof reason.stack !== "undefined") {
+        unhandledReasons.push(reason.stack);
+    } else {
+        unhandledReasons.push("(no stack) " + reason);
+    }
+    displayUnhandledReasons();
+}
+
+function untrackRejection(promise) {
+    if (!trackUnhandledRejections) {
+        return;
+    }
+
+    var at = array_indexOf(unhandledRejections, promise);
+    if (at !== -1) {
+        unhandledRejections.splice(at, 1);
+        unhandledReasons.splice(at, 1);
+    }
+}
+
+Q.resetUnhandledRejections = resetUnhandledRejections;
+
+Q.getUnhandledReasons = function () {
+    // Make a copy so that consumers can't interfere with our internal state.
+    return unhandledReasons.slice();
+};
+
+Q.stopUnhandledRejectionTracking = function () {
+    resetUnhandledRejections();
+    if (typeof process !== "undefined" && process.on) {
+        process.removeListener("exit", logUnhandledReasons);
+    }
+    trackUnhandledRejections = false;
+};
+
+resetUnhandledRejections();
+
+//// END UNHANDLED REJECTION TRACKING
+
+/**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+Q.reject = reject;
+function reject(reason) {
+    var rejection = Promise({
+        "when": function (rejected) {
+            // note that the error has been handled
+            if (rejected) {
+                untrackRejection(this);
+            }
+            return rejected ? rejected(reason) : this;
+        }
+    }, function fallback() {
+        return this;
+    }, function inspect() {
+        return { state: "rejected", reason: reason };
+    });
+
+    // Note that the reason has not been handled.
+    trackRejection(rejection, reason);
+
+    return rejection;
+}
+
+/**
+ * Constructs a fulfilled promise for an immediate reference.
+ * @param value immediate reference
+ */
+Q.fulfill = fulfill;
+function fulfill(value) {
+    return Promise({
+        "when": function () {
+            return value;
+        },
+        "get": function (name) {
+            return value[name];
+        },
+        "set": function (name, rhs) {
+            value[name] = rhs;
+        },
+        "delete": function (name) {
+            delete value[name];
+        },
+        "post": function (name, args) {
+            // Mark Miller proposes that post with no name should apply a
+            // promised function.
+            if (name === null || name === void 0) {
+                return value.apply(void 0, args);
+            } else {
+                return value[name].apply(value, args);
+            }
+        },
+        "apply": function (thisp, args) {
+            return value.apply(thisp, args);
+        },
+        "keys": function () {
+            return object_keys(value);
+        }
+    }, void 0, function inspect() {
+        return { state: "fulfilled", value: value };
+    });
+}
+
+/**
+ * Converts thenables to Q promises.
+ * @param promise thenable promise
+ * @returns a Q promise
+ */
+function coerce(promise) {
+    var deferred = defer();
+    nextTick(function () {
+        try {
+            promise.then(deferred.resolve, deferred.reject, deferred.notify);
+        } catch (exception) {
+            deferred.reject(exception);
+        }
+    });
+    return deferred.promise;
+}
+
+/**
+ * Annotates an object such that it will never be
+ * transferred away from this process over any promise
+ * communication channel.
+ * @param object
+ * @returns promise a wrapping of that object that
+ * additionally responds to the "isDef" message
+ * without a rejection.
+ */
+Q.master = master;
+function master(object) {
+    return Promise({
+        "isDef": function () {}
+    }, function fallback(op, args) {
+        return dispatch(object, op, args);
+    }, function () {
+        return Q(object).inspect();
+    });
+}
+
+/**
+ * Spreads the values of a promised array of arguments into the
+ * fulfillment callback.
+ * @param fulfilled callback that receives variadic arguments from the
+ * promised array
+ * @param rejected callback that receives the exception if the promise
+ * is rejected.
+ * @returns a promise for the return value or thrown exception of
+ * either callback.
+ */
+Q.spread = spread;
+function spread(value, fulfilled, rejected) {
+    return Q(value).spread(fulfilled, rejected);
+}
+
+Promise.prototype.spread = function (fulfilled, rejected) {
+    return this.all().then(function (array) {
+        return fulfilled.apply(void 0, array);
+    }, rejected);
+};
+
+/**
+ * The async function is a decorator for generator functions, turning
+ * them into asynchronous generators.  Although generators are only part
+ * of the newest ECMAScript 6 drafts, this code does not cause syntax
+ * errors in older engines.  This code should continue to work and will
+ * in fact improve over time as the language improves.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled.  SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form.  This function
+ * works on both kinds of generators.
+ *
+ * Decorates a generator function such that:
+ *  - it may yield promises
+ *  - execution will continue when that promise is fulfilled
+ *  - the value of the yield expression will be the fulfilled value
+ *  - it returns a promise for the return value (when the generator
+ *    stops iterating)
+ *  - the decorated function returns a promise for the return value
+ *    of the generator or the first rejected promise among those
+ *    yielded.
+ *  - if an error is thrown in the generator, it propagates through
+ *    every following yield until it is caught, or until it escapes
+ *    the generator function altogether, and is translated into a
+ *    rejection for the promise returned by the decorated generator.
+ */
+Q.async = async;
+function async(makeGenerator) {
+    return function () {
+        // when verb is "send", arg is a value
+        // when verb is "throw", arg is an exception
+        function continuer(verb, arg) {
+            var result;
+            if (hasES6Generators) {
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    return reject(exception);
+                }
+                if (result.done) {
+                    return result.value;
+                } else {
+                    return when(result.value, callback, errback);
+                }
+            } else {
+                // FIXME: Remove this case when SM does ES6 generators.
+                try {
+                    result = generator[verb](arg);
+                } catch (exception) {
+                    if (isStopIteration(exception)) {
+                        return exception.value;
+                    } else {
+                        return reject(exception);
+                    }
+                }
+                return when(result, callback, errback);
+            }
+        }
+        var generator = makeGenerator.apply(this, arguments);
+        var callback = continuer.bind(continuer, "next");
+        var errback = continuer.bind(continuer, "throw");
+        return callback();
+    };
+}
+
+/**
+ * The spawn function is a small wrapper around async that immediately
+ * calls the generator and also ends the promise chain, so that any
+ * unhandled errors are thrown instead of forwarded to the error
+ * handler. This is useful because it's extremely common to run
+ * generators at the top-level to work with libraries.
+ */
+Q.spawn = spawn;
+function spawn(makeGenerator) {
+    Q.done(Q.async(makeGenerator)());
+}
+
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
+/**
+ * Throws a ReturnValue exception to stop an asynchronous generator.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey.  In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
+ * @param value the return value for the surrounding generator
+ * @throws ReturnValue exception with the value.
+ * @example
+ * // ES6 style
+ * Q.async(function* () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      return foo + bar;
+ * })
+ * // Older SpiderMonkey style
+ * Q.async(function () {
+ *      var foo = yield getFooPromise();
+ *      var bar = yield getBarPromise();
+ *      Q.return(foo + bar);
+ * })
+ */
+Q["return"] = _return;
+function _return(value) {
+    throw new QReturnValue(value);
+}
+
+/**
+ * The promised function decorator ensures that any promise arguments
+ * are settled and passed as values (`this` is also settled and passed
+ * as a value).  It will also ensure that the result of a function is
+ * always a promise.
+ *
+ * @example
+ * var add = Q.promised(function (a, b) {
+ *     return a + b;
+ * });
+ * add(Q(a), Q(B));
+ *
+ * @param {function} callback The function to decorate
+ * @returns {function} a function that has been decorated.
+ */
+Q.promised = promised;
+function promised(callback) {
+    return function () {
+        return spread([this, all(arguments)], function (self, args) {
+            return callback.apply(self, args);
+        });
+    };
+}
+
+/**
+ * sends a message to a value in a future turn
+ * @param object* the recipient
+ * @param op the name of the message operation, e.g., "when",
+ * @param args further arguments to be forwarded to the operation
+ * @returns result {Promise} a promise for the result of the operation
+ */
+Q.dispatch = dispatch;
+function dispatch(object, op, args) {
+    return Q(object).dispatch(op, args);
+}
+
+Promise.prototype.dispatch = function (op, args) {
+    var self = this;
+    var deferred = defer();
+    nextTick(function () {
+        self.promiseDispatch(deferred.resolve, op, args);
+    });
+    return deferred.promise;
+};
+
+/**
+ * Gets the value of a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to get
+ * @return promise for the property value
+ */
+Q.get = function (object, key) {
+    return Q(object).dispatch("get", [key]);
+};
+
+Promise.prototype.get = function (key) {
+    return this.dispatch("get", [key]);
+};
+
+/**
+ * Sets the value of a property in a future turn.
+ * @param object    promise or immediate reference for object object
+ * @param name      name of property to set
+ * @param value     new value of property
+ * @return promise for the return value
+ */
+Q.set = function (object, key, value) {
+    return Q(object).dispatch("set", [key, value]);
+};
+
+Promise.prototype.set = function (key, value) {
+    return this.dispatch("set", [key, value]);
+};
+
+/**
+ * Deletes a property in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of property to delete
+ * @return promise for the return value
+ */
+Q.del = // XXX legacy
+Q["delete"] = function (object, key) {
+    return Q(object).dispatch("delete", [key]);
+};
+
+Promise.prototype.del = // XXX legacy
+Promise.prototype["delete"] = function (key) {
+    return this.dispatch("delete", [key]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param value     a value to post, typically an array of
+ *                  invocation arguments for promises that
+ *                  are ultimately backed with `resolve` values,
+ *                  as opposed to those backed with URLs
+ *                  wherein the posted value can be any
+ *                  JSON serializable object.
+ * @return promise for the return value
+ */
+// bound locally because it is used by other methods
+Q.mapply = // XXX As proposed by "Redsandro"
+Q.post = function (object, name, args) {
+    return Q(object).dispatch("post", [name, args]);
+};
+
+Promise.prototype.mapply = // XXX As proposed by "Redsandro"
+Promise.prototype.post = function (name, args) {
+    return this.dispatch("post", [name, args]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @param name      name of method to invoke
+ * @param ...args   array of invocation arguments
+ * @return promise for the return value
+ */
+Q.send = // XXX Mark Miller's proposed parlance
+Q.mcall = // XXX As proposed by "Redsandro"
+Q.invoke = function (object, name /*...args*/) {
+    return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
+};
+
+Promise.prototype.send = // XXX Mark Miller's proposed parlance
+Promise.prototype.mcall = // XXX As proposed by "Redsandro"
+Promise.prototype.invoke = function (name /*...args*/) {
+    return this.dispatch("post", [name, array_slice(arguments, 1)]);
+};
+
+/**
+ * Applies the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param args      array of application arguments
+ */
+Q.fapply = function (object, args) {
+    return Q(object).dispatch("apply", [void 0, args]);
+};
+
+Promise.prototype.fapply = function (args) {
+    return this.dispatch("apply", [void 0, args]);
+};
+
+/**
+ * Calls the promised function in a future turn.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q["try"] =
+Q.fcall = function (object /* ...args*/) {
+    return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
+};
+
+Promise.prototype.fcall = function (/*...args*/) {
+    return this.dispatch("apply", [void 0, array_slice(arguments)]);
+};
+
+/**
+ * Binds the promised function, transforming return values into a fulfilled
+ * promise and thrown errors into a rejected one.
+ * @param object    promise or immediate reference for target function
+ * @param ...args   array of application arguments
+ */
+Q.fbind = function (object /*...args*/) {
+    var promise = Q(object);
+    var args = array_slice(arguments, 1);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+Promise.prototype.fbind = function (/*...args*/) {
+    var promise = this;
+    var args = array_slice(arguments);
+    return function fbound() {
+        return promise.dispatch("apply", [
+            this,
+            args.concat(array_slice(arguments))
+        ]);
+    };
+};
+
+/**
+ * Requests the names of the owned properties of a promised
+ * object in a future turn.
+ * @param object    promise or immediate reference for target object
+ * @return promise for the keys of the eventually settled object
+ */
+Q.keys = function (object) {
+    return Q(object).dispatch("keys", []);
+};
+
+Promise.prototype.keys = function () {
+    return this.dispatch("keys", []);
+};
+
+/**
+ * Turns an array of promises into a promise for an array.  If any of
+ * the promises gets rejected, the whole array is rejected immediately.
+ * @param {Array*} an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns a promise for an array of the corresponding values
+ */
+// By Mark Miller
+// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
+Q.all = all;
+function all(promises) {
+    return when(promises, function (promises) {
+        var countDown = 0;
+        var deferred = defer();
+        array_reduce(promises, function (undefined, promise, index) {
+            var snapshot;
+            if (
+                isPromise(promise) &&
+                (snapshot = promise.inspect()).state === "fulfilled"
+            ) {
+                promises[index] = snapshot.value;
+            } else {
+                ++countDown;
+                when(
+                    promise,
+                    function (value) {
+                        promises[index] = value;
+                        if (--countDown === 0) {
+                            deferred.resolve(promises);
+                        }
+                    },
+                    deferred.reject,
+                    function (progress) {
+                        deferred.notify({ index: index, value: progress });
+                    }
+                );
+            }
+        }, void 0);
+        if (countDown === 0) {
+            deferred.resolve(promises);
+        }
+        return deferred.promise;
+    });
+}
+
+Promise.prototype.all = function () {
+    return all(this);
+};
+
+/**
+ * Waits for all promises to be settled, either fulfilled or
+ * rejected.  This is distinct from `all` since that would stop
+ * waiting at the first rejection.  The promise returned by
+ * `allResolved` will never be rejected.
+ * @param promises a promise for an array (or an array) of promises
+ * (or values)
+ * @return a promise for an array of promises
+ */
+Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
+function allResolved(promises) {
+    return when(promises, function (promises) {
+        promises = array_map(promises, Q);
+        return when(all(array_map(promises, function (promise) {
+            return when(promise, noop, noop);
+        })), function () {
+            return promises;
+        });
+    });
+}
+
+Promise.prototype.allResolved = function () {
+    return allResolved(this);
+};
+
+/**
+ * @see Promise#allSettled
+ */
+Q.allSettled = allSettled;
+function allSettled(promises) {
+    return Q(promises).allSettled();
+}
+
+/**
+ * Turns an array of promises into a promise for an array of their states (as
+ * returned by `inspect`) when they have all settled.
+ * @param {Array[Any*]} values an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns {Array[State]} an array of states for the respective values.
+ */
+Promise.prototype.allSettled = function () {
+    return this.then(function (promises) {
+        return all(array_map(promises, function (promise) {
+            promise = Q(promise);
+            function regardless() {
+                return promise.inspect();
+            }
+            return promise.then(regardless, regardless);
+        }));
+    });
+};
+
+/**
+ * Captures the failure of a promise, giving an oportunity to recover
+ * with a callback.  If the given promise is fulfilled, the returned
+ * promise is fulfilled.
+ * @param {Any*} promise for something
+ * @param {Function} callback to fulfill the returned promise if the
+ * given promise is rejected
+ * @returns a promise for the return value of the callback
+ */
+Q.fail = // XXX legacy
+Q["catch"] = function (object, rejected) {
+    return Q(object).then(void 0, rejected);
+};
+
+Promise.prototype.fail = // XXX legacy
+Promise.prototype["catch"] = function (rejected) {
+    return this.then(void 0, rejected);
+};
+
+/**
+ * Attaches a listener that can respond to progress notifications from a
+ * promise's originating deferred. This listener receives the exact arguments
+ * passed to ``deferred.notify``.
+ * @param {Any*} promise for something
+ * @param {Function} callback to receive any progress notifications
+ * @returns the given promise, unchanged
+ */
+Q.progress = progress;
+function progress(object, progressed) {
+    return Q(object).then(void 0, void 0, progressed);
+}
+
+Promise.prototype.progress = function (progressed) {
+    return this.then(void 0, void 0, progressed);
+};
+
+/**
+ * Provides an opportunity to observe the settling of a promise,
+ * regardless of whether the promise is fulfilled or rejected.  Forwards
+ * the resolution to the returned promise when the callback is done.
+ * The callback can return a promise to defer completion.
+ * @param {Any*} promise
+ * @param {Function} callback to observe the resolution of the given
+ * promise, takes no arguments.
+ * @returns a promise for the resolution of the given promise when
+ * ``fin`` is done.
+ */
+Q.fin = // XXX legacy
+Q["finally"] = function (object, callback) {
+    return Q(object)["finally"](callback);
+};
+
+Promise.prototype.fin = // XXX legacy
+Promise.prototype["finally"] = function (callback) {
+    callback = Q(callback);
+    return this.then(function (value) {
+        return callback.fcall().then(function () {
+            return value;
+        });
+    }, function (reason) {
+        // TODO attempt to recycle the rejection with "this".
+        return callback.fcall().then(function () {
+            throw reason;
+        });
+    });
+};
+
+/**
+ * Terminates a chain of promises, forcing rejections to be
+ * thrown as exceptions.
+ * @param {Any*} promise at the end of a chain of promises
+ * @returns nothing
+ */
+Q.done = function (object, fulfilled, rejected, progress) {
+    return Q(object).done(fulfilled, rejected, progress);
+};
+
+Promise.prototype.done = function (fulfilled, rejected, progress) {
+    var onUnhandledError = function (error) {
+        // forward to a future turn so that ``when``
+        // does not catch it and turn it into a rejection.
+        nextTick(function () {
+            makeStackTraceLong(error, promise);
+            if (Q.onerror) {
+                Q.onerror(error);
+            } else {
+                throw error;
+            }
+        });
+    };
+
+    // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
+    var promise = fulfilled || rejected || progress ?
+        this.then(fulfilled, rejected, progress) :
+        this;
+
+    if (typeof process === "object" && process && process.domain) {
+        onUnhandledError = process.domain.bind(onUnhandledError);
+    }
+
+    promise.then(void 0, onUnhandledError);
+};
+
+/**
+ * Causes a promise to be rejected if it does not get fulfilled before
+ * some milliseconds time out.
+ * @param {Any*} promise
+ * @param {Number} milliseconds timeout
+ * @param {String} custom error message (optional)
+ * @returns a promise for the resolution of the given promise if it is
+ * fulfilled before the timeout, otherwise rejected.
+ */
+Q.timeout = function (object, ms, message) {
+    return Q(object).timeout(ms, message);
+};
+
+Promise.prototype.timeout = function (ms, message) {
+    var deferred = defer();
+    var timeoutId = setTimeout(function () {
+        deferred.reject(new Error(message || "Timed out after " + ms + " ms"));
+    }, ms);
+
+    this.then(function (value) {
+        clearTimeout(timeoutId);
+        deferred.resolve(value);
+    }, function (exception) {
+        clearTimeout(timeoutId);
+        deferred.reject(exception);
+    }, deferred.notify);
+
+    return deferred.promise;
+};
+
+/**
+ * Returns a promise for the given value (or promised value), some
+ * milliseconds after it resolved. Passes rejections immediately.
+ * @param {Any*} promise
+ * @param {Number} milliseconds
+ * @returns a promise for the resolution of the given promise after milliseconds
+ * time has elapsed since the resolution of the given promise.
+ * If the given promise rejects, that is passed immediately.
+ */
+Q.delay = function (object, timeout) {
+    if (timeout === void 0) {
+        timeout = object;
+        object = void 0;
+    }
+    return Q(object).delay(timeout);
+};
+
+Promise.prototype.delay = function (timeout) {
+    return this.then(function (value) {
+        var deferred = defer();
+        setTimeout(function () {
+            deferred.resolve(value);
+        }, timeout);
+        return deferred.promise;
+    });
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided as an array, and returns a promise.
+ *
+ *      Q.nfapply(FS.readFile, [__filename])
+ *      .then(function (content) {
+ *      })
+ *
+ */
+Q.nfapply = function (callback, args) {
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfapply = function (args) {
+    var deferred = defer();
+    var nodeArgs = array_slice(args);
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided individually, and returns a promise.
+ * @example
+ * Q.nfcall(FS.readFile, __filename)
+ * .then(function (content) {
+ * })
+ *
+ */
+Q.nfcall = function (callback /*...args*/) {
+    var args = array_slice(arguments, 1);
+    return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfcall = function (/*...args*/) {
+    var nodeArgs = array_slice(arguments);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.fapply(nodeArgs).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Wraps a NodeJS continuation passing function and returns an equivalent
+ * version that returns a promise.
+ * @example
+ * Q.nfbind(FS.readFile, __filename)("utf-8")
+ * .then(console.log)
+ * .done()
+ */
+Q.nfbind =
+Q.denodeify = function (callback /*...args*/) {
+    var baseArgs = array_slice(arguments, 1);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        Q(callback).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nfbind =
+Promise.prototype.denodeify = function (/*...args*/) {
+    var args = array_slice(arguments);
+    args.unshift(this);
+    return Q.denodeify.apply(void 0, args);
+};
+
+Q.nbind = function (callback, thisp /*...args*/) {
+    var baseArgs = array_slice(arguments, 2);
+    return function () {
+        var nodeArgs = baseArgs.concat(array_slice(arguments));
+        var deferred = defer();
+        nodeArgs.push(deferred.makeNodeResolver());
+        function bound() {
+            return callback.apply(thisp, arguments);
+        }
+        Q(bound).fapply(nodeArgs).fail(deferred.reject);
+        return deferred.promise;
+    };
+};
+
+Promise.prototype.nbind = function (/*thisp, ...args*/) {
+    var args = array_slice(arguments, 0);
+    args.unshift(this);
+    return Q.nbind.apply(void 0, args);
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback with a given array of arguments, plus a provided callback.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param {Array} args arguments to pass to the method; the callback
+ * will be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nmapply = // XXX As proposed by "Redsandro"
+Q.npost = function (object, name, args) {
+    return Q(object).npost(name, args);
+};
+
+Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
+Promise.prototype.npost = function (name, args) {
+    var nodeArgs = array_slice(args || []);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback, forwarding the given variadic arguments, plus a provided
+ * callback argument.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param ...args arguments to pass to the method; the callback will
+ * be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nsend = // XXX Based on Mark Miller's proposed "send"
+Q.nmcall = // XXX Based on "Redsandro's" proposal
+Q.ninvoke = function (object, name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 2);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
+Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
+Promise.prototype.ninvoke = function (name /*...args*/) {
+    var nodeArgs = array_slice(arguments, 1);
+    var deferred = defer();
+    nodeArgs.push(deferred.makeNodeResolver());
+    this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+    return deferred.promise;
+};
+
+/**
+ * If a function would like to support both Node continuation-passing-style and
+ * promise-returning-style, it can end its internal promise chain with
+ * `nodeify(nodeback)`, forwarding the optional nodeback argument.  If the user
+ * elects to use a nodeback, the result will be sent there.  If they do not
+ * pass a nodeback, they will receive the result promise.
+ * @param object a result (or a promise for a result)
+ * @param {Function} nodeback a Node.js-style callback
+ * @returns either the promise or nothing
+ */
+Q.nodeify = nodeify;
+function nodeify(object, nodeback) {
+    return Q(object).nodeify(nodeback);
+}
+
+Promise.prototype.nodeify = function (nodeback) {
+    if (nodeback) {
+        this.then(function (value) {
+            nextTick(function () {
+                nodeback(null, value);
+            });
+        }, function (error) {
+            nextTick(function () {
+                nodeback(error);
+            });
+        });
+    } else {
+        return this;
+    }
+};
+
+// All code before this point will be filtered from stack traces.
+var qEndingLine = captureLine();
+
+return Q;
+
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js
new file mode 100644
index 0000000..7f7523c
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+
+/**
+ * Utility functions
+ */
+use(function() {
+
+    return {
+        /**
+         * 
+         * Convert a java Map instance to a JS object
+         * @param  {object} javaMap - instance of java.lang.Map
+         * @return {{}} the resulting object
+         * @ignore
+         */
+        mapToObject: function (javaMap) {
+            var props = {};
+            var it = javaMap.entrySet().iterator();
+            while (it.hasNext()) {
+                var entry = it.next();
+                props[entry.getKey()] = entry.getValue();
+            }
+            return props;
+        }
+    };
+});
\ No newline at end of file
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js
new file mode 100644
index 0000000..65b08ae
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+
+/**
+ * Wrapper factory that transforms objects
+ * obtained synchronously in promises
+ */
+use(function() {
+    return function(Q) {
+        return {
+            success: function(x) {
+                return Q(x);
+            },
+
+            failure: function(err) {
+                return Q.reject(err);
+            }
+        };
+    }
+});
\ No newline at end of file
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js
new file mode 100644
index 0000000..1a75d2f
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+use(function(_) {
+
+    function convertParams(paramMap) {
+        var result = {};
+        var it = paramMap.entrySet().iterator();
+        while (it.hasNext()) {
+            var entry = it.next();
+            var paramName = entry.getKey();
+            var paramValues = entry.getValue();
+            if (paramValues) {
+                paramValues = paramValues.map(function (param) {
+                    return param.getString();
+                });
+            }
+            result[paramName] = paramValues;
+        }
+        return result;
+    }
+
+    /**
+     * @constructor
+     * @class Properties that contain parts of the request
+     * @name RequestPathInfo
+     * @param {object} nativePathInfo The native path info object
+     */
+    function RequestPathInfo(nativePathInfo) {
+        /** @private */
+        this.nativePathInfo = nativePathInfo;
+    }
+
+    Object.defineProperties(RequestPathInfo.prototype, {
+
+        /**
+         * The resource path
+         * @name RequestPathInfo~resourcePath
+         * @type {String}
+         * @member
+         */
+        resourcePath: {
+            get: function() {
+                return this.nativePathInfo.getResourcePath();
+            }
+        },
+
+        /**
+         * The extension in the path
+         * @name RequestPathInfo~extension
+         * @type {String}
+         * @member
+         */
+        extension: {
+            get: function() {
+                return this.nativePathInfo.getExtension();
+            }
+        },
+
+        /**
+         * The selector string segment
+         * @name RequestPathInfo~selectorString
+         * @type {String}
+         * @member
+         */
+        selectorString: {
+            get: function() {
+                return this.nativePathInfo.getSelectorString();
+            }
+        },
+
+        /**
+         * The selectors in the request
+         * @name RequestPathInfo~selectors
+         * @type {Array.<String>}
+         * @member
+         */
+        selectors: {
+            get: function() {
+                return this.nativePathInfo.getSelectors();
+            }
+        },
+
+        /**
+         * The suffix in the request path
+         * @name RequestPathInfo~suffix
+         * @type {String}
+         * @member
+         */
+        suffix: {
+            get: function() {
+                return this.nativePathInfo.getSuffix();
+            }
+        }
+    });
+
+    /**
+     * @constructor
+     * @name Request
+     * @class The request class
+     * @param {object} nativeRequest The nativeResource request object
+     */
+    function Request(nativeRequest) {
+        /** @private */
+        this.nativeRequest = nativeRequest;
+
+        /**
+         * A map of the parameters in this request
+         * @name Request~parameters
+         * @type {object.<string, string>}
+         * @member
+         */
+        this.parameters = convertParams(nativeRequest.getRequestParameterMap());
+
+        /**
+         * The path info associated with this request
+         * @name Request~pathInfo
+         * @type {RequestPathInfo}
+         * @member
+         */
+        this.pathInfo = new RequestPathInfo(nativeRequest.getRequestPathInfo());
+    }
+
+    return Request;
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js
new file mode 100644
index 0000000..ba0eb0a
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+use(['helper.js'], function(helper) {
+       
+    function getParentPath(path) {
+        var index = path.lastIndexOf('/');
+        if (index == -1) {
+            return null;
+        }
+        return path.substring(0, index);
+    }
+
+
+    function getProperties(nativeResource) {
+        var valueMap = nativeResource.adaptTo(Packages.org.apache.sling.api.resource.ValueMap);
+        return (valueMap) ? helper.mapToObject(valueMap) : {};
+    }
+
+    /**
+     * @name Resource
+     * @constructor
+     * @class The Resource class
+     * @param {object} nativeResource The nativeResource resource object
+     */
+    function Resource(nativeResource, promise) {
+        /**
+         * The absolute path for this resource
+         * @name Resource~path
+         * @member
+         * @type {string}
+         */
+        this.path = nativeResource.getPath();
+
+        /**
+         * The map of properties for this object
+         * @name Resource~properties
+         * @member
+         * @type {object.<string, object>}
+         */
+        this.properties = getProperties(nativeResource);
+
+        /** @private */
+        this.nativeResource = nativeResource;
+
+        if (!promise) {
+            throw new Error('No promise library provided');
+        }
+        this._promise = promise;
+    }
+
+    Resource.prototype = /** @lends Resource.prototype */ {
+        constructor: Resource,
+
+        /**
+         * Get the parent resource
+         * @return {promise.<Resource>} a promise with the parent of this resource, or null if
+         * the resource has no parent
+         */
+        getParent: function() {
+            var parentPath = getParentPath(this.path);
+            if (!parentPath) {
+                return null;
+            }
+            var resolver = this.nativeResource.getResourceResolver();
+            var parent = resolver.resolve(parentPath);
+            return this._promise.success(new Resource(parent, this._promise));
+        },
+
+        /**
+         * Get the children of this resource
+         * @return {promise.<array.<Resource>>} a promise with the array of children resource
+         */
+        getChildren: function() {
+            var resolver = this.nativeResource.getResourceResolver();
+            var children = [];
+            var it = resolver.listChildren(this.nativeResource);
+            var promise = this._promise;
+            while (it.hasNext()) {
+                var childNativeResource = it.next();
+                children.push(new Resource(childNativeResource, promise));
+            }
+            return this._promise.success(children);
+        },
+
+        /**
+         * Returns the name of this resource. The name of a resource is the last segment of the path.
+         * @returns {string} the name of this resource
+         */
+        getName: function () {
+            var index = this.path.lastIndexOf('/');
+            if (index == -1) {
+                return this.path;
+            }
+            return this.path.substring(index + 1);
+        },
+
+        /**
+         * The resource type is meant to point to rendering/processing scripts, editing dialogs, etc.
+         * @return {string} the resource type of this resource
+         */
+        getResourceType: function () {
+            return this.nativeResource.resourceType;
+        },
+
+        /**
+         * Resolve a path to a resource. The path may be relative
+         * to this path
+         * @param  {string} path the requested path
+         * @return {promise.<Resource>} the promise of a resource. If the resource
+         * does not exist, the promise will fail
+         */
+        resolve: function(path) {
+            var resolver = this.nativeResource.getResourceResolver();
+            var res = resolver.getResource(this.nativeResource, path);
+            if (res == null) {
+                return this._promise.failure(new Error('No resource found at path: ' + path));
+            }
+            return this._promise.success(new Resource(res, this._promise));
+        }
+    };
+
+    Object.defineProperties(Resource.prototype, {
+        /**
+         * The name of the resource
+         * @name Resource~name
+         * @member
+         * @type {string}
+         */
+        name: {
+            get: function() {
+                return this.getName();
+            }
+        },
+
+        /**
+         * The resource type
+         * @name Resource~resourceType
+         * @member
+         * @type {string}
+         */
+        resourceType: {
+            get: function() {
+                return this.getResourceType();
+            }
+        }
+    });
+
+    return Resource;
+
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js
new file mode 100644
index 0000000..82b6770
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+use(['resource.js', 'request.js', 'promise.js'], function(Resource, Request, promiseFactory) {
+
+    return function(bindings, Q) {
+        var promiseLib = promiseFactory(Q);
+        var slyResource;
+        var slyProperties;
+        var slyRequest;
+        if (bindings.containsKey('resource')) {
+            slyResource = new Resource(bindings.get('resource'), promiseLib);
+            slyProperties = slyResource.properties;
+        }
+        if (bindings.containsKey('request')) {
+            slyRequest = new Request(bindings.get('request'));
+        }
+
+        /**
+         * @namespace sly
+         */
+        return /** @lends sly */ {
+
+            /**
+             * The current resource of the request
+             * @type {Resource}
+             */
+            resource: slyResource,
+
+            /**
+             * The properties of the current resource
+             * @type {Object.<string, Object>}
+             */
+            properties: slyResource.properties,
+
+            /**
+             * The request object
+             * @type {Request}
+             */
+            request: slyRequest
+        };
+    }
+
+});

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.