diff --git a/websql/websql-tests.ts b/websql/websql-tests.ts
new file mode 100644
index 0000000000..131883c243
--- /dev/null
+++ b/websql/websql-tests.ts
@@ -0,0 +1,303 @@
+///
+
+interface DbState {
+ database: Database;
+ version: DOMString;
+ transaction: (callback: SQLTransactionCallback, errorCallback?: SQLTransactionErrorCallback, successCallback?: SQLVoidCallback) => void;
+ readTransaction: (callback: SQLTransactionCallback, errorCallback?: SQLTransactionErrorCallback, successCallback?: SQLVoidCallback) => void;
+ changeVersion: (oldVersion: DOMString, newVersion: DOMString, callback?: SQLTransactionCallback, errorCallback?: SQLTransactionErrorCallback, successCallback?: SQLVoidCallback) => void
+}
+
+interface SqlQuery {
+ sql: DOMString;
+ args?: ObjectArray;
+}
+
+interface Results {
+ data: any[];
+ sqlResultSet: SQLResultSet;
+}
+
+
+(function () {
+ var dbName: string = "websql-tests";
+
+ // run the test
+ createDatabase(dbName);
+
+
+ function log(...msg: any[]) {
+ if (console && console.log) {
+ console.log.apply(console, msg);
+ }
+ }
+
+
+ function createDatabase(name: string) {
+ var tableOne = {
+ name: "tableOne",
+ columns: ["i_id", "s_name", "d_last"],
+ };
+ var tableTwo = {
+ name: "tableTwo",
+ columns: ["i_thing", "s_description", "d_decimal"],
+ };
+
+ openDatabase(dbName, "1", dbName, 1024 * 1024, function (dbState) {
+ createTables(dbState, [
+ { name: tableOne.name, columns: tableOne.columns },
+ { name: tableTwo.name, columns: tableTwo.columns },
+ ], function (res) {
+ writeRecords(dbState, [
+ { name: tableOne.name, data: createMockRecord(tableOne.columns) },
+ { name: tableOne.name, data: createMockRecord(tableOne.columns) },
+ { name: tableTwo.name, data: createMockRecord(tableTwo.columns) },
+ { name: tableTwo.name, data: createMockRecord(tableTwo.columns) },
+ { name: tableTwo.name, data: createMockRecord(tableTwo.columns) },
+ ], function (ress) {
+ log(ress);
+ readRecords(dbState, [tableOne.name, tableTwo.name], function (resss) {
+ log(resss);
+ });
+ });
+ });
+ });
+ }
+
+
+ function initDb(db: Database): DbState {
+ return {
+ database: db,
+ version: db.version,
+ transaction: db.transaction.bind(db),
+ readTransaction: db.readTransaction.bind(db),
+ changeVersion: db.changeVersion.bind(db)
+ };
+ }
+
+
+ function openDatabase(name: string, version: string, displayName: string, estimatedSize: number, callback: (dbState: DbState) => void) {
+ estimatedSize = estimatedSize || (5 * 1024 * 1024);
+
+ try {
+ if (!window.openDatabase) {
+ log("WebSQL not implemented");
+ } else {
+ // seems to synchronously open WebSQL, even though window.openDatabase is async
+ var db: Database = window.openDatabase(name, version, displayName, estimatedSize);
+ var dbState = initDb(db);
+ if (db) {
+ callback(dbState);
+ } else {
+ log("Failed to open database");
+ }
+ }
+ } catch (ex) {
+ log("Failed to open database '" + name + "': " + (JSON && JSON.stringify ? JSON.stringify(ex) : ex));
+ }
+ }
+
+
+ function createTables(dbState: DbState, tableDefs: { name: string; columns: string[]; }[], callback: (rec: SQLResultSet[][]) => void) {
+ var res: SQLResultSet[][] = [];
+ var resI = 0, count = tableDefs.length;
+ for (var i = 0; i < count; i++) {
+ createTable(dbState, tableDefs[i].name, tableDefs[i].columns, function (rec: SQLResultSet[]) {
+ res.push(rec);
+ resI++;
+ if (resI == count) {
+ callback(res);
+ }
+ });
+ }
+ }
+
+
+ function createTable(dbState: DbState, name: string, columns: string[], callback: (rec: SQLResultSet[]) => void) {
+ var sqls: SqlQuery[] = [];
+ var colDefs = columns.map((col) => {
+ var colType: string = null;
+ var type = col.charAt(0);
+ switch (type) {
+ case 'i':
+ case 'l':
+ colType = "INTEGER";
+ break;
+ case 'f':
+ case 'd':
+ colType = "REAL";
+ break;
+ case 'b':
+ case 'z':
+ colType = "INTEGER";
+ break;
+ case 's':
+ colType = "TEXT";
+ break;
+ default:
+ colType = "TEXT";
+ }
+ return col + " " + colType;
+ });
+ sqls.push({ sql: "CREATE TABLE IF NOT EXISTS " + name + " (" + colDefs.join(",") + ")", args: [] });
+ execSqlStatements(dbState.transaction, sqls, callback);
+ }
+
+
+ /** column name prefixes
+ * 'i', 'l' - integer
+ * 'f', 'd' - float
+ * 'b', 'z' - boolean
+ * 's' - string
+ * other - null
+ */
+ function createMockRecord(columns: string[]): any {
+ var rec: { [id: string]: any } = {};
+ for (var i = 0, size = columns.length; i < size; i++) {
+ var col = columns[i];
+ var type = col.charAt(0);
+ switch (type) {
+ case 'i':
+ case 'l':
+ rec[col] = parseInt((Math.random() * 1000000).toString());
+ break;
+ case 'f':
+ case 'd':
+ rec[col] = (Math.random() - 0.5) * 2000000;
+ break;
+ case 'b':
+ case 'z':
+ rec[col] = Math.random() > 0.5;
+ break;
+ case 's':
+ rec[col] = String.fromCharCode.apply(String, new Array(Math.round(Math.random() * 21)).join(".").split("").map((k) => 65 + Math.round(Math.random() * 25)));
+ break;
+ default:
+ rec[col] = null;
+ }
+ }
+ return rec;
+ }
+
+
+ function writeRecords(dbState: DbState, tableDefs: { name: string; data: any; }[], callback: (rec: SQLResultSet[][]) => void) {
+ var res: SQLResultSet[][] = [];
+ var resI = 0, count = tableDefs.length;
+ for (var i = 0; i < count; i++) {
+ writeRecord(dbState, tableDefs[i].name, tableDefs[i].data, function (rec: SQLResultSet[]) {
+ res.push(rec);
+ resI++;
+ if (resI == count) {
+ callback(res);
+ }
+ });
+ }
+ }
+
+
+ function writeRecord(dbState: DbState, tableName: string, record: any, callback: (rec: SQLResultSet[]) => void) {
+ var props = Object.keys(record);
+ var propCount = props.length;
+ var args: ObjectArray = [];
+ var sqlStatement = {
+ sql: "INSERT INTO " + tableName + " values(" + (new Array(propCount + 1).join("?").split("").join(",")) + ")",
+ args: args
+ };
+ for (var i = 0; i < propCount; i++) {
+ var prop = props[i];
+ args.push(record[prop]);
+ }
+
+ execSqlStatements(dbState.transaction, [sqlStatement], callback);
+ }
+
+
+ function readRecords(dbState: DbState, tableNames: string[], callback: (rec: Results[]) => void) {
+ var res: Results[] = [];
+ var resI = 0, count = tableNames.length;
+ for (var i = 0; i < count; i++) {
+ readRecord(dbState, tableNames[i], function (rec: Results) {
+ res.push(rec);
+ resI++;
+ if (resI == count) {
+ callback(res);
+ }
+ });
+ }
+ }
+
+
+ function readRecord(dbState: DbState, tableName: string, callback: (rec: Results) => void) {
+ var args: ObjectArray = [];
+ var sqlStatement = {
+ sql: "SELECT * FROM " + tableName,
+ args: args
+ };
+
+ execSqlStatements(dbState.transaction, [sqlStatement], function (rec: SQLResultSet[]) {
+ var resObjs: Results[] = resultSetToResults(rec);
+ callback(resObjs[0]);
+ });
+ }
+
+
+ function resultSetToResults(results: SQLResultSet[]) {
+ var resObjs: Results[] = [];
+ for (var i = 0, count = results.length; i < count; i++) {
+ var resAry: any[] = [];
+ var sqlRsI = results[i];
+ var resObj = {
+ data: resAry,
+ sqlResultSet: sqlRsI
+ };
+ resObjs.push(resObj);
+ var rows = sqlRsI.rows;
+ var rI = 0;
+ for (var rI = 0, rCount = sqlRsI.rows.length; rI < rCount; rI++) {
+ var r = rows.item(rI);
+ resAry.push(r);
+ }
+ }
+ return resObjs;
+ }
+
+
+ function compareRecords(rec1: any, rec2: any): boolean {
+ var props = Object.keys(rec1);
+ for (var i = 0, size = props.length; i < size; i++) {
+ var prop = props[i];
+ if (rec1[prop] !== rec2[prop]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ function execSqlStatements(xactMethod: (callback: SQLTransactionCallback) => void, sqlStatement: SqlQuery[],
+ callback: (rs: SQLResultSet[]) => void) {
+ var results: SQLResultSet[] = [];
+ var sqlCount = sqlStatement.length;
+ var resI = 0;
+
+ function execCommand(xact: SQLTransaction, sql: DOMString, args: ObjectArray) {
+ xact.executeSql(sql, args || [], function (xact: SQLTransaction, rs: SQLResultSet): void {
+ results.push(rs);
+ resI++;
+ if (resI === sqlCount) {
+ callback(results);
+ }
+ }, function (transaction, err) {
+ log(err, transaction);
+ return false;
+ });
+ }
+
+ xactMethod(function (xact: SQLTransaction): void {
+ for (var i = 0; i < sqlCount; i++) {
+ execCommand(xact, sqlStatement[i].sql, sqlStatement[i].args);
+ }
+ });
+ }
+
+} ());
diff --git a/websql/websql.d.ts b/websql/websql.d.ts
new file mode 100644
index 0000000000..7a9a248ea1
--- /dev/null
+++ b/websql/websql.d.ts
@@ -0,0 +1,216 @@
+// Type definitions for websql
+// Project: http://www.w3.org/TR/webdatabase/
+// Definitions by: TeamworkGuy2
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+// W3C spec: http://www.w3.org/TR/webdatabase/#database
+// Spec revision: 2010-11-18
+// NOTE: the W3C websql spec has been deprecated
+
+// uncomment to integrate with Window global object
+interface Window extends WindowDatabase { }
+interface WorkerUtils extends WorkerUtilsDatabase { }
+
+// util interfaces
+interface DOMString extends String { }
+interface ObjectArray extends Array { }
+
+
+//[Supplemental, NoInterfaceObject]
+interface WindowDatabase {
+ openDatabase(name: DOMString, version: DOMString, displayName: DOMString, estimatedSize: number,
+ creationCallback?: DatabaseCallback): Database;
+}
+
+//[Supplemental, NoInterfaceObject]
+interface WorkerUtilsDatabase {
+ openDatabase(name: DOMString, version: DOMString, displayName: DOMString, estimatedSize: number,
+ creationCallback?: DatabaseCallback): Database;
+
+ openDatabaseSync(name: DOMString, version: DOMString, displayName: DOMString, estimatedSize: number,
+ creationCallback?: DatabaseCallback): DatabaseSync;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface DatabaseCallback {
+ /*handleEvent*/(database: Database): void;
+}
+
+
+/** 4.3 Asynchronous database API - The transaction() and readTransaction() methods takes
+ * one to three arguments. When called, these methods must immediately return and then
+ * asynchronously run the transaction steps with the transaction callback being the
+ * first argument, the error callback being the second argument, if any, the success
+ * callback being the third argument, if any, and with no preflight operation or
+ * postflight operation
+ */
+interface Database {
+ /*readonly/const*/version: DOMString;
+
+ transaction(callback: SQLTransactionCallback, errorCallback?: SQLTransactionErrorCallback,
+ successCallback?: SQLVoidCallback): void;
+
+ readTransaction(callback: SQLTransactionCallback, errorCallback?: SQLTransactionErrorCallback,
+ successCallback?: SQLVoidCallback): void;
+
+ /** The changeVersion() method allows scripts to atomically verify the version number and change
+ * it at the same time as doing a schema update. When the method is invoked, it must immediately
+ * return, and then asynchronously run the transaction steps with the transaction callback being
+ * the third argument, the error callback being the fourth argument, the success callback being
+ * the fifth argument
+ */
+ changeVersion(oldVersion: DOMString, newVersion: DOMString, callback?: SQLTransactionCallback,
+ errorCallback?: SQLTransactionErrorCallback, successCallback?: SQLVoidCallback): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLVoidCallback {
+ /*handleEvent*/(): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLTransactionCallback {
+ /*handleEvent*/(transaction: SQLTransaction): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLTransactionErrorCallback {
+ /*handleEvent*/(error: SQLError): void;
+}
+
+/** 4.3.1 Executing SQL statements
+ */
+interface SQLTransaction {
+ executeSql(sqlStatement: DOMString, arguments?: ObjectArray, callback?: SQLStatementCallback,
+ errorCallback?: SQLStatementErrorCallback): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLStatementCallback {
+ /*handleEvent*/(transaction: SQLTransaction, resultSet: SQLResultSet): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLStatementErrorCallback {
+ /*handleEvent*/(transaction: SQLTransaction, error: SQLError): boolean;
+}
+
+
+/** 4.4 Synchronous database API
+ */
+interface DatabaseSync {
+ /*readonly/const*/version: DOMString;
+
+ transaction(callback: SQLTransactionSyncCallback): void;
+
+ readTransaction(callback: SQLTransactionSyncCallback): void;
+
+ changeVersion(oldVersion: DOMString, newVersion: DOMString, callback: SQLTransactionSyncCallback): void;
+}
+
+//[Callback = FunctionOnly, NoInterfaceObject]
+interface SQLTransactionSyncCallback {
+ /*handleEvent*/(transaction: SQLTransactionSync): void;
+}
+
+/** 4.4.1 Executing SQL statements
+ */
+interface SQLTransactionSync {
+ executeSql(sqlStatement: DOMString, arguments?: ObjectArray): SQLResultSet;
+}
+
+
+/** 4.5 Database query results
+ * The insertId attribute must return the row ID of the row that the SQLResultSet
+ * object's SQL statement inserted into the database, if the statement inserted a row.
+ * If the statement inserted multiple rows, the ID of the last row must be the one returned.
+ * If the statement did not insert a row, then the attribute must instead raise an INVALID_ACCESS_ERR exception.
+ *
+ * The rowsAffected attribute must return the number of rows that were changed by the SQL statement.
+ * If the statement did not affected any rows, then the attribute must return zero.
+ * For "SELECT" statements, this returns zero (querying the database doesn't affect any rows).
+ *
+ * The rows attribute must return a SQLResultSetRowList representing the rows returned,
+ * in the order returned by the database. The same object must be returned each time.
+ * If no rows were returned, then the object will be empty (its length will be zero)
+ */
+interface SQLResultSet {
+ insertId: number;
+ rowsAffected: number;
+ rows: SQLResultSetRowList;
+}
+
+/** SQLResultSetRowList objects have a length attribute that must return the number of
+ * rows it represents (the number of rows returned by the database). This is the length.
+ * Fetching the length might be expensive, and authors are thus encouraged to avoid using
+ * it (or enumerating over the object, which implicitly uses it) where possible.
+ * The object's supported property indices are the numbers in the range zero to length-1,
+ * unless the length is zero, in which case there are no supported property indices.
+ * The item(index) attribute must return the row with the given index index.
+ * If there is no such row, then the method must return null.
+ *
+ * Each row must be represented by a native ordered dictionary data type. In the
+ * JavaScript binding, this must be Object. Each row object must have one property
+ * (or dictionary entry) per column, with those properties enumerating in the order
+ * that these columns were returned by the database. Each property must have the
+ * name of the column and the value of the cell, as they were returned by the database
+ */
+interface SQLResultSetRowList {
+ length: number;
+ item(index: number): any;
+}
+
+
+/** 4.6 Errors and exceptions - asynchronous database API error
+ */
+declare class SQLError {
+ static UNKNOWN_ERR: number; // = 0;
+ static DATABASE_ERR: number; // = 1;
+ static VERSION_ERR: number; // = 2;
+ static TOO_LARGE_ERR: number; // = 3;
+ static QUOTA_ERR: number; // = 4;
+ static SYNTAX_ERR: number; // = 5;
+ static CONSTRAINT_ERR: number; // = 6;
+ static TIMEOUT_ERR: number; // = 7;
+
+ code: number;
+ message: DOMString;
+}
+
+// synchronous database API error
+declare class SQLException {
+ /** Code 0 - The transaction failed for reasons unrelated to the database itself
+ * and not covered by any other error code.
+ */
+ static UNKNOWN_ERR: number; // = 0;
+ /** Code 1 - The statement failed for database reasons not covered by any other error code. */
+ static DATABASE_ERR: number; // = 1;
+ /** Code 2 - The operation failed because the actual database version was not what it should be.
+ * For example, a statement found that the actual database version no longer matched the
+ * expected version of the Database or DatabaseSync object, or the Database.changeVersion()
+ * or DatabaseSync.changeVersion() methods were passed a version that doesn't match the actual database version.
+ */
+ static VERSION_ERR: number; // = 2;
+ /** Code 3 - The statement failed because the data returned from the database was too large.
+ * The SQL "LIMIT" modifier might be useful to reduce the size of the result set.
+ */
+ static TOO_LARGE_ERR: number; // = 3;
+ /** Code 4 - The statement failed because there was not enough remaining storage space,
+ * or the storage quota was reached and the user declined to give more space to the database.
+ */
+ static QUOTA_ERR: number; // = 4;
+ /** Code 5 - The statement failed because of a syntax error, or the number of arguments did
+ * not match the number of ? placeholders in the statement, or the statement tried to use a
+ * statement that is not allowed, such as BEGIN, COMMIT, or ROLLBACK, or the statement tried
+ * to use a verb that could modify the database but the transaction was read-only. */
+ static SYNTAX_ERR: number; // = 5;
+ /** Code 6 - An INSERT, UPDATE, or REPLACE statement failed due to a constraint failure.
+ * For example, because a row was being inserted and the value given for the primary
+ * key column duplicated the value of an existing row. */
+ static CONSTRAINT_ERR: number; // = 6;
+ /** Code 7 - A lock for the transaction could not be obtained in a reasonable time. */
+ static TIMEOUT_ERR: number; // = 7;
+
+ code: number;
+ message: DOMString;
+}