@colyseus/schema
Version:
Binary state serializer with delta encoding for games
2,348 lines • 79.6 kB
JavaScript
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var path = require('path');
var ts$1 = require('typescript');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
var ts__namespace = /*#__PURE__*/_interopNamespaceDefault(ts$1);
/**
* @author Ethan Davis
* https://github.com/ethanent/gar
*/
var argv = (sargs) => {
let props = {};
let lones = [];
const convertIfApplicable = (value) => (isNaN(value) ? (value.toString().toLowerCase() === 'true' ? true : (value.toString().toLowerCase() === 'false' ? false : value)) : Number(value));
const removeStartHyphens = (value) => value.replace(/^\-+/g, '');
for (let i = 0; i < sargs.length; i++) {
const equalsIndex = sargs[i].indexOf('=');
const isNextRefProp = sargs[i].charAt(0) === '-' && sargs.length - 1 >= i + 1 && sargs[i + 1].indexOf('=') === -1 && sargs[i + 1].charAt(0) !== '-';
const argName = equalsIndex === -1 ? removeStartHyphens(sargs[i]) : removeStartHyphens(sargs[i].slice(0, equalsIndex));
if (equalsIndex !== -1) {
props[argName] = convertIfApplicable(sargs[i].slice(equalsIndex + 1));
}
else if (isNextRefProp) {
props[argName] = convertIfApplicable(sargs[i + 1]);
i++;
}
else if (sargs[i].charAt(0) === '-') {
if (sargs[i].charAt(1) === '-') {
props[argName] = true;
}
else {
for (let b = 0; b < argName.length; b++) {
props[argName.charAt(b)] = true;
}
}
}
else {
lones.push(convertIfApplicable(argName));
}
}
return Object.assign(props, {
'_': lones
});
};
if (typeof (__dirname) === "undefined") {
global.__dirname = path__namespace.dirname(new URL((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))).pathname);
}
const VERSION = JSON.parse(fs__namespace.readFileSync(__dirname + "/../../package.json").toString()).version;
const COMMENT_HEADER = `
THIS FILE HAS BEEN GENERATED AUTOMATICALLY
DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING
GENERATED USING @colyseus/schema ${VERSION}
`;
function getCommentHeader(singleLineComment = "//") {
return `${COMMENT_HEADER.split("\n").map(line => `${singleLineComment} ${line}`).join("\n")}`;
}
class Context {
classes = [];
interfaces = [];
enums = [];
getStructures() {
return {
classes: this.classes.filter(klass => {
if (this.isSchemaClass(klass)) {
return true;
}
else {
let parentClass = klass;
while (parentClass = this.getParentClass(parentClass)) {
if (this.isSchemaClass(parentClass)) {
return true;
}
}
}
return false;
}),
interfaces: this.interfaces,
enums: this.enums,
};
}
addStructure(structure) {
if (structure.context === this) {
return;
} // skip if already added.
structure.context = this;
if (structure instanceof Class) {
this.classes.push(structure);
}
else if (structure instanceof Interface) {
this.interfaces.push(structure);
}
else if (structure instanceof Enum) {
this.enums.push(structure);
}
}
getParentClass(klass) {
return this.classes.find(c => c.name === klass.extends);
}
isSchemaClass(klass) {
let isSchema = false;
let currentClass = klass;
while (!isSchema && currentClass) {
//
// TODO: ideally we should check for actual @colyseus/schema module
// reference rather than arbitrary strings.
//
isSchema = (currentClass.extends === "Schema" ||
currentClass.extends === "schema.Schema" ||
currentClass.extends === "Schema.Schema");
//
// When extending from `schema.Schema`, it is required to
// normalize as "Schema" for code generation.
//
if (currentClass === klass && isSchema) {
klass.extends = "Schema";
}
currentClass = this.getParentClass(currentClass);
}
return isSchema;
}
}
class Interface {
context;
name;
properties = [];
addProperty(property) {
if (property.type.indexOf("[]") >= 0) {
// is array!
property.childType = property.type.match(/([^\[]+)/i)[1];
property.type = "array";
this.properties.push(property);
}
else {
this.properties.push(property);
}
}
}
class Class {
context;
name;
properties = [];
extends;
addProperty(property) {
property.index = this.properties.length;
this.properties.push(property);
}
postProcessing() {
/**
* Ensure the proprierties `index` are correct using inheritance
*/
let parentKlass = this;
while (parentKlass &&
(parentKlass = this.context.classes.find(k => k.name === parentKlass.extends))) {
this.properties.forEach(prop => {
prop.index += parentKlass.properties.length;
});
}
}
}
class Enum {
context;
name;
properties = [];
addProperty(property) {
this.properties.push(property);
}
}
class Property {
index;
name;
type;
childType;
deprecated;
}
function getInheritanceTree(klass, allClasses, includeSelf = true) {
let currentClass = klass;
let inheritanceTree = [];
if (includeSelf) {
inheritanceTree.push(currentClass);
}
while (currentClass.extends !== "Schema") {
currentClass = allClasses.find(klass => klass.name == currentClass.extends);
inheritanceTree.push(currentClass);
}
return inheritanceTree;
}
let currentStructure;
let currentProperty;
let globalContext;
function defineProperty(property, initializer) {
if (ts__namespace.isIdentifier(initializer)) {
property.type = "ref";
property.childType = initializer.text;
}
else if (initializer.kind == ts__namespace.SyntaxKind.ObjectLiteralExpression) {
property.type = initializer.properties[0].name.text;
property.childType = initializer.properties[0].initializer.text;
}
else if (initializer.kind == ts__namespace.SyntaxKind.ArrayLiteralExpression) {
property.type = "array";
property.childType = initializer.elements[0].text;
}
else {
property.type = initializer.text;
}
}
function inspectNode(node, context, decoratorName) {
switch (node.kind) {
case ts__namespace.SyntaxKind.ImportClause:
const specifier = node.parent.moduleSpecifier;
if (specifier && specifier.text.startsWith('.')) {
const currentDir = path__namespace.dirname(node.getSourceFile().fileName);
const pathToImport = path__namespace.resolve(currentDir, specifier.text);
parseFiles([pathToImport], decoratorName, globalContext);
}
break;
case ts__namespace.SyntaxKind.ClassDeclaration:
currentStructure = new Class();
const heritageClauses = node.heritageClauses;
if (heritageClauses && heritageClauses.length > 0) {
currentStructure.extends = heritageClauses[0].types[0].expression.getText();
}
context.addStructure(currentStructure);
break;
case ts__namespace.SyntaxKind.InterfaceDeclaration:
//
// Only generate Interfaces if it has "Message" on its name.
// Example: MyMessage
//
const interfaceName = node.name.escapedText.toString();
if (interfaceName.indexOf("Message") !== -1) {
currentStructure = new Interface();
currentStructure.name = interfaceName;
context.addStructure(currentStructure);
}
break;
case ts__namespace.SyntaxKind.EnumDeclaration:
const enumName = node.name.escapedText.toString();
currentStructure = new Enum();
currentStructure.name = enumName;
context.addStructure(currentStructure);
break;
case ts__namespace.SyntaxKind.ExtendsKeyword:
// console.log(node.getText());
break;
case ts__namespace.SyntaxKind.PropertySignature:
if (currentStructure instanceof Interface) {
const parent = node.parent;
// Only process direct children of InterfaceDeclaration, skip TypeLiterals
if (!ts__namespace.isInterfaceDeclaration(parent)) {
break;
}
// Skip if property if for a another interface than the one we're interested in.
if (currentStructure.name !== parent.name.escapedText.toString()) {
break;
}
// define a property of an interface
const property = new Property();
property.name = node.name.escapedText.toString();
property.type = node.type.getText();
currentStructure.addProperty(property);
}
break;
case ts__namespace.SyntaxKind.Identifier:
if (node.getText() === "deprecated" &&
node.parent.kind !== ts__namespace.SyntaxKind.ImportSpecifier) {
currentProperty = new Property();
currentProperty.deprecated = true;
break;
}
if (node.getText() === decoratorName) {
const prop = node.parent?.parent?.parent;
const propDecorator = getDecorators(prop);
const hasExpression = prop?.expression?.arguments;
const hasDecorator = (propDecorator?.length > 0);
/**
* neither a `@type()` decorator or `type()` call. skip.
*/
if (!hasDecorator && !hasExpression) {
break;
}
// using as decorator
if (propDecorator) {
/**
* Calling `@type()` as decorator
*/
const typeDecorator = propDecorator.find((decorator => {
return decorator.expression.expression.escapedText === decoratorName;
})).expression;
const property = currentProperty || new Property();
property.name = prop.name.escapedText;
currentStructure.addProperty(property);
const typeArgument = typeDecorator.arguments[0];
defineProperty(property, typeArgument);
}
else if (prop.expression.arguments?.[1] &&
prop.expression.expression.arguments?.[0]) {
/**
* Calling `type()` as a regular method
*/
const property = currentProperty || new Property();
property.name = prop.expression.arguments[1].text;
currentStructure.addProperty(property);
const typeArgument = prop.expression.expression.arguments[0];
defineProperty(property, typeArgument);
}
}
else if (node.getText() === "setFields" &&
(node.parent.kind === ts__namespace.SyntaxKind.CallExpression ||
node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) {
/**
* Metadata.setFields(klassName, { ... })
*/
const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)
? node.parent.parent
: node.parent;
/**
* Skip if @codegen-ignore comment is found before the call expression
* TODO: currently, if @codegen-ignore is on the file, it will skip all the setFields calls.
*/
const sourceFile = node.getSourceFile();
const fullText = sourceFile.getFullText();
const nodeStart = callExpression.getFullStart();
const textBeforeNode = fullText.substring(0, nodeStart);
if (textBeforeNode.includes('@codegen-ignore')) {
break;
}
if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) {
break;
}
const classNameNode = callExpression.arguments[0];
const className = ts__namespace.isClassExpression(classNameNode)
? classNameNode.name?.escapedText.toString()
: classNameNode.getText();
// skip if no className is provided
if (!className) {
break;
}
if (currentStructure?.name !== className) {
currentStructure = new Class();
}
context.addStructure(currentStructure);
currentStructure.extends = "Schema"; // force extends to Schema
currentStructure.name = className;
const types = callExpression.arguments[1];
for (let i = 0; i < types.properties.length; i++) {
const prop = types.properties[i];
const property = currentProperty || new Property();
property.name = prop.name.escapedText;
currentStructure.addProperty(property);
defineProperty(property, prop.initializer);
}
}
else if (node.getText() === "defineTypes" &&
(node.parent.kind === ts__namespace.SyntaxKind.CallExpression ||
node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) {
/**
* JavaScript source file (`.js`)
* Using `defineTypes()`
*/
const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)
? node.parent.parent
: node.parent;
if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) {
break;
}
const className = callExpression.arguments[0].getText();
currentStructure.name = className;
const types = callExpression.arguments[1];
for (let i = 0; i < types.properties.length; i++) {
const prop = types.properties[i];
const property = currentProperty || new Property();
property.name = prop.name.escapedText;
currentStructure.addProperty(property);
defineProperty(property, prop.initializer);
}
}
if (node.parent.kind === ts__namespace.SyntaxKind.ClassDeclaration) {
currentStructure.name = node.getText();
}
currentProperty = undefined;
break;
case ts__namespace.SyntaxKind.CallExpression:
/**
* Defining schema via `schema.schema({ ... })`
* - schema.schema({})
* - schema({})
* - ClassName.extends({})
*/
if (((node.expression?.getText() === "schema.schema" ||
node.expression?.getText() === "schema") ||
(node.expression?.getText().indexOf(".extends") !== -1)) &&
node.arguments[0].kind === ts__namespace.SyntaxKind.ObjectLiteralExpression) {
const callExpression = node;
let className = callExpression.arguments[1]?.getText();
if (!className && callExpression.parent.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
className = callExpression.parent.name?.getText();
}
// skip if no className is provided
if (!className) {
break;
}
if (currentStructure?.name !== className) {
currentStructure = new Class();
context.addStructure(currentStructure);
}
if (node.expression?.getText().indexOf(".extends") !== -1) {
// if it's using `.extends({})`
const extendsClass = node.expression?.expression?.escapedText;
// skip if no extendsClass is provided
if (!extendsClass) {
break;
}
currentStructure.extends = extendsClass;
}
else {
// if it's using `schema({})`
currentStructure.extends = "Schema"; // force extends to Schema
}
currentStructure.name = className;
const types = callExpression.arguments[0];
for (let i = 0; i < types.properties.length; i++) {
const prop = types.properties[i];
const property = currentProperty || new Property();
property.name = prop.name.escapedText;
currentStructure.addProperty(property);
defineProperty(property, prop.initializer);
}
}
break;
case ts__namespace.SyntaxKind.EnumMember:
if (currentStructure instanceof Enum) {
const initializer = node.initializer?.text;
const name = node.getFirstToken().getText();
const property = currentProperty || new Property();
property.name = name;
if (initializer !== undefined) {
property.type = initializer;
}
currentStructure.addProperty(property);
currentProperty = undefined;
}
break;
}
ts__namespace.forEachChild(node, (n) => inspectNode(n, context, decoratorName));
}
let parsedFiles;
function parseFiles(fileNames, decoratorName = "type", context = new Context()) {
if (typeof ts__namespace.createSourceFile !== "function") {
// typescript@7+ (native) no longer ships the JS compiler API
throw new Error(`schema-codegen requires the TypeScript compiler API, which the installed "typescript@${ts__namespace.version}" package does not provide.\n` +
`TypeScript 7+ no longer ships the JS compiler API — install typescript 5.x or 6.x (e.g. \`npm install --save-dev typescript@6\`) to use schema-codegen.`);
}
/**
* Re-set globalContext for each test case
*/
if (globalContext !== context) {
parsedFiles = {};
globalContext = context;
}
fileNames.forEach((fileName) => {
let sourceFile;
let sourceFileName;
const fileNameAlternatives = [];
if (!fileName.endsWith(".ts") &&
!fileName.endsWith(".js") &&
!fileName.endsWith(".mjs")) {
fileNameAlternatives.push(`${fileName}.ts`);
fileNameAlternatives.push(`${fileName}/index.ts`);
}
else if (fileName.endsWith(".js")) {
// Handle .js extensions by also trying .ts (ESM imports often use .js extension)
fileNameAlternatives.push(fileName);
fileNameAlternatives.push(fileName.replace(/\.js$/, ".ts"));
}
else {
fileNameAlternatives.push(fileName);
}
for (let i = 0; i < fileNameAlternatives.length; i++) {
try {
sourceFileName = path__namespace.resolve(fileNameAlternatives[i]);
if (parsedFiles[sourceFileName]) {
break;
}
sourceFile = ts__namespace.createSourceFile(sourceFileName, fs.readFileSync(sourceFileName).toString(), ts__namespace.ScriptTarget.Latest, true);
parsedFiles[sourceFileName] = true;
break;
}
catch (e) {
// only swallow fs errors (ENOENT/EISDIR) while probing alternatives
if (!e?.code) {
throw e;
}
}
}
if (sourceFile) {
inspectNode(sourceFile, context, decoratorName);
}
});
return context.getStructures();
}
/**
* TypeScript 4.8+ has introduced a change on how to access decorators.
* - https://github.com/microsoft/TypeScript/pull/49089
* - https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees
*/
function getDecorators(node) {
if (node == undefined) {
return undefined;
}
// TypeScript 4.7 and below
// @ts-ignore
if (node.decorators) {
return node.decorators;
}
// TypeScript 4.8 and above
// @ts-ignore
if (ts__namespace.canHaveDecorators && ts__namespace.canHaveDecorators(node)) {
// @ts-ignore
const decorators = ts__namespace.getDecorators(node);
return decorators ? Array.from(decorators) : undefined;
}
// @ts-ignore
return node.modifiers?.filter(ts__namespace.isDecorator);
}
const name$8 = "Unity/C#";
const typeMaps$8 = {
"string": "string",
"number": "float",
"boolean": "bool",
"int8": "sbyte",
"uint8": "byte",
"int16": "short",
"uint16": "ushort",
"int32": "int",
"uint32": "uint",
"int64": "long",
"uint64": "ulong",
"float32": "float",
"float64": "double",
};
const COMMON_IMPORTS$5 = `using Colyseus.Schema;
#if UNITY_5_3_OR_NEWER
using UnityEngine.Scripting;
#endif`;
/**
* C# Code Generator
*/
const capitalize$1 = (s) => {
if (typeof s !== 'string')
return '';
return s.charAt(0).toUpperCase() + s.slice(1);
};
/**
* Generate individual files for each class/interface/enum
*/
function generate$9(context, options) {
// enrich typeMaps with enums
context.enums.forEach((structure) => {
typeMaps$8[structure.name] = structure.name;
});
return [
...context.classes.map(structure => ({
name: `${structure.name}.cs`,
content: generateClass$8(structure, options.namespace)
})),
...context.interfaces.map(structure => ({
name: `${structure.name}.cs`,
content: generateInterface$1(structure, options.namespace),
})),
...context.enums.filter(structure => structure.name !== 'OPERATION').map((structure) => ({
name: `${structure.name}.cs`,
content: generateEnum$1(structure, options.namespace),
})),
];
}
/**
* Generate a single bundled file containing all classes, interfaces, and enums
*/
function renderBundle$8(context, options) {
const fileName = options.namespace ? `${options.namespace}.cs` : "Schema.cs";
const indent = options.namespace ? "\t" : "";
// enrich typeMaps with enums
context.enums.forEach((structure) => {
typeMaps$8[structure.name] = structure.name;
});
// Collect all bodies
const classBodies = context.classes.map(klass => generateClassBody$8(klass, indent));
const interfaceBodies = context.interfaces.map(iface => generateInterfaceBody$1(iface, indent));
const enumBodies = context.enums
.filter(structure => structure.name !== 'OPERATION')
.map(e => generateEnumBody$1(e, indent));
const allBodies = [...classBodies, ...interfaceBodies, ...enumBodies].join("\n\n");
const content = `${getCommentHeader()}
${COMMON_IMPORTS$5}
${options.namespace ? `\nnamespace ${options.namespace} {\n` : ""}
${allBodies}
${options.namespace ? "}" : ""}`;
return { name: fileName, content };
}
/**
* Generate just the class body (without imports/namespace) for bundling
*/
function generateClassBody$8(klass, indent = "") {
return `${indent}public partial class ${klass.name} : ${klass.extends} {
#if UNITY_5_3_OR_NEWER
[Preserve]
#endif
public ${klass.name}() { }
${klass.properties.map((prop) => generateProperty$4(prop, indent)).join("\n\n")}
${indent}}`;
}
/**
* Generate a complete class file with imports/namespace (for individual file mode)
*/
function generateClass$8(klass, namespace) {
const indent = (namespace) ? "\t" : "";
return `${getCommentHeader()}
${COMMON_IMPORTS$5}
${namespace ? `\nnamespace ${namespace} {` : ""}
${generateClassBody$8(klass, indent)}
${namespace ? "}" : ""}
`;
}
/**
* Check if all enum members resolve to non-negative integers,
* allowing emission as a native C# `enum` (which only supports integral types).
*/
function canUseNativeEnum(_enum) {
return _enum.properties.every((prop) => {
if (!prop.type)
return true;
const n = Number(prop.type);
return Number.isInteger(n) && n >= 0;
});
}
/**
* Generate just the enum body (without imports/namespace) for bundling
*/
function generateEnumBody$1(_enum, indent = "") {
if (canUseNativeEnum(_enum)) {
const members = _enum.properties
.map((prop, i) => {
const value = prop.type ? Number(prop.type) : i;
return `${indent}\t${prop.name} = ${value},`;
})
.join("\n");
return `${indent}public enum ${_enum.name} : int {
${members}
${indent}}`;
}
return `${indent}public struct ${_enum.name} {
${_enum.properties
.map((prop) => {
let dataType = "int";
let value;
if (prop.type) {
if (isNaN(Number(prop.type))) {
value = `"${prop.type}"`;
dataType = "string";
}
else {
value = Number(prop.type);
dataType = Number.isInteger(value) ? 'int' : 'float';
}
}
else {
value = _enum.properties.indexOf(prop);
}
return `${indent}\tpublic const ${dataType} ${prop.name} = ${value};`;
})
.join("\n")}
${indent}}`;
}
/**
* Generate a complete enum file with imports/namespace (for individual file mode)
*/
function generateEnum$1(_enum, namespace) {
const indent = namespace ? "\t" : "";
return `${getCommentHeader()}
${namespace ? `\nnamespace ${namespace} {` : ""}
${generateEnumBody$1(_enum, indent)}
${namespace ? "}" : ""}`;
}
function generateProperty$4(prop, indent = "") {
let typeArgs = `"${prop.type}"`;
let property = "public";
let langType;
let initializer = "";
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
langType = getType(prop);
typeArgs += `, typeof(${langType})`;
if (!isUpcaseFirst) {
typeArgs += `, "${prop.childType}"`;
}
initializer = `null`;
}
else {
langType = getType(prop);
initializer = `default(${langType})`;
}
property += ` ${langType} ${prop.name}`;
let ret = (prop.deprecated) ? `\t\t[System.Obsolete("field '${prop.name}' is deprecated.", true)]\n` : '';
return ret + `\t${indent}[Type(${prop.index}, ${typeArgs})]
\t${indent}${property} = ${initializer};`;
}
/**
* Generate just the interface body (without imports/namespace) for bundling
*/
function generateInterfaceBody$1(struct, indent = "") {
return `${indent}public class ${struct.name} {
${struct.properties.map(prop => `\t${indent}public ${getType(prop)} ${prop.name};`).join("\n")}
${indent}}`;
}
/**
* Generate a complete interface file with imports/namespace (for individual file mode)
*/
function generateInterface$1(struct, namespace) {
const indent = (namespace) ? "\t" : "";
return `${getCommentHeader()}
using Colyseus.Schema;
${namespace ? `\nnamespace ${namespace} {` : ""}
${generateInterfaceBody$1(struct, indent)}
${namespace ? "}" : ""}
`;
}
function getChildType(prop) {
return typeMaps$8[prop.childType];
}
function getType(prop) {
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
let type;
if (prop.type === "ref") {
type = (isUpcaseFirst)
? prop.childType
: getChildType(prop);
}
else {
const containerClass = capitalize$1(prop.type);
type = (isUpcaseFirst)
? `${containerClass}Schema<${prop.childType}>`
: `${containerClass}Schema<${getChildType(prop)}>`;
}
return type;
}
else {
return (prop.type === "array")
? `${typeMaps$8[prop.childType] || prop.childType}[]`
: typeMaps$8[prop.type];
}
}
var csharp = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$9,
name: name$8,
renderBundle: renderBundle$8
});
const name$7 = "C++";
const typeMaps$7 = {
"string": "string",
"number": "varint_t",
"boolean": "bool",
"int8": "int8_t",
"uint8": "uint8_t",
"int16": "int16_t",
"uint16": "uint16_t",
"int32": "int32_t",
"uint32": "uint32_t",
"int64": "int64_t",
"uint64": "uint64_t",
"float32": "float32_t",
"float64": "float64_t",
};
const typeInitializer$2 = {
"string": '""',
"number": "0",
"boolean": "false",
"int8": "0",
"uint8": "0",
"int16": "0",
"uint16": "0",
"int32": "0",
"uint32": "0",
"int64": "0",
"uint64": "0",
"float32": "0",
"float64": "0",
};
const COMMON_INCLUDES$1 = `#include "schema.h"
#include <typeinfo>
#include <typeindex>
using namespace colyseus::schema;`;
/**
* C++ Code Generator
*/
const capitalize = (s) => {
if (typeof s !== 'string')
return '';
return s.charAt(0).toUpperCase() + s.slice(1);
};
const distinct$5 = (value, index, self) => self.indexOf(value) === index;
/**
* Generate individual files for each class
*/
function generate$8(context, options) {
return context.classes.map(klass => ({
name: klass.name + ".hpp",
content: generateClass$7(klass, options.namespace, context.classes)
}));
}
/**
* Generate a single bundled header file containing all classes
*/
function renderBundle$7(context, options) {
const fileName = options.namespace ? `${options.namespace}.hpp` : "schema.hpp";
const guardName = `__SCHEMA_CODEGEN_${(options.namespace || "SCHEMA").toUpperCase()}_H__`;
const classBodies = context.classes.map(klass => generateClassBody$7(klass, context.classes, options.namespace));
const content = `${getCommentHeader()}
#ifndef ${guardName}
#define ${guardName} 1
${COMMON_INCLUDES$1}
${options.namespace ? `namespace ${options.namespace} {\n` : ""}
${classBodies.join("\n\n")}
${options.namespace ? "}" : ""}
#endif
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without includes/guards) for bundling
*/
function generateClassBody$7(klass, allClasses, namespace) {
const propertiesPerType = {};
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
if (!propertiesPerType[type]) {
propertiesPerType[type] = [];
}
propertiesPerType[type].push(property);
// keep all refs list
if ((type === "ref" || type === "array" || type === "map")) {
allRefs.push(property);
}
});
const allProperties = getAllProperties$1(klass, allClasses);
const createInstanceMethod = (allRefs.length === 0) ? "" :
`\tinline Schema* createInstance(std::type_index type) {
\t\t${generateFieldIfElseChain(allRefs, (property) => `type == typeid(${property.childType})`, (property) => `return new ${property.childType}();`, (property) => typeMaps$7[property.childType] === undefined)}
\t\treturn ${klass.extends}::createInstance(type);
\t}`;
return `class ${klass.name} : public ${klass.extends} {
public:
${klass.properties.map(prop => generateProperty$3(prop)).join("\n")}
\t${klass.name}() {
\t\tthis->_indexes = ${generateAllIndexes(allProperties)};
\t\tthis->_types = ${generateAllTypes(allProperties)};
\t\tthis->_childPrimitiveTypes = ${generateAllChildPrimitiveTypes(allProperties)};
\t\tthis->_childSchemaTypes = ${generateAllChildSchemaTypes(allProperties)};
\t}
\tvirtual ~${klass.name}() {
\t\t${generateDestructors(allProperties).join("\n\t\t")}
\t}
protected:
${Object.keys(propertiesPerType).map(type => generateGettersAndSetters(klass, type, propertiesPerType[type])).
join("\n")}
${createInstanceMethod}
};`;
}
/**
* Generate a complete class file with includes/guards (for individual file mode)
*/
function generateClass$7(klass, namespace, allClasses) {
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
// keep all refs list
if ((type === "ref" || type === "array" || type === "map")) {
allRefs.push(property);
}
});
const localIncludes = allRefs.
filter(ref => ref.childType && typeMaps$7[ref.childType] === undefined).
map(ref => ref.childType).
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
filter(distinct$5).
map(childType => `#include "${childType}.hpp"`).
join("\n");
return `${getCommentHeader()}
#ifndef __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__
#define __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__ 1
${COMMON_INCLUDES$1}
${localIncludes}
${namespace ? `namespace ${namespace} {` : ""}
${generateClassBody$7(klass, allClasses)}
${namespace ? "}" : ""}
#endif
`;
}
function generateProperty$3(prop) {
let property = "";
let langType;
let initializer = "";
let isPropPointer = "";
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (prop.type === "ref") {
langType = `${prop.childType}`;
initializer = `new ${prop.childType}()`;
}
else if (prop.type === "array") {
langType = (isUpcaseFirst)
? `ArraySchema<${prop.childType}*>`
: `ArraySchema<${typeMaps$7[prop.childType]}>`;
initializer = `new ${langType}()`;
}
else if (prop.type === "map") {
langType = (isUpcaseFirst)
? `MapSchema<${prop.childType}*>`
: `MapSchema<${typeMaps$7[prop.childType]}>`;
initializer = `new ${langType}()`;
}
isPropPointer = "*";
}
else {
langType = typeMaps$7[prop.type];
initializer = typeInitializer$2[prop.type];
}
property += ` ${langType} ${isPropPointer}${prop.name}`;
return `\t${property} = ${initializer};`;
}
function generateGettersAndSetters(klass, type, properties) {
let langType = typeMaps$7[type];
let typeCast = "";
const getMethodName = `get${capitalize(type)}`;
const setMethodName = `set${capitalize(type)}`;
if (type === "ref") {
langType = "Schema*";
}
else if (type === "array") {
langType = `ArraySchema<char*> *`;
typeCast = `(ArraySchema<char*> *)`;
}
else if (type === "map") {
langType = `MapSchema<char*> *`;
typeCast = `(MapSchema<char*> *)`;
}
return `\tinline ${langType} ${getMethodName}(const string &field)
\t{
\t\t${generateFieldIfElseChain(properties, (property) => `field == "${property.name}"`, (property) => `return ${typeCast}this->${property.name};`)}
\t\treturn ${klass.extends}::${getMethodName}(field);
\t}
\tinline void ${setMethodName}(const string &field, ${langType} value)
\t{
\t\t${generateFieldIfElseChain(properties, (property) => `field == "${property.name}"`, (property) => {
const isSchemaType = (typeMaps$7[property.childType] === undefined);
if (type === "ref") {
langType = `${property.childType}*`;
typeCast = (isSchemaType)
? `(${property.childType}*)`
: `/* bug? */`;
}
else if (type === "array") {
typeCast = (isSchemaType)
? `(ArraySchema<${property.childType}*> *)`
: `(ArraySchema<${typeMaps$7[property.childType]}> *)`;
}
else if (type === "map") {
typeCast = (isSchemaType)
? `(MapSchema<${property.childType}*> *)`
: `(MapSchema<${typeMaps$7[property.childType]}> *)`;
}
return `this->${property.name} = ${typeCast}value;\n\t\t\treturn;`;
})}
\t\treturn ${klass.extends}::${setMethodName}(field, value);
\t}`;
}
function generateFieldIfElseChain(properties, ifCallback, callback, filter = (_) => true) {
let chain = "";
const uniqueChecks = [];
properties.filter(filter).forEach((property, i) => {
const check = ifCallback(property);
if (uniqueChecks.indexOf(check) === -1) {
uniqueChecks.push(check);
}
else {
return;
}
if (i === 0) {
chain += "if ";
}
else {
chain += " else if ";
}
chain += `(${check})
\t\t{
\t\t\t${callback(property)}\n
\t\t}`;
});
return chain;
}
function generateAllIndexes(properties) {
return `{${properties.map((property, i) => `{${i}, "${property.name}"}`).join(", ")}}`;
}
function generateAllTypes(properties) {
return `{${properties.map((property, i) => `{${i}, "${property.type}"}`).join(", ")}}`;
}
function generateAllChildSchemaTypes(properties) {
return `{${properties.map((property, i) => {
if (property.childType && typeMaps$7[property.childType] === undefined) {
return `{${i}, typeid(${property.childType})}`;
}
else {
return null;
}
}).filter(r => r !== null).join(", ")}}`;
}
function generateAllChildPrimitiveTypes(properties) {
return `{${properties.map((property, i) => {
if (typeMaps$7[property.childType] !== undefined) {
return `{${i}, "${property.childType}"}`;
}
else {
return null;
}
}).filter(r => r !== null).join(", ")}}`;
}
function generateDestructors(properties) {
return properties.map((property, i) => {
if (property.childType) {
return `delete this->${property.name};`;
}
else {
return null;
}
}).filter(r => r !== null);
}
function getAllProperties$1(klass, allClasses) {
let properties = [];
getInheritanceTree(klass, allClasses).reverse().forEach((klass) => {
properties = properties.concat(klass.properties);
});
return properties;
}
var cpp = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$8,
name: name$7,
renderBundle: renderBundle$7
});
const name$6 = "Haxe";
const typeMaps$6 = {
"string": "String",
"number": "Dynamic",
"boolean": "Bool",
"int8": "Int",
"uint8": "UInt",
"int16": "Int",
"uint16": "UInt",
"int32": "Int",
"uint32": "UInt",
"int64": "Int",
"uint64": "UInt",
"float32": "Float",
"float64": "Float",
};
const typeInitializer$1 = {
"string": '""',
"number": "0",
"boolean": "false",
"int8": "0",
"uint8": "0",
"int16": "0",
"uint16": "0",
"int32": "0",
"uint32": "0",
"int64": "0",
"uint64": "0",
"float32": "0",
"float64": "0",
};
const COMMON_IMPORTS$4 = `import io.colyseus.serializer.schema.Schema;
import io.colyseus.serializer.schema.types.*;`;
/**
* Generate individual files for each class
*/
function generate$7(context, options) {
return context.classes.map(klass => ({
name: klass.name + ".hx",
content: generateClass$6(klass, options.namespace, context.classes)
}));
}
/**
* Generate a single bundled file containing all classes
*/
function renderBundle$6(context, options) {
const fileName = options.namespace ? `${options.namespace}.hx` : "Schema.hx";
const classBodies = context.classes.map(klass => generateClassBody$6(klass));
const content = `${getCommentHeader()}
${options.namespace ? `package ${options.namespace};` : ""}
${COMMON_IMPORTS$4}
${classBodies.join("\n\n")}
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without package/imports) for bundling
*/
function generateClassBody$6(klass) {
return `class ${klass.name} extends ${klass.extends} {
${klass.properties.map(prop => generateProperty$2(prop)).join("\n")}
}`;
}
/**
* Generate a complete class file with package/imports (for individual file mode)
*/
function generateClass$6(klass, namespace, allClasses) {
return `${getCommentHeader()}
${namespace ? `package ${namespace};` : ""}
${COMMON_IMPORTS$4}
${generateClassBody$6(klass)}
`;
}
function generateProperty$2(prop) {
let langType;
let initializer = "";
let typeArgs = `"${prop.type}"`;
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (isUpcaseFirst) {
typeArgs += `, ${prop.childType}`;
}
else {
typeArgs += `, "${prop.childType}"`;
}
if (prop.type === "ref") {
langType = `${prop.childType}`;
initializer = `new ${prop.childType}()`;
}
else if (prop.type === "array") {
langType = (isUpcaseFirst)
? `ArraySchema<${prop.childType}>`
: `ArraySchema<${typeMaps$6[prop.childType]}>`;
initializer = `new ${langType}()`;
}
else if (prop.type === "map") {
langType = (isUpcaseFirst)
? `MapSchema<${prop.childType}>`
: `MapSchema<${typeMaps$6[prop.childType]}>`;
initializer = `new ${langType}()`;
}
}
else {
langType = typeMaps$6[prop.type];
initializer = typeInitializer$1[prop.type];
}
// TODO: remove initializer. The callbacks at the Haxe decoder side have a
// "FIXME" comment about this on Decoder.hx
return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType} = ${initializer};\n`;
// return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType};\n`
}
var haxe = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$7,
name: name$6,
renderBundle: renderBundle$6
});
const name$5 = "TypeScript";
const typeMaps$5 = {
"string": "string",
"number": "number",
"boolean": "boolean",
"int8": "number",
"uint8": "number",
"int16": "number",
"uint16": "number",
"int32": "number",
"uint32": "number",
"int64": "number",
"uint64": "number",
"float32": "number",
"float64": "number",
};
const COMMON_IMPORTS$3 = `import { Schema, type, ArraySchema, MapSchema, SetSchema, DataChange } from '@colyseus/schema';`;
const distinct$4 = (value, index, self) => self.indexOf(value) === index;
/**
* Generate individual files for each class/interface
*/
function generate$6(context, options) {
return [
...context.classes.map(structure => ({
name: structure.name + ".ts",
content: generateClass$5(structure, options.namespace, context.classes)
})),
...context.interfaces.map(structure => ({
name: structure.name + ".ts",
content: generateInterface(structure, options.namespace, context.classes),
}))
];
}
/**
* Generate a single bundled file containing all classes and interfaces
*/
function renderBundle$5(context, options) {
const fileName = options.namespace ? `${options.namespace}.ts` : "schema.ts";
// Collect all class bodies
const classBodies = context.classes.map(klass => generateClassBody$5(klass));
// Collect all interface bodies
const interfaceBodies = context.interfaces.map(iface => generateInterfaceBody(iface));
const content = `${getCommentHeader()}
${COMMON_IMPORTS$3}
${classBodies.join("\n\n")}
${interfaceBodies.length > 0 ? "\n" + interfaceBodies.join("\n\n") : ""}`;
return { name: fileName, content };
}
/**
* Generate just the class body (without imports) for bundling
*/
function generateClassBody$5(klass) {
return `export class ${klass.name} extends ${klass.extends} {
${klass.properties.map(prop => ` ${generateProperty$1(prop)}`).join("\n")}
}`;
}
/**
* Generate just the interface body (without imports) for bundling
*/
function generateInterfaceBody(iface) {
return `export interface ${iface.name} {
${iface.properties.map(prop => ` ${prop.name}: ${prop.type};`).join("\n")}
}`;
}
/**
* Generate a complete class file with imports (for individual file mode)
*/
function generateClass$5(klass, namespace, allClasses) {
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
// keep all refs list
if ((type === "ref" || type === "array" || type === "map" || type === "set")) {
allRefs.push(property);
}
});
const localImports = allRefs.
filter(ref => ref.childType && typeMaps$5[ref.childType] === undefined).
map(ref => ref.childType).
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
filter(distinct$4).
map(childType => `import { ${childType} } from './${childType}'`).
join("\n");
return `${getCommentHeader()}
${COMMON_IMPORTS$3}
${localImports}
${generateClassBody$5(klass)}
`;
}
function generateProperty$1(prop) {
let langType;
let initializer = "";
let typeArgs;
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (isUpcaseFirst) {
typeArgs += `, ${prop.childType}`;
}
else {
typeArgs += `, "${prop.childType}"`;
}
if (prop.type === "ref") {
langType = `${prop.childType}`;
initializer = `new ${prop.childType}()`;
typeArgs = `${prop.childType}`;
}
else if (prop.type === "array") {
langType = (isUpcaseFirst)
? `ArraySchema<${prop.childType}>`
: `ArraySchema<${typeMaps$5[prop.childType]}>`;
initializer = `new ${langType}()`;
typeArgs = (isUpcaseFirst)
? `[ ${prop.childType} ]`
: `[ "${prop.childType}" ]`;
}
else if (prop.type === "map") {
langType = (isUpcaseFirst)
? `MapSchema<${prop.childType}>`
: `MapSchema<${typeMaps$5[prop.childType]}>`;
initializer = `new ${langType}()`;
typeArgs = (isUpcaseFirst)
? `{ map: ${prop.childType} }`
: `{ map: "${prop.childType}" }`;
}
else if (prop.type === "set") {
langType = (isUpcaseFirst)
? `SetSchema<${prop.childType}>`
: `SetSchema<${typeMaps$5[prop.childType]}>`;
initializer = `new ${langType}()`;
typeArgs = (isUpcaseFirst)
? `{ set: ${prop.childType} }`
: `{ set: "${prop.childType}" }`;
}
}
else {
langType = typeMaps$5[prop.type];
typeArgs = `"${prop.type}"`;
}
// TS1263: "Declarations with initializers cannot also have definite assignment assertions"
const definiteAssertion = initializer ? "" : "!";
return `@type(${typeArgs}) public ${prop.name}${definiteAssertion}: ${langType}${(initializer) ? ` = ${initializer}` : ""};`;
}
/**
* Generate a complete interface file with header (for individual file mode)
*/
function generateInterface(structure, namespace, allClasses) {
return `${getCommentHeader()}
${generateInterfaceBody(structure)}
`;
}
var ts = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$6,
name: name$5,
renderBundle: renderBundle$5
});
const name$4 = "JavaScript";
const typeMaps$4 = {
"string": "string",
"number": "number",
"boolean": "boolean",
"int8": "number",
"uint8": "number",
"int16": "number",
"uint16": "number",
"int32": "number",
"uint32": "number",
"int64": "number",
"uint64": "number",
"float32": "number",
"float64": "number",
};
const COMMON_IMPORTS$2 = `const schema = require("@colyseus/schema");
const Schema = schema.Schema;
const type = schema.type;`;
const distinct$3 = (value, index, self) => self.indexOf(value) === index;
/**
* Generate individual files for each class
*/
function generate$5(context, options) {
return context.classes.map(klass => ({
name: klass.name + ".js",
content: generateClass$4(klass, options.namespace, context.classes)
}));
}
/**
* Generate a single bundled file containing all classes
*/
function renderBundle$4(context, options) {
const fileName = options.namespace ? `${options.namespace}.js` : "schema.js";
const classBodies = context.classes.map(klass => generateClassBody$4(klass));
const classExports = context.classes.map(klass => ` ${klass.name},`).join("\n");
const content = `${getCommentHeader()}
${COMMON_IMPORTS$2}
${classBodies.join("\n\n")}
module.exports = {
${classExports}
};
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without imports) for bundling
*/
function generateClassBody$4(klass) {
return `class ${klass.name} extends ${klass.extends} {
constructor () {
super();
${klass.properties.
filter(prop => prop.childType !== undefined).
map(prop => " " + generatePropertyInitializer(prop)).join("\n")}
}
}
${klass.properties.map(prop => generatePropertyDeclaration$1(klass.name, prop)).join("\n")}`;
}
/**
* Generate a complete class file with imports (for individual file mode)
*/
function generateClass$4(klass, namespace, allClasses) {
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
// keep all refs list
if ((type === "ref" || type === "array" || type === "map")) {
allRefs.push(property);
}
});
const localImports = allRefs.
filter(ref => ref.childType && typeMaps$4[ref.childType] === undefined).
map(ref => ref.childType).
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
filter(distinct$3).
map(childType => `const ${childType} = require("./${childType}");`).
join("\n");
return `${getCommentHeader()}
${COMMON_IMPORTS$2}
${localImports}
${generateClassBody$4(klass)}
export default ${klass.name};
`;
}
function generatePropertyDeclaration$1(className, prop) {
let typeArgs;
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (isUpcaseFirst) {
typeArgs += `, ${prop.childType}`;
}
else {
typeArgs += `, "${prop.childType}"`;
}
if (prop.type === "ref") {
typeArgs = `${prop.childType}`;
}
else if (prop.type === "array") {
typeArgs = (isUpcaseFirst)
? `[ ${prop.childType} ]`
: `[ "${prop.childType}" ]`;
}
else if (prop.type === "map") {
typeArgs = (isUpcaseFirst)
? `{ map: ${prop.childType} }`
: `{ map: "${prop.childType}" }`;
}
}
else {
typeArgs = `"${prop.type}"`;
}
return `type(${typeArgs})(${className}.prototype, "${prop.name}");`;
}
function generatePropertyInitializer(prop) {
let initializer = "";
if (prop.type === "ref") {
initializer = `new ${prop.childType}()`;
}
else if (prop.type === "array") {
initializer = `new schema.ArraySchema()`;
}
else if (prop.type === "map") {
initializer = `new schema.MapSchema()`;
}
return `this.${prop.name} = ${initializer}`;
}
var js = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$5,
name: name$4,
renderBundle: renderBundle$4
});
const name$3 = "Java";
const typeMaps$3 = {
"string": "String",
"number": "float",
"boolean": "boolean",
"int8": "byte",
"uint8": "short",
"int16": "short",
"uint16": "int",
"int32": "int",
"uint32": "long",
"int64": "long",
"uint64": "long",
"float32": "float",
"float64": "double",
};
const typeInitializer = {
"string": '""',
"number": "0",
"boolean": "false",
"int8": "0",
"uint8": "0",
"int16": "0",
"uint16": "0",
"int32": "0",
"uint32": "0",
"int64": "0",
"uint64": "0",
"float32": "0",
"float64": "0",
};
const COMMON_IMPORTS$1 = `import io.colyseus.serializer.schema.Schema;
import io.colyseus.serializer.schema.annotations.SchemaClass;
import io.colyseus.serializer.schema.annotations.SchemaField;`;
/**
* Java Code Generator
*/
/**
* Generate individual files for each class
*/
function generate$4(context, options) {
return context.classes.map(klass => ({
name: klass.name + ".java",
content: generateClass$3(klass, options.namespace)
}));
}
/**
* Generate a single bundled file containing all classes
* Note: Java typically requires one public class per file, so bundled mode
* generates all classes in a single file with package-private visibility
*/
function renderBundle$3(context, options) {
const fileName = options.namespace ? `Schema.java` : "Schema.java";
const classBodies = context.classes.map(klass => generateClassBody$3(klass));
const content = `${getCommentHeader()}
${options.namespace ? `\npackage ${options.namespace};` : ""}
${COMMON_IMPORTS$1}
${classBodies.join("\n\n")}
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without package/imports) for bundling
*/
function generateClassBody$3(klass) {
return `@SchemaClass
class ${klass.name} extends ${klass.extends} {
${klass.properties.map(prop => generateProperty(prop, "")).join("\n\n")}
}`;
}
/**
* Generate a complete class file with package/imports (for individual file mode)
*/
function generateClass$3(klass, namespace) {
const indent = (namespace) ? "\t" : "";
return `${getCommentHeader()}
${namespace ? `\npackage ${namespace};` : ""}
${COMMON_IMPORTS$1}
@SchemaClass
${indent}public class ${klass.name} extends ${klass.extends} {
${klass.properties.map(prop => generateProperty(prop, indent)).join("\n\n")}
${indent}}
${namespace ? "}" : ""}
`;
}
function generateProperty(prop, indent = "") {
let typeArgs = `${prop.index}/${prop.type}`;
let property = "public";
let langType;
let ctorArgs = "";
let initializer = "";
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (prop.type !== "ref" && isUpcaseFirst) {
ctorArgs = `${prop.childType}.class`;
}
if (prop.type === "ref") {
langType = (isUpcaseFirst)
? prop.childType
: typeMaps$3[prop.childType];
initializer = `new ${langType}${(prop.type !== "ref" && isUpcaseFirst) ? "<>" : ""}(${ctorArgs})`;
}
else if (prop.type === "array") {
langType = (isUpcaseFirst)
? `ArraySchema<${prop.childType}>`
: `ArraySchema`;
initializer = `new ArraySchema${(isUpcaseFirst) ? "<>" : ""}(${ctorArgs})`;
}
else if (prop.type === "map") {
langType = (isUpcaseFirst)
? `MapSchema<${prop.childType}>`
: `MapSchema`;
initializer = `new MapSchema${(isUpcaseFirst) ? "<>" : ""}(${ctorArgs})`;
}
if (prop.type !== "ref") {
typeArgs += (isUpcaseFirst)
? `/ref`
: `/${prop.childType}`;
}
}
else {
langType = typeMaps$3[prop.type];
initializer = typeInitializer[prop.type];
}
property += ` ${langType} ${prop.name}`;
return `\t@SchemaField("${typeArgs}")\t${indent}
\t${indent}${property} = ${initializer};`;
}
var java = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$4,
name: name$3,
renderBundle: renderBundle$3
});
const name$2 = "LUA";
/**
TODO:
- Support inheritance
- Support importing Schema dependencies
*/
const typeMaps$2 = {
"string": "string",
"number": "number",
"boolean": "boolean",
"int8": "number",
"uint8": "number",
"int16": "number",
"uint16": "number",
"int32": "number",
"uint32": "number",
"int64": "number",
"uint64": "number",
"float32": "number",
"float64": "number",
};
const COMMON_IMPORTS = `local schema = require 'colyseus.serializer.schema.schema'`;
const distinct$2 = (value, index, self) => self.indexOf(value) === index;
/**
* Generate individual files for each class
*/
function generate$3(context, options) {
return context.classes.map(klass => ({
name: klass.name + ".lua",
content: generateClass$2(klass, options.namespace, context.classes)
}));
}
/**
* Generate a single bundled file containing all classes
*/
function renderBundle$2(context, options) {
const fileName = options.namespace ? `${options.namespace}.lua` : "schema.lua";
const classBodies = context.classes.map(klass => generateClassBody$2(klass));
const classNames = context.classes.map(klass => ` ${klass.name} = ${klass.name},`).join("\n");
const content = `${getCommentHeader().replace(/\/\//mg, "--")}
${COMMON_IMPORTS}
${classBodies.join("\n\n")}
return {
${classNames}
}
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without requires) for bundling
*/
function generateClassBody$2(klass) {
// Inheritance support
const inherits = (klass.extends !== "Schema")
? `, ${klass.extends}`
: "";
return `---@class ${klass.name}: ${klass.extends}
${klass.properties.map(prop => `---@field ${prop.name} ${getLUATypeAnnotation(prop)}`).join("\n")}
local ${klass.name} = schema.define({
${klass.properties.map(prop => generatePropertyDeclaration(prop)).join(",\n")},
["_fields_by_index"] = { ${klass.properties.map(prop => `"${prop.name}"`).join(", ")} },
}${inherits})`;
}
/**
* Generate a complete class file with requires (for individual file mode)
*/
function generateClass$2(klass, namespace, allClasses) {
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
// keep all refs list
if ((type === "ref" || type === "array" || type === "map")) {
allRefs.push(property);
}
});
const localRequires = allRefs.
filter(ref => ref.childType && typeMaps$2[ref.childType] === undefined).
map(ref => ref.childType).
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
filter(distinct$2).
map(childType => `local ${childType} = require '${(namespace ? `${namespace}.` : '')}${childType}'`).
join("\n");
return `${getCommentHeader().replace(/\/\//mg, "--")}
${COMMON_IMPORTS}
${localRequires}
${generateClassBody$2(klass)}
return ${klass.name}
`;
}
function generatePropertyDeclaration(prop) {
let typeArgs;
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
if (isUpcaseFirst) {
typeArgs += `${prop.childType}`;
}
else {
typeArgs += `"${prop.childType}"`;
}
if (prop.type === "ref") {
typeArgs = (isUpcaseFirst)
? `${prop.childType}`
: `"${prop.childType}"`;
}
else {
typeArgs = (isUpcaseFirst)
? `{ ${prop.type} = ${prop.childType} }`
: `{ ${prop.type} = "${prop.childType}" }`;
}
}
else {
typeArgs = `"${prop.type}"`;
}
return ` ["${prop.name}"] = ${typeArgs}`;
}
function getLUATypeAnnotation(prop) {
if (prop.type === "ref") {
return prop.childType;
}
else if (prop.type === "array") {
return "ArraySchema";
}
else if (prop.type === "map") {
return "MapSchema";
}
else {
return typeMaps$2[prop.type];
}
}
var lua = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$3,
name: name$2,
renderBundle: renderBundle$2
});
const name$1 = "C";
/**
* Type mappings for C
*/
const typeMaps$1 = {
"string": "char*",
"number": "double",
"boolean": "bool",
"int8": "int8_t",
"uint8": "uint8_t",
"int16": "int16_t",
"uint16": "uint16_t",
"int32": "int32_t",
"uint32": "uint32_t",
"int64": "int64_t",
"uint64": "uint64_t",
"float32": "float",
"float64": "double",
};
/**
* Colyseus field type enum mappings
*/
const fieldTypeMaps = {
"string": "COLYSEUS_FIELD_STRING",
"number": "COLYSEUS_FIELD_NUMBER",
"boolean": "COLYSEUS_FIELD_BOOLEAN",
"int8": "COLYSEUS_FIELD_INT8",
"uint8": "COLYSEUS_FIELD_UINT8",
"int16": "COLYSEUS_FIELD_INT16",
"uint16": "COLYSEUS_FIELD_UINT16",
"int32": "COLYSEUS_FIELD_INT32",
"uint32": "COLYSEUS_FIELD_UINT32",
"int64": "COLYSEUS_FIELD_INT64",
"uint64": "COLYSEUS_FIELD_UINT64",
"float32": "COLYSEUS_FIELD_FLOAT32",
"float64": "COLYSEUS_FIELD_FLOAT64",
"ref": "COLYSEUS_FIELD_REF",
"array": "COLYSEUS_FIELD_ARRAY",
"map": "COLYSEUS_FIELD_MAP",
};
const COMMON_INCLUDES = `#include "colyseus/schema/types.h"
#include "colyseus/schema/collections.h"
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>`;
/**
* Native C Code Generator
*/
const toSnakeCase = (s) => {
return s.replace(/([A-Z])/g, (match, p1, offset) => (offset > 0 ? '_' : '') + p1.toLowerCase());
};
const distinct$1 = (value, index, self) => self.indexOf(value) === index;
/**
* Generate individual files for each class
*/
function generate$2(context, options) {
return context.classes.map(klass => ({
name: toSnakeCase(klass.name) + ".h",
content: generateClass$1(klass, options.namespace, context.classes)
}));
}
/**
* Generate a single bundled header file containing all classes
*/
function renderBundle$1(context, options) {
const fileName = options.namespace ? `${toSnakeCase(options.namespace)}.h` : "schema.h";
const guardName = `__SCHEMA_CODEGEN_${(options.namespace || "SCHEMA").toUpperCase()}_H__`;
const classBodies = context.classes.map(klass => generateClassBody$1(klass, context.classes)).join("\n\n");
const content = `${getCommentHeader()}
#ifndef ${guardName}
#define ${guardName} 1
${COMMON_INCLUDES}
${classBodies}
#endif
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without guards/includes) for bundling
*/
function generateClassBody$1(klass, allClasses) {
const snakeName = toSnakeCase(klass.name);
const typeName = `${snakeName}_t`;
const allProperties = getAllProperties(klass, allClasses);
return `${generateTypedef(klass, typeName, allClasses)}
${generateFieldsArray(klass, typeName, snakeName, allProperties)}
${generateCreateFunction(snakeName, typeName)}
${generateDestroyFunction(klass, snakeName, typeName, allProperties)}
${generateVtable(klass, snakeName, typeName, allProperties)}`;
}
/**
* Generate a complete class file with guards/includes (for individual file mode)
*/
function generateClass$1(klass, namespace, allClasses) {
toSnakeCase(klass.name);
const guardName = `__SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__`;
const allRefs = [];
klass.properties.forEach(property => {
if (property.type === "ref" || property.type === "array" || property.type === "map") {
allRefs.push(property);
}
});
// Generate includes for referenced schema types
const refIncludes = allRefs
.filter(ref => ref.childType && typeMaps$1[ref.childType] === undefined)
.map(ref => ref.childType)
.concat(getInheritanceTree(klass, allClasses, false).map(k => k.name))
.filter(distinct$1)
.map(childType => `#include "${toSnakeCase(childType)}.h"`)
.join("\n");
return `${getCommentHeader()}
#ifndef ${guardName}
#define ${guardName} 1
${COMMON_INCLUDES}
${refIncludes ? `\n${refIncludes}\n` : ""}
${generateClassBody$1(klass, allClasses)}
#endif
`;
}
function generateTypedef(klass, typeName, allClasses) {
const allProperties = getAllProperties(klass, allClasses);
const fields = allProperties.map(prop => {
const cType = getCType(prop);
return ` ${cType} ${prop.name};`;
}).join("\n");
return `typedef struct {
colyseus_schema_t __base;
${fields}
} ${typeName};`;
}
function getCType(prop) {
if (prop.type === "ref") {
return `${toSnakeCase(prop.childType)}_t*`;
}
else if (prop.type === "array") {
if (typeMaps$1[prop.childType]) {
return `colyseus_array_schema_t*`;
}
else {
return `colyseus_array_schema_t*`;
}
}
else if (prop.type === "map") {
if (typeMaps$1[prop.childType]) {
return `colyseus_map_schema_t*`;
}
else {
return `colyseus_map_schema_t*`;
}
}
else {
return typeMaps$1[prop.type] || `${toSnakeCase(prop.type)}_t*`;
}
}
function getFieldType(prop) {
return fieldTypeMaps[prop.type] || "COLYSEUS_FIELD_REF";
}
function getFieldTypeString(prop) {
// Always return the type itself (ref, array, map, string, number, etc.)
return prop.type;
}
function generateFieldsArray(klass, typeName, snakeName, allProperties) {
if (allProperties.length === 0) {
return `static const colyseus_field_t ${snakeName}_fields[] = {};`;
}
const fields = allProperties.map((prop, i) => {
const fieldType = getFieldType(prop);
const typeString = getFieldTypeString(prop);
let vtableRef = "NULL";
if (prop.type === "ref" && prop.childType && !typeMaps$1[prop.childType]) {
const childSnake = toSnakeCase(prop.childType);
vtableRef = `&${childSnake}_vtable`;
}
else if ((prop.type === "array" || prop.type === "map") && prop.childType && !typeMaps$1[prop.childType]) {
const childSnake = toSnakeCase(prop.childType);
vtableRef = `&${childSnake}_vtable`;
}
return ` {${prop.index}, "${prop.name}", ${fieldType}, "${typeString}", offsetof(${typeName}, ${prop.name}), ${vtableRef}, NULL}`;
}).join(",\n");
return `static const colyseus_field_t ${snakeName}_fields[] = {
${fields}
};`;
}
function generateCreateFunction(snakeName, typeName) {
return `static ${typeName}* ${snakeName}_create(void) {
${typeName}* instance = calloc(1, sizeof(${typeName}));
return instance;
}`;
}
function generateDestroyFunction(klass, snakeName, typeName, allProperties) {
const freeStatements = [];
allProperties.forEach(prop => {
if (prop.type === "string") {
freeStatements.push(` if (instance->${prop.name}) free(instance->${prop.name});`);
}
else if (prop.type === "ref") {
if (typeMaps$1[prop.childType]) {
freeStatements.push(` if (instance->${prop.name}) free(instance->${prop.name});`);
}
else {
const childSnake = toSnakeCase(prop.childType);
freeStatements.push(` if (instance->${prop.name}) ${childSnake}_destroy((colyseus_schema_t*)instance->${prop.name});`);
}
}
else if (prop.type === "array" || prop.type === "map") ;
});
const freeCode = freeStatements.length > 0 ? freeStatements.join("\n") + "\n" : "";
return `static void ${snakeName}_destroy(colyseus_schema_t* schema) {
${typeName}* instance = (${typeName}*)schema;
${freeCode} free(instance);
}`;
}
function generateVtable(klass, snakeName, typeName, allProperties) {
const fieldCount = allProperties.length;
return `static const colyseus_schema_vtable_t ${snakeName}_vtable = {
"${klass.name}",
sizeof(${typeName}),
(colyseus_schema_t* (*)(void))${snakeName}_create,
${snakeName}_destroy,
${snakeName}_fields,
${fieldCount}
};`;
}
function getAllProperties(klass, allClasses) {
let properties = [];
getInheritanceTree(klass, allClasses).reverse().forEach((k) => {
properties = properties.concat(k.properties);
});
return properties;
}
var c = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$2,
name: name$1,
renderBundle: renderBundle$1
});
const name = "GDScript";
/**
* Type mappings from schema types to GDScript Colyseus.Schema type constants
*/
const typeMaps = {
"string": "Colyseus.Schema.STRING",
"number": "Colyseus.Schema.NUMBER",
"boolean": "Colyseus.Schema.BOOLEAN",
"int8": "Colyseus.Schema.INT8",
"uint8": "Colyseus.Schema.UINT8",
"int16": "Colyseus.Schema.INT16",
"uint16": "Colyseus.Schema.UINT16",
"int32": "Colyseus.Schema.INT32",
"uint32": "Colyseus.Schema.UINT32",
"int64": "Colyseus.Schema.INT64",
"uint64": "Colyseus.Schema.UINT64",
"float32": "Colyseus.Schema.FLOAT32",
"float64": "Colyseus.Schema.FLOAT64",
};
const containerMaps = {
"array": "Colyseus.Schema.ARRAY",
"map": "Colyseus.Schema.MAP",
"ref": "Colyseus.Schema.REF",
};
const distinct = (value, index, self) => self.indexOf(value) === index;
/**
* GDScript Code Generator
*/
/**
* Generate individual files for each class
*/
function generate$1(context, options) {
// Enrich typeMaps with enums
context.enums.forEach((structure) => {
typeMaps[structure.name] = structure.name;
});
return [
...context.classes.map(klass => ({
name: `${klass.name}.gd`,
content: generateClass(klass, options.namespace, context.classes)
})),
...context.enums.filter(structure => structure.name !== 'OPERATION').map((structure) => ({
name: `${structure.name}.gd`,
content: generateEnum(structure, options.namespace),
})),
];
}
/**
* Generate a single bundled file containing all classes and enums
*/
function renderBundle(context, options) {
const fileName = options.namespace ? `${options.namespace}.gd` : "schema.gd";
// Enrich typeMaps with enums
context.enums.forEach((structure) => {
typeMaps[structure.name] = structure.name;
});
const enumBodies = context.enums
.filter(structure => structure.name !== 'OPERATION')
.map(e => generateEnumBody(e));
const classBodies = context.classes.map(klass => generateClassBody(klass));
const content = `${getCommentHeader("#")}
${enumBodies.length > 0 ? enumBodies.join("\n\n") + "\n\n" : ""}${classBodies.join("\n\n")}
`;
return { name: fileName, content };
}
/**
* Generate just the class body (without preload) for bundling
*/
function generateClassBody(klass) {
// Determine parent class
const parentClass = (klass.extends !== "Schema")
? klass.extends
: "Colyseus.Schema";
const properties = klass.properties;
const fieldsContent = properties.length > 0
? properties.map(prop => generateFieldDefinition(prop)).join(",\n") + ","
: "";
// Generate _to_string() method
const toStringMethod = generateToStringMethod(klass.name, properties);
return `class ${klass.name} extends ${parentClass}:
static func definition():
return [
${fieldsContent}
]
${toStringMethod}`;
}
/**
* Generate _to_string() method for the class
*/
function generateToStringMethod(className, properties) {
const fieldNames = properties.map(prop => prop.name);
const allFields = ["__ref_id", ...fieldNames];
const formatParts = allFields.map(name => `${name}: %s`).join(", ");
const formatString = `${className}(${formatParts})`;
const selfReferences = allFields.map(name => `self.${name}`).join(", ");
return `\tfunc _to_string() -> String:
return "${formatString}" % [${selfReferences}]`;
}
/**
* Generate a complete class file with preload (for individual file mode)
*/
function generateClass(klass, namespace, allClasses) {
const allRefs = [];
klass.properties.forEach(property => {
let type = property.type;
// Keep all refs list
if ((type === "ref" || type === "array" || type === "map")) {
allRefs.push(property);
}
});
// Get required preloads for referenced types
const preloads = allRefs
.filter(ref => ref.childType && typeMaps[ref.childType] === undefined)
.map(ref => ref.childType)
.concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name))
.filter(distinct)
.map(childType => `const ${childType} = preload("${childType}.gd")`)
.join("\n");
return `${getCommentHeader("#")}
${preloads ? preloads + "\n\n" : ""}${generateClassBody(klass)}
`;
}
/**
* Generate a field definition for the definition() array
*/
function generateFieldDefinition(prop) {
let args;
if (prop.childType) {
const isUpcaseFirst = prop.childType.match(/^[A-Z]/);
// Array or Map container
const containerType = containerMaps[prop.type];
const childTypeRef = isUpcaseFirst ? prop.childType : typeMaps[prop.childType] || `"${prop.childType}"`;
args = [`"${prop.name}"`, containerType, childTypeRef];
}
else {
// Primitive type
const typeRef = typeMaps[prop.type] || `"${prop.type}"`;
args = [`"${prop.name}"`, typeRef];
}
return `\t\t\tColyseus.Schema.Field.new(${args.join(", ")})`;
}
/**
* Generate just the enum body for bundling
*/
function generateEnumBody(_enum) {
const enumValues = _enum.properties.map((prop, index) => {
let value;
if (prop.type) {
if (isNaN(Number(prop.type))) {
value = `"${prop.type}"`;
}
else {
value = Number(prop.type);
}
}
else {
value = index;
}
return `\t"${prop.name}": ${value},`;
}).join("\n");
return `const ${_enum.name} = {
${enumValues}
}`;
}
/**
* Generate a complete enum file (for individual file mode)
*/
function generateEnum(_enum, _namespace) {
return `${getCommentHeader("#")}
${generateEnumBody(_enum)}
`;
}
var gdscript = /*#__PURE__*/Object.freeze({
__proto__: null,
generate: generate$1,
name: name,
renderBundle: renderBundle
});
const generators = { csharp, cpp, haxe, ts, js, java, lua, c, gdscript, };
function generate(targetId, options) {
const generator = generators[targetId];
if (!generator) {
throw new Error("You must provide a valid generator as argument, such as: --csharp, --haxe or --cpp");
}
if (!fs__namespace.existsSync(options.output)) {
console.log("Creating", options.output, "directory");
fs__namespace.mkdirSync(options.output);
}
/**
* Default `@type()` decorator name
*/
if (!options.decorator) {
options.decorator = "type";
}
// resolve wildcard files
options.files = options.files.reduce((acc, cur) => {
if (cur.endsWith("*")) {
acc.push(...recursiveFiles(cur.slice(0, -1)).filter(filename => /\.(js|ts|mjs)$/.test(filename)));
}
else {
acc.push(cur);
}
return acc;
}, []);
const structures = parseFiles(options.files, options.decorator);
// Post-process classes before generating
structures.classes.forEach(klass => klass.postProcessing());
if (options.bundle && generator.renderBundle) {
// Bundle mode: generate all classes/interfaces/enums into a single file
const bundled = generator.renderBundle(structures, options);
const outputPath = path__namespace.resolve(options.output, bundled.name);
fs__namespace.writeFileSync(outputPath, bundled.content);
console.log("generated (bundled):", bundled.name);
}
else {
// Standard mode: write individual files
const generatedFiles = generator.generate(structures, options);
generatedFiles.forEach((file) => {
const outputPath = path__namespace.resolve(options.output, file.name);
fs__namespace.writeFileSync(outputPath, file.content);
console.log("generated:", file.name);
});
}
}
function recursiveFiles(dir) {
const files = fs__namespace.readdirSync(dir, { withFileTypes: true });
let collect = [];
files.forEach(file => {
const filename = path__namespace.resolve(dir, file.name);
file.isDirectory() ? collect.push(...recursiveFiles(filename)) : collect.push(filename);
});
return collect;
}
function displayHelp(exitCode = 0) {
console.log(`\nschema-codegen [path/to/Schema.ts]
Usage (C#/Unity)
schema-codegen src/Schema.ts --output client-side/ --csharp --namespace MyGame.Schema
Valid options:
--output: the output directory for generated client-side schema files
--bundle: bundle all generated files into a single file
Generators:
${Object.
keys(generators).
map((targetId) => (` --${targetId}: generate for ${generators[targetId].name}`)).
join("\n")}
Optional:
--namespace: generate namespace on output code
--decorator: custom name for @type decorator to scan for`);
process.exit(exitCode);
}
const args = argv(process.argv.slice(2));
if (args.help) {
displayHelp();
}
let targetId;
for (let target in generators) {
if (args[target]) {
targetId = target;
}
}
if (!args.output) {
console.error("You must provide a valid --output directory.");
displayHelp(1);
}
try {
args.files = args._;
generate(targetId, {
files: args._,
decorator: args.decorator,
output: args.output,
namespace: args.namespace,
bundle: args.bundle
});
}
catch (e) {
console.error(e.message);
console.error(e.stack);
displayHelp(1);
}
//# sourceMappingURL=cli.cjs.map