@globalart/zod-to-proto 1.0.1

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.
@@ -0,0 +1,28 @@
1
+ import { ZodTypeAny } from "zod";
2
+
3
+ //#region src/types.d.ts
4
+ interface ZodToProtobufOptions {
5
+ packageName?: string;
6
+ rootMessageName?: string;
7
+ typePrefix?: string;
8
+ services?: ServiceDefinition[];
9
+ skipRootMessage?: boolean;
10
+ }
11
+ interface ServiceMethod {
12
+ name: string;
13
+ request: ZodTypeAny;
14
+ response: ZodTypeAny;
15
+ streaming?: 'client' | 'server' | 'bidirectional';
16
+ }
17
+ interface ServiceDefinition {
18
+ name: string;
19
+ methods: ServiceMethod[];
20
+ }
21
+ declare class UnsupportedTypeException extends Error {
22
+ constructor(type: string);
23
+ }
24
+ //#endregion
25
+ //#region src/zod-to-protobuf.d.ts
26
+ declare const zodToProtobuf: (schema?: ZodTypeAny, options?: ZodToProtobufOptions) => string;
27
+ //#endregion
28
+ export { type ServiceDefinition, type ServiceMethod, UnsupportedTypeException, type ZodToProtobufOptions, zodToProtobuf };
package/dist/index.mjs ADDED
@@ -0,0 +1,264 @@
1
+ import * as inflection from "inflection";
2
+ import { ZodArray, ZodBigInt, ZodBoolean, ZodDate, ZodEnum, ZodMap, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodSet, ZodString, ZodTuple, ZodType } from "zod";
3
+
4
+ //#region src/types.ts
5
+ var UnsupportedTypeException = class extends Error {
6
+ constructor(type) {
7
+ super(`Unsupported type: ${type}`);
8
+ this.name = "UnsupportedTypeException";
9
+ }
10
+ };
11
+
12
+ //#endregion
13
+ //#region src/utils.ts
14
+ const getNumberTypeName = ({ value }) => {
15
+ return value.isInt ? "int32" : "double";
16
+ };
17
+ const toPascalCase = ({ value }) => {
18
+ return value.split(".").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
19
+ };
20
+ const protobufFieldToType = ({ field }) => {
21
+ return field.types.filter(Boolean).join(" ");
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/traversers.ts
26
+ const traverseArray = ({ key, value, messages, enums, typePrefix }) => {
27
+ const nestedValue = value instanceof ZodArray ? value.element : value instanceof ZodSet ? value._def.valueType : value._def.element;
28
+ const singularKey = inflection.singularize(key);
29
+ return traverseKey({
30
+ key: singularKey,
31
+ value: nestedValue,
32
+ messages,
33
+ enums,
34
+ isOptional: false,
35
+ isInArray: true,
36
+ typePrefix
37
+ }).map((field) => ({
38
+ ...field,
39
+ types: ["repeated", ...field.types],
40
+ name: field.name.replace(singularKey, key)
41
+ }));
42
+ };
43
+ const traverseMap = ({ key, value, messages, enums, typePrefix }) => {
44
+ const keyType = traverseKey({
45
+ key: `${key}Key`,
46
+ value: value._def.keyType,
47
+ messages,
48
+ enums,
49
+ isOptional: false,
50
+ isInArray: true,
51
+ typePrefix
52
+ });
53
+ const valueType = traverseKey({
54
+ key: `${key}Value`,
55
+ value: value._def.valueType,
56
+ messages,
57
+ enums,
58
+ isOptional: false,
59
+ isInArray: true,
60
+ typePrefix
61
+ });
62
+ if (!keyType[0] || keyType.length !== 1) throw new UnsupportedTypeException(`${key} map key`);
63
+ if (!valueType[0] || valueType.length !== 1) throw new UnsupportedTypeException(`${key} map value`);
64
+ return [{
65
+ types: [`map<${protobufFieldToType({ field: keyType[0] })}, ${protobufFieldToType({ field: valueType[0] })}>`],
66
+ name: key
67
+ }];
68
+ };
69
+ const traverseKey = ({ key, value, messages, enums, isOptional, isInArray, typePrefix }) => {
70
+ if (value instanceof ZodOptional || value instanceof ZodNullable) return traverseKey({
71
+ key,
72
+ value: value.unwrap(),
73
+ messages,
74
+ enums,
75
+ isOptional: true,
76
+ isInArray,
77
+ typePrefix
78
+ });
79
+ if (value instanceof ZodArray || value instanceof ZodSet) return traverseArray({
80
+ key,
81
+ value,
82
+ messages,
83
+ enums,
84
+ typePrefix
85
+ });
86
+ if (value instanceof ZodMap) return traverseMap({
87
+ key,
88
+ value,
89
+ messages,
90
+ enums,
91
+ typePrefix
92
+ });
93
+ const optional = isOptional && !isInArray ? "optional" : null;
94
+ if (value instanceof ZodObject) {
95
+ let messageName = toPascalCase({ value: key });
96
+ if (typePrefix) messageName = `${typePrefix}${messageName}`;
97
+ const nestedMessageFields = traverseSchema({
98
+ schema: value,
99
+ messages,
100
+ enums,
101
+ typePrefix
102
+ });
103
+ messages.set(messageName, nestedMessageFields);
104
+ return [{
105
+ types: [optional, messageName],
106
+ name: key
107
+ }];
108
+ }
109
+ if (value instanceof ZodString) return [{
110
+ types: [optional, "string"],
111
+ name: key
112
+ }];
113
+ if (value instanceof ZodNumber) return [{
114
+ types: [optional, getNumberTypeName({ value })],
115
+ name: key
116
+ }];
117
+ if (value instanceof ZodBoolean) return [{
118
+ types: [optional, "bool"],
119
+ name: key
120
+ }];
121
+ if (value instanceof ZodEnum) {
122
+ const enumFields = value.options.map((option, index) => ` ${String(option)} = ${index};`).join("\n");
123
+ let enumName = toPascalCase({ value: key });
124
+ if (typePrefix) enumName = `${typePrefix}${enumName}`;
125
+ enums.set(enumName, [`enum ${enumName} {\n${enumFields}\n}`]);
126
+ return [{
127
+ types: [optional, enumName],
128
+ name: key
129
+ }];
130
+ }
131
+ if (value instanceof ZodDate) return [{
132
+ types: [optional, "string"],
133
+ name: key
134
+ }];
135
+ if (value instanceof ZodBigInt) return [{
136
+ types: [optional, "int64"],
137
+ name: key
138
+ }];
139
+ if (value instanceof ZodTuple) {
140
+ const tupleFields = value._def.items.flatMap((item, index) => {
141
+ return traverseKey({
142
+ key: `${key}_${index}`,
143
+ value: item,
144
+ messages,
145
+ enums,
146
+ isOptional: false,
147
+ isInArray,
148
+ typePrefix
149
+ });
150
+ });
151
+ const tupleMessageName = toPascalCase({ value: key });
152
+ messages.set(tupleMessageName, tupleFields.map((field, index) => ` ${field.types.join(" ")} ${field.name} = ${index + 1};`));
153
+ return [{
154
+ types: [optional, tupleMessageName],
155
+ name: key
156
+ }];
157
+ }
158
+ if (value instanceof ZodType) throw new UnsupportedTypeException(value.constructor.name);
159
+ throw new UnsupportedTypeException(typeof value);
160
+ };
161
+ const traverseSchema = ({ schema, messages, enums, typePrefix }) => {
162
+ if (!(schema instanceof ZodObject)) throw new UnsupportedTypeException(schema.constructor.name);
163
+ return Object.entries(schema.shape).flatMap(([key, value]) => {
164
+ return traverseKey({
165
+ key,
166
+ value,
167
+ messages,
168
+ enums,
169
+ isOptional: false,
170
+ isInArray: false,
171
+ typePrefix
172
+ });
173
+ }).map((field, index) => `${protobufFieldToType({ field })} ${field.name} = ${index + 1};`);
174
+ };
175
+
176
+ //#endregion
177
+ //#region src/service-generator.ts
178
+ const generateRequestMessageName = (methodName, typePrefix) => {
179
+ const messageName = toPascalCase({ value: `${methodName}Request` });
180
+ return typePrefix ? `${typePrefix}${messageName}` : messageName;
181
+ };
182
+ const generateResponseMessageName = (methodName, typePrefix) => {
183
+ const messageName = toPascalCase({ value: `${methodName}Response` });
184
+ return typePrefix ? `${typePrefix}${messageName}` : messageName;
185
+ };
186
+ const processServiceMethod = (method, context) => {
187
+ const { messages, enums, typePrefix } = context;
188
+ const requestName = generateRequestMessageName(method.name, typePrefix);
189
+ const responseName = generateResponseMessageName(method.name, typePrefix);
190
+ if (!messages.has(requestName)) {
191
+ const requestFields = traverseSchema({
192
+ schema: method.request,
193
+ messages,
194
+ enums,
195
+ typePrefix
196
+ });
197
+ messages.set(requestName, requestFields);
198
+ }
199
+ if (!messages.has(responseName)) {
200
+ const responseFields = traverseSchema({
201
+ schema: method.response,
202
+ messages,
203
+ enums,
204
+ typePrefix
205
+ });
206
+ messages.set(responseName, responseFields);
207
+ }
208
+ return {
209
+ requestName,
210
+ responseName
211
+ };
212
+ };
213
+ const generateServices = (services, context) => {
214
+ return services.map((service) => {
215
+ const methods = service.methods.map((method) => {
216
+ const { requestName, responseName } = processServiceMethod(method, context);
217
+ const requestStreaming = method.streaming === "client" || method.streaming === "bidirectional";
218
+ const responseStreaming = method.streaming === "server" || method.streaming === "bidirectional";
219
+ const requestType = requestStreaming ? `stream ${requestName}` : requestName;
220
+ const responseType = responseStreaming ? `stream ${responseName}` : responseName;
221
+ return ` rpc ${method.name}(${requestType}) returns (${responseType});`;
222
+ });
223
+ return `service ${service.name} {\n${methods.join("\n")}\n}`;
224
+ });
225
+ };
226
+
227
+ //#endregion
228
+ //#region src/zod-to-protobuf.ts
229
+ const zodToProtobuf = (schema, options = {}) => {
230
+ const { packageName = "default", rootMessageName = "Message", typePrefix = "", services = [], skipRootMessage = false } = options;
231
+ const messages = /* @__PURE__ */ new Map();
232
+ const enums = /* @__PURE__ */ new Map();
233
+ if (schema && !skipRootMessage) {
234
+ const fields = traverseSchema({
235
+ schema,
236
+ messages,
237
+ enums,
238
+ typePrefix
239
+ });
240
+ if (fields.length > 0) {
241
+ const rootMessageKey = `${typePrefix}${rootMessageName}`;
242
+ messages.set(rootMessageKey, fields);
243
+ }
244
+ }
245
+ const context = {
246
+ messages,
247
+ enums,
248
+ typePrefix: typePrefix || null
249
+ };
250
+ if (services.length > 0) generateServices(services, context);
251
+ return `
252
+ syntax = "proto3";
253
+ package ${packageName};
254
+
255
+ ${[
256
+ Array.from(enums.values()).map((enumDef) => enumDef.join("\n")),
257
+ Array.from(messages.entries()).map(([name, fields]) => `message ${name} {\n${fields.map((field) => ` ${field}`).join("\n")}\n}`),
258
+ services.length > 0 ? generateServices(services, context) : []
259
+ ].filter((strings) => !!strings.length).map((strings) => strings.join("\n\n")).join("\n\n")}
260
+ `.trim();
261
+ };
262
+
263
+ //#endregion
264
+ export { UnsupportedTypeException, zodToProtobuf };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@globalart/zod-to-proto",
3
+ "version": "1.0.1",
4
+ "author": {
5
+ "name": "GlobalArt, Inc"
6
+ },
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": "./dist/index.js",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "scripts": {
22
+ "format": "prettier --write \"**/*.ts\"",
23
+ "test": "jest --runInBand --passWithNoTests",
24
+ "test:cov": "jest --coverage --passWithNoTests",
25
+ "coveralls": "yarn run test:cov --coverageReporters=text-lcov | coveralls",
26
+ "build": "tsdown",
27
+ "build:watch": "tsdown --watch",
28
+ "prepublishOnly": "npm run build",
29
+ "publish:dev": "npm publish --access public --tag dev",
30
+ "publish:npm": "release-it --config ../../.release-it.json"
31
+ },
32
+ "description": "OpenID Connect strategy for GlobalArt SSO integration",
33
+ "devDependencies": {
34
+ "@types/express": "^5.0.0",
35
+ "@types/node": "^24.9.1",
36
+ "@types/passport": "^1.0.17",
37
+ "prettier": "^3.6.2",
38
+ "release-it": "19.0.6",
39
+ "ts-node": "^10.9.2",
40
+ "tsdown": "0.16.4",
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "dependencies": {
44
+ "inflection": "3.0.2",
45
+ "zod": "4.1.12"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }