@polytric/openws-sdkgen 0.0.2 → 0.0.4

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/main.js ADDED
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/build-ir.ts
4
+ import S from "@pocketgems/schema";
5
+ var validateIr = S.obj({
6
+ package: S.obj({
7
+ project: S.str,
8
+ service: S.str,
9
+ description: S.str.optional(),
10
+ version: S.str.optional()
11
+ }),
12
+ networks: S.arr(
13
+ S.obj({
14
+ name: S.str,
15
+ description: S.str.optional(),
16
+ version: S.str.optional(),
17
+ roles: S.arr(
18
+ S.obj({
19
+ name: S.str,
20
+ description: S.str.optional(),
21
+ isHost: S.bool,
22
+ endpoints: S.arr(
23
+ S.obj({
24
+ scheme: S.str.enum("ws", "wss"),
25
+ host: S.str,
26
+ port: S.int.min(0).max(65535),
27
+ path: S.str
28
+ })
29
+ )
30
+ })
31
+ ),
32
+ handlers: S.arr(
33
+ S.obj({
34
+ roleName: S.str,
35
+ handlerName: S.str,
36
+ description: S.str.optional()
37
+ })
38
+ ),
39
+ messages: S.arr(
40
+ S.obj({
41
+ roleName: S.str,
42
+ handlerName: S.str,
43
+ description: S.str.optional()
44
+ })
45
+ ),
46
+ models: S.arr(
47
+ S.obj({
48
+ scopeName: S.str,
49
+ modelName: S.str,
50
+ type: S.str,
51
+ description: S.str.optional(),
52
+ properties: S.arr(
53
+ S.obj({
54
+ type: S.str,
55
+ scopeName: S.str,
56
+ modelName: S.str,
57
+ description: S.str.optional(),
58
+ required: S.bool.optional(),
59
+ items: S.obj({
60
+ type: S.str,
61
+ scopeName: S.str,
62
+ modelName: S.str,
63
+ description: S.str.optional()
64
+ }).optional()
65
+ })
66
+ ).optional()
67
+ })
68
+ )
69
+ })
70
+ ).desc("An array of network definitions")
71
+ }).compile("IrValidator");
72
+ function buildIrModels(scopeName, modelName, schema) {
73
+ const type = schema.type;
74
+ const model = {
75
+ type,
76
+ scopeName,
77
+ modelName,
78
+ description: schema.description
79
+ };
80
+ switch (type) {
81
+ case "string":
82
+ case "number":
83
+ case "integer":
84
+ case "boolean":
85
+ case "null":
86
+ return [];
87
+ case "array": {
88
+ if (!schema.items) return [];
89
+ return buildIrModels(scopeName, modelName, schema.items);
90
+ }
91
+ case "object": {
92
+ const properties = [];
93
+ model.properties = properties;
94
+ const models = [];
95
+ if (!schema.properties) return [model];
96
+ for (const [subName, subSchema] of Object.entries(schema.properties)) {
97
+ const subModels = buildIrModels(scopeName, subName, subSchema);
98
+ const mainModel = subModels.find((m) => m.modelName === subName) ?? subSchema;
99
+ const property = {
100
+ type: mainModel.type,
101
+ description: mainModel.description,
102
+ scopeName: mainModel.scopeName ?? scopeName,
103
+ modelName: mainModel.modelName ?? subName,
104
+ required: schema.required?.includes(subName)
105
+ };
106
+ const itemsSource = mainModel.properties?.[0]?.items ?? mainModel.items;
107
+ if (itemsSource) {
108
+ property.items = {
109
+ type: itemsSource.type,
110
+ description: itemsSource.description,
111
+ scopeName: itemsSource?.scopeName ?? scopeName,
112
+ modelName: itemsSource?.modelName ?? subName
113
+ };
114
+ }
115
+ properties.push(property);
116
+ models.push(...subModels);
117
+ }
118
+ return [model, ...models];
119
+ }
120
+ default:
121
+ return [];
122
+ }
123
+ }
124
+ function buildIr(ctx) {
125
+ const { request, spec } = ctx;
126
+ if (!request) throw new Error("request is required");
127
+ if (!spec) throw new Error("spec is required");
128
+ const { hostRoles } = request;
129
+ const ir = {
130
+ package: {
131
+ project: request.project,
132
+ service: spec.name,
133
+ description: spec.description,
134
+ version: spec.version
135
+ },
136
+ networks: []
137
+ };
138
+ for (const [networkName, networkSpec] of Object.entries(spec.networks)) {
139
+ const hostRoleSpecs = hostRoles.map((hostRole) => networkSpec.roles[hostRole]);
140
+ const otherRoleSpecs = {};
141
+ for (const [roleName, roleSpec] of Object.entries(networkSpec.roles)) {
142
+ if (!hostRoles.includes(roleName)) {
143
+ otherRoleSpecs[roleName] = roleSpec;
144
+ }
145
+ }
146
+ const requiredRoles = /* @__PURE__ */ new Set();
147
+ for (const hostRoleSpec of hostRoleSpecs) {
148
+ for (const handlerSpec of Object.values(hostRoleSpec.messages)) {
149
+ if (handlerSpec.from) {
150
+ for (const fromRoleName of handlerSpec.from) {
151
+ requiredRoles.add(fromRoleName);
152
+ }
153
+ } else {
154
+ for (const key of Object.keys(otherRoleSpecs)) {
155
+ requiredRoles.add(key);
156
+ }
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ const irNetwork = {
162
+ name: networkName,
163
+ description: networkSpec.description,
164
+ version: networkSpec.version,
165
+ roles: [],
166
+ handlers: [],
167
+ messages: [],
168
+ models: []
169
+ };
170
+ for (const hostRoleSpec of hostRoleSpecs) {
171
+ irNetwork.roles.push({
172
+ name: hostRoleSpec.name,
173
+ description: hostRoleSpec.description,
174
+ isHost: true,
175
+ endpoints: hostRoleSpec.endpoints || []
176
+ });
177
+ for (const [handlerName, handlerSpec] of Object.entries(hostRoleSpec.messages)) {
178
+ irNetwork.handlers.push({
179
+ roleName: hostRoleSpec.name,
180
+ handlerName,
181
+ description: handlerSpec.description
182
+ });
183
+ irNetwork.models.push(
184
+ ...buildIrModels(hostRoleSpec.name, handlerName, handlerSpec.payload)
185
+ );
186
+ }
187
+ }
188
+ for (const [roleName, roleSpec] of Object.entries(otherRoleSpecs)) {
189
+ if (!requiredRoles.has(roleName)) continue;
190
+ irNetwork.roles.push({
191
+ name: roleName,
192
+ description: roleSpec.description,
193
+ isHost: false,
194
+ endpoints: roleSpec.endpoints || []
195
+ });
196
+ for (const [handlerName, handlerSpec] of Object.entries(roleSpec.messages)) {
197
+ irNetwork.messages.push({
198
+ roleName,
199
+ handlerName,
200
+ description: handlerSpec.description
201
+ });
202
+ irNetwork.models.push(...buildIrModels(roleName, handlerName, handlerSpec.payload));
203
+ }
204
+ }
205
+ ir.networks.push(irNetwork);
206
+ }
207
+ validateIr(ir);
208
+ return {
209
+ ...ctx,
210
+ ir
211
+ };
212
+ }
213
+
214
+ // src/build-plan.ts
215
+ var planIndex = {
216
+ csharp: {
217
+ unity: "./plans/dotnet.js"
218
+ }
219
+ };
220
+ async function dispatchBuildPlan(ctx) {
221
+ const { request } = ctx;
222
+ if (!request) throw new Error("request is required");
223
+ const language = Object.keys(request.target)[0];
224
+ const targetConfig = request.target[language];
225
+ if (!targetConfig) throw new Error(`No target config for language: ${language}`);
226
+ const environment = targetConfig.environment;
227
+ const planPath = planIndex[language]?.[environment];
228
+ if (!planPath) throw new Error(`No plan for ${language}/${environment}`);
229
+ const { default: plan } = await import(planPath);
230
+ return plan(ctx);
231
+ }
232
+
233
+ // src/build-request.ts
234
+ import path from "path";
235
+ import process2 from "process";
236
+ import S2 from "@pocketgems/schema";
237
+ var validateBuildRequest = S2.obj({
238
+ specPath: S2.str,
239
+ outputPath: S2.str,
240
+ project: S2.str,
241
+ hostRoles: S2.arr(S2.str),
242
+ target: S2.obj({
243
+ csharp: S2.obj({
244
+ environment: S2.str.enum("unity"),
245
+ frameworks: S2.arr(S2.str.enum("newtonsoft")).optional()
246
+ }).optional(),
247
+ javascript: S2.obj({
248
+ environment: S2.str.enum("node", "browser"),
249
+ frameworks: S2.arr(S2.str.enum("fastify")).optional()
250
+ }).optional()
251
+ }).min(1).max(1).desc("The target platform to generate code for")
252
+ }).compile("BuildRequestValidator");
253
+ function buildRequest(ctx) {
254
+ const { rawInput } = ctx;
255
+ if (!rawInput) throw new Error("rawInput is required");
256
+ console.log("Host roles:", rawInput.hostRole);
257
+ const request = {
258
+ specPath: path.join(process2.cwd(), rawInput.spec),
259
+ outputPath: path.join(process2.cwd(), rawInput.out),
260
+ project: rawInput.project,
261
+ hostRoles: rawInput.hostRole,
262
+ target: {
263
+ [rawInput.language]: {
264
+ environment: rawInput.environment,
265
+ frameworks: rawInput.frameworks
266
+ }
267
+ }
268
+ };
269
+ validateBuildRequest(request);
270
+ return {
271
+ ...ctx,
272
+ request
273
+ };
274
+ }
275
+
276
+ // src/execute-plan.ts
277
+ import fs from "fs";
278
+ import path2 from "path";
279
+ import ejs from "ejs";
280
+ var rendererCache = {};
281
+ function renderTemplate(templatePath, data) {
282
+ if (rendererCache[templatePath]) {
283
+ return rendererCache[templatePath](data);
284
+ }
285
+ const templateContent = fs.readFileSync(templatePath, "utf8");
286
+ const renderer = ejs.compile(templateContent);
287
+ rendererCache[templatePath] = renderer;
288
+ return renderer(data);
289
+ }
290
+ function executePlan(ctx) {
291
+ const { plan } = ctx;
292
+ if (!plan) throw new Error("plan is required");
293
+ for (const step of plan) {
294
+ switch (step.command) {
295
+ case "copy":
296
+ if (step.input) {
297
+ fs.cpSync(step.input, step.output, { recursive: true });
298
+ }
299
+ break;
300
+ case "render": {
301
+ const { getData, template, output } = step;
302
+ if (!getData || !template) continue;
303
+ const data = getData();
304
+ console.log(data);
305
+ fs.mkdirSync(path2.dirname(output), { recursive: true });
306
+ fs.writeFileSync(output, renderTemplate(template, { ctx: data }));
307
+ break;
308
+ }
309
+ }
310
+ }
311
+ return ctx;
312
+ }
313
+
314
+ // src/load-spec.ts
315
+ import fs2 from "fs";
316
+ function loadSpec(ctx) {
317
+ const { request } = ctx;
318
+ if (!request) throw new Error("request is required");
319
+ const { specPath } = request;
320
+ const spec = JSON.parse(fs2.readFileSync(specPath, "utf8"));
321
+ return {
322
+ ...ctx,
323
+ spec
324
+ };
325
+ }
326
+
327
+ // src/parse-input.ts
328
+ import yargs from "yargs";
329
+ import { hideBin } from "yargs/helpers";
330
+ function parseInput(ctx) {
331
+ const args = yargs(hideBin(ctx.argv)).scriptName("openws-sdkgen").version(false).option("spec", {
332
+ type: "string",
333
+ description: "The path to the OpenWS spec file",
334
+ demandOption: true
335
+ }).option("out", {
336
+ type: "string",
337
+ description: "The path to the output directory",
338
+ demandOption: true
339
+ }).option("project", {
340
+ type: "string",
341
+ description: "The project name",
342
+ demandOption: true
343
+ }).option("hostRole", {
344
+ type: "array",
345
+ string: true,
346
+ description: "The target participant roles that use the generated code",
347
+ demandOption: true
348
+ }).option("language", {
349
+ type: "string",
350
+ description: "The language to generate code for",
351
+ choices: ["csharp", "javascript"],
352
+ default: "csharp"
353
+ }).option("environment", {
354
+ type: "string",
355
+ description: "The environment to generate code for",
356
+ choices: ["unity", "node", "browser"],
357
+ default: "unity"
358
+ }).option("frameworks", {
359
+ type: "array",
360
+ string: true,
361
+ description: "The frameworks to generate code for",
362
+ choices: ["fastify", "newtonsoft"]
363
+ }).strict().help().parseSync();
364
+ return {
365
+ ...ctx,
366
+ rawInput: args
367
+ };
368
+ }
369
+
370
+ // src/prepare-output.ts
371
+ import fs3 from "fs";
372
+ function prepareOutput(ctx) {
373
+ const { request } = ctx;
374
+ if (!request) throw new Error("request is required");
375
+ const { outputPath } = request;
376
+ fs3.rmSync(outputPath, { recursive: true, force: true });
377
+ fs3.mkdirSync(outputPath, { recursive: true });
378
+ return ctx;
379
+ }
380
+
381
+ // src/main.ts
382
+ var Pipeline = [
383
+ parseInput,
384
+ buildRequest,
385
+ prepareOutput,
386
+ loadSpec,
387
+ buildIr,
388
+ dispatchBuildPlan,
389
+ executePlan
390
+ ];
391
+ async function main() {
392
+ let ctx = { argv: process.argv };
393
+ for (const step of Pipeline) {
394
+ ctx = await step(ctx);
395
+ }
396
+ return ctx;
397
+ }
398
+ main().then((result) => {
399
+ console.log(JSON.stringify(result, null, 2));
400
+ }).catch((err) => {
401
+ console.error(err);
402
+ process.exit(1);
403
+ });
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/plans/dotnet.ts
31
+ var dotnet_exports = {};
32
+ __export(dotnet_exports, {
33
+ default: () => createPlan
34
+ });
35
+ module.exports = __toCommonJS(dotnet_exports);
36
+
37
+ // ../../node_modules/.pnpm/tsup@8.5.1_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
38
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
39
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
+
41
+ // src/plans/dotnet.ts
42
+ var import_node_path = __toESM(require("path"), 1);
43
+ var import_node_url = require("url");
44
+ var __dirname = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
45
+ var TEMPLATE_DIR = import_node_path.default.join(__dirname, "../templates/dotnet");
46
+ function pascalCase(str) {
47
+ return str.charAt(0).toUpperCase() + str.slice(1);
48
+ }
49
+ function camelCase(str) {
50
+ return str.charAt(0).toLowerCase() + str.slice(1);
51
+ }
52
+ function createPlan(ctx) {
53
+ const { ir, request } = ctx;
54
+ if (!ir) throw new Error("ir is required");
55
+ if (!request) throw new Error("request is required");
56
+ const assemblyName = `${pascalCase(ir.package.project)}.${pascalCase(ir.package.service)}.Sdk`;
57
+ ir.assemblyName = assemblyName;
58
+ const plan = [
59
+ {
60
+ name: "assembly definition",
61
+ command: "render",
62
+ getData: () => ir,
63
+ template: import_node_path.default.join(TEMPLATE_DIR, "Service.asmdef.ejs"),
64
+ output: import_node_path.default.join(request.outputPath, assemblyName, `${assemblyName}.asmdef`)
65
+ },
66
+ {
67
+ name: "user assembly reference",
68
+ command: "render",
69
+ getData: () => ir,
70
+ template: import_node_path.default.join(TEMPLATE_DIR, "UserService.asmref.ejs"),
71
+ output: import_node_path.default.join(
72
+ request.outputPath,
73
+ `${assemblyName}.User`,
74
+ `${assemblyName}.User.asmref`
75
+ )
76
+ }
77
+ ];
78
+ for (const networkIr of ir.networks) {
79
+ const networkNamespace = `${pascalCase(ir.package.project)}.${pascalCase(ir.package.service)}.${pascalCase(networkIr.name)}`;
80
+ const networkClassName = `${pascalCase(networkIr.name)}Network`;
81
+ const networkOutputPath = import_node_path.default.join(
82
+ request.outputPath,
83
+ assemblyName,
84
+ pascalCase(networkIr.name)
85
+ );
86
+ const userNetworkOutputPath = import_node_path.default.join(
87
+ request.outputPath,
88
+ `${assemblyName}.User`,
89
+ pascalCase(networkIr.name)
90
+ );
91
+ const hostRoles = [];
92
+ const remoteRoles = [];
93
+ for (const role of networkIr.roles) {
94
+ const roleInfo = {
95
+ roleName: role.name,
96
+ className: pascalCase(role.name),
97
+ varName: camelCase(role.name),
98
+ description: role.description || "",
99
+ baseClassName: role.isHost ? "HostRole" : "RemoteRole",
100
+ endpoints: role.endpoints || []
101
+ };
102
+ if (role.isHost) {
103
+ hostRoles.push(roleInfo);
104
+ } else {
105
+ remoteRoles.push(roleInfo);
106
+ }
107
+ }
108
+ const allRoles = [...hostRoles, ...remoteRoles];
109
+ const allModelImports = allRoles.map((role) => `${networkNamespace}.Models.${role.className}`);
110
+ plan.push({
111
+ name: `network ${networkIr.name}`,
112
+ command: "render",
113
+ getData: () => ({
114
+ namespace: networkNamespace,
115
+ networkClassName,
116
+ networkName: networkIr.name,
117
+ description: networkIr.description,
118
+ version: networkIr.version,
119
+ allRoles
120
+ }),
121
+ template: import_node_path.default.join(TEMPLATE_DIR, "Network.cs.ejs"),
122
+ output: import_node_path.default.join(networkOutputPath, `${networkClassName}.cs`)
123
+ });
124
+ for (const modelIr of networkIr.models) {
125
+ modelIr.namespace = `${networkNamespace}.Models.${pascalCase(modelIr.scopeName)}`;
126
+ modelIr.className = pascalCase(modelIr.modelName);
127
+ if (modelIr.properties) {
128
+ for (const propertyIr of modelIr.properties) {
129
+ propertyIr.propertyName = pascalCase(propertyIr.modelName);
130
+ propertyIr.typeName = mapType(propertyIr);
131
+ }
132
+ }
133
+ }
134
+ for (const handlerIr of networkIr.handlers) {
135
+ handlerIr.modelClassName = pascalCase(handlerIr.handlerName);
136
+ handlerIr.messageName = handlerIr.handlerName;
137
+ handlerIr.methodName = pascalCase(handlerIr.handlerName);
138
+ }
139
+ for (const messageIr of networkIr.messages) {
140
+ messageIr.modelClassName = pascalCase(messageIr.handlerName);
141
+ messageIr.messageName = messageIr.handlerName;
142
+ messageIr.methodName = pascalCase(messageIr.handlerName);
143
+ }
144
+ for (const hostRole of hostRoles) {
145
+ const roleHandlers = networkIr.handlers.filter((h) => h.roleName === hostRole.roleName);
146
+ const modelImports = [`${networkNamespace}.Models.${hostRole.className}`];
147
+ plan.push({
148
+ name: `host role ${hostRole.className}`,
149
+ command: "render",
150
+ getData: () => ({
151
+ namespace: `${networkNamespace}.Roles`,
152
+ handlers: roleHandlers,
153
+ remoteRoles,
154
+ modelImports,
155
+ ...hostRole
156
+ }),
157
+ template: import_node_path.default.join(TEMPLATE_DIR, "HostRole.cs.ejs"),
158
+ output: import_node_path.default.join(networkOutputPath, "Roles", `${hostRole.className}.cs`)
159
+ });
160
+ plan.push({
161
+ name: `user host role ${hostRole.className}`,
162
+ command: "render",
163
+ getData: () => ({
164
+ namespace: `${networkNamespace}.Roles`,
165
+ handlers: roleHandlers,
166
+ remoteRoles,
167
+ modelImports: allModelImports,
168
+ ...hostRole
169
+ }),
170
+ template: import_node_path.default.join(TEMPLATE_DIR, "UserHostRole.cs.ejs"),
171
+ output: import_node_path.default.join(userNetworkOutputPath, "Roles", `${hostRole.className}.cs`)
172
+ });
173
+ }
174
+ for (const remoteRole of remoteRoles) {
175
+ const roleMessages = networkIr.messages.filter((m) => m.roleName === remoteRole.roleName);
176
+ const modelImports = [`${networkNamespace}.Models.${remoteRole.className}`];
177
+ plan.push({
178
+ name: `remote role ${remoteRole.className}`,
179
+ command: "render",
180
+ getData: () => ({
181
+ namespace: `${networkNamespace}.Roles`,
182
+ messages: roleMessages,
183
+ modelImports,
184
+ ...remoteRole
185
+ }),
186
+ template: import_node_path.default.join(TEMPLATE_DIR, "RemoteRole.cs.ejs"),
187
+ output: import_node_path.default.join(networkOutputPath, "Roles", `${remoteRole.className}.cs`)
188
+ });
189
+ }
190
+ for (const modelIr of networkIr.models) {
191
+ if (modelIr.type !== "object") continue;
192
+ plan.push({
193
+ name: `model ${modelIr.className}`,
194
+ command: "render",
195
+ getData: () => modelIr,
196
+ template: import_node_path.default.join(TEMPLATE_DIR, "Model.cs.ejs"),
197
+ output: import_node_path.default.join(
198
+ networkOutputPath,
199
+ "Models",
200
+ pascalCase(modelIr.scopeName),
201
+ `${modelIr.className}.cs`
202
+ )
203
+ });
204
+ }
205
+ }
206
+ return {
207
+ ...ctx,
208
+ plan
209
+ };
210
+ }
211
+ function mapType(property) {
212
+ switch (property.type) {
213
+ case "string":
214
+ return "string";
215
+ case "number":
216
+ return "double";
217
+ case "integer":
218
+ return "int";
219
+ case "boolean":
220
+ return "bool";
221
+ case "array":
222
+ if (!property.items) return "List<object>";
223
+ return `List<${mapType(property.items)}>`;
224
+ case "object":
225
+ return pascalCase(property.modelName);
226
+ default:
227
+ return "object";
228
+ }
229
+ }