@alepha/protobuf 0.10.5 → 0.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/providers/ProtobufProvider.ts +303 -217
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ declare class ProtobufProvider {
|
|
|
6
6
|
protected readonly alepha: Alepha;
|
|
7
7
|
protected readonly schemas: Map<string | TObject, Type>;
|
|
8
8
|
protected readonly protobuf: typeof protobufjs;
|
|
9
|
+
protected readonly enumDefinitions: Map<string, string[]>;
|
|
9
10
|
/**
|
|
10
11
|
* Encode an object to a Uint8Array.
|
|
11
12
|
*/
|
|
@@ -33,6 +34,24 @@ declare class ProtobufProvider {
|
|
|
33
34
|
* Convert a primitive TypeBox schema type to a Protobuf spec type.
|
|
34
35
|
*/
|
|
35
36
|
protected convertType(schema: TSchema): string;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a schema is an enum type.
|
|
39
|
+
* TypeBox enums have an "enum" property with an array of values.
|
|
40
|
+
*/
|
|
41
|
+
protected isEnum(schema: TSchema): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Extract enum values from a TypeBox enum schema.
|
|
44
|
+
*/
|
|
45
|
+
protected getEnumValues(schema: TSchema): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Register an enum and return its type name.
|
|
48
|
+
* Generates a PascalCase name from the field name.
|
|
49
|
+
*/
|
|
50
|
+
protected registerEnum(fieldName: string, values: string[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Generate a protobuf enum definition.
|
|
53
|
+
*/
|
|
54
|
+
protected generateEnumDefinition(enumName: string, values: string[]): string;
|
|
36
55
|
}
|
|
37
56
|
type ProtobufSchema = string;
|
|
38
57
|
interface CreateProtobufSchemaOptions {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/providers/ProtobufProvider.ts"],"sourcesContent":[],"mappings":";;;;cAWa,gBAAA;EAAA,mBAAgB,MAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/providers/ProtobufProvider.ts"],"sourcesContent":[],"mappings":";;;;cAWa,gBAAA;EAAA,mBAAgB,MAAA,EACF,MADE;EACF,mBAAA,OAAA,EACG,GADH,CAAA,MAAA,GACgB,OADhB,EACyB,IADzB,CAAA;EACgB,mBAAA,QAAA,EAAA,OACL,UADK;EAAS,mBAAA,eAAA,EAEd,GAFc,CAAA,MAAA,EAAA,MAAA,EAAA,CAAA;EAAtB;;;EAON,MAAA,CAAA,MAAA,EAAA,OAAA,EAAA,IAAA,EAAA,GAAA,CAAA,EAAqB,UAArB;EAAqB;;;EAOO,MAAA,CAAA,UAA1B,OAA0B,CAAA,CAAA,MAAA,EAAT,CAAS,EAAA,IAAA,EAAA,UAAA,CAAA,EAAa,MAAb,CAAoB,CAApB,CAAA;EAAoB;;;EAQ3C,KAAA,CAAA,MAAA,EAAjB,cAAiB,GAAA,OAAA,EAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EAExB,IAFwB;EAExB;;;EAoDI,oBAAA,CAAA,MAAA,EApCG,OAoCH,EAAA,OAAA,CAAA,EAnCI,2BAmCJ,CAAA,EAAA,MAAA;EAsIuB;;;EA4CS,UAAA,2BAAA,CAAA,GAAA,EAlLhC,OAkLgC,EAAA,UAAA,EAAA,MAAA,CAAA,EAAA;IA0C7B,OAAA,EAAA,MAAc;IAET,WAAA,EAAA,MAAA,EAAA;;;;;gCAxFe;;;;;2BAqCL;;;;kCAOO;;;;;;;;;;;KA0CtB,cAAA;UAEK,2BAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var ProtobufProvider = class {
|
|
|
6
6
|
alepha = $inject(Alepha);
|
|
7
7
|
schemas = /* @__PURE__ */ new Map();
|
|
8
8
|
protobuf = protobufjs;
|
|
9
|
+
enumDefinitions = /* @__PURE__ */ new Map();
|
|
9
10
|
/**
|
|
10
11
|
* Encode an object to a Uint8Array.
|
|
11
12
|
*/
|
|
@@ -25,8 +26,7 @@ var ProtobufProvider = class {
|
|
|
25
26
|
const exists = this.schemas.get(schema);
|
|
26
27
|
if (exists) return exists;
|
|
27
28
|
const pbSchema = typeof schema === "string" ? schema : this.createProtobufSchema(schema);
|
|
28
|
-
const
|
|
29
|
-
const type = result.root.lookupType(typeName);
|
|
29
|
+
const type = this.protobuf.parse(pbSchema).root.lookupType(typeName);
|
|
30
30
|
this.schemas.set(schema, type);
|
|
31
31
|
return type;
|
|
32
32
|
}
|
|
@@ -35,12 +35,14 @@ var ProtobufProvider = class {
|
|
|
35
35
|
*/
|
|
36
36
|
createProtobufSchema(schema, options = {}) {
|
|
37
37
|
const { rootName = "root", mainMessageName = "Target" } = options;
|
|
38
|
+
this.enumDefinitions.clear();
|
|
38
39
|
const context = {
|
|
39
40
|
proto: `package ${rootName};\nsyntax = "proto3";\n\n`,
|
|
40
41
|
fieldIndex: 1
|
|
41
42
|
};
|
|
42
43
|
if (t.schema.isObject(schema)) {
|
|
43
44
|
const { message, subMessages } = this.parseObjectWithDependencies(schema, mainMessageName);
|
|
45
|
+
for (const [enumName, values] of this.enumDefinitions) context.proto += this.generateEnumDefinition(enumName, values);
|
|
44
46
|
context.proto += subMessages.join("");
|
|
45
47
|
context.proto += message;
|
|
46
48
|
}
|
|
@@ -59,6 +61,12 @@ var ProtobufProvider = class {
|
|
|
59
61
|
let fieldIndex = 1;
|
|
60
62
|
for (const [key, value] of Object.entries(obj.properties)) {
|
|
61
63
|
if (t.schema.isArray(value)) {
|
|
64
|
+
if (this.isEnum(value.items)) {
|
|
65
|
+
const enumValues = this.getEnumValues(value.items);
|
|
66
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
67
|
+
fields.push(` repeated ${enumName} ${key} = ${fieldIndex++};`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
62
70
|
if (t.schema.isObject(value.items)) {
|
|
63
71
|
const subMessageName = "title" in value.items && typeof value.items.title === "string" ? value.items.title : `${parentName}_${key}`;
|
|
64
72
|
const { message: subMessage, subMessages: nestedSubMessages } = this.parseObjectWithDependencies(value.items, subMessageName);
|
|
@@ -82,6 +90,12 @@ var ProtobufProvider = class {
|
|
|
82
90
|
if (t.schema.isUnion(value)) {
|
|
83
91
|
const nonNullType = value.anyOf.find((type) => !t.schema.isNull(type));
|
|
84
92
|
if (nonNullType) {
|
|
93
|
+
if (this.isEnum(nonNullType)) {
|
|
94
|
+
const enumValues = this.getEnumValues(nonNullType);
|
|
95
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
96
|
+
fields.push(` ${enumName} ${key} = ${fieldIndex++};`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
85
99
|
if (t.schema.isObject(nonNullType)) {
|
|
86
100
|
const subMessageName = "title" in nonNullType && typeof nonNullType.title === "string" ? nonNullType.title : `${parentName}_${key}`;
|
|
87
101
|
const { message: subMessage, subMessages: nestedSubMessages } = this.parseObjectWithDependencies(nonNullType, subMessageName);
|
|
@@ -108,12 +122,17 @@ var ProtobufProvider = class {
|
|
|
108
122
|
continue;
|
|
109
123
|
}
|
|
110
124
|
}
|
|
125
|
+
if (this.isEnum(value)) {
|
|
126
|
+
const enumValues = this.getEnumValues(value);
|
|
127
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
128
|
+
fields.push(` ${enumName} ${key} = ${fieldIndex++};`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
111
131
|
const fieldType = this.convertType(value);
|
|
112
132
|
fields.push(` ${fieldType} ${key} = ${fieldIndex++};`);
|
|
113
133
|
}
|
|
114
|
-
const message = `message ${parentName} {\n${fields.join("\n")}\n}\n`;
|
|
115
134
|
return {
|
|
116
|
-
message
|
|
135
|
+
message: `message ${parentName} {\n${fields.join("\n")}\n}\n`,
|
|
117
136
|
subMessages
|
|
118
137
|
};
|
|
119
138
|
}
|
|
@@ -135,6 +154,38 @@ var ProtobufProvider = class {
|
|
|
135
154
|
if (t.schema.isUnsafe(schema)) return "string";
|
|
136
155
|
throw new Error(`Unsupported type: ${JSON.stringify(schema)}`);
|
|
137
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a schema is an enum type.
|
|
159
|
+
* TypeBox enums have an "enum" property with an array of values.
|
|
160
|
+
*/
|
|
161
|
+
isEnum(schema) {
|
|
162
|
+
return "enum" in schema && Array.isArray(schema.enum);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Extract enum values from a TypeBox enum schema.
|
|
166
|
+
*/
|
|
167
|
+
getEnumValues(schema) {
|
|
168
|
+
if ("enum" in schema && Array.isArray(schema.enum)) return schema.enum.map(String);
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Register an enum and return its type name.
|
|
173
|
+
* Generates a PascalCase name from the field name.
|
|
174
|
+
*/
|
|
175
|
+
registerEnum(fieldName, values) {
|
|
176
|
+
const enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
177
|
+
const valueKey = values.join(",");
|
|
178
|
+
const existingEnum = Array.from(this.enumDefinitions.entries()).find(([_, enumValues]) => enumValues.join(",") === valueKey);
|
|
179
|
+
if (existingEnum) return existingEnum[0];
|
|
180
|
+
this.enumDefinitions.set(enumName, values);
|
|
181
|
+
return enumName;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Generate a protobuf enum definition.
|
|
185
|
+
*/
|
|
186
|
+
generateEnumDefinition(enumName, values) {
|
|
187
|
+
return `enum ${enumName} {\n${values.map((value, index) => ` ${value} = ${index};`).join("\n")}\n}\n`;
|
|
188
|
+
}
|
|
138
189
|
};
|
|
139
190
|
|
|
140
191
|
//#endregion
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["fields: string[]","subMessages: string[]","fieldType","valueSchema: TSchema | undefined"],"sources":["../src/providers/ProtobufProvider.ts"],"sourcesContent":["import {\n\t$inject,\n\tAlepha,\n\ttype Static,\n\ttype TObject,\n\ttype TSchema,\n\tt,\n} from \"@alepha/core\";\nimport type { Type } from \"protobufjs\";\nimport protobufjs from \"protobufjs\";\n\nexport class ProtobufProvider {\n\tprotected readonly alepha = $inject(Alepha);\n\tprotected readonly schemas: Map<string | TObject, Type> = new Map();\n\tprotected readonly protobuf: typeof protobufjs = protobufjs;\n\n\t/**\n\t * Encode an object to a Uint8Array.\n\t */\n\tpublic encode(schema: TObject, data: any): Uint8Array {\n\t\treturn this.parse(schema).encode(this.alepha.parse(schema, data)).finish();\n\t}\n\n\t/**\n\t * Decode a Uint8Array to an object.\n\t */\n\tpublic decode<T extends TObject>(schema: T, data: Uint8Array): Static<T> {\n\t\treturn this.alepha.parse(schema, this.parse(schema).decode(data));\n\t}\n\n\t/**\n\t * Parse a TypeBox schema to a Protobuf Type schema ready for encoding/decoding.\n\t */\n\tpublic parse(\n\t\tschema: ProtobufSchema | TObject,\n\t\ttypeName = \"root.Target\",\n\t): Type {\n\t\tconst exists = this.schemas.get(schema);\n\t\tif (exists) return exists;\n\n\t\tconst pbSchema =\n\t\t\ttypeof schema === \"string\" ? schema : this.createProtobufSchema(schema);\n\t\tconst result = this.protobuf.parse(pbSchema);\n\t\tconst type = result.root.lookupType(typeName);\n\t\tthis.schemas.set(schema, type);\n\t\treturn type;\n\t}\n\n\t/**\n\t * Convert a TypeBox schema to a Protobuf schema as a string.\n\t */\n\tpublic createProtobufSchema(\n\t\tschema: TSchema,\n\t\toptions: CreateProtobufSchemaOptions = {},\n\t): string {\n\t\tconst { rootName = \"root\", mainMessageName = \"Target\" } = options;\n\t\tconst context = {\n\t\t\tproto: `package ${rootName};\\nsyntax = \"proto3\";\\n\\n`,\n\t\t\tfieldIndex: 1,\n\t\t};\n\n\t\tif (t.schema.isObject(schema)) {\n\t\t\tconst { message, subMessages } = this.parseObjectWithDependencies(\n\t\t\t\tschema,\n\t\t\t\tmainMessageName,\n\t\t\t);\n\t\t\t// Add all sub-messages first\n\t\t\tcontext.proto += subMessages.join(\"\");\n\t\t\t// Then add the main message\n\t\t\tcontext.proto += message;\n\t\t}\n\n\t\treturn context.proto;\n\t}\n\n\t/**\n\t * Parse an object schema with dependencies (sub-messages).\n\t */\n\tprotected parseObjectWithDependencies(\n\t\tobj: TSchema,\n\t\tparentName: string,\n\t): { message: string; subMessages: string[] } {\n\t\tif (!t.schema.isObject(obj)) {\n\t\t\treturn { message: \"\", subMessages: [] };\n\t\t}\n\n\t\tconst fields: string[] = [];\n\t\tconst subMessages: string[] = [];\n\t\tlet fieldIndex = 1;\n\n\t\tfor (const [key, value] of Object.entries(obj.properties)) {\n\t\t\t// Handle arrays\n\t\t\tif (t.schema.isArray(value)) {\n\t\t\t\tif (t.schema.isObject(value.items)) {\n\t\t\t\t\tconst subMessageName =\n\t\t\t\t\t\t\"title\" in value.items && typeof value.items.title === \"string\"\n\t\t\t\t\t\t\t? value.items.title\n\t\t\t\t\t\t\t: `${parentName}_${key}`;\n\t\t\t\t\tconst { message: subMessage, subMessages: nestedSubMessages } =\n\t\t\t\t\t\tthis.parseObjectWithDependencies(value.items, subMessageName);\n\t\t\t\t\tsubMessages.push(...nestedSubMessages);\n\t\t\t\t\tsubMessages.push(subMessage);\n\t\t\t\t\tfields.push(` repeated ${subMessageName} ${key} = ${fieldIndex++};`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst itemType = this.convertType(value.items);\n\t\t\t\tfields.push(` repeated ${itemType} ${key} = ${fieldIndex++};`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Handle nested objects\n\t\t\tif (t.schema.isObject(value)) {\n\t\t\t\tconst subMessageName =\n\t\t\t\t\t\"title\" in value && typeof value.title === \"string\"\n\t\t\t\t\t\t? value.title\n\t\t\t\t\t\t: `${parentName}_${key}`;\n\t\t\t\tconst { message: subMessage, subMessages: nestedSubMessages } =\n\t\t\t\t\tthis.parseObjectWithDependencies(value, subMessageName);\n\t\t\t\tsubMessages.push(...nestedSubMessages);\n\t\t\t\tsubMessages.push(subMessage);\n\t\t\t\tfields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Handle union types (nullable fields)\n\t\t\tif (t.schema.isUnion(value)) {\n\t\t\t\tconst nonNullType = value.anyOf.find(\n\t\t\t\t\t(type: TSchema) => !t.schema.isNull(type),\n\t\t\t\t);\n\t\t\t\tif (nonNullType) {\n\t\t\t\t\tif (t.schema.isObject(nonNullType)) {\n\t\t\t\t\t\tconst subMessageName =\n\t\t\t\t\t\t\t\"title\" in nonNullType && typeof nonNullType.title === \"string\"\n\t\t\t\t\t\t\t\t? nonNullType.title\n\t\t\t\t\t\t\t\t: `${parentName}_${key}`;\n\t\t\t\t\t\tconst { message: subMessage, subMessages: nestedSubMessages } =\n\t\t\t\t\t\t\tthis.parseObjectWithDependencies(nonNullType, subMessageName);\n\t\t\t\t\t\tsubMessages.push(...nestedSubMessages);\n\t\t\t\t\t\tsubMessages.push(subMessage);\n\t\t\t\t\t\tfields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst fieldType = this.convertType(nonNullType);\n\t\t\t\t\tfields.push(` ${fieldType} ${key} = ${fieldIndex++};`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle records (maps)\n\t\t\tif (t.schema.isRecord(value)) {\n\t\t\t\t// TypeBox records use additionalProperties or patternProperties for the value type\n\t\t\t\tlet valueSchema: TSchema | undefined;\n\t\t\t\tif (\n\t\t\t\t\t\"additionalProperties\" in value &&\n\t\t\t\t\tvalue.additionalProperties &&\n\t\t\t\t\ttypeof value.additionalProperties === \"object\"\n\t\t\t\t) {\n\t\t\t\t\tvalueSchema = value.additionalProperties;\n\t\t\t\t} else if (\n\t\t\t\t\tvalue.patternProperties &&\n\t\t\t\t\ttypeof value.patternProperties === \"object\"\n\t\t\t\t) {\n\t\t\t\t\t// Get the first pattern property (usually \"^(.*)$\" or similar)\n\t\t\t\t\tconst patterns = Object.values(value.patternProperties);\n\t\t\t\t\tif (patterns.length > 0 && typeof patterns[0] === \"object\") {\n\t\t\t\t\t\tvalueSchema = patterns[0] as TSchema;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (valueSchema) {\n\t\t\t\t\tconst valueType = this.convertType(valueSchema);\n\t\t\t\t\tfields.push(` map<string, ${valueType}> ${key} = ${fieldIndex++};`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle regular fields\n\t\t\tconst fieldType = this.convertType(value);\n\t\t\tfields.push(` ${fieldType} ${key} = ${fieldIndex++};`);\n\t\t}\n\n\t\tconst message = `message ${parentName} {\\n${fields.join(\"\\n\")}\\n}\\n`;\n\t\treturn { message, subMessages };\n\t}\n\n\t/**\n\t * Convert a primitive TypeBox schema type to a Protobuf spec type.\n\t */\n\tprotected convertType(schema: TSchema): string {\n\t\tif (t.schema.isBoolean(schema)) return \"bool\";\n\t\tif (t.schema.isNumber(schema) && schema.format === \"int64\") return \"int64\";\n\t\tif (t.schema.isNumber(schema)) return \"double\";\n\t\tif (t.schema.isInteger(schema)) return \"int32\";\n\t\tif (t.schema.isBigInt(schema)) return \"int64\";\n\t\tif (t.schema.isString(schema)) return \"string\";\n\n\t\t// Handle union types (nullable)\n\t\tif (t.schema.isUnion(schema)) {\n\t\t\t// Find the non-null type in the union\n\t\t\tconst nonNullType = schema.anyOf.find(\n\t\t\t\t(type: TSchema) => !t.schema.isNull(type),\n\t\t\t);\n\t\t\tif (nonNullType) {\n\t\t\t\treturn this.convertType(nonNullType);\n\t\t\t}\n\t\t}\n\n\t\t// Handle optional types\n\t\tif (t.schema.isOptional(schema)) {\n\t\t\treturn this.convertType(schema);\n\t\t}\n\n\t\t// Handle unsafe types (like enums)\n\t\tif (t.schema.isUnsafe(schema)) {\n\t\t\t// if it's an enum or other unsafe types, default to string\n\t\t\treturn \"string\";\n\t\t}\n\n\t\tthrow new Error(`Unsupported type: ${JSON.stringify(schema)}`);\n\t}\n}\n\nexport type ProtobufSchema = string;\n\nexport interface CreateProtobufSchemaOptions {\n\trootName?: string;\n\tmainMessageName?: string;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["fields: string[]","subMessages: string[]","fieldType","valueSchema: TSchema | undefined"],"sources":["../src/providers/ProtobufProvider.ts"],"sourcesContent":["import {\n $inject,\n Alepha,\n type Static,\n type TObject,\n type TSchema,\n t,\n} from \"@alepha/core\";\nimport type { Type } from \"protobufjs\";\nimport protobufjs from \"protobufjs\";\n\nexport class ProtobufProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly schemas: Map<string | TObject, Type> = new Map();\n protected readonly protobuf: typeof protobufjs = protobufjs;\n protected readonly enumDefinitions: Map<string, string[]> = new Map();\n\n /**\n * Encode an object to a Uint8Array.\n */\n public encode(schema: TObject, data: any): Uint8Array {\n return this.parse(schema).encode(this.alepha.parse(schema, data)).finish();\n }\n\n /**\n * Decode a Uint8Array to an object.\n */\n public decode<T extends TObject>(schema: T, data: Uint8Array): Static<T> {\n return this.alepha.parse(schema, this.parse(schema).decode(data));\n }\n\n /**\n * Parse a TypeBox schema to a Protobuf Type schema ready for encoding/decoding.\n */\n public parse(\n schema: ProtobufSchema | TObject,\n typeName = \"root.Target\",\n ): Type {\n const exists = this.schemas.get(schema);\n if (exists) return exists;\n\n const pbSchema =\n typeof schema === \"string\" ? schema : this.createProtobufSchema(schema);\n const result = this.protobuf.parse(pbSchema);\n const type = result.root.lookupType(typeName);\n this.schemas.set(schema, type);\n return type;\n }\n\n /**\n * Convert a TypeBox schema to a Protobuf schema as a string.\n */\n public createProtobufSchema(\n schema: TSchema,\n options: CreateProtobufSchemaOptions = {},\n ): string {\n const { rootName = \"root\", mainMessageName = \"Target\" } = options;\n // Clear enum definitions for this schema generation\n this.enumDefinitions.clear();\n\n const context = {\n proto: `package ${rootName};\\nsyntax = \"proto3\";\\n\\n`,\n fieldIndex: 1,\n };\n\n if (t.schema.isObject(schema)) {\n const { message, subMessages } = this.parseObjectWithDependencies(\n schema,\n mainMessageName,\n );\n\n // Add all enum definitions first\n for (const [enumName, values] of this.enumDefinitions) {\n context.proto += this.generateEnumDefinition(enumName, values);\n }\n\n // Add all sub-messages\n context.proto += subMessages.join(\"\");\n // Then add the main message\n context.proto += message;\n }\n\n return context.proto;\n }\n\n /**\n * Parse an object schema with dependencies (sub-messages).\n */\n protected parseObjectWithDependencies(\n obj: TSchema,\n parentName: string,\n ): { message: string; subMessages: string[] } {\n if (!t.schema.isObject(obj)) {\n return { message: \"\", subMessages: [] };\n }\n\n const fields: string[] = [];\n const subMessages: string[] = [];\n let fieldIndex = 1;\n\n for (const [key, value] of Object.entries(obj.properties)) {\n // Handle arrays\n if (t.schema.isArray(value)) {\n // Check if array items are enums\n if (this.isEnum(value.items)) {\n const enumValues = this.getEnumValues(value.items);\n const enumName = this.registerEnum(key, enumValues);\n fields.push(` repeated ${enumName} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n if (t.schema.isObject(value.items)) {\n const subMessageName =\n \"title\" in value.items && typeof value.items.title === \"string\"\n ? value.items.title\n : `${parentName}_${key}`;\n const { message: subMessage, subMessages: nestedSubMessages } =\n this.parseObjectWithDependencies(value.items, subMessageName);\n subMessages.push(...nestedSubMessages);\n subMessages.push(subMessage);\n fields.push(` repeated ${subMessageName} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n const itemType = this.convertType(value.items);\n fields.push(` repeated ${itemType} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n // Handle nested objects\n if (t.schema.isObject(value)) {\n const subMessageName =\n \"title\" in value && typeof value.title === \"string\"\n ? value.title\n : `${parentName}_${key}`;\n const { message: subMessage, subMessages: nestedSubMessages } =\n this.parseObjectWithDependencies(value, subMessageName);\n subMessages.push(...nestedSubMessages);\n subMessages.push(subMessage);\n fields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n // Handle union types (nullable fields)\n if (t.schema.isUnion(value)) {\n const nonNullType = value.anyOf.find(\n (type: TSchema) => !t.schema.isNull(type),\n );\n if (nonNullType) {\n // Check if it's an enum\n if (this.isEnum(nonNullType)) {\n const enumValues = this.getEnumValues(nonNullType);\n const enumName = this.registerEnum(key, enumValues);\n fields.push(` ${enumName} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n if (t.schema.isObject(nonNullType)) {\n const subMessageName =\n \"title\" in nonNullType && typeof nonNullType.title === \"string\"\n ? nonNullType.title\n : `${parentName}_${key}`;\n const { message: subMessage, subMessages: nestedSubMessages } =\n this.parseObjectWithDependencies(nonNullType, subMessageName);\n subMessages.push(...nestedSubMessages);\n subMessages.push(subMessage);\n fields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);\n continue;\n }\n const fieldType = this.convertType(nonNullType);\n fields.push(` ${fieldType} ${key} = ${fieldIndex++};`);\n continue;\n }\n }\n\n // Handle records (maps)\n if (t.schema.isRecord(value)) {\n // TypeBox records use additionalProperties or patternProperties for the value type\n let valueSchema: TSchema | undefined;\n if (\n \"additionalProperties\" in value &&\n value.additionalProperties &&\n typeof value.additionalProperties === \"object\"\n ) {\n valueSchema = value.additionalProperties;\n } else if (\n value.patternProperties &&\n typeof value.patternProperties === \"object\"\n ) {\n // Get the first pattern property (usually \"^(.*)$\" or similar)\n const patterns = Object.values(value.patternProperties);\n if (patterns.length > 0 && typeof patterns[0] === \"object\") {\n valueSchema = patterns[0] as TSchema;\n }\n }\n\n if (valueSchema) {\n const valueType = this.convertType(valueSchema);\n fields.push(` map<string, ${valueType}> ${key} = ${fieldIndex++};`);\n continue;\n }\n }\n\n // Handle enum fields\n if (this.isEnum(value)) {\n const enumValues = this.getEnumValues(value);\n const enumName = this.registerEnum(key, enumValues);\n fields.push(` ${enumName} ${key} = ${fieldIndex++};`);\n continue;\n }\n\n // Handle regular fields\n const fieldType = this.convertType(value);\n fields.push(` ${fieldType} ${key} = ${fieldIndex++};`);\n }\n\n const message = `message ${parentName} {\\n${fields.join(\"\\n\")}\\n}\\n`;\n return { message, subMessages };\n }\n\n /**\n * Convert a primitive TypeBox schema type to a Protobuf spec type.\n */\n protected convertType(schema: TSchema): string {\n if (t.schema.isBoolean(schema)) return \"bool\";\n if (t.schema.isNumber(schema) && schema.format === \"int64\") return \"int64\";\n if (t.schema.isNumber(schema)) return \"double\";\n if (t.schema.isInteger(schema)) return \"int32\";\n if (t.schema.isBigInt(schema)) return \"int64\";\n if (t.schema.isString(schema)) return \"string\";\n\n // Handle union types (nullable)\n if (t.schema.isUnion(schema)) {\n // Find the non-null type in the union\n const nonNullType = schema.anyOf.find(\n (type: TSchema) => !t.schema.isNull(type),\n );\n if (nonNullType) {\n return this.convertType(nonNullType);\n }\n }\n\n // Handle optional types\n if (t.schema.isOptional(schema)) {\n return this.convertType(schema);\n }\n\n // Handle unsafe types (like enums)\n if (t.schema.isUnsafe(schema)) {\n // if it's an enum or other unsafe types, default to string\n return \"string\";\n }\n\n throw new Error(`Unsupported type: ${JSON.stringify(schema)}`);\n }\n\n /**\n * Check if a schema is an enum type.\n * TypeBox enums have an \"enum\" property with an array of values.\n */\n protected isEnum(schema: TSchema): boolean {\n return \"enum\" in schema && Array.isArray(schema.enum);\n }\n\n /**\n * Extract enum values from a TypeBox enum schema.\n */\n protected getEnumValues(schema: TSchema): string[] {\n if (\"enum\" in schema && Array.isArray(schema.enum)) {\n return schema.enum.map(String);\n }\n return [];\n }\n\n /**\n * Register an enum and return its type name.\n * Generates a PascalCase name from the field name.\n */\n protected registerEnum(fieldName: string, values: string[]): string {\n // Capitalize first letter of field name for enum type name\n const enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);\n\n // Check if we already have this exact enum registered\n const valueKey = values.join(\",\");\n const existingEnum = Array.from(this.enumDefinitions.entries()).find(\n ([_, enumValues]) => enumValues.join(\",\") === valueKey,\n );\n\n if (existingEnum) {\n // Reuse existing enum with same values\n return existingEnum[0];\n }\n\n // Register new enum\n this.enumDefinitions.set(enumName, values);\n return enumName;\n }\n\n /**\n * Generate a protobuf enum definition.\n */\n protected generateEnumDefinition(enumName: string, values: string[]): string {\n const enumValues = values\n .map((value, index) => ` ${value} = ${index};`)\n .join(\"\\n\");\n return `enum ${enumName} {\\n${enumValues}\\n}\\n`;\n }\n}\n\nexport type ProtobufSchema = string;\n\nexport interface CreateProtobufSchemaOptions {\n rootName?: string;\n mainMessageName?: string;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/protobuf",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"src"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@alepha/core": "0.10.
|
|
16
|
+
"@alepha/core": "0.10.7",
|
|
17
17
|
"protobufjs": "^7.5.4"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@biomejs/biome": "^2.2.
|
|
21
|
-
"tsdown": "^0.15.
|
|
20
|
+
"@biomejs/biome": "^2.2.6",
|
|
21
|
+
"tsdown": "^0.15.9",
|
|
22
22
|
"typescript": "^5.9.3",
|
|
23
23
|
"vitest": "^3.2.4"
|
|
24
24
|
},
|
|
@@ -1,229 +1,315 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
$inject,
|
|
3
|
+
Alepha,
|
|
4
|
+
type Static,
|
|
5
|
+
type TObject,
|
|
6
|
+
type TSchema,
|
|
7
|
+
t,
|
|
8
8
|
} from "@alepha/core";
|
|
9
9
|
import type { Type } from "protobufjs";
|
|
10
10
|
import protobufjs from "protobufjs";
|
|
11
11
|
|
|
12
12
|
export class ProtobufProvider {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
13
|
+
protected readonly alepha = $inject(Alepha);
|
|
14
|
+
protected readonly schemas: Map<string | TObject, Type> = new Map();
|
|
15
|
+
protected readonly protobuf: typeof protobufjs = protobufjs;
|
|
16
|
+
protected readonly enumDefinitions: Map<string, string[]> = new Map();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Encode an object to a Uint8Array.
|
|
20
|
+
*/
|
|
21
|
+
public encode(schema: TObject, data: any): Uint8Array {
|
|
22
|
+
return this.parse(schema).encode(this.alepha.parse(schema, data)).finish();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Decode a Uint8Array to an object.
|
|
27
|
+
*/
|
|
28
|
+
public decode<T extends TObject>(schema: T, data: Uint8Array): Static<T> {
|
|
29
|
+
return this.alepha.parse(schema, this.parse(schema).decode(data));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a TypeBox schema to a Protobuf Type schema ready for encoding/decoding.
|
|
34
|
+
*/
|
|
35
|
+
public parse(
|
|
36
|
+
schema: ProtobufSchema | TObject,
|
|
37
|
+
typeName = "root.Target",
|
|
38
|
+
): Type {
|
|
39
|
+
const exists = this.schemas.get(schema);
|
|
40
|
+
if (exists) return exists;
|
|
41
|
+
|
|
42
|
+
const pbSchema =
|
|
43
|
+
typeof schema === "string" ? schema : this.createProtobufSchema(schema);
|
|
44
|
+
const result = this.protobuf.parse(pbSchema);
|
|
45
|
+
const type = result.root.lookupType(typeName);
|
|
46
|
+
this.schemas.set(schema, type);
|
|
47
|
+
return type;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert a TypeBox schema to a Protobuf schema as a string.
|
|
52
|
+
*/
|
|
53
|
+
public createProtobufSchema(
|
|
54
|
+
schema: TSchema,
|
|
55
|
+
options: CreateProtobufSchemaOptions = {},
|
|
56
|
+
): string {
|
|
57
|
+
const { rootName = "root", mainMessageName = "Target" } = options;
|
|
58
|
+
// Clear enum definitions for this schema generation
|
|
59
|
+
this.enumDefinitions.clear();
|
|
60
|
+
|
|
61
|
+
const context = {
|
|
62
|
+
proto: `package ${rootName};\nsyntax = "proto3";\n\n`,
|
|
63
|
+
fieldIndex: 1,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (t.schema.isObject(schema)) {
|
|
67
|
+
const { message, subMessages } = this.parseObjectWithDependencies(
|
|
68
|
+
schema,
|
|
69
|
+
mainMessageName,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Add all enum definitions first
|
|
73
|
+
for (const [enumName, values] of this.enumDefinitions) {
|
|
74
|
+
context.proto += this.generateEnumDefinition(enumName, values);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add all sub-messages
|
|
78
|
+
context.proto += subMessages.join("");
|
|
79
|
+
// Then add the main message
|
|
80
|
+
context.proto += message;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return context.proto;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse an object schema with dependencies (sub-messages).
|
|
88
|
+
*/
|
|
89
|
+
protected parseObjectWithDependencies(
|
|
90
|
+
obj: TSchema,
|
|
91
|
+
parentName: string,
|
|
92
|
+
): { message: string; subMessages: string[] } {
|
|
93
|
+
if (!t.schema.isObject(obj)) {
|
|
94
|
+
return { message: "", subMessages: [] };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fields: string[] = [];
|
|
98
|
+
const subMessages: string[] = [];
|
|
99
|
+
let fieldIndex = 1;
|
|
100
|
+
|
|
101
|
+
for (const [key, value] of Object.entries(obj.properties)) {
|
|
102
|
+
// Handle arrays
|
|
103
|
+
if (t.schema.isArray(value)) {
|
|
104
|
+
// Check if array items are enums
|
|
105
|
+
if (this.isEnum(value.items)) {
|
|
106
|
+
const enumValues = this.getEnumValues(value.items);
|
|
107
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
108
|
+
fields.push(` repeated ${enumName} ${key} = ${fieldIndex++};`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (t.schema.isObject(value.items)) {
|
|
113
|
+
const subMessageName =
|
|
114
|
+
"title" in value.items && typeof value.items.title === "string"
|
|
115
|
+
? value.items.title
|
|
116
|
+
: `${parentName}_${key}`;
|
|
117
|
+
const { message: subMessage, subMessages: nestedSubMessages } =
|
|
118
|
+
this.parseObjectWithDependencies(value.items, subMessageName);
|
|
119
|
+
subMessages.push(...nestedSubMessages);
|
|
120
|
+
subMessages.push(subMessage);
|
|
121
|
+
fields.push(` repeated ${subMessageName} ${key} = ${fieldIndex++};`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const itemType = this.convertType(value.items);
|
|
126
|
+
fields.push(` repeated ${itemType} ${key} = ${fieldIndex++};`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle nested objects
|
|
131
|
+
if (t.schema.isObject(value)) {
|
|
132
|
+
const subMessageName =
|
|
133
|
+
"title" in value && typeof value.title === "string"
|
|
134
|
+
? value.title
|
|
135
|
+
: `${parentName}_${key}`;
|
|
136
|
+
const { message: subMessage, subMessages: nestedSubMessages } =
|
|
137
|
+
this.parseObjectWithDependencies(value, subMessageName);
|
|
138
|
+
subMessages.push(...nestedSubMessages);
|
|
139
|
+
subMessages.push(subMessage);
|
|
140
|
+
fields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle union types (nullable fields)
|
|
145
|
+
if (t.schema.isUnion(value)) {
|
|
146
|
+
const nonNullType = value.anyOf.find(
|
|
147
|
+
(type: TSchema) => !t.schema.isNull(type),
|
|
148
|
+
);
|
|
149
|
+
if (nonNullType) {
|
|
150
|
+
// Check if it's an enum
|
|
151
|
+
if (this.isEnum(nonNullType)) {
|
|
152
|
+
const enumValues = this.getEnumValues(nonNullType);
|
|
153
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
154
|
+
fields.push(` ${enumName} ${key} = ${fieldIndex++};`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (t.schema.isObject(nonNullType)) {
|
|
159
|
+
const subMessageName =
|
|
160
|
+
"title" in nonNullType && typeof nonNullType.title === "string"
|
|
161
|
+
? nonNullType.title
|
|
162
|
+
: `${parentName}_${key}`;
|
|
163
|
+
const { message: subMessage, subMessages: nestedSubMessages } =
|
|
164
|
+
this.parseObjectWithDependencies(nonNullType, subMessageName);
|
|
165
|
+
subMessages.push(...nestedSubMessages);
|
|
166
|
+
subMessages.push(subMessage);
|
|
167
|
+
fields.push(` ${subMessageName} ${key} = ${fieldIndex++};`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const fieldType = this.convertType(nonNullType);
|
|
171
|
+
fields.push(` ${fieldType} ${key} = ${fieldIndex++};`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle records (maps)
|
|
177
|
+
if (t.schema.isRecord(value)) {
|
|
178
|
+
// TypeBox records use additionalProperties or patternProperties for the value type
|
|
179
|
+
let valueSchema: TSchema | undefined;
|
|
180
|
+
if (
|
|
181
|
+
"additionalProperties" in value &&
|
|
182
|
+
value.additionalProperties &&
|
|
183
|
+
typeof value.additionalProperties === "object"
|
|
184
|
+
) {
|
|
185
|
+
valueSchema = value.additionalProperties;
|
|
186
|
+
} else if (
|
|
187
|
+
value.patternProperties &&
|
|
188
|
+
typeof value.patternProperties === "object"
|
|
189
|
+
) {
|
|
190
|
+
// Get the first pattern property (usually "^(.*)$" or similar)
|
|
191
|
+
const patterns = Object.values(value.patternProperties);
|
|
192
|
+
if (patterns.length > 0 && typeof patterns[0] === "object") {
|
|
193
|
+
valueSchema = patterns[0] as TSchema;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (valueSchema) {
|
|
198
|
+
const valueType = this.convertType(valueSchema);
|
|
199
|
+
fields.push(` map<string, ${valueType}> ${key} = ${fieldIndex++};`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Handle enum fields
|
|
205
|
+
if (this.isEnum(value)) {
|
|
206
|
+
const enumValues = this.getEnumValues(value);
|
|
207
|
+
const enumName = this.registerEnum(key, enumValues);
|
|
208
|
+
fields.push(` ${enumName} ${key} = ${fieldIndex++};`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Handle regular fields
|
|
213
|
+
const fieldType = this.convertType(value);
|
|
214
|
+
fields.push(` ${fieldType} ${key} = ${fieldIndex++};`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const message = `message ${parentName} {\n${fields.join("\n")}\n}\n`;
|
|
218
|
+
return { message, subMessages };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Convert a primitive TypeBox schema type to a Protobuf spec type.
|
|
223
|
+
*/
|
|
224
|
+
protected convertType(schema: TSchema): string {
|
|
225
|
+
if (t.schema.isBoolean(schema)) return "bool";
|
|
226
|
+
if (t.schema.isNumber(schema) && schema.format === "int64") return "int64";
|
|
227
|
+
if (t.schema.isNumber(schema)) return "double";
|
|
228
|
+
if (t.schema.isInteger(schema)) return "int32";
|
|
229
|
+
if (t.schema.isBigInt(schema)) return "int64";
|
|
230
|
+
if (t.schema.isString(schema)) return "string";
|
|
231
|
+
|
|
232
|
+
// Handle union types (nullable)
|
|
233
|
+
if (t.schema.isUnion(schema)) {
|
|
234
|
+
// Find the non-null type in the union
|
|
235
|
+
const nonNullType = schema.anyOf.find(
|
|
236
|
+
(type: TSchema) => !t.schema.isNull(type),
|
|
237
|
+
);
|
|
238
|
+
if (nonNullType) {
|
|
239
|
+
return this.convertType(nonNullType);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Handle optional types
|
|
244
|
+
if (t.schema.isOptional(schema)) {
|
|
245
|
+
return this.convertType(schema);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Handle unsafe types (like enums)
|
|
249
|
+
if (t.schema.isUnsafe(schema)) {
|
|
250
|
+
// if it's an enum or other unsafe types, default to string
|
|
251
|
+
return "string";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw new Error(`Unsupported type: ${JSON.stringify(schema)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if a schema is an enum type.
|
|
259
|
+
* TypeBox enums have an "enum" property with an array of values.
|
|
260
|
+
*/
|
|
261
|
+
protected isEnum(schema: TSchema): boolean {
|
|
262
|
+
return "enum" in schema && Array.isArray(schema.enum);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Extract enum values from a TypeBox enum schema.
|
|
267
|
+
*/
|
|
268
|
+
protected getEnumValues(schema: TSchema): string[] {
|
|
269
|
+
if ("enum" in schema && Array.isArray(schema.enum)) {
|
|
270
|
+
return schema.enum.map(String);
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Register an enum and return its type name.
|
|
277
|
+
* Generates a PascalCase name from the field name.
|
|
278
|
+
*/
|
|
279
|
+
protected registerEnum(fieldName: string, values: string[]): string {
|
|
280
|
+
// Capitalize first letter of field name for enum type name
|
|
281
|
+
const enumName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
282
|
+
|
|
283
|
+
// Check if we already have this exact enum registered
|
|
284
|
+
const valueKey = values.join(",");
|
|
285
|
+
const existingEnum = Array.from(this.enumDefinitions.entries()).find(
|
|
286
|
+
([_, enumValues]) => enumValues.join(",") === valueKey,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (existingEnum) {
|
|
290
|
+
// Reuse existing enum with same values
|
|
291
|
+
return existingEnum[0];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Register new enum
|
|
295
|
+
this.enumDefinitions.set(enumName, values);
|
|
296
|
+
return enumName;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Generate a protobuf enum definition.
|
|
301
|
+
*/
|
|
302
|
+
protected generateEnumDefinition(enumName: string, values: string[]): string {
|
|
303
|
+
const enumValues = values
|
|
304
|
+
.map((value, index) => ` ${value} = ${index};`)
|
|
305
|
+
.join("\n");
|
|
306
|
+
return `enum ${enumName} {\n${enumValues}\n}\n`;
|
|
307
|
+
}
|
|
222
308
|
}
|
|
223
309
|
|
|
224
310
|
export type ProtobufSchema = string;
|
|
225
311
|
|
|
226
312
|
export interface CreateProtobufSchemaOptions {
|
|
227
|
-
|
|
228
|
-
|
|
313
|
+
rootName?: string;
|
|
314
|
+
mainMessageName?: string;
|
|
229
315
|
}
|