diff --git a/README.md b/README.md index 5a56d29548..0c0a629192 100755 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ List of Definitions * [linq.js](http://linqjs.codeplex.com/) (by [Marcin Najder](https://github.com/marcinnajder)) * [Livestamp.js](https://github.com/mattbradley/livestampjs) (by [Vincent Bortone](https://github.com/vbortone)) * [Marked](https://github.com/chjj/marked) (by [William Orr](https://github.com/worr)) +* [Meteor](https://www.meteor.com) (by [Dave Allen](https://github.com/fullflavedave)) * [Modernizr](http://modernizr.com/) (by [Boris Yankov](https://github.com/borisyankov) and [Theodore Brown](https://github.com/theodorejb/)) * [Moment.js](https://github.com/timrwood/moment) (by [Michael Lakerveld](https://github.com/Lakerfield)) * [Mousetrap](http://craig.is/killing/mice) (by [Dániel Tar](https://github.com/qcz)) diff --git a/meteor/meteor-tests.ts b/meteor/meteor-tests.ts new file mode 100644 index 0000000000..ca1973f638 --- /dev/null +++ b/meteor/meteor-tests.ts @@ -0,0 +1,610 @@ +/// + +/** + * All code below was copied from the examples at http://docs.meteor.com/. + * When necessary, code was added to make the examples work (e.g. declaring a variable + * that was assumed to have been declared earlier) + */ + + +/*********************************** Begin setup for tests ******************************/ + +// A developer must declare a var Template like this in a separate file to use this TypeScript type definition file +interface ITemplate { + adminDashboard: IMeteorViewModel; + chat: IMeteorViewModel; +} +declare var Template: ITemplate; + +var Rooms = new Meteor.Collection('rooms'); +var Messages = new Meteor.Collection('messages'); +var Monkeys = new Meteor.Collection('monkeys'); + +/********************************** End setup for tests *********************************/ + + +/** + * From Core, Meteor.startup section + * Tests Meteor.isServer, Meteor.startup, Collection.insert(), Collection.find() + */ +if (Meteor.isServer) { + Meteor.startup(function () { + if (Rooms.find().count() === 0) { + Rooms.insert({name: "Initial room"}); + } + }); +} + +/** + * From Publish and Subscribe, Meteor.publish section + **/ +Meteor.publish("rooms", function () { + return Rooms.find({}, {fields: {secretInfo: 0}}); +}); + +Meteor.publish("adminSecretInfo", function () { + return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}}); +}); + +Meteor.publish("roomAndMessages", function (roomId) { + check(roomId, String); + return [ + Rooms.find({_id: roomId}, {fields: {secretInfo: 0}}), + Messages.find({roomId: roomId}) + ]; +}); + +/** + * Also from Publish and Subscribe, Meteor.publish section + */ +Meteor.publish("counts-by-room", function (roomId) { + var self = this; + check(roomId, String); + var count = 0; + var initializing = true; + var handle = Messages.find({roomId: roomId}).observeChanges({ + added: function (id) { + count++; +// if (!initializing) + +// Todo: Not sure how to define in typescript +// self.changed("counts", roomId, {count: count}); + }, + removed: function (id) { + count--; +// Todo: Not sure how to define in typescript +// self.changed("counts", roomId, {count: count}); + } + }); + + initializing = false; + +// Todo: Not sure how to define in typescript +// self.added("counts", roomId, {count: count}); + self.ready(); + + self.onStop(function () { + handle.stop(); + }); +}); + +var Counts = new Meteor.Collection("counts"); + +Deps.autorun(function () { + Meteor.subscribe("counts-by-room", Session.get("roomId")); +}); + +console.log("Current room has " + + Counts.findOne(Session.get("roomId")).count + + " messages."); + +/** + * From Publish and Subscribe, Meteor.subscribe section + */ +Meteor.subscribe("allplayers"); + +/** + * Also from Meteor.subscribe section + */ +Deps.autorun(function () { + Meteor.subscribe("chat", {room: Session.get("current-room")}); + Meteor.subscribe("privateMessages"); +}); + +/** + * From Methods, Meteor.methods section + */ +Meteor.methods({ + foo: function (arg1, arg2) { + check(arg1, String); + check(arg2, [Number]); + + var you_want_to_throw_an_error = true; + if (you_want_to_throw_an_error) + throw new Meteor.Error(404, "Can't find my pants"); + return "some return value"; + }, + + bar: function () { + // .. do other stuff .. + return "baz"; + } +}); + +/** + * From Methods, Meteor.call section + */ +Meteor.call('foo', 1, 2, function (error, result) {} ); +var result = Meteor.call('foo', 1, 2); + +/** + * From Collections, Meteor.Collection section + */ +// DA: I added the "var" keyword in there +var Chatrooms = new Meteor.Collection("chatrooms"); +Messages = new Meteor.Collection("messages"); + +var myMessages = Messages.find({userId: Session.get('myUserId')}).fetch(); + +Messages.insert({text: "Hello, world!"}); + +Messages.update(myMessages[0]._id, {$set: {important: true}}); + +var Posts = new Meteor.Collection("posts"); +Posts.insert({title: "Hello world", body: "First post"}); + +// Couldn't find assert() in the meteor docs +//assert(Posts.find().count() === 1); + +/** + * Todo: couldn't figure out how to make this next line work with Typescript + * since there is already a Collection constructor with a different signature + * + var Scratchpad = new Meteor.Collection; +for (var i = 0; i < 10; i++) + Scratchpad.insert({number: i * 2}); +assert(Scratchpad.find({number: {$lt: 9}}).count() === 5); +**/ + +var Animal = function (doc) { +// _.extend(this, doc); +}; + +// DA: I altered this to remove dependencies on Underscore +Animal.prototype = { + makeNoise: function () { + console.log(this.sound); + } +} + + +// Define a Collection that uses Animal as its document +var Animals = new Meteor.Collection("Animals", { + transform: function (doc) { return new Animal(doc); } +}); + +// Create an Animal and call its makeNoise method +Animals.insert({name: "raptor", sound: "roar"}); +Animals.findOne({name: "raptor"}).makeNoise(); // prints "roar" + +/** + * From Collections, Collection.insert section + */ +// DA: I added the variable declaration statements to make this work +var Lists = new Meteor.Collection('Lists'); +var Items = new Meteor.Collection('Lists'); + +var groceriesId = Lists.insert({name: "Groceries"}); +Items.insert({list: groceriesId, name: "Watercress"}); +Items.insert({list: groceriesId, name: "Persimmons"}); + +/** + * From Collections, collection.update section + */ +var Players = new Meteor.Collection('Players'); + +Template.adminDashboard.events({ + 'click .givePoints': function () { + Players.update(Session.get("currentPlayer"), {$inc: {score: 5}}); + } +}); + +/** + * Also from Collections, collection.update section + */ +Meteor.methods({ + declareWinners: function () { + Players.update({score: {$gt: 10}}, + {$addToSet: {badges: "Winner"}}, + {multi: true}); + } +}); + +/** + * From Collections, collection.remove section + */ +Template.chat.events({ + 'click .remove': function () { + Messages.remove(this._id); + } +}); + +// DA: I added this next line +var Logs = new Meteor.Collection('logs'); + +Meteor.startup(function () { + if (Meteor.isServer) { + Logs.remove({}); + Players.remove({karma: {$lt: -2}}); + } +}); + +/*** + * From Collections, collection.allow section + */ +Posts = new Meteor.Collection("posts"); + +Posts.allow({ + insert: function (userId, doc) { + // the user must be logged in, and the document must be owned by the user + return (userId && doc.owner === userId); + }, + update: function (userId, doc, fields, modifier) { + // can only change your own documents + return doc.owner === userId; + }, + remove: function (userId, doc) { + // can only remove your own documents + return doc.owner === userId; + }, + fetch: ['owner'] +}); + +Posts.deny({ + update: function (userId, docs, fields, modifier) { + // can't change owners + return docs.userId = userId; + }, + remove: function (userId, doc) { + // can't remove locked documents + return doc.locked; + }, + fetch: ['locked'] // no need to fetch 'owner' +}); + +/** + * From Collections, cursor.forEach section + */ +var topPosts = Posts.find({}, {sort: {score: -1}, limit: 5}); +var count = 0; +topPosts.forEach(function (post) { + console.log("Title of post " + count + ": " + post.title); + count += 1; +}); + +/** + * From Collections, cursor.count section + */ +var frag = Meteor.render(function () { + var highScoring = Posts.find({score: {$gt: 10}}); + return "

There are " + highScoring.count() + " posts with " + + "scores greater than 10

"; +}); +document.body.appendChild(frag); + +/** + * From Collections, cursor.observeChanges section + */ +// DA: I added this line to make it work +var Users = new Meteor.Collection('users'); + +var count1 = 0; +var query = Users.find({admin: true, onlineNow: true}); +var handle = query.observeChanges({ + added: function (id, user) { + count1++; + console.log(user.name + " brings the total to " + count1 + " admins."); + }, + removed: function () { + count1--; + console.log("Lost one. We're now down to " + count1 + " admins."); + } +}); + +// After five seconds, stop keeping the count. +setTimeout(function () {handle.stop();}, 5000); + +/** + * From Sessions, Session.set section + */ +Deps.autorun(function () { + Meteor.subscribe("chat-history", {room: Session.get("currentRoomId")}); +}); + +// Causes the function passed to Deps.autorun to be re-run, so +// that the chat-history subscription is moved to the room "home". +Session.set("currentRoomId", "home"); + +/** + * From Sessions, Session.get section + */ +Session.set("enemy", "Eastasia"); +var frag1 = Meteor.render(function () { + return "

We've always been at war with " + + Session.get("enemy") + "

"; +}); + +// Page will say "We've always been at war with Eastasia" + +// DA: commented out since transpiler didn't like append() +//document.body.append(frag1); + +// Page will change to say "We've always been at war with Eurasia" +Session.set("enemy", "Eurasia"); + +/** + * From Sessions, Session.equals section + */ +var value; +Session.get("key") === value; +Session.equals("key", value); + +/** + * From Accounts, Meteor.users section + */ +Meteor.publish("userData", function () { + return Meteor.users.find({_id: this.userId}, + {fields: {'other': 1, 'things': 1}}); +}); + +Meteor.users.deny({update: function () { return true; }}); + +/** + * From Accounts, Meteor.loginWithExternalService section + */ +Accounts.loginServiceConfiguration.remove({ + service: "weibo" +}); +Accounts.loginServiceConfiguration.insert({ + service: "weibo", + clientId: "1292962797", + secret: "75a730b58f5691de5522789070c319bc" +}); + +Meteor.loginWithGithub({ + requestPermissions: ['user', 'public_repo'] +}, function (err) { + if (err) + Session.set('errorMessage', err.reason || 'Unknown error'); +}); + +/** + * From Accounts, Accounts.ui.config section + */ +Accounts.ui.config({ + requestPermissions: { + facebook: ['user_likes'], + github: ['user', 'repo'] + }, + requestOfflineToken: { + google: true + }, + passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' +}); + +/** + * From Accounts, Accounts.validateNewUser section + */ +Accounts.validateNewUser(function (user) { + if (user.username && user.username.length >= 3) + return true; + throw new Meteor.Error(403, "Username must have at least 3 characters"); +}); +// Validate username, without a specific error message. +Accounts.validateNewUser(function (user) { + return user.username !== "root"; +}); + +/** + * From Accounts, Accounts.onCreateUser section + */ +Accounts.onCreateUser(function(options, user) { + var d6 = function () { return Math.floor(Math.random() * 6) + 1; }; + user.dexterity = d6() + d6() + d6(); + // We still want the default hook's 'profile' behavior. + if (options.profile) + user.profile = options.profile; + return user; +}); + +/** + * From Passwords, Accounts.emailTemplates section + */ +Accounts.emailTemplates.siteName = "AwesomeSite"; +Accounts.emailTemplates.from = "AwesomeSite Admin "; +Accounts.emailTemplates.enrollAccount.subject = function (user) { + return "Welcome to Awesome Town, " + user.profile.name; +}; +Accounts.emailTemplates.enrollAccount.text = function (user, url) { + return "You have been selected to participate in building a better future!" + + " To activate your account, simply click the link below:\n\n" + + url; +}; + +/** + * From Templates, Template.myTemplate.helpers section + */ +Template.adminDashboard.helpers({ + foo: function () { + return Session.get("foo"); + } +}); + +/** + * From Templates, Template.myTemplate.preserve + */ +Template.adminDashboard.preserve({ + 'input[id]': function (node) { return node.id; } +}); + +/** + * From Templates, Meteor.render section + */ +var frag2 = Meteor.render(function () { + return "

There are " + Players.find({online: true}).count() + + " players online.

"; +}); +document.body.appendChild(frag2); + +Players.update({idleTime: {$gt: 30}}, {$set: {online: false}}); + +/** + * From Templates, Meteor.renderList section + */ +var frag3 = Meteor.renderList( + Posts.find({tags: "frontpage"}), + function(post) { + var style = Session.equals("selectedId", post._id) ? "selected" : ""; + // A real app would need to quote/sanitize post.name + return '
' + post.name + '
'; + }); +document.body.appendChild(frag3); + +var somePost = Posts.findOne({tags: "frontpage"}); +Session.set("selectedId", somePost._id); + +var eventTester = { + 'click p': function (event: IMeteorEvent) { + var paragraph = event.currentTarget; // always a P + var clickedElement = event.target; // could be the P or a child element + } +} + +/** + * From Match section + */ +var Chats = new Meteor.Collection('chats'); + +Meteor.publish("chats-in-room", function (roomId) { + // Make sure roomId is a string, not an arbitrary mongo selector object. + check(roomId, String); + return Chats.find({room: roomId}); +}); + +Meteor.methods({addChat: function (roomId, message) { + check(roomId, String); + check(message, { + text: String, + timestamp: Date, + // Optional, but if present must be an array of strings. + tags: Match.Optional('Test String') + }); + + // ... do something with the message ... +}}); + +/** + * From Match patterns section + */ +var pat = { name: Match.Optional('test') }; +check({ name: "something" }, pat) // OK +check({}, pat) // OK +check({ name: undefined }, pat) // Throws an exception + +// Outside an object +check(undefined, Match.Optional('test')); // OK + +/** + * From Deps, Deps.autorun section + */ +Deps.autorun(function () { + var oldest = Monkeys.findOne('age = 20'); + + if (oldest) + Session.set("oldest", oldest.name); +}); + +Deps.autorun(function (c) { + if (! Session.equals("shouldAlert", true)) + return; + + c.stop(); + alert("Oh no!"); +}); + +/** + * From Deps, Deps.Computation + */ +if (Deps.active) { + Deps.onInvalidate(function () { + Monkeys.destroy(); + Rooms.finalize(); + }); +} + +/** + * From Deps, Deps.Dependency + */ +var weather = "sunny"; +var weatherDep = new Deps.Dependency; + +var getWeather = function () { + weatherDep.depend() + return weather; +}; + +var setWeather = function (w) { + weather = w; + // (could add logic here to only call changed() + // if the new value is different from the old) + weatherDep.changed(); +}; + +/** + * From HTTP, HTTP.call section + */ +Meteor.methods({checkTwitter: function (userId) { + check(userId, String); + this.unblock(); + var result = HTTP.call("GET", "http://api.twitter.com/xyz", + {params: {user: userId}}); + if (result.statusCode === 200) + return true + return false; +}}); + + +HTTP.call("POST", "http://api.twitter.com/xyz", + {data: {some: "json", stuff: 1}}, + function (error, result) { + if (result.statusCode === 200) { + Session.set("twizzled", true); + } + }); + +/** + * From Email, Email.send section + */ +Meteor.methods({ + sendEmail: function (to, from, subject, text) { + check([to, from, subject, text], [String]); + + // Let other method calls from the same client start running, + // without waiting for the email sending to complete. + this.unblock(); + + Email.send({ + to: to, + from: from, + subject: subject, + text: text + }); + } +}); + +// In your client code: asynchronously send an email +Meteor.call('sendEmail', + 'alice@example.com', + 'Hello from Meteor!', + 'This is a test of Email.send.'); + diff --git a/meteor/meteor.d.ts b/meteor/meteor.d.ts new file mode 100644 index 0000000000..83c3c94285 --- /dev/null +++ b/meteor/meteor.d.ts @@ -0,0 +1,591 @@ +// Type definitions for Meteor 0.6.5 +// Project: http://www.meteor.com/ +// Definitions by: Dave Allen +// Definitions: https://github.com/borisyankov/DefinitelyTyped + + +interface IMeteor { + + /******** + * Core * + ********/ + isClient: boolean; + isServer: boolean; + startup(func: Function): void; + absoluteUrl(path: string, + options: { + secure?: boolean; + replaceLocalhost?: boolean; + rootUrl?: string; + }): void; + settings: Object; + release: string; + + + /************************* + * Publish and Subscribe * + *************************/ + + /** + * Publish a record set. + * + * @param name Name of the attribute set. If null, the set has no name, and the record set is + * automatically sent to all connected clients. + * @param func Function called on the server each time a client subscribes. Inside the function, + * this is the publish handler object, described below. If the client passed arguments + * to subscribe, the function is called with the same arguments. + */ + publish(name: string, func: Function): any; + //Todo: Figure out a way to define this.userId, this.added, this.changed, etc that can be called from within publish + + /** + * Subscribe to a record set. Returns a handle that provides stop() and ready() methods. + * + * @param name Name of the subscription. Matches name of server's publish() call. + * @param arg1,arg2,arg3 Optional arguments passed to publisher function on server. + * @param callbacks Optional. May include onError and onReady callbacks. Can be Object or Function. If a function + * is passed instead of an object, it is interpreted as an onReady callback. + */ + subscribe(name: string, arg1?: any, arg2?: any, ars3?: any, arg4?: any, callbacks?: Object): IMeteorHandle; + + + /*********** + * Methods * + ***********/ + methods(methods: Object): void; + Error(error: number, reason?: string, details?: string): void; + // DA: Really should be defined like this: call(name: string, ...args?: any[], asyncCallback?: Function): void; + // But typescript does not allow Rest parameter (..args) to not be the last parameter defined + call(name: string, param1?: Object, param2?: Object, param3?: Object, param4?: Object, asyncCallback?: Function): void; + apply(name: string, options: any[], asyncCallback?: Function): void; + defer(callback: Function): void; + + + /********************* + * ServerConnections * + *********************/ + status(): { + connected: boolean + status: string; + retryCount: number; + retryTime: number; + reason: string; + } + reconnect(): void; + disconnect(): void; + + + /*************** + * Collections * + ***************/ + Collection(name: string, + options?: { + connection?: Object; + idGeneration?: string; + transform?: Function; + }): void; + + + /************ + * Accounts * + ************/ + user(): { + _id: string; + username: string; + emails?: { + address: string; + verified: boolean; + } + profile?: Object; + services?: Object; + createdAt?: number; + }; + userId(): string; + users: IMeteorCollection; + loggingIn(): boolean; + logout(callback?: Function): void; + loginWithPassword(user: Object, password: string, callback?: Function): void; + loginWithExternalService(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithFacebook(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithGithub(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithGoogle(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithMeetup(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithTwitter(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + loginWithWeibo(options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + }, + callback?: Function): void; + + /************* + * Templates * + *************/ + render(htmlFunc: Function): DocumentFragment; + renderList(observable: IMeteorCursor, docFunc: Function, elseFunc?: Function): DocumentFragment; + + /********** + * Timers * + **********/ + setTimeout(func: Function, delay: number): void; + setInterval(func: Function, delay: number): void; + clearTimeout(id: number): void; + clearInterval(id: number): void; + + + /******************** Begin definitions for contributed packages from Atmosphere (or elsewhere) *******************/ + + /************************************************** + * For Paginated-Subscription contributed package * + **************************************************/ + subscribeWithPagination(collection: string, limit: number): IMeteorHandle; + Template(): void; + + /************************************************* + * For Router or Iron-Router contributed package * + *************************************************/ + Router: IMeteorRouter; + + /********************************* + * For Error contributed package * + *********************************/ + Errors: IMeteorErrors; + + /******************** End definitions for contributed packages from Atmosphere (or elsewhere) *********************/ + +} // End Meteor.someFunction definitions + + +/*************** + * Collections * + ***************/ +interface IMeteorCollection { + find(selector?, options?: Object): IMeteorCursor; + findOne(selector, options?: Object): Object; + insert(doc, callback?: Function): number; + update(selector, modifier, options?: Object, callback?: Function): void; + remove(selector, callback?: Function): void; + allow(options: Object): boolean; + deny(options: Object): boolean; + ObjectID(hexString?: string): Object; +} + +interface IMeteorCursor { + forEach(callback: Function): void; + map(callback: Function): void; + fetch(): any[]; + count(): number; + rewind(): void; + observe(callbacks: Object): void; + observeChanges(callbacks: Object): void; +} + + +/************* + * Templates * + *************/ + +/** + * To use Meteor's Template.templateName.function, you must define an interface in a separate file with + * extension ".d.ts". Within the interface, every template name must have a property by that name that + * is of type IMeteorViewModel or IMeteorManager (choose either depending on your philosophical + * preference -- both work the same) + * e.g. file ".../client/views/view-model-types.d.ts": + * + * interface ITemplate { + * postsList: IMeteorViewModel; + * comment: IMeteorViewModel; + * notifications: IMeteorViewModel; + * [your template name]: IMeteorViewModel; + * } + * declare var Template: ITemplate; + */ +interface IMeteorViewModel { + rendered(callback: Function): void; + created(callback: Function): void; + destroyed(callback: Function): void; + events(eventMap: {[eventName: string]: Function;}): void; + helpers(helpers: Object): void; + preserve(selector: Object): void; +} + +interface IMeteorManager { + rendered(callback: Function): void; + created(callback: Function): void; + destroyed(callback: Function): void; + events(eventMap: {[eventType: string]: Function}): void; + helpers(helpers: Object): Object; + preserve(selector: Object): void; +} + +// DA: Currently not used, but I'd like to figure out a way to define the function signature +// for teh callbacks in IMeteorViewModel, IMeteorManager, and many other interfaces +interface IMeteorEvent { + type?: MeteorEventType.Value; + target?: Element; + currentTarget?: Element; + which?: number; + stopPropogation(): void; + stopImmediatePropogation(): void; + preventDefault(): void; + isPropogationStopped(): boolean; + isImmediatePropogationStopped(): boolean; + isDefaultPrevented(): boolean; +} + +declare module MeteorEventType { + export enum Value {'click', 'dblclick', 'focus', 'blur', 'change', + 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'keydown', 'keypress', 'keyup', 'tap'} +} + +/*********** + * Session * + ***********/ +interface IMeteorSession { + set(key: string, value: Object): void; + setDefault(key: string, value: Object): void; + get(key: string): Object; + equals(key: string, value: any): void; +} + +interface IMeteorHandle { + loaded(): number; + limit(): number; + ready(): boolean; + loadNextPage(): void; +} + +/************************** + * Accounts and Passwords * + **************************/ +interface IMeteorAccounts { + config(options: { + sendVerificationEmail?: boolean; + forbidClientAccountCreation?: boolean; + }): void; + ui: { + config(options: { + requestPermissions?: Object; + requestOfflineToken?: Object; + passwordSignupFields?: string; + }); + } + validateNewUser(func: Function): void; + onCreateUser(func: Function): void; + createUser(options: { + username?: string; + email?: string; + password?: string; + profile?: string; + }, + callback?: Function): void; + changePassword(oldPassword: string, newPassword: string, callback?: Function): void; + forgotPassword(options: { + email: string + }, + callback?: Function): void; + resetPassword(token: string, newPassword: string, callback?: Function): void; + setPassword(userId: string, newPassword: string): void; + verifyEmail(token: string, callback?: Function): void; + sendResetPasswordEmail(userId: string, email?: string): void; + sendEnrollmentEmail(userId: string, email?: string): void; + sendVerificationEmail(userId: string, email?: string): void; + emailTemplates: { + from: string; + siteName: string; + resetPassword: IMeteorEmailValues; + enrollAccount: IMeteorEmailValues; + verifyEmail: IMeteorEmailValues; + } + // DA: I didn't see the signature for this, but it appears in the examples + loginServiceConfiguration: { + remove(options: Object): void; + insert(options: Object): void; + } +} + +interface IMeteorEmailValues { + subject?: Function; + text?: Function; +} + +interface IMeteorMatch { + test(value: any, pattern: any): boolean; + Any; + String; + Number; + Boolean; + undefined; + null; + Integer; + ObjectIncluding; + Object; + Optional(pattern: string); + OneOf(...args: string[]); + Where(condition: boolean); +} + +interface IExternalServiceParams { + options?: { + requestPermissions?: string[]; + requestOfflineToken?: boolean; + forceApprovalPrompt?: boolean; + } + callback?: Function; +} + +/******** + * Deps * + ********/ +interface IMeteorDeps { + autorun(runFunc: Function): IMeteorComputationObject; + flush(): void; + nonreactive(func: Function): void; + active: boolean; + currentComputation: IMeteorComputationObject; + onInvalidate(callback: Function): void; + afterFlush(callback: Function): void; + + /** + * @constructor + */ + Computation(): void; + + /** + * @constructor + */ + Dependency(): void; +} + +interface IMeteorComputationObject { + stop(): void; + invalidate(): void; + onInvalidate(callback: Function): void; + stopped: boolean; + invalidated: boolean; + firstRun: boolean; +} + +interface IMeteorDependencyObject { + changed(): void; + depend(fromComputation?: IMeteorComputationObject): boolean; + hasDependents(): boolean; +} + +/********* + * EJSON * + *********/ +interface IMeteorEJSON { + parse(str: string): void; + stringify(val: any): string; + fromJSONValue(val): any; + toJSONValue(val): JSON; + equals(any: any): boolean; + clone(val: any): any; + newBinary(size: number): void; + isBinary(x: any): boolean; + addType(name: string, factory: Function): void; +} + +/**************** + * HTTP package * + ****************/ +interface IMeteorHTTP { + call(method: string, url: string, options: { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; + }, asyncCallback?: Function): IMeteorHTTPResult; + get(url: string, options?: { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; + }, asyncCallback?: Function): IMeteorHTTPResult; + post(url: string, options?: { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; + }, asyncCallback?: Function): IMeteorHTTPResult; + put(url: string, options?: { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; + }, asyncCallback?: Function): IMeteorHTTPResult; + del(url: string, options?: { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; + }, asyncCallback?: Function): IMeteorHTTPResult; +} + +// DA: Currently not used +// I would like to figure out a way to specify this as type for options for IMeteorHTTP methods. +// Tests don't work if I simply specify this interface as the type. +interface IMeteorHTTPCallOptions { + content?: string; + data?: Object; + query?: string; + params?: Object; + auth?: string; + headers?: Object; + timeout?: number; + followRedirects?: boolean; +} + +interface IMeteorHTTPResult { + statusCode: number; + content: string; + data?: JSON; + headers: Object; +} + +/********* + * Email * + *********/ +interface IMeteorEmail { + send(options: { + from?: string; + to: any; + cc?: any; + bcc?: any; + replyTo?: any; + subject?: string; + text?: string; + html?: string; + headers?: Object; + }): void; +} + + +/********** + * Assets * + **********/ +interface IMeteorAssets { + getText(assetPath: string, asyncCallback?: Function): string; + getBinary(assetPath: string, asyncCallback?: Function): any; +} + +/******* + * DPP * + *******/ +interface IMeteorDPP { + connect(url: string): void; +} + +declare var Meteor: IMeteor; +declare var Collection: IMeteorCollection; +declare var Session: IMeteorSession; +declare var Deps: IMeteorDeps; +declare var Accounts: IMeteorAccounts; +declare var Match: IMeteorMatch; +declare function check(value: any, pattern: any): void; +declare var Computation: IMeteorComputationObject; +declare var Dependency: IMeteorDependencyObject; +declare var EJSON: IMeteorEJSON; +declare var HTTP: IMeteorHTTP; +declare var Email: IMeteorEmail; +declare var Assets: IMeteorAssets; +declare var DPP: IMeteorDPP; + +declare function changed(collection: string, id: string, fields, Object): void; + +/******************** Begin definitions for contributed packages from Atmosphere (or elsewhere) *********************/ + +/*************************************************** + * For Router and Iron-Router contributed packages * + ***************************************************/ +interface IMeteorRouter { + + // These are for Router + page(): void; + add(route: Object): void; + to(path: string, ...args: any[]): void; + filters(filtersMap: Object); + filter(filterName: string, options?: Object); + + // These are for Iron-Router + map(routeMap: Function): void; + path(route: string, params?: Object): void; + url(route: string): void; + routes: Object; + configure(options: IMeteorRouterConfig): void; +} + +// For Iron-Router +interface IMeteorRouterConfig { + layout: string; + notFoundTemplate: string; + loadingTemplate: string; + renderTemplates: Object; +} + +interface IMeteorErrors { + throw(message: string): void; + clear(): void; +} + +// For Router and Iron-Router contributed packages +declare var Router: IMeteorRouter; + +/******************** End definitions for contributed packages from Atmosphere (or elsewhere) ***********************/ + +/** + * Todo: + * Define "this.function" functions. + * Define the signatures of callback functions and other functions. + ***/ \ No newline at end of file