mirror of
https://github.com/FlipsideCrypto/DefinitelyTyped.git
synced 2026-02-06 10:56:53 +00:00
🤖 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:
parent
3181e16275
commit
e8d83de7f5
198
types/meteor-dburles-collection-helpers/collectionHelpers.d.ts
vendored
Normal file
198
types/meteor-dburles-collection-helpers/collectionHelpers.d.ts
vendored
Normal 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>>;
|
||||
}
|
||||
8
types/meteor-dburles-collection-helpers/index.d.ts
vendored
Normal file
8
types/meteor-dburles-collection-helpers/index.d.ts
vendored
Normal 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" />
|
||||
@ -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!();
|
||||
103
types/meteor-dburles-collection-helpers/mongo.d.ts
vendored
Normal file
103
types/meteor-dburles-collection-helpers/mongo.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
types/meteor-dburles-collection-helpers/tsconfig.json
Normal file
24
types/meteor-dburles-collection-helpers/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
1
types/meteor-dburles-collection-helpers/tslint.json
Normal file
1
types/meteor-dburles-collection-helpers/tslint.json
Normal file
@ -0,0 +1 @@
|
||||
{ "extends": "dtslint/dt.json" }
|
||||
Loading…
Reference in New Issue
Block a user