Skip to content

Commit

Permalink
New: Simplified reusing / replacing internal constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
dcodeIO committed Mar 23, 2017
1 parent 270cc94 commit b9574ad
Show file tree
Hide file tree
Showing 19 changed files with 66 additions and 58 deletions.
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,28 +289,27 @@ Detailed information on the reflection structure is available within the [docume

### Using custom classes

You can also extend runtime message classes with your own custom functionality by registering your own class with a reflected message type:
You can also extend runtime message classes with your own custom functionality and even register your own constructor with a reflected message type:

```js
...

// Define your own prototypal class
// Define your own constructor
function AwesomeMessage(properties) {
protobuf.Message.call(this, properties); // call the super constructor
// custom initialization code
...
}

// Register your custom class with its reflected type (*)
protobuf.Class.create(root.lookup("awesomepackage.AwesomeMessage") /* or use reflection */, AwesomeMessage);
// Register your constructor with its reflected type (*)
root.lookupType("awesomepackage.AwesomeMessage").ctor = AwesomeMessage;

// Define your custom functionality
AwesomeMessage.customStaticMethod = function() { ... };
AwesomeMessage.prototype.customInstanceMethod = function() { ... };

// Continue at "Create a message"
// Continue at "Create a new message" above
```

Afterwards, decoded messages of this type are `instanceof AwesomeMessage`.

(*) Besides referencing its reflected type through `AwesomeMessage.$type` and `AwesomeMesage#$type`, the respective custom class is automatically populated with:

* `AwesomeMessage.create`
Expand All @@ -319,6 +318,23 @@ Afterwards, decoded messages of this type are `instanceof AwesomeMessage`.
* `AwesomeMessage.verify`
* `AwesomeMessage.fromObject`, `AwesomeMessage.toObject`, `AwesomeMessage#toObject` and `AwesomeMessage#toJSON`

Afterwards, decoded messages of this type are `instanceof AwesomeMessage`.

Alternatively, you can also just reuse and extend the internal constructor if custom initialization code is not required:

```js
...

// Reuse the internal constructor
var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage").ctor;

// Define your custom functionality
AwesomeMessage.customStaticMethod = function() { ... };
AwesomeMessage.prototype.customInstanceMethod = function() { ... };

// Continue at "Create a new message" above
```

### Using services

The library also supports services but it doesn't make any assumptions about the actual transport channel. Instead, a user must provide a suitable RPC implementation, which is an asynchronous function that takes the reflected service method, the binary request and a node-style callback as its parameters:
Expand Down
15 changes: 7 additions & 8 deletions dist/light/protobuf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/light/protobuf.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/light/protobuf.min.js

Large diffs are not rendered by default.

Binary file modified dist/light/protobuf.min.js.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion dist/light/protobuf.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/minimal/protobuf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/minimal/protobuf.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified dist/minimal/protobuf.min.js.gz
Binary file not shown.
15 changes: 7 additions & 8 deletions dist/protobuf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/protobuf.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/protobuf.min.js

Large diffs are not rendered by default.

Binary file modified dist/protobuf.min.js.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion dist/protobuf.min.js.map

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class Class {
* @param {Type} type Reflected message type
* @param {*} [ctor] Custom constructor to set up, defaults to create a generic one if omitted
* @returns {Message} Message prototype
* @deprecated Assign the constructor to {@link Type#ctor} instead
*/
public static create(type: Type, ctor?: any): Message;

Expand Down Expand Up @@ -555,23 +556,17 @@ export class MapField extends Field {

/**
* Constructs a new message instance.
*
* This function should also be called from your custom constructors, i.e. `Message.call(this, properties)`.
* @classdesc Abstract runtime message.
* @constructor
* @param {Object.<string,*>} [properties] Properties to set
* @see {@link Class.create}
*/
export class Message {

/**
* Constructs a new message instance.
*
* This function should also be called from your custom constructors, i.e. `Message.call(this, properties)`.
* @classdesc Abstract runtime message.
* @constructor
* @param {Object.<string,*>} [properties] Properties to set
* @see {@link Class.create}
*/
constructor(properties?: { [k: string]: any });

Expand Down Expand Up @@ -1711,6 +1706,7 @@ export class Type extends NamespaceBase {

/**
* 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 {Class}
*/
Expand Down
1 change: 1 addition & 0 deletions src/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Class.generate = function generate(type) { // eslint-disable-line no-unused-vars
* @param {Type} type Reflected message type
* @param {*} [ctor] Custom constructor to set up, defaults to create a generic one if omitted
* @returns {Message} Message prototype
* @deprecated Assign the constructor to {@link Type#ctor} instead
*/
Class.create = Class;

Expand Down
4 changes: 1 addition & 3 deletions src/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ var util = require("./util");

/**
* Constructs a new message instance.
*
* This function should also be called from your custom constructors, i.e. `Message.call(this, properties)`.
* @classdesc Abstract runtime message.
* @constructor
* @param {Object.<string,*>} [properties] Properties to set
* @see {@link Class.create}
*/
function Message(properties) {
// not used internally
if (properties)
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
this[keys[i]] = properties[keys[i]];
Expand Down
8 changes: 4 additions & 4 deletions src/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,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 {Class}
*/
Expand All @@ -200,10 +201,9 @@ Object.defineProperties(Type.prototype, {
},
set: function(ctor) {
if (ctor && !(ctor.prototype instanceof Message))
throw TypeError("ctor must be a Message constructor");
if (!ctor.from)
ctor.from = Message.from;
this._ctor = ctor;
Class(this, ctor);
else
this._ctor = ctor;
}
}
});
Expand Down
19 changes: 9 additions & 10 deletions tests/api_type.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,16 @@ tape.test("reflected types", function(test) {
type = protobuf.Type.fromJSON("Test", def2);
test.same(JSON.parse(JSON.stringify(type)), JSON.parse(JSON.stringify(def2)), "should construct from and convert back to JSON (complex parsed)");

function MyMessage() {}
function MyMessageAuto() {}
type.ctor = MyMessageAuto;
test.ok(MyMessageAuto.prototype instanceof protobuf.Message, "should properly register a constructor through assignment");
test.ok(typeof MyMessageAuto.encode === "function", "should populate static methods on assigned constructors");

test.throws(function() {
type.ctor = MyMessage;
}, TypeError, "should throw when registering a constructor that doesn't extend Message");

MyMessage.prototype = Object.create(protobuf.Message.prototype);

test.doesNotThrow(function() {
type.ctor = MyMessage;
}, "should not throw when registering a constructor that extends Message");
function MyMessageManual() {}
MyMessageManual.prototype = Object.create(protobuf.Message.prototype);
type.ctor = MyMessageManual;
test.ok(MyMessageManual.prototype instanceof protobuf.Message, "should properly register a constructor through assignment if already extending message");
test.notOk(typeof MyMessageManual.encode === "function", "should not populate static methods on assigned constructors if already extending message");

type = protobuf.Type.fromJSON("My", {
fields: {
Expand Down

0 comments on commit b9574ad

Please sign in to comment.