Skip to content

Commit

Permalink
Other: TypeScript generics improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
dcodeIO committed Apr 12, 2017
1 parent e49bef8 commit 23f26de
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 139 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,34 +543,40 @@ export class AwesomeMessage extends Message<AwesomeMessage> {
@Field.d(2, AwesomeSubMessage)
public awesomeSubMessage: AwesomeSubMessage;

@Field.d(3, AwesomeEnum)
@Field.d(3, AwesomeEnum, "optional", AwesomeEnum.ONE)
public awesomeEnum: AwesomeEnum;

@OneOf.d("awesomeSubMessage", "awesomeEnum")
public which: string;

}

let message = new AwesomeMessage({ awesomeField: "hi" });
// example code
let message = new AwesomeMessage({ awesomeField: "hello" });
let buffer = AwesomeMessage.encode(message).finish();
let decoded = AwesomeMessage.decode(buffer);
```

Supported decorators are:

* **Type.d(typeName?: `string`)**<br />
optionally annotates a class as a protobuf message type. If `typeName` is not specified, the constructor's runtime function name is used.
* **Type.d(typeName?: `string`)** &nbsp; *(optional)*<br />
annotates a class as a protobuf message type. If `typeName` is not specified, the constructor's runtime function name is used for the reflected type.

* **Field.d&lt;T>(fieldId: `number`, fieldType: `string | TMessageConstructor<TField>`, fieldRule?: `"optional" | "required" | "repeated"`, defaultValue?: `T`)**<br />
annotates a property as a protobuf field with the specified id and protobuf type.

* **MapField.d&lt;T extends { [key: string]: any }>(fieldId: `number`, fieldKeyType: `string`, fieldValueType. `string | TConstructor<{}>`)**<br />
* **MapField.d&lt;T extends { [key: string]: any }>(fieldId: `number`, fieldKeyType: `string`, fieldValueType. `string | Constructor<{}>`)**<br />
annotates a property as a protobuf map field with the specified id, protobuf key and value type.

* **OneOf.d&lt;T extends string>(...fieldNames: `string[]`)**<br />
annotates a property as a protobuf oneof covering the specified fields.

Decorated types reside in `protobuf.roots["decorators"]` using a flat structure (no duplicate names).
Other notes:

* Decorated types reside in `protobuf.roots["decorated"]` using a flat structure, so no duplicate names.
* Enums are copied to a reflected enum with a generic name on decorator evaluation because referenced enum objects have no runtime name the decorator could use.
* Default values must be specified as arguments to the decorator instead of using a property initializer for proper prototype behavior.
* Property names on decorated classes must not be renamed on compile time (i.e. by a minifier) because decorators just receive the original field name as a string.

Command line
------------
Expand Down
3 changes: 2 additions & 1 deletion config/eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@
"no-shadow-restricted-names": 1,
"no-shadow": 0, // this is javascript. it has forEach and all that stuff.
"no-undef-init": 1,
"no-undef": 1,
"no-undef": 2,
"no-undefined": 0, // produces shorter code when testing against this
"no-use-before-define": 0, // can actually be used for a better overview, i.e. with module.exports
"no-unused-vars": 1, // a warning is sufficient

// Node.js and CommonJS
"callback-return": 1,
Expand Down
146 changes: 85 additions & 61 deletions index.d.ts

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions lib/inquire/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,18 @@ function inquire(moduleName) {
} catch (e) {} // eslint-disable-line no-empty
return null;
}

/*
// maybe worth a shot to prevent renaming issues:
// see: https://github.com/webpack/webpack/blob/master/lib/dependencies/CommonJsRequireDependencyParserPlugin.js
// triggers on:
// - expression require.cache
// - expression require (???)
// - call require
// - call require:commonjs:item
// - call require:commonjs:context
Object.defineProperty(Function.prototype, "__self", { get: function() { return this; } });
var r = require.__self;
delete Function.prototype.__self;
*/
2 changes: 0 additions & 2 deletions src/common.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use strict";
module.exports = common;

var Type = require("./type");

/**
* Provides common type definitions.
* Can also be used to provide additional google types or your own custom types.
Expand Down
4 changes: 2 additions & 2 deletions src/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ Field.prototype.resolve = function resolve() {
* @param {"optional"|"required"|"repeated"} [fieldRule="optional"] Field rule
* @param {T} [defaultValue] Default value
* @returns {FieldDecorator} Decorator function
* @template T
* @template T extends number | number[] | Long | Long[] | string | string[] | boolean | boolean[] | Uint8Array | Uint8Array[] | Buffer | Buffer[]
*/
Field.d = function decorateField(fieldId, fieldType, fieldRule, defaultValue) {

Expand All @@ -350,7 +350,7 @@ Field.d = function decorateField(fieldId, fieldType, fieldRule, defaultValue) {
* @name Field.d
* @function
* @param {number} fieldId Field id
* @param {TMessageConstructor<T>} fieldType Field type
* @param {Constructor<T>} fieldType Field type
* @param {"optional"|"required"|"repeated"} [fieldRule="optional"] Field rule
* @returns {FieldDecorator} Decorator function
* @template T extends Message<T>
Expand Down
4 changes: 2 additions & 2 deletions src/mapfield.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ MapField.prototype.resolve = function resolve() {
* @function
* @param {number} fieldId Field id
* @param {"int32"|"uint32"|"sint32"|"fixed32"|"sfixed32"|"int64"|"uint64"|"sint64"|"fixed64"|"sfixed64"|"bool"|"string"} fieldKeyType Field key type
* @param {"double"|"float"|"int32"|"uint32"|"sint32"|"fixed32"|"sfixed32"|"int64"|"uint64"|"sint64"|"fixed64"|"sfixed64"|"bool"|"string"|"bytes"|Object|TConstructor<{}>} fieldValueType Field value type
* @param {"double"|"float"|"int32"|"uint32"|"sint32"|"fixed32"|"sfixed32"|"int64"|"uint64"|"sint64"|"fixed64"|"sfixed64"|"bool"|"string"|"bytes"|Object|Constructor<{}>} fieldValueType Field value type
* @returns {FieldDecorator} Decorator function
* @template T extends { [key: string]: any }
* @template T extends { [key: string]: number | Long | string | boolean | Uint8Array | Buffer | number[] | Message<{}> }
*/
MapField.d = function decorateMapField(fieldId, fieldKeyType, fieldValueType) {

Expand Down
25 changes: 9 additions & 16 deletions src/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,12 @@ module.exports = Message;

var util = require("./util");

/**
* Properties of a message instance.
* @typedef TMessageProperties
* @template T
* @tstype { [P in keyof T]?: T[P] }
*/

/**
* Constructs a new message instance.
* @classdesc Abstract runtime message.
* @constructor
* @param {TMessageProperties<T>} [properties] Properties to set
* @template T
* @param {Properties<T>} [properties] Properties to set
* @template T extends object
*/
function Message(properties) {
// not used internally
Expand Down Expand Up @@ -45,7 +38,7 @@ function Message(properties) {
* @param {Object.<string,*>} [properties] Properties to set
* @returns {Message<T>} Message instance
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.create = function create(properties) {
return this.$type.create(properties);
Expand All @@ -57,7 +50,7 @@ Message.create = function create(properties) {
* @param {Writer} [writer] Writer to use
* @returns {Writer} Writer
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.encode = function encode(message, writer) {
return this.$type.encode(message, writer);
Expand All @@ -69,7 +62,7 @@ Message.encode = function encode(message, writer) {
* @param {Writer} [writer] Writer to use
* @returns {Writer} Writer
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.encodeDelimited = function encodeDelimited(message, writer) {
return this.$type.encodeDelimited(message, writer);
Expand All @@ -82,7 +75,7 @@ Message.encodeDelimited = function encodeDelimited(message, writer) {
* @param {Reader|Uint8Array} reader Reader or buffer to decode
* @returns {T} Decoded message
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.decode = function decode(reader) {
return this.$type.decode(reader);
Expand All @@ -95,7 +88,7 @@ Message.decode = function decode(reader) {
* @param {Reader|Uint8Array} reader Reader or buffer to decode
* @returns {T} Decoded message
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.decodeDelimited = function decodeDelimited(reader) {
return this.$type.decodeDelimited(reader);
Expand All @@ -117,7 +110,7 @@ Message.verify = function verify(message) {
* @param {Object.<string,*>} object Plain object
* @returns {T} Message instance
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.fromObject = function fromObject(object) {
return this.$type.fromObject(object);
Expand All @@ -129,7 +122,7 @@ Message.fromObject = function fromObject(object) {
* @param {ConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
* @template T extends Message<T>
* @this TMessageConstructor<T>
* @this Constructor<T>
*/
Message.toObject = function toObject(message, options) {
return this.$type.toObject(message, options);
Expand Down
2 changes: 1 addition & 1 deletion src/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var rpc = exports;
* RPC implementation passed to {@link Service#create} performing a service request on network level, i.e. by utilizing http requests or websockets.
* @typedef RPCImpl
* @type {function}
* @param {Method|rpc.ServiceMethod<{},{}>} method Reflected or static method being called
* @param {Method|rpc.ServiceMethod<Message<{}>,Message<{}>>} method Reflected or static method being called
* @param {Uint8Array} requestData Request data
* @param {RPCImplCallback} callback Callback function
* @returns {undefined}
Expand Down
14 changes: 7 additions & 7 deletions src/rpc/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var util = require("../util/minimal");
*
* Differs from {@link RPCImplCallback} in that it is an actual callback of a service method which may not return `response = null`.
* @typedef rpc.ServiceMethodCallback
* @template TRes
* @template TRes extends Message<TRes>
* @type {function}
* @param {?Error} error Error, if any
* @param {?TRes} [response] Response message
Expand All @@ -21,10 +21,10 @@ var util = require("../util/minimal");
/**
* A service method part of a {@link rpc.Service} as created by {@link Service.create}.
* @typedef rpc.ServiceMethod
* @template TReq
* @template TRes
* @template TReq extends Message<TReq>
* @template TRes extends Message<TRes>
* @type {function}
* @param {TReq|TMessageProperties<TReq>} request Request message or plain object
* @param {TReq|Properties<TReq>} request Request message or plain object
* @param {rpc.ServiceMethodCallback<TRes>} [callback] Node-style callback called with the error, if any, and the response message
* @returns {Promise<Message<TRes>>} Promise if `callback` has been omitted, otherwise `undefined`
*/
Expand Down Expand Up @@ -68,9 +68,9 @@ function Service(rpcImpl, requestDelimited, responseDelimited) {
/**
* Calls a service method through {@link rpc.Service#rpcImpl|rpcImpl}.
* @param {Method|rpc.ServiceMethod<TReq,TRes>} method Reflected or static method
* @param {TMessageConstructor<TReq>} requestCtor Request constructor
* @param {TMessageConstructor<TRes>} responseCtor Response constructor
* @param {TReq|TMessageProperties<TReq>} request Request message or plain object
* @param {Constructor<TReq>} requestCtor Request constructor
* @param {Constructor<TRes>} responseCtor Response constructor
* @param {TReq|Properties<TReq>} request Request message or plain object
* @param {rpc.ServiceMethodCallback<TRes>} callback Service callback
* @returns {undefined}
* @template TReq extends Message<TReq>
Expand Down
2 changes: 1 addition & 1 deletion src/tokenize.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ function tokenize(source) {

/**
* Gets a comment.
* @param {number=} trailingLine Trailing line number if applicable
* @param {number} [trailingLine] Trailing line number if applicable
* @returns {?string} Comment text
* @inner
*/
Expand Down
7 changes: 3 additions & 4 deletions src/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function Type(name, options) {

/**
* Cached constructor.
* @type {TConstructor<{}>}
* @type {Constructor<{}>}
* @private
*/
this._ctor = null;
Expand Down Expand Up @@ -148,7 +148,7 @@ Object.defineProperties(Type.prototype, {
* The registered constructor, if any registered, otherwise a generic constructor.
* Assigning a function replaces the internal constructor. If the function does not extend {@link Message} yet, its prototype will be setup accordingly and static methods will be populated. If it already extends {@link Message}, it will just replace the internal constructor.
* @name Type#ctor
* @type {TConstructor<{}>}
* @type {Constructor<{}>}
*/
ctor: {
get: function() {
Expand Down Expand Up @@ -416,7 +416,6 @@ Type.prototype.isReservedName = function isReservedName(name) {
* Creates a new message of this type using the specified properties.
* @param {Object.<string,*>} [properties] Properties to set
* @returns {Message<{}>} Message instance
* @template T
*/
Type.prototype.create = function create(properties) {
return new this.ctor(properties);
Expand Down Expand Up @@ -573,7 +572,7 @@ Type.prototype.toObject = function toObject(message, options) {
* Decorator function as returned by {@link Type.d} (TypeScript).
* @typedef TypeDecorator
* @type {function}
* @param {TMessageConstructor<T>} target Target constructor
* @param {Constructor<T>} target Target constructor
* @returns {undefined}
* @template T extends Message<T>
*/
Expand Down
26 changes: 6 additions & 20 deletions src/typescript.jsdoc
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
/**
* Constructor type.
* @typedef TConstructor
* @template T
* @typedef Constructor
* @template T extends object
* @tstype { new(...params: any[]): T }
*/

/**
* Properties of a message instance.
* @typedef TMessageProperties
* @template T extends Message<T>
* @tstype { [P in keyof T]?: T[P] }
*/

/**
* Message constructor type.
* @typedef TMessageConstructor
* @template T extends Message<T>
* @tstype { new(properties?: TMessageProperties<T>): T }
*/

/**
* Object type.
* @typedef TObject
* @template V
* @tstype { [key: string]: V }
* Properties type.
* @typedef Properties
* @template T extends object
* @tstype { [P in keyof T]?: T[P] } & { [key: string]: any }
*/
4 changes: 2 additions & 2 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ util.compareFieldsById = function compareFieldsById(a, b) {

/**
* Decorator helper for types (TypeScript).
* @param {TMessageConstructor<T>} ctor Constructor function
* @param {Constructor<T>} ctor Constructor function
* @param {string} [typeName] Type name, defaults to the constructor's name
* @returns {Type} Reflected type
* @template T extends Message<T>
Expand Down Expand Up @@ -127,6 +127,6 @@ util.decorateEnum = function decorateEnum(object) {
*/
Object.defineProperty(util, "decorateRoot", {
get: function() {
return roots["decorators"] || (roots["decorators"] = new (require("./root"))());
return roots["decorated"] || (roots["decorated"] = new (require("./root"))());
}
});
12 changes: 6 additions & 6 deletions src/util/minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ util.isSet = function isSet(obj, prop) {

/**
* Node's Buffer class if available.
* @type {TConstructor<Buffer>}
* @type {Constructor<Buffer>}
*/
util.Buffer = (function() {
try {
Expand Down Expand Up @@ -160,7 +160,7 @@ util.newBuffer = function newBuffer(sizeOrArray) {

/**
* Array implementation used in the browser. `Uint8Array` if supported, otherwise `Array`.
* @type {TConstructor<Uint8Array>}
* @type {Constructor<Uint8Array>}
*/
util.Array = typeof Uint8Array !== "undefined" ? Uint8Array /* istanbul ignore next */ : Array;

Expand All @@ -176,7 +176,7 @@ util.Array = typeof Uint8Array !== "undefined" ? Uint8Array /* istanbul ignore n

/**
* Long.js's Long class if available.
* @type {TConstructor<Long>}
* @type {Constructor<Long>}
*/
util.Long = /* istanbul ignore next */ global.dcodeIO && /* istanbul ignore next */ global.dcodeIO.Long || util.inquire("long");

Expand Down Expand Up @@ -255,7 +255,7 @@ util.lcFirst = function lcFirst(str) {
* Creates a custom error constructor.
* @memberof util
* @param {string} name Error name
* @returns {TConstructor<Error>} Custom error constructor
* @returns {Constructor<Error>} Custom error constructor
*/
function newError(name) {

Expand Down Expand Up @@ -297,10 +297,10 @@ util.newError = newError;
* @classdesc Error subclass indicating a protocol specifc error.
* @memberof util
* @extends Error
* @template T
* @template T extends Message<T>
* @constructor
* @param {string} message Error message
* @param {Object.<string,*>=} properties Additional properties
* @param {Object.<string,*>} [properties] Additional properties
* @example
* try {
* MyMessage.decode(someBuffer); // throws if required fields are missing
Expand Down
Loading

0 comments on commit 23f26de

Please sign in to comment.