diff --git a/dexie/dexie-tests.ts b/dexie/dexie-tests.ts
new file mode 100644
index 000000000..89318f070
--- /dev/null
+++ b/dexie/dexie-tests.ts
@@ -0,0 +1,256 @@
+///
+
+import Dexie = require("Dexie");
+
+module Utils {
+
+ export class Console {
+ textarea: HTMLTextAreaElement;
+
+ constructor() {
+ this.textarea = document.createElement('textarea');
+ }
+
+ log(txt: string, type?: string) {
+ if (type) this.textarea.value += type + " ";
+ this.textarea.value += txt + "\n";
+ }
+ error = function (txt: string) {
+ this.log(txt, "ERROR!");
+ }
+ }
+
+}
+
+module AppDb {
+
+ export class AppDatabase extends Dexie {
+
+ contacts: Dexie.Table;
+ emails: Dexie.Table;
+ phones: Dexie.Table;
+
+ constructor() {
+
+ super("MyTypeScriptAppDb");
+
+ var db = this;
+
+ //
+ // Define tables and indexes
+ //
+ db.version(1).stores({
+ contacts: '++id, first, last',
+ emails: '++id, contactId, type, email',
+ phones: '++id, contactId, type, phone',
+ });
+
+ // Let's physically map Contact class to contacts table.
+ // This will make it possible to call loadEmailsAndPhones()
+ // directly on retrieved database objects.
+ db.contacts.mapToClass(Contact);
+ }
+ }
+
+ /* Just for code completion and compilation - defines
+ * the interface of objects stored in the emails table.
+ */
+ export interface IEmailAddress {
+ id?: number;
+ contactId: number;
+ type: string;
+ email: string;
+ }
+
+ /* Just for code completion and compilation - defines
+ * the interface of objects stored in the phones table.
+ */
+ export interface IPhoneNumber {
+ id?: number;
+ contactId: number;
+ type: string;
+ phone: string;
+ }
+
+ /* This is a 'physical' class that is mapped to
+ * the contacts table. We can have methods on it that
+ * we could call on retrieved database objects.
+ */
+ export class Contact {
+ id: number;
+ first: string;
+ last: string;
+ emails: IEmailAddress[];
+ phones: IPhoneNumber[];
+
+ constructor(first: string, last: string, id?: number) {
+ this.first = first;
+ this.last = last;
+ if (id) this.id = id;
+ }
+
+ loadEmailsAndPhones() {
+ return Dexie.Promise.all(
+ db.emails
+ .where('contactId').equals(this.id)
+ .toArray(emails => this.emails = emails)
+ ,
+ db.phones
+ .where('contactId').equals(this.id)
+ .toArray(phones => this.phones = phones)
+
+ ).then(x => this);
+ }
+
+ save() {
+ return db.transaction('rw', db.contacts, db.emails, db.phones, () => {
+ Dexie.Promise.all(
+ // Save existing arrays
+ Dexie.Promise.all(this.emails.map(email => db.emails.put(email))),
+ Dexie.Promise.all(this.phones.map(phone => db.phones.put(phone))))
+ .then(results => {
+ // Remove items from DB that is was not saved here:
+ var emailIds = results[0], // array of resulting primary keys
+ phoneIds = results[1]; // array of resulting primary keys
+
+ db.emails.where('contactId').equals(this.id)
+ .and(email => emailIds.indexOf(email.id) === -1)
+ .delete();
+
+ db.phones.where('contactId').equals(this.id)
+ .and(phone => phoneIds.indexOf(phone.id) === -1)
+ .delete();
+
+ // At last, save our own properties.
+ // (Must not do put(this) because we would get
+ // reduntant emails/phones arrays saved into db)
+ db.contacts.put(
+ new Contact(this.first, this.last, this.id))
+ .then(id => this.id = id);
+ });
+ });
+ }
+ }
+
+ export var db = new AppDatabase();
+ db.open();
+}
+
+
+import Console = Utils.Console;
+import db = AppDb.db;
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ // Initialize our Console widget - it will log browser window.
+ var console = new Console();
+ document.getElementById('consoleArea').appendChild(console.textarea);
+
+ // Test it:
+ console.log("Hello world!");
+
+ // Make sure to never miss any unexpected error:
+ Dexie.Promise.on.error.subscribe(e => {
+ // Log any uncatched error:
+ console.error(e);
+ });
+
+ //
+ // Let's clear and re-seed the database:
+ //
+ clearDatabase()
+ .then(seedDatabase)
+ .then(playALittle_add_phone_to_adam)
+ .then(printContacts);
+
+ function clearDatabase() {
+ console.log("Clearing database...");
+ return Dexie.Promise.all(
+ db.contacts.clear(),
+ db.emails.clear(),
+ db.phones.clear());
+ }
+
+ function seedDatabase() {
+ console.log("Seeding database with some contacts...");
+ return db.transaction('rw', db.contacts, db.emails, db.phones, () => {
+ // Populate a contact
+ db.contacts.add(new AppDb.Contact('Arnold', 'Fitzgerald')).then(id => {
+ // Populate some emails and phone numbers for the contact
+ db.emails.add({ contactId: id, type: 'home', email: 'arnold@email.com' });
+ db.emails.add({ contactId: id, type: 'work', email: 'arnold@abc.com' });
+ db.phones.add({ contactId: id, type: 'home', phone: '12345678' });
+ db.phones.add({ contactId: id, type: 'work', phone: '987654321' });
+ });
+
+ // ... and another one...
+ db.contacts.add(new AppDb.Contact('Adam', 'Tensta')).then(id => {
+ // Populate some emails and phone numbers for the contact
+ db.emails.add({ contactId: id, type: 'home', email: 'adam@tensta.se' });
+ db.phones.add({ contactId: id, type: 'work', phone: '88888888' });
+ });
+ });
+ }
+
+ function playALittle_add_phone_to_adam() {
+ // Now, just to examplify how to use the save() method as an alternative
+ // to db.phones.add(), we will add yet another phone number
+ // to an existing contact and then re-save it:
+ console.log("Playing a little: adding another phone entry for Adam Tensta...");
+ return db.contacts
+ .where('last').equals('Tensta').first(c => c.loadEmailsAndPhones())
+ .then(contact => {
+ // Also add another phone number to Adam Tensta:
+ contact.phones.push({
+ contactId: contact.id,
+ type: 'custom',
+ phone: '112'
+ });
+ contact.save();
+ });
+ }
+
+ function printContacts() {
+
+ // Now we're gonna list all contacts starting with letter 'A'
+ // and print them out.
+ // For each contact, also resolve its collection of
+ // phone number entries and email addresses by reverse-quering
+ // the foreign tables.
+
+ // For atomicity and speed, use a single transaction for the
+ // queries to make:
+ db.transaction('r', [db.contacts, db.phones, db.emails], () => {
+
+ // Query some contacts
+ return db.contacts
+ .where('first').startsWithIgnoreCase('a')
+ .sortBy('id')
+ .then(contacts =>
+
+ // Resolve array properties 'emails' and 'phones'
+ // on each and every contact:
+ Dexie.Promise.all(
+ contacts.map(contact =>
+ contact.loadEmailsAndPhones()))
+ );
+
+ }).then(contacts => {
+
+ // Print result
+ console.log("Database contains the following contacts:");
+ contacts.forEach(contact => {
+ console.log(contact.id + ". " + contact.first + " " + contact.last);
+ console.log(" Phone numbers: ");
+ contact.phones.forEach(phone => {
+ console.log(" " + phone.phone + "(" + phone.type + ")");
+ });
+ console.log(" Emails: ");
+ contact.emails.forEach(email => {
+ console.log(" " + email.email + "(" + email.type + ")");
+ });
+ });
+ });
+ }
+});
+
diff --git a/dexie/dexie.d.ts b/dexie/dexie.d.ts
new file mode 100644
index 000000000..7d498f4d3
--- /dev/null
+++ b/dexie/dexie.d.ts
@@ -0,0 +1,316 @@
+
+interface Thenable {
+ then(onFulfilled: (value: R) => Thenable, onRejected: (error: any) => Thenable): Thenable;
+ then(onFulfilled: (value: R) => Thenable, onRejected?: (error: any) => U): Thenable;
+ then(onFulfilled: (value: R) => U, onRejected: (error: any) => Thenable): Thenable;
+ then(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): Thenable;
+}
+
+declare class Dexie {
+ constructor(databaseName: string);
+
+ constructor(databaseName: string, options: { addons: Array<(db: Dexie) => void> });
+
+ name: string;
+ tables: Dexie.Table[];
+ verno: number;
+
+ static addons: Array<(db: Dexie) => void>;
+ static version: number;
+
+ static getDatabaseNames(): Dexie.Promise>;
+
+ static getDatabaseNames(onFulfilled: (value: Array) => Thenable): Dexie.Promise;
+
+ static getDatabaseNames(onFulfilled: (value: Array) => U): Dexie.Promise;
+
+ static getByKeyPath(obj: Object, keyPath: string): any;
+
+ static setByKeyPath(obj: Object, keyPath: string, value: any): void;
+
+ static delByKeyPath(obj: Object, keyPath: string): void;
+
+ static shallowClone(obj: Object): Object;
+
+ static deepClone(obj: Object): Object;
+
+ version(versionNumber: Number): Dexie.Version
+
+ on: {
+ (eventName: string, subscriber: () => any): void;
+ (eventName: string, subscriber: () => any, bSticky: boolean): void;
+ ready: Dexie.DexieOnReadyEvent;
+ error: Dexie.DexieErrorEvent;
+ populate: Dexie.DexieEvent;
+ blocked: Dexie.DexieEvent;
+ versionchange: Dexie.DexieVersionChangeEvent;
+ }
+
+ open(): Dexie.Promise;
+
+ table(tableName: string): Dexie.Table;
+
+ table(tableName: string): Dexie.Table;
+
+ table(tableName: string): Dexie.Table;
+
+ transaction(mode: string, table: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, table3: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, tables: Dexie.Table[], scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, table3: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, tables: Dexie.Table[], scope: () => U): Dexie.Promise;
+
+ close(): void;
+
+ delete(): Dexie.Promise;
+
+ isOpen(): boolean;
+
+ hasFailed(): boolean;
+
+ backendDB(): IDBDatabase;
+
+ vip(scopeFunction: () => U): U;
+}
+
+declare module Dexie {
+
+ class Promise implements Thenable {
+ constructor(callback: (resolve: (value?: Thenable) => void, reject: (error?: any) => void) => void);
+
+ constructor(callback: (resolve: (value?: R) => void, reject: (error?: any) => void) => void);
+
+ then(onFulfilled: (value: R) => Thenable, onRejected: (error: any) => Thenable): Promise;
+
+ then(onFulfilled: (value: R) => Thenable, onRejected?: (error: any) => U): Promise;
+
+ then(onFulfilled: (value: R) => U, onRejected: (error: any) => Thenable): Promise;
+
+ then(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): Promise;
+
+ catch(onRejected: (error: any) => Promise): Promise;
+
+ catch(ExceptionType: Function, onRejected: (error : any) => Promise): Promise;
+
+ catch(errorName: string, onRejected: (error : any) => Promise): Promise;
+
+ finally(onFinally: () => any): Promise;
+
+ onuncatched: () => any;
+ }
+
+ module Promise {
+ function resolve(value?: Thenable): Promise;
+
+ function resolve(value?: R): Promise;
+
+ function reject(error: any): Promise;
+
+ function all(promises: Thenable[]): Promise;
+
+ function all(...promises: Thenable[]): Promise;
+
+ function race(promises: Thenable[]): Promise;
+
+ function newPSD(scope: () => R): R;
+
+ var PSD: any;
+
+ var on: {
+ (eventName: string, subscriber: (...args : any[]) => any): void;
+ error: DexieErrorEvent;
+ }
+ }
+
+
+ interface Version {
+ stores(schema: { [key: string]: string }): Version;
+ upgrade(fn: (trans: Transaction) => void): Version;
+ }
+
+ interface Transaction {
+ active: boolean;
+ db: Dexie;
+ mode: string;
+ idbtrans: IDBTransaction;
+ tables: { [type: string]: Table };
+ storeNames: Array;
+ on: {
+ (eventName: string, subscriber: () => any): void;
+ complete: DexieEvent;
+ abort: DexieEvent;
+ error: DexieEvent;
+ }
+ abort(): void;
+ table(tableName: string): Table;
+ table(tableName: string): Table;
+ table(tableName: string): Table;
+ }
+
+ interface DexieEvent {
+ subscribe(fn: () => any) : void;
+ unsubscribe(fn: () => any) : void;
+ fire() : any;
+ }
+
+ interface DexieErrorEvent {
+ subscribe(fn: (error: any) => any) : void;
+ unsubscribe(fn: (error: any) => any) : void;
+ fire(error: any) : any;
+ }
+
+ interface DexieVersionChangeEvent {
+ subscribe(fn: (event: IDBVersionChangeEvent) => any) : void;
+ unsubscribe(fn: (event: IDBVersionChangeEvent) => any) : void;
+ fire(event: IDBVersionChangeEvent) : any;
+ }
+
+ interface DexieOnReadyEvent
+ {
+ subscribe(fn: () => any, bSticky: boolean) : void;
+ unsubscribe(fn: () => any) : void;
+ fire() : any;
+ }
+
+ interface Table {
+ name: string;
+ schema: TableSchema;
+ hook: {
+ (eventName: string, subscriber: () => any): void;
+ creating: DexieEvent;
+ reading: DexieEvent;
+ updating: DexieEvent;
+ deleting: DexieEvent;
+ }
+
+ get(key: Key): Promise;
+ where(index: string): WhereClause;
+
+ filter(fn: (obj: T) => boolean): Collection;
+
+ count(): Promise;
+ count(onFulfilled: (value: number) => Thenable): Promise;
+ count(onFulfilled: (value: number) => U): Promise;
+
+ offset(n: number): Collection;
+
+ limit(n: number): Collection;
+
+ each(callback: (obj: T, cursor: IDBCursor) => any): Promise;
+
+ toArray(): Promise>;
+ toArray(onFulfilled: (value: Array) => Thenable): Promise;
+ toArray(onFulfilled: (value: Array) => U): Promise;
+
+ toCollection(): Collection;
+ orderBy(index: string): Collection;
+ reverse(): Collection;
+ mapToClass(constructor: Function): Function;
+ add(item: T, key?: Key): Promise;
+ update(key: Key, changes: { [keyPath: string]: any }) : Promise;
+ put(item: T, key?: Key): Promise;
+ delete(key: Key): Promise;
+ clear(): Promise;
+ }
+
+ interface WhereClause {
+ above(key: number): Collection;
+ above(key: string): Collection;
+ above(key: Date): Collection;
+ above(key: Array): Collection;
+ aboveOrEqual(key: number): Collection;
+ aboveOrEqual(key: string): Collection;
+ aboveOrEqual(key: Date): Collection;
+ aboveOrEqual(key: Array): Collection;
+ anyOf(keys: Array): Collection;
+ anyOf(...keys: any[]): Collection;
+ below(key: number): Collection;
+ below(key: string): Collection;
+ below(key: Date): Collection;
+ below(key: Array): Collection;
+ belowOrEqual(key: number): Collection;
+ belowOrEqual(key: string): Collection;
+ belowOrEqual(key: Date): Collection;
+ belowOrEqual(key: Array): Collection;
+ between(lower: number, upper: number, includeLower?: boolean, includeUpper?: boolean): Collection;
+ between(lower: string, upper: string, includeLower?: boolean, includeUpper?: boolean): Collection;
+ between(lower: Date, upper: Date, includeLower?: boolean, includeUpper?: boolean): Collection;
+ between(lower: Array, upper: Array, includeLower?: boolean, includeUpper?: boolean): Collection;
+ equals(key: number): Collection;
+ equals(key: string): Collection;
+ equals(key: Date): Collection;
+ equals(key: Array): Collection;
+ equalsIgnoreCase(key: string): Collection;
+ startsWith(key: string): Collection;
+ startsWithIgnoreCase(key: string): Collection;
+ }
+
+ interface Collection {
+ and(filter: (x: T) => boolean): Collection;
+ count(): Promise;
+ count(onFulfilled: (value: number) => Thenable): Promise;
+ count(onFulfilled: (value: number) => U): Promise;
+ distinct(): Collection;
+ each(callback: (obj: T, cursor: IDBCursor) => any): Promise;
+ eachKey(callback: (key: Key, cursor: IDBCursor) => any): Promise;
+ eachUniqueKey(callback: (key: Key, cursor: IDBCursor) => any): Promise;
+ first(): Promise;
+ first(onFulfilled: (value: T) => Thenable): Promise;
+ first(onFulfilled: (value: T) => U): Promise;
+ keys(): Promise;
+ keys(onFulfilled: (value: Key[]) => Thenable): Promise;
+ keys(onFulfilled: (value: Key[]) => U): Promise;
+ last(): Promise;
+ last(onFulfilled: (value: T) => Thenable): Promise;
+ last(onFulfilled: (value: T) => U): Promise;
+ limit(n: number): Collection;
+ offset(n: number): Collection;
+ or(indexOrPrimayKey: string): WhereClause;
+ reverse(): Collection;
+ sortBy(keyPath: string): Promise;
+ sortBy(keyPath: string, onFulfilled: (value: T[]) => Thenable): Promise;
+ sortBy(keyPath: string, onFulfilled: (value: T[]) => U): Promise;
+ toArray(): Promise>;
+ toArray(onFulfilled: (value: Array) => Thenable): Promise;
+ toArray(onFulfilled: (value: Array) => U): Promise;
+ uniqueKeys(): Promise;
+ uniqueKeys(onFulfilled: (value: Key[]) => Thenable): Promise;
+ uniqueKeys(onFulfilled: (value: Key[]) => U): Promise;
+ until(filter: (value: T) => boolean, includeStopEntry?: boolean): Collection;
+ // WriteableCollection:
+ delete(): Promise;
+ modify(changes: { [keyPath: string]: any }): Promise;
+ modify(changeCallback: (obj: T) => void): Promise;
+ }
+
+ interface TableSchema {
+ name: string;
+ primKey: IndexSpec;
+ indexes: IndexSpec[];
+ mappedClass: Function;
+ }
+
+ interface IndexSpec {
+ name: string;
+ keyPath: any;
+ unique: boolean;
+ multi: boolean;
+ auto: boolean;
+ compound: boolean;
+ src: string;
+ }
+}
+
+declare module 'Dexie' {
+ export = Dexie;
+}