🤖 Merge PR #46527 Types for meteor/dburles:collection-helpers [meteor-dburles-collection-helpers] by @artemiswkearney

* Types for meteor/dburles:collection-helpers

* new Collection<T> now returns a Collection<T>, not a Collection<Data<T>>

* Modifier<T> -> Modifier<Data<T>>

* Retab mongo.d.ts; Data<T> no longer uses NonData<T>

* Types now distribute over unions

* Multiple improvements

- Fix behavior with null, undefined, void, and unions containing them
- Add OptionalHelper<T>
- Add AllowPartial
- Thoroughly document collectionHelpers.d.ts
This commit is contained in:
Artemis Kearney 2020-08-11 21:09:08 -07:00 committed by GitHub
parent 3181e16275
commit e8d83de7f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 794 additions and 0 deletions

View File

@ -0,0 +1,198 @@
// tslint:disable-next-line no-single-declare-module
declare module 'meteor/dburles:collection-helpers' {
export {};
type PropertyNamesMatching<T, TPred> = {
[K in keyof T]: T[K] extends TPred ? K : never;
}[keyof T];
type PropertyNamesNotMatching<T, TPred> = {
[K in keyof T]: T[K] extends TPred ? never : K;
}[keyof T];
/**
* Use to declare a non-function helper - it'll be optional when inserting values but required when adding helpers
* Tip: Use OptionalHelper<T> to declare a helper you might or might not provide - it won't be required
* when providing helpers, and won't be guaranteed on Full<T>
*/
// "T extends T ? ... : never" looks tautological, but actually serves to distribute over union types
// https://github.com/microsoft/TypeScript/issues/28791#issuecomment-443520161
export type Helper<T> = T extends T
? T extends FlavorUnsupportedTypes
? T | HelperBrand
: T & HelperFlavor
: never;
// where possible, helpers are tagged as (T & HelperFlavor)
// this is a technique called "flavoring" - (T & HelperFlavor) is assignable both to and from T for most T,
// but the presence of the flavor can be checked since (T & HelperFlavor) is also assignable to HelperFlavor
// (note that HelperFlavor is a "weak type" - since all its properties are optional, you might think anything
// would be assignable to it, but Typescript prohibits assigning any type that doesn't share at least one
// property with it)
// weirdly, ({} & HelperFlavor) still accepts {}!
interface HelperFlavor {
_meteor_dburles_collection_helpers_isHelper?: Flavor;
}
// for types where (T & HelperFlavor) === never, (T | HelperBrand) is used instead,
// and the HelperBrand is stripped off in HelpersOf
// this is less preferable, because it means:
// - on a value of the original interface type TInterface, Helper<null> and Helper<T | null> properties will
// not be assignable to null or to (T | null) respectively
// - Helpers<Helpers<T>> !== Helpers<T> when T has Helper<null> or Helper<T | null> properties
// however, this appears to be a limitation of Typescript (with strict null checks on, null and undefined
// simply *can't* extend anything besides themselves (and void in undefined's case), so there's no way to
// flavor them)
interface HelperBrand {
_meteor_dburles_collection_helpers_isBrandUnsupportedHelper: Brand;
}
// types where HelperBrand will be used instead of HelperFlavor
type FlavorUnsupportedTypes = null | undefined;
/**
* All methods and Helper<T>s declared on the type, made non-optional.
* This is what's required when defining helpers for a collection.
*/
export type Helpers<T> = FlavorAsHelpers<
T,
// methods will only ever get called on a Full<T> (unless you directly declare a Helpers<T>, but *why*)
ThisType<Full<T>> & (T extends T ? HelpersOf<NonHelpers<NonData<T>>> : never)
>;
// used to flavor Helpers<T> so we can get back to the original T if needed
// not to be confused with HelperFlavor
interface HelpersFlavor<T> {
_meteor_dburles_collection_helpers_isHelpersOf?: [Flavor, T];
}
// apply HelpersFlavor, but only if the resulting type wouldn't be never or weak
type FlavorAsHelpers<TOriginal, TToFlavor> = [TToFlavor] extends [TToFlavor & HelpersFlavor<TOriginal>]
? TToFlavor & HelpersFlavor<TOriginal>
: TToFlavor;
/**
* NonHelpers<Helpers<T>> === T
*/
export type NonHelpers<T> = T extends HelpersFlavor<infer U> ? U : T;
// To get all the helper properties, we union the list of function properties and the list of Helper<T> properties.
// Make anything not marked optional required, and anything marked optional optional.
type HelpersOf<T> = T extends T
? RemoveHelperBrands<
Required<
Pick<
T,
Exclude<
PropertyNamesMatching<Required<T>, Func> | HelperNames<Required<T>>,
OptionalHelperNames<Required<T>>
>
>
> &
Partial<Pick<T, OptionalHelperNames<Required<T>>>>
>
: never;
type Func = (...args: any[]) => any;
// The names of all properties of T with either a HelperBrand or a HelperFlavor (whether required or optional)
type HelperNames<T> = T extends T
? {
[K in keyof T]: Exclude<T[K], undefined> extends infer NoUndefined
? [HelperBrand] extends [NoUndefined]
? K
: [HelperBrand | OptionalHelperBrand] extends [NoUndefined]
? K
: [Required<NoUndefined>] extends [Required<HelperFlavor>]
? K
: [Required<NoUndefined>] extends [Required<HelperFlavor & OptionalHelperFlavor>]
? K
: never
: never;
}[keyof T]
: never;
// We also want to strip brands from the flavor-unsupported types - since the brands are no longer needed to
// tell us which types are helpers, *
type RemoveHelperBrands<T> = {
[K in keyof T]: Exclude<RemoveHelperFlavorForVoid<T[K]>, HelperBrand | OptionalHelperBrand>;
};
// void is a bit of a weird case; unlike the others, it doesn't explicitly become never when flavored,
// and (void & HelperFlavor) *is* assignable to void, but only other expressions of type (void & HelperFlavor)
// are assignable to it
// however, this is just enough to let us support it with a bit of special-casing - convert it back to normal
// void when stripping helper brands, and Helpers<TInterface>.helperVoidProperty can be assigned undefined!
// This is one way to make a helper with an optional value that can be read off a raw TInterface (although
// Helper<T | false> would work almost as well).
// tslint:disable-next-line void-return
type RemoveHelperFlavorForVoid<T> = T extends void & HelperFlavor ? void : T;
// however, we can do better
/**
* Use this to indicate a helper which can be omitted when providing helpers.
* Just marking the property as optional won't make it optional when providing helpers, as that's overloaded
* as meaning "an instance of this interface can be created from an object literal without providing its
* helpers". If you actually want a helper that's only sometimes there, use this.
*/
export type OptionalHelper<T> = T extends T
? T extends FlavorUnsupportedTypes
? T | HelperBrand | OptionalHelperBrand | undefined
: (T & HelperFlavor & OptionalHelperFlavor) | undefined
: never;
interface OptionalHelperFlavor {
_meteor_dburles_collection_helpers_isOptionalHelper?: Flavor;
}
interface OptionalHelperBrand {
_meteor_dburles_collection_helpers_isBrandUnsupportedOptionalHelper?: Brand;
}
type OptionalHelperNames<T> = T extends T
? {
[K in keyof T]: Exclude<T[K], undefined> extends infer NoUndefined
? [HelperBrand | OptionalHelperBrand] extends [NoUndefined]
? K
: [Required<NoUndefined>] extends [Required<HelperFlavor & OptionalHelperFlavor>]
? K
: never
: never;
}[keyof T]
: never;
interface DataFlavor<T> {
_meteor_dburles_collection_helpers_isData?: [Flavor, T];
}
/**
* Just the non-method/Helper properties of the type, with the methods and Helpers made optional.
* No need to declare a Collection<Data<T>>; all Collection methods already accept a Data<T>.
*/
export type Data<T> = DataFlavor<T> & NonHelpersOf<T> & (T extends T ? Partial<HelpersOf<T>> : never);
/**
* NonData<Data<T>> === T
*/
export type NonData<T> = T extends DataFlavor<infer U> ? U : T;
// All the members of T that aren't helpers
type NonHelpersOf<T> = T extends T
? Pick<
T,
Exclude<
PropertyNamesNotMatching<Required<T>, Func>,
HelperNames<Required<T>> | OptionalHelperNames<Required<T>>
>
>
: never;
/**
* The version of a type that comes out of the collection (with helpers attached).
*/
export type Full<T> = NonData<NonHelpers<T>> & (T extends T ? HelpersOf<NonData<NonHelpers<T>>> : never);
type Brand = "_meteor_dburles_collection_helpers_brand";
type Flavor = "_meteor_dburles_collection_helpers_flavor";
/**
* Collection.helpers<AllowPartial>() allows declaring only some of a collection's helpers,
* rather than enforcing that they be declared all at once.
* Only use this if you know what you're doing - it's assumed that all of a collection's helpers
* will be provided before any items are retrieved from it.
*/
export type AllowPartial = "_meteor_dburles_collection_helpers_allowPartial";
/**
* Some subset of a collection's helpers - only used by Collection.helpers<allowPartial>()
*/
export type PartialHelpers<T> = ThisType<NonData<NonHelpers<T>>> & Partial<Helpers<T>>;
}

View File

@ -0,0 +1,8 @@
// Type definitions for non-npm package Atmosphere package dburles:collection-helpers 1.1
// Project: https://github.com/dburles/meteor-collection-helpers
// Definitions by: Artemis Kearney <https://github.com/artemiswkearney>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.7
/// <reference path="./mongo.d.ts" />
/// <reference path="./collectionHelpers.d.ts" />

View File

@ -0,0 +1,460 @@
import { Mongo } from 'meteor/mongo';
import {
Helper,
OptionalHelper,
Data,
Full,
Helpers,
AllowPartial,
} from 'meteor/dburles:collection-helpers';
interface Author {
_id?: string;
firstName: string;
lastName: string;
// function properties are automatically detected as helpers; no need to specify
fullName: () => string;
books: () => Mongo.Cursor<Book>;
}
interface Book {
_id?: string;
authorId: string;
name: string;
author: () => Author | undefined;
// use Helper<T> to declare non-function helpers
foo: Helper<string>;
}
const Books = new Mongo.Collection<Book>('books');
const Authors = new Mongo.Collection<Author>('authors');
// $ExpectType Collection<Book>
Books;
// when inserting items, only data properties are required
const author1 = Authors.insert({
firstName: 'Charles',
lastName: 'Darwin',
});
const author2 = Authors.insert({
firstName: 'Carl',
lastName: 'Sagan',
});
const book1 = Books.insert({
authorId: author1,
name: 'On the Origin of Species',
});
const book2 = Books.insert({
authorId: author2,
name: 'Contact',
});
// when providing helpers, no data properties but all helpers are required
// all helpers must be provided at once; this is the only way to typecheck that none are forgotten
// $ExpectType void
Books.helpers({
author() {
return Authors.findOne(this.authorId);
},
foo: 'bar',
});
// $ExpectError
Books.helpers({
author() {
return Authors.findOne(this.authorId);
},
});
// you can override this behavior if you explicitly request to
// (don't do this unless you intend to provide all the helpers sooner or later!)
// $ExpectType void
Authors.helpers<AllowPartial>({
fullName() {
return `${this.firstName} ${this.lastName}`;
},
});
// $ExpectType void
Authors.helpers<AllowPartial>({
books() {
return Books.find({ authorId: this._id });
},
});
const book = Books.findOne(book1)!;
const author: Author | undefined = book.author();
// can modify resulting Book and update Books with it, even though it has helpers attached
book.name = 'Renamed Book';
// $ExpectType number
Books.update(book._id!, book);
// with mandatory helpers, new objects can be declared directly as Data<T>
const bookData: Data<Book> = {
authorId: 'Author',
name: 'Name',
};
// this interface has its helpers declared as optional; this makes instantiating the interface easier,
// but means you need to specify Full<T> to say an object has had its helpers attached
interface OptionalHelpers {
_id: string;
value: number;
increment?: () => void;
zero?: Helper<number>;
}
const optionalHelpers = new Mongo.Collection<OptionalHelpers>('optionalHelpers');
// optional helpers still have to be provided when calling helpers
// $ExpectError
optionalHelpers.helpers({});
// $ExpectError
optionalHelpers.helpers({
increment() {
this.value++;
},
});
// $ExpectType void
optionalHelpers.helpers({
increment() {
this.value++;
},
zero: 0,
});
const optionalHelper1 = optionalHelpers.insert({ value: 2 });
// Helpers are still guaranteed on the results of findOne, even though helpers were declared optional
// $ExpectType void
optionalHelpers.findOne(optionalHelper1)!.increment();
// same goes for find
// $ExpectType void
optionalHelpers.find(optionalHelper1).fetch()[0].increment();
const foundOptionalHelpers1: OptionalHelpers = optionalHelpers.findOne(optionalHelper1)!;
// however, variables of the interface type will be missing their helpers unless declared as Full<T>
// $ExpectError
foundOptionalHelpers1.increment();
// you can do this, but it's kinda ugly imo
const foundOptionalHelpers1Full: Full<OptionalHelpers> = optionalHelpers.findOne(optionalHelper1)!;
// $ExpectType void
foundOptionalHelpers1Full.increment();
// the benefit of this declaration style is that you can pass around instances without ever putting them in a collection:
const takesOptionalHelpers = (arg: OptionalHelpers) => {
return arg.value;
};
const literalOptHelp: OptionalHelpers = {
_id: "id",
value: 4,
};
// $ExpectType number
takesOptionalHelpers(literalOptHelp);
// $ExpectType number
takesOptionalHelpers({
_id: "another id",
value: 13
});
// this might be a better choice if your interface is a general data type that you just happen to put in collections sometimes,
// rather than a collection schema you work with retrieved instances of often
// helpers can call themselves recursively
interface RecursiveHelpers {
_id: string;
value: number;
factorial: (arg: number) => number;
}
const recursiveHelpers = new Mongo.Collection<RecursiveHelpers>('recursiveHelpers');
recursiveHelpers.helpers({
factorial(x) {
if (x <= 1) return 1;
return this.factorial(x - 1);
},
});
const rh1 = recursiveHelpers.insert({ value: 3 });
// $ExpectType number
recursiveHelpers.findOne(rh1)!.factorial(4);
// even when optional!
interface RecursiveOptionalHelpers {
_id: string;
value: number;
factorial?: (arg: number) => number;
}
const recursiveOptionalHelpers = new Mongo.Collection<RecursiveHelpers>('recursiveHelpers');
recursiveHelpers.helpers({
factorial(x) {
if (x <= 1) return 1;
return this.factorial(x - 1);
},
});
const roh1 = recursiveOptionalHelpers.insert({ value: 3 });
// $ExpectType number
recursiveHelpers.findOne(rh1)!.factorial(4);
// regression test:
// { ...OptionalId<Data<T>>, _id: T['id'] } should be assignable to Data<T>
// (tested here with upsert)
interface MandatoryId {
_id: string;
value: number;
}
const mandatoryIds = new Mongo.Collection<MandatoryId>('mandatoryIds');
mandatoryIds.helpers({});
const withoutId: Mongo.OptionalId<MandatoryId> = {
value: 3,
};
// $ExpectType number | undefined
mandatoryIds.upsert('new ID', { ...withoutId, _id: 'new ID' }).numberAffected;
// regression test:
// union properties on interfaces:
// - are helpers if all their members are helpers
// - aren't helpers if none of their members are helpers
// - may or may not be helpers if only some of their members are helpers
// (consider this undefined behavior; I don't think there's *any* correct behavior
// for this situation, so I'm just letting it work out however it does without intervention)
// unions of Helper<T> and unmarked methods don't work; if you legitimately need this functionality,
// tell me and I'll fix it; use Helper<T | YourMethodType> as a workaround until then
//
// null does not count as a helper type
// however, Helper<null> and Helper<T | null> should work more or less correctly
interface ComplicatedMembers {
_id?: string;
nullable: number | null;
alwaysNull: null;
helperNull: Helper<null>;
helperNumber: Helper<number>;
helperNullableFalse: Helper<false | null>;
optionalHelperString?: Helper<string>;
// tslint:disable-next-line void-return
helperVoidableNumber: Helper<number | void>;
methodUnion: (() => boolean) | ((arg: number) => boolean);
helperUnion: Helper<string> | Helper<number>;
nonHelperUnion: number | string;
helperMethodOrString: Helper<(() => string) | string>;
}
const complicatedMembers = new Mongo.Collection<ComplicatedMembers>('complicatedMembers');
// every member recognized as a helper is required when providing helpers
// $ExpectType void
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperNull: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperNull: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperNumber: 5,
helperNullableFalse: null,
helperNull: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNullableFalse: null,
helperNull: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNull: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperNull: null,
helperVoidableNumber: undefined,
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperVoidableNumber: undefined,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperNull: null,
optionalHelperString: 'foo',
helperMethodOrString: () => "method",
});
// $ExpectError
complicatedMembers.helpers({
methodUnion: () => true,
helperUnion: 3,
helperNumber: 5,
helperNullableFalse: null,
helperVoidableNumber: undefined,
helperNull: null,
optionalHelperString: 'foo',
});
const complicatedMembersId = complicatedMembers.insert({
nullable: 2,
alwaysNull: null,
nonHelperUnion: 'test',
});
const complicatedMembersInstance = complicatedMembers.findOne(complicatedMembersId)!;
// $ExpectType number
complicatedMembersInstance.helperNumber + 1;
// $ExpectType string
complicatedMembersInstance.optionalHelperString.slice();
// $ExpectType boolean
complicatedMembersInstance.methodUnion(3);
const strOrNum: string | number = complicatedMembersInstance.helperUnion;
const falseOrNull: false | null = complicatedMembersInstance.helperNullableFalse;
const helperNullAccess: null = complicatedMembersInstance.helperNull;
// Limitation: null/undefined helpers, and helpers whose types are unions with null or undefined, can't be properly
// read off a raw ComplicatedMembers
const asComplicatedMembers: ComplicatedMembers = complicatedMembersInstance;
// const falseOrNull2 : false | null = asComplicatedMembers.helperNullableFalse;
// you can work with these by:
// - accepting Helper<null> rather than null
const falseOrNull2: false | Helper<null> = asComplicatedMembers.helperNullableFalse;
// - using a "voidable" rather than a nullable
// tslint:disable-next-line void-return
const numberOrVoid: number | void = asComplicatedMembers.helperVoidableNumber;
// - or, the recommended solution: using OptionalHelper<T>
// OptionalHelper<T> defines a helper which can be left undefined when providing a collection's helpers
// that property can be required or optional on the interface; either way, it'll be optional on Helpers<T> and
// on Full<T>
// and it even works on methods
interface ActuallyOptionalHelpers {
_id?: string;
value: number;
getValue: OptionalHelper<() => number>;
incrementValue?: OptionalHelper<() => void>;
optionalHelperValue: OptionalHelper<number>;
optionalHelperName?: OptionalHelper<string>;
}
const actuallyOptionalHelpers = new Mongo.Collection<ActuallyOptionalHelpers>('actuallyOptionalHelpers');
// every one of those helpers is totally optional - we can even provide an empty object!
// $ExpectType void
actuallyOptionalHelpers.helpers({});
// $ExpectType void
actuallyOptionalHelpers.helpers({
getValue() {
return this.value;
},
});
// $ExpectType void
actuallyOptionalHelpers.helpers({
incrementValue() {
this.value++;
},
});
// $ExpectType void
actuallyOptionalHelpers.helpers({
optionalHelperValue: 3,
});
// $ExpectType void
actuallyOptionalHelpers.helpers({
optionalHelperName: 'foo',
});
// $ExpectType void
actuallyOptionalHelpers.helpers({
getValue() {
return this.value;
},
incrementValue() {
this.value++;
},
optionalHelperValue: 3,
optionalHelperName: 'foo',
});
// as usual, only non-helper properties are required when inserting an item
const aohId = actuallyOptionalHelpers.insert({
value: 7,
});
const aohInstance = actuallyOptionalHelpers.findOne(aohId)!;
// since they don't have to be provided, users of an item with optional helpers aren't promised those helpers will be present
let aohName: string | undefined = aohInstance.optionalHelperName;
const aohValue: number | undefined = aohInstance.optionalHelperValue;
// $ExpectError
aohInstance.getValue();
// asserting their existence works
// $ExpectType number
aohInstance.getValue!();
// as does control-flow analysis
if (aohInstance.getValue) {
// $ExpectType number
aohInstance.getValue();
}
// can still put retrieved items back into the container
actuallyOptionalHelpers.insert(aohInstance);
// unlike with Helper<T | null>, these work directly from the original interface type
const asAoh: ActuallyOptionalHelpers = aohInstance;
aohName = asAoh.optionalHelperName;
// $ExpectType number
asAoh.getValue!();

View File

@ -0,0 +1,103 @@
import { Helpers, Full, Data, AllowPartial, PartialHelpers } from 'meteor/dburles:collection-helpers';
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
// Cursor<T> and Collection<T> are pulled from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/meteor/mongo.d.ts
// and should be kept in sync with any changes to it
// only modified properties are included
// tslint:disable-next-line no-single-declare-module
declare module 'meteor/mongo' {
namespace Mongo {
interface Collection<T> {
/**
* Provide the definitions here for the methods and Helper<T>s you declared on your item interface.
* Use helpers<AllowPartial> if you want to provide the helpers across multiple calls.
* Tip: If you make those properties non-optional, they still won't be required when inserting items,
* but you'll know that any object of your interface type has them.
* Alternatively, if they're marked optional on your interface, they'll still be guaranteed on any
* object you get out of the collection.
* If you plan to pass around and create a lot of items pre-insertion, make them optional and use Full<T>
* for ones with helpers attached.
* If you plan to mostly pass around items that came out of a collection, make them required and use Data<T>
* when creating new items.
*/
helpers<allowPartial extends (false | AllowPartial) = false>(
// tslint:disable-next-line no-unnecessary-generics
helpers: (allowPartial extends AllowPartial ? PartialHelpers<T> : Helpers<T>)
): void;
// modifications:
// - replaced T with Full<T> & T everywhere Collection._transform is applied
// - replaced T with Data<T> everywhere the user provides a T
allow(options: {
insert?: (userId: string, doc: Full<T> & T) => boolean;
update?: (userId: string, doc: Full<T> & T, fieldNames: string[], modifier: any) => boolean;
remove?: (userId: string, doc: Full<T> & T) => boolean;
fetch?: string[];
// ditto
// tslint:disable-next-line ban-types
transform?: Function | null;
}): boolean;
deny(options: {
insert?: (userId: string, doc: Full<T> & T) => boolean;
update?: (userId: string, doc: Full<T> & T, fieldNames: string[], modifier: any) => boolean;
remove?: (userId: string, doc: Full<T> & T) => boolean;
fetch?: string[];
// ditto
// tslint:disable-next-line ban-types
transform?: Function | null;
}): boolean;
findOne(
selector?: Selector<T> | ObjectID | string,
options?: {
sort?: SortSpecifier;
skip?: number;
fields?: FieldSpecifier;
reactive?: boolean;
// ditto
// tslint:disable-next-line ban-types
transform?: Function | null;
},
): (Full<T> & T) | undefined;
// ditto
// tslint:disable-next-line ban-types
insert(doc: OptionalId<Data<T>>, callback?: Function): string;
update(
selector: Selector<T> | ObjectID | string,
modifier: Modifier<Data<T>>,
options?: {
multi?: boolean;
upsert?: boolean;
arrayFilters?: Array<{ [identifier: string]: any }>;
},
// ditto
// tslint:disable-next-line ban-types
callback?: Function,
): number;
upsert(
selector: Selector<T> | ObjectID | string,
modifier: Modifier<Data<T>>,
options?: {
multi?: boolean;
},
// ditto
// tslint:disable-next-line ban-types
callback?: Function,
): {
numberAffected?: number;
insertedId?: string;
};
}
// modifications: replaced T with Full<T> & T everywhere Collection._transform is applied
// note: it's not applied for observeChanges; however, ObserveChangesCallbacks uses Partial<T> anyway
interface Cursor<T> {
fetch(): Array<Full<T> & T>;
forEach(callback: (doc: Full<T> & T, index: number, cursor: Cursor<T>) => void, thisArg?: any): void;
map<U>(callback: (doc: Full<T> & T, index: number, cursor: Cursor<T>) => U, thisArg?: any): U[];
observe(callbacks: ObserveCallbacks<Full<T> & T>): Meteor.LiveQueryHandle;
}
}
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"meteor-dburles-collection-helpers-tests.ts"
]
}

View File

@ -0,0 +1 @@
{ "extends": "dtslint/dt.json" }