or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

binary-io.mdbytestring-utilities.mddebug-utilities.mdextension-fields.mdindex.mdmap-operations.mdmessage-operations.md
tile.json

extension-fields.mddocs/

Extension Fields

Protocol Buffer extension fields allow you to add new fields to existing message types without modifying the original .proto file. The JavaScript library provides ExtensionFieldInfo and ExtensionFieldBinaryInfo classes to support extension field operations.

Extension Field Basics { .api }

const { ExtensionFieldInfo, Message } = require('google-protobuf');

// Define extension field metadata
const customExtension = new ExtensionFieldInfo(
  1001,                    // Field number (must be in extension range)
  {custom_field: 0},       // Field name object  
  null,                    // Constructor (null for primitive types)
  null,                    // toObject function (null for primitives)
  0                        // isRepeated (0 = false, 1 = true)
);

// Use extension with message
const message = new BaseMessage();
message.setExtension(customExtension, "Extension value");

const value = message.getExtension(customExtension);
console.log(value); // "Extension value"

Type Definitions:

/**
 * @constructor
 * @param {number} fieldNumber Field number within extension range
 * @param {!Object} fieldName Field name object mapping
 * @param {?Function} ctor Constructor for message types (null for primitives)
 * @param {?Function} toObjectFn toObject function reference (null for primitives)  
 * @param {number} isRepeated 0 for singular, 1 for repeated fields
 */

ExtensionFieldInfo Class { .api }

Properties and Structure

const { ExtensionFieldInfo } = require('google-protobuf');

// Create extension field info
const stringExtension = new ExtensionFieldInfo(
  2001,
  {user_nickname: 0},
  null,  // No constructor - primitive string
  null,  // No toObject function - primitive
  0      // Not repeated
);

// Access properties
console.log(stringExtension.fieldIndex);    // 2001
console.log(stringExtension.fieldName);     // {user_nickname: 0}
console.log(stringExtension.ctor);          // null
console.log(stringExtension.toObjectFn);    // null  
console.log(stringExtension.isRepeated);    // 0

// Repeated primitive extension
const tagsExtension = new ExtensionFieldInfo(
  2002,
  {user_tags: 0},
  null,  // Primitive array
  null,
  1      // Repeated
);

Primitive Extension Fields

const { ExtensionFieldInfo } = require('google-protobuf');

// String extension
const nameExtension = new ExtensionFieldInfo(
  1001, {display_name: 0}, null, null, 0
);

// Integer extension  
const priorityExtension = new ExtensionFieldInfo(
  1002, {priority_level: 0}, null, null, 0
);

// Boolean extension
const activeExtension = new ExtensionFieldInfo(
  1003, {is_featured: 0}, null, null, 0
);

// Repeated string extension
const labelsExtension = new ExtensionFieldInfo(
  1004, {labels: 0}, null, null, 1
);

// Usage with message
const item = new ItemMessage();

item.setExtension(nameExtension, "Featured Item");
item.setExtension(priorityExtension, 5);
item.setExtension(activeExtension, true);
item.setExtension(labelsExtension, ["urgent", "reviewed", "approved"]);

// Access values
console.log(item.getExtension(nameExtension));     // "Featured Item"
console.log(item.getExtension(priorityExtension)); // 5
console.log(item.getExtension(activeExtension));   // true
console.log(item.getExtension(labelsExtension));   // ["urgent", "reviewed", "approved"]

Message Extension Fields

const { ExtensionFieldInfo } = require('google-protobuf');

// Define message type for extension values
class MetadataMessage extends Message {
  getCreatedBy() { return this.created_by_; }
  setCreatedBy(value) { this.created_by_ = value; }
  
  getCreatedAt() { return this.created_at_; }  
  setCreatedAt(value) { this.created_at_ = value; }
  
  getTags() { return this.tags_; }
  setTags(value) { this.tags_ = value; }
  
  toObject(includeInstance) {
    return {
      createdBy: this.getCreatedBy(),
      createdAt: this.getCreatedAt(),
      tags: this.getTags()
    };
  }
}

// Message extension field
const metadataExtension = new ExtensionFieldInfo(
  3001,
  {metadata: 0},
  MetadataMessage,           // Constructor function
  MetadataMessage.prototype.toObject,  // toObject function
  0                          // Not repeated
);

// Repeated message extension
const commentsExtension = new ExtensionFieldInfo(
  3002,
  {comments: 0},
  CommentMessage,
  CommentMessage.prototype.toObject,
  1                          // Repeated
);

// Usage
const document = new DocumentMessage();

// Set message extension
const metadata = new MetadataMessage();
metadata.setCreatedBy("john@example.com");
metadata.setCreatedAt("2023-01-15T10:30:00Z");
metadata.setTags(["draft", "review-needed"]);

document.setExtension(metadataExtension, metadata);

// Set repeated message extension
const comment1 = new CommentMessage();
comment1.setText("Looks good!");
comment1.setAuthor("reviewer1");

const comment2 = new CommentMessage();
comment2.setText("Minor typo in section 3");
comment2.setAuthor("reviewer2");

document.setExtension(commentsExtension, [comment1, comment2]);

// Access message extensions
const retrievedMetadata = document.getExtension(metadataExtension);
console.log(retrievedMetadata.getCreatedBy()); // "john@example.com"

const comments = document.getExtension(commentsExtension);
comments.forEach((comment, index) => {
  console.log(`Comment ${index + 1}: ${comment.getText()}`);
});

ExtensionFieldBinaryInfo Class { .api }

For binary serialization of extension fields:

const { ExtensionFieldBinaryInfo, ExtensionFieldInfo } = require('google-protobuf');

// Create binary info for extension field
const binaryExtension = new ExtensionFieldBinaryInfo(
  extensionFieldInfo,           // ExtensionFieldInfo instance
  binaryReaderFn,              // Function to read from BinaryReader
  binaryWriterFn,              // Function to write to BinaryWriter  
  binaryMessageSerializeFn,    // Message serialization function
  binaryMessageDeserializeFn,  // Message deserialization function
  opt_isPacked                 // Optional: whether field is packed (for repeated numerics)
);

// Example: String extension with binary support
const stringExtInfo = new ExtensionFieldInfo(
  4001, {custom_string: 0}, null, null, 0
);

const stringBinaryInfo = new ExtensionFieldBinaryInfo(
  stringExtInfo,
  (reader) => reader.readString(),           // Read string from binary
  (writer, field, value) => writer.writeString(field, value), // Write string to binary
  null,  // No message serialization needed
  null,  // No message deserialization needed
  false  // Not packed
);

Type Definitions:

/**
 * @constructor  
 * @param {!ExtensionFieldInfo} fieldInfo Extension field metadata
 * @param {!Function} binaryReaderFn Function to read from BinaryReader
 * @param {!Function} binaryWriterFn Function to write to BinaryWriter
 * @param {?Function} binaryMessageSerializeFn Message serialize function
 * @param {?Function} binaryMessageDeserializeFn Message deserialize function
 * @param {boolean=} opt_isPacked Whether field uses packed encoding
 */

Extension Registration and Usage { .api }

Extension Registry

const { ExtensionFieldInfo } = require('google-protobuf');

// Create extension registry for a message type
class UserExtensions {
  static NICKNAME = new ExtensionFieldInfo(
    5001, {nickname: 0}, null, null, 0
  );
  
  static AVATAR_URL = new ExtensionFieldInfo(
    5002, {avatar_url: 0}, null, null, 0
  );
  
  static PREFERENCES = new ExtensionFieldInfo(
    5003, {preferences: 0}, UserPreferencesMessage, 
    UserPreferencesMessage.prototype.toObject, 0
  );
  
  static GROUPS = new ExtensionFieldInfo(
    5004, {groups: 0}, null, null, 1
  );
}

// Usage with registry
const user = new UserMessage();

user.setExtension(UserExtensions.NICKNAME, "johndoe");
user.setExtension(UserExtensions.AVATAR_URL, "https://example.com/avatar.jpg");
user.setExtension(UserExtensions.GROUPS, ["admin", "developers", "reviewers"]);

const prefs = new UserPreferencesMessage();
prefs.setTheme("dark");
prefs.setLanguage("en");
user.setExtension(UserExtensions.PREFERENCES, prefs);

// Access extensions
console.log("Nickname:", user.getExtension(UserExtensions.NICKNAME));
console.log("Groups:", user.getExtension(UserExtensions.GROUPS));

const userPrefs = user.getExtension(UserExtensions.PREFERENCES);
if (userPrefs) {
  console.log("Theme:", userPrefs.getTheme());
}

Cross-Package Extensions

// Extensions can be defined in different packages/modules

// common/extensions.js
const { ExtensionFieldInfo } = require('google-protobuf');

exports.AUDIT_INFO = new ExtensionFieldInfo(
  10001, {audit_info: 0}, AuditInfoMessage,
  AuditInfoMessage.prototype.toObject, 0
);

exports.VERSION_INFO = new ExtensionFieldInfo(
  10002, {version_info: 0}, VersionInfoMessage,
  VersionInfoMessage.prototype.toObject, 0
);

// Usage across different modules
const { AUDIT_INFO, VERSION_INFO } = require('./common/extensions');

// In user service
const user = new UserMessage();
const auditInfo = new AuditInfoMessage();
auditInfo.setCreatedBy("system");
auditInfo.setCreatedAt(Date.now());
user.setExtension(AUDIT_INFO, auditInfo);

// In document service  
const document = new DocumentMessage();
const versionInfo = new VersionInfoMessage();
versionInfo.setVersion("1.0");
versionInfo.setLastModified(Date.now());
document.setExtension(VERSION_INFO, versionInfo);

Serialization with Extensions { .api }

Binary Serialization

const { Message } = require('google-protobuf');

// Extensions are automatically included in binary serialization
const message = new BaseMessage();
message.setName("Base Message");
message.setExtension(customExtension, "Extension Value");

// Serialize - includes extensions
const bytes = message.serializeBinary();

// Deserialize - restores extensions  
const restored = BaseMessage.deserializeBinary(bytes);
console.log(restored.getName());                        // "Base Message"
console.log(restored.getExtension(customExtension));    // "Extension Value"

// Manual extension serialization (for advanced use cases)
const writer = new BinaryWriter();

// Serialize base message fields
writer.writeString(1, message.getName());

// Serialize extensions manually if needed
const extensions = new Map();
extensions.set(customExtension.fieldIndex, customExtension);

Message.serializeBinaryExtensions(message, writer, extensions, (msg, writer, ext) => {
  const value = msg.getExtension(ext);
  if (value !== undefined) {
    writer.writeString(ext.fieldIndex, value);
  }
});

const manualBytes = writer.getResultBuffer();

JSON Serialization

// Extensions work with toObject() for JSON serialization
const message = new ExtendedMessage();
message.setName("Test Message");
message.setExtension(nicknameExtension, "TestNick");

const obj = message.toObject();
/*
Result includes extensions:
{
  name: "Test Message",
  nickname: "TestNick"  // Extension field included
}
*/

// Manual extension handling for custom JSON
function messageToJSON(message, extensions) {
  const obj = message.toObject();
  
  // Add extensions manually if needed
  extensions.forEach(extension => {
    const value = message.getExtension(extension);
    if (value !== undefined) {
      const fieldName = Object.keys(extension.fieldName)[0];
      obj[fieldName] = value;
    }
  });
  
  return JSON.stringify(obj);
}

const jsonStr = messageToJSON(message, [nicknameExtension, priorityExtension]);

Advanced Extension Patterns { .api }

Dynamic Extension Discovery

const { ExtensionFieldInfo } = require('google-protobuf');

// Utility for working with unknown extensions
function findExtensions(message, fieldNumberRange) {
  const extensions = [];
  const messageArray = message.toArray();
  
  // Scan message array for extension field numbers
  for (let i = 0; i < messageArray.length; i++) {
    if (messageArray[i] !== undefined) {
      const fieldNumber = i + 1; // Field numbers are 1-based
      if (fieldNumber >= fieldNumberRange.start && 
          fieldNumber <= fieldNumberRange.end) {
        
        // Found potential extension field
        extensions.push({
          fieldNumber: fieldNumber,
          value: messageArray[i]
        });
      }
    }
  }
  
  return extensions;
}

// Usage
const message = new ExtendableMessage();
message.setExtension(customExtension, "value");

const foundExtensions = findExtensions(message, {start: 1000, end: 9999});
foundExtensions.forEach(ext => {
  console.log(`Extension field ${ext.fieldNumber}: ${ext.value}`);
});

Extension Validation

const { ExtensionFieldInfo } = require('google-protobuf');

// Validate extension field definitions
function validateExtension(extension) {
  const errors = [];
  
  if (!extension.fieldIndex || extension.fieldIndex < 1) {
    errors.push("Invalid field number");
  }
  
  if (extension.fieldIndex < 1000) {
    errors.push("Extension field numbers should be >= 1000");
  }
  
  if (!extension.fieldName || typeof extension.fieldName !== 'object') {
    errors.push("Field name must be an object");
  }
  
  if (extension.isRepeated !== 0 && extension.isRepeated !== 1) {
    errors.push("isRepeated must be 0 or 1");
  }
  
  return errors;
}

// Validate before use
const errors = validateExtension(myExtension);
if (errors.length > 0) {
  console.error("Extension validation failed:", errors);
} else {
  // Safe to use extension
  message.setExtension(myExtension, value);
}

Extension Composition

const { ExtensionFieldInfo } = require('google-protobuf');

// Compose multiple extensions into a set
class ExtensionSet {
  constructor() {
    this.extensions = new Map();
  }
  
  add(name, extension) {
    this.extensions.set(name, extension);
    return this;
  }
  
  applyTo(message, values) {
    for (const [name, value] of Object.entries(values)) {
      const extension = this.extensions.get(name);
      if (extension) {
        message.setExtension(extension, value);
      }
    }
  }
  
  extractFrom(message) {
    const values = {};
    for (const [name, extension] of this.extensions) {
      const value = message.getExtension(extension);
      if (value !== undefined) {
        values[name] = value;
      }
    }
    return values;
  }
}

// Usage
const userExtensions = new ExtensionSet()
  .add('nickname', new ExtensionFieldInfo(6001, {nickname: 0}, null, null, 0))
  .add('avatar', new ExtensionFieldInfo(6002, {avatar: 0}, null, null, 0))
  .add('tags', new ExtensionFieldInfo(6003, {tags: 0}, null, null, 1));

// Apply extensions
const user = new UserMessage();
userExtensions.applyTo(user, {
  nickname: "johndoe",
  avatar: "avatar.jpg", 
  tags: ["developer", "admin"]
});

// Extract extensions
const extractedValues = userExtensions.extractFrom(user);
console.log(extractedValues);
// { nickname: "johndoe", avatar: "avatar.jpg", tags: ["developer", "admin"] }

Best Practices

Field Number Management

// Use consistent field number ranges for different extension types
const EXTENSION_RANGES = {
  USER_EXTENSIONS: { start: 5000, end: 5999 },
  AUDIT_EXTENSIONS: { start: 10000, end: 10999 },
  CUSTOM_EXTENSIONS: { start: 50000, end: 59999 }
};

// Example usage
const userNickname = new ExtensionFieldInfo(
  5001, // Within USER_EXTENSIONS range
  {nickname: 0}, null, null, 0
);

const auditInfo = new ExtensionFieldInfo(
  10001, // Within AUDIT_EXTENSIONS range  
  {audit_info: 0}, AuditMessage, AuditMessage.prototype.toObject, 0
);

Type Safety

// Wrapper functions for type safety
function createStringExtension(fieldNumber, fieldName) {
  return new ExtensionFieldInfo(fieldNumber, {[fieldName]: 0}, null, null, 0);
}

function createMessageExtension(fieldNumber, fieldName, MessageClass) {
  return new ExtensionFieldInfo(
    fieldNumber, 
    {[fieldName]: 0}, 
    MessageClass,
    MessageClass.prototype.toObject,
    0
  );
}

function createRepeatedExtension(fieldNumber, fieldName, MessageClass = null) {
  return new ExtensionFieldInfo(
    fieldNumber,
    {[fieldName]: 0},
    MessageClass,
    MessageClass ? MessageClass.prototype.toObject : null,
    1
  );
}

// Type-safe extension creation
const nameExt = createStringExtension(7001, "display_name");
const metaExt = createMessageExtension(7002, "metadata", MetadataMessage);
const tagsExt = createRepeatedExtension(7003, "tags");