@polytric/openws-sdkgen 0.0.4 → 0.0.6

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.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/dist/main.cjs +53 -5
  3. package/dist/main.js +53 -5
  4. package/dist/plans/dotnet.cjs +3 -3
  5. package/dist/plans/dotnet.d.cts +2 -143
  6. package/dist/plans/dotnet.d.ts +2 -143
  7. package/dist/plans/dotnet.js +2 -2
  8. package/dist/plans/typescript.cjs +569 -0
  9. package/dist/plans/typescript.d.cts +6 -0
  10. package/dist/plans/typescript.d.ts +6 -0
  11. package/dist/plans/typescript.js +532 -0
  12. package/dist/templates/dotnet/HostRole.cs.ejs +23 -8
  13. package/dist/templates/dotnet/Model.cs.ejs +1 -1
  14. package/dist/templates/dotnet/RemoteRole.cs.ejs +7 -2
  15. package/dist/templates/dotnet/UserHostRole.cs.ejs +26 -4
  16. package/dist/templates/typescript/package.json.ejs +41 -0
  17. package/dist/templates/typescript/src/core/index.ts.ejs +6 -0
  18. package/dist/templates/typescript/src/core/models/index.ts.ejs +3 -0
  19. package/dist/templates/typescript/src/core/models/model.ts.ejs +41 -0
  20. package/dist/templates/typescript/src/core/network.ts.ejs +517 -0
  21. package/dist/templates/typescript/src/core/roles/index.ts.ejs +3 -0
  22. package/dist/templates/typescript/src/core/roles/role.ts.ejs +104 -0
  23. package/dist/templates/typescript/src/index.ts.ejs +4 -0
  24. package/dist/templates/typescript/src/sdk/index.ts.ejs +3 -0
  25. package/dist/templates/typescript/src/sdk/role.ts.ejs +372 -0
  26. package/dist/templates/typescript/tsconfig.json.ejs +14 -0
  27. package/dist/templates/typescript/tsup.config.ts.ejs +10 -0
  28. package/dist/types-BdZPs123.d.cts +115 -0
  29. package/dist/types-BdZPs123.d.ts +115 -0
  30. package/package.json +13 -4
@@ -0,0 +1,104 @@
1
+ <% const modelImportPath = ctx.isTypeScript ? `../models/${ctx.fileName}` : `../models/${ctx.fileName}/index.js` -%>
2
+ <% if (ctx.isTypeScript) { -%>
3
+ <% if (ctx.messages.length > 0) { -%>
4
+ import type {
5
+ <% for (const message of ctx.messages) { -%>
6
+ <%= message.payloadType %>,
7
+ <%= message.payloadType %>Init,
8
+ <% } -%>
9
+ } from '<%= modelImportPath %>'
10
+ <% } -%>
11
+ <% const importedPeerRoles = new Map(); for (const message of ctx.messages) { for (const fromRole of message.fromRoles ?? []) { if (fromRole.roleName !== ctx.roleName) importedPeerRoles.set(fromRole.roleName, fromRole) } } -%>
12
+ <% if (importedPeerRoles.size > 0) { -%>
13
+ import {
14
+ <% for (const fromRole of importedPeerRoles.values()) { -%>
15
+ <%= fromRole.roleClassName %>,
16
+ <% } -%>
17
+ } from './index'
18
+ <% } -%>
19
+
20
+ export interface <%= ctx.apiName %> {
21
+ <% for (const message of ctx.messages) { -%>
22
+ <%= message.methodName %>(payload: <%= message.payloadType %> | <%= message.payloadType %>Init): Promise<void>
23
+ <% } -%>
24
+ }
25
+
26
+ export class <%= ctx.roleClassName %> {
27
+ static readonly CONFIG = {
28
+ name: <%- JSON.stringify(ctx.roleName) %>,
29
+ description: <%- JSON.stringify(ctx.description) %>,
30
+ messages: {
31
+ <% for (const message of ctx.messages) { -%>
32
+ <%= message.messageName %>: {
33
+ payload: <%- JSON.stringify(message.schema, null, 16) %>,
34
+ },
35
+ <% } -%>
36
+ },
37
+ }
38
+ static readonly endpoints = <%- JSON.stringify(ctx.endpoints, null, 4) %>
39
+ }
40
+
41
+ export class <%= ctx.hostRoleClassName %> {
42
+ static get CONFIG() {
43
+ return {
44
+ name: <%- JSON.stringify(ctx.roleName) %>,
45
+ description: <%- JSON.stringify(ctx.description) %>,
46
+ handlers: {
47
+ <% for (const message of ctx.messages) { -%>
48
+ <%= message.messageName %>: {
49
+ payload: <%= ctx.roleClassName %>.CONFIG.messages.<%= message.messageName %>.payload,
50
+ <% if (message.fromRoles) { -%>
51
+ from: [<%= message.fromRoles.map(fromRole => fromRole.roleClassName).join(', ') %>],
52
+ <% } -%>
53
+ },
54
+ <% } -%>
55
+ },
56
+ endpoints: <%= ctx.roleClassName %>.endpoints,
57
+ }
58
+ }
59
+ }
60
+ <% } else { -%>
61
+ <% const importedPeerRoles = new Map(); for (const message of ctx.messages) { for (const fromRole of message.fromRoles ?? []) { if (fromRole.roleName !== ctx.roleName) importedPeerRoles.set(fromRole.roleName, fromRole) } } -%>
62
+ <% if (importedPeerRoles.size > 0) { -%>
63
+ import {
64
+ <% for (const fromRole of importedPeerRoles.values()) { -%>
65
+ <%= fromRole.roleClassName %>,
66
+ <% } -%>
67
+ } from './index.js'
68
+ <% } -%>
69
+
70
+ export class <%= ctx.roleClassName %> {
71
+ static CONFIG = {
72
+ name: <%- JSON.stringify(ctx.roleName) %>,
73
+ description: <%- JSON.stringify(ctx.description) %>,
74
+ messages: {
75
+ <% for (const message of ctx.messages) { -%>
76
+ <%= message.messageName %>: {
77
+ payload: <%- JSON.stringify(message.schema, null, 16) %>,
78
+ },
79
+ <% } -%>
80
+ },
81
+ }
82
+ static endpoints = <%- JSON.stringify(ctx.endpoints, null, 4) %>
83
+ }
84
+
85
+ export class <%= ctx.hostRoleClassName %> {
86
+ static get CONFIG() {
87
+ return {
88
+ name: <%- JSON.stringify(ctx.roleName) %>,
89
+ description: <%- JSON.stringify(ctx.description) %>,
90
+ handlers: {
91
+ <% for (const message of ctx.messages) { -%>
92
+ <%= message.messageName %>: {
93
+ payload: <%= ctx.roleClassName %>.CONFIG.messages.<%= message.messageName %>.payload,
94
+ <% if (message.fromRoles) { -%>
95
+ from: [<%= message.fromRoles.map(fromRole => fromRole.roleClassName).join(', ') %>],
96
+ <% } -%>
97
+ },
98
+ <% } -%>
99
+ },
100
+ endpoints: <%= ctx.roleClassName %>.endpoints,
101
+ }
102
+ }
103
+ }
104
+ <% } -%>
@@ -0,0 +1,4 @@
1
+ <% for (const network of ctx.networkExports) { -%>
2
+ export * as <%= network.exportName %> from './<%= network.fileName %><%= ctx.isTypeScript ? '' : '/index.js' %>'
3
+ <% } -%>
4
+ export * as sdk from './sdk<%= ctx.isTypeScript ? '' : '/index.js' %>'
@@ -0,0 +1,3 @@
1
+ <% for (const role of ctx.roles) { -%>
2
+ export * from './<%= role.fileName %><%= ctx.isTypeScript ? '' : '.js' %>'
3
+ <% } -%>
@@ -0,0 +1,372 @@
1
+ <% const networkImport = ctx.isTypeScript ? '../core/network' : '../core/network.js' -%>
2
+ <% const modelImportPath = ctx.isTypeScript ? `../core/models/${ctx.fileName}` : `../core/models/${ctx.fileName}/index.js` -%>
3
+ <% const rolesImport = ctx.isTypeScript ? '../core/roles' : '../core/roles/index.js' -%>
4
+ <% if (ctx.isTypeScript) { -%>
5
+ import * as WS from '@polytric/openws/class'
6
+ import * as Fluent from '@polytric/openws/fluent'
7
+ import type { ApiProto } from '@polytric/openws/fluent'
8
+ import type { BindTransportOptions, OpenWsEndpoint, OpenWsEnvelope, Transport, Unsubscribe } from '<%= networkImport %>'
9
+ import { WsTransport, bindTransport, canBindTransport, decodeEnvelope, encodeEnvelope } from '<%= networkImport %>'
10
+ <% if (ctx.handlers.length > 0) { -%>
11
+ import type {
12
+ <% for (const handler of ctx.handlers) { -%>
13
+ <%= handler.payloadType %>,
14
+ <% } -%>
15
+ } from '<%= modelImportPath %>'
16
+ export * from '<%= modelImportPath %>'
17
+ <% } -%>
18
+ import {
19
+ <%= ctx.hostRoleClassName %>,
20
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
21
+ <%= remoteRole.roleClassName %>,
22
+ type <%= remoteRole.apiName %>,
23
+ <% } -%>
24
+ } from '<%= rolesImport %>'
25
+
26
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
27
+ export type <%= remoteRole.scopedApiName %> = Pick<<%= remoteRole.apiName %>, <%- remoteRole.allowedMethodNames.length > 0 ? remoteRole.allowedMethodNames.map(methodName => JSON.stringify(methodName)).join(' | ') : 'never' %>>
28
+ <% } -%>
29
+
30
+ <% if (ctx.remoteRoles.length > 0) { -%>
31
+ export type <%= ctx.className %>PeerApi = <%= ctx.remoteRoles.map(remoteRole => remoteRole.scopedApiName).join(' | ') %>
32
+
33
+ <% } else { -%>
34
+ export type <%= ctx.className %>PeerApi = unknown
35
+
36
+ <% } -%>
37
+ <% if (ctx.handlers.length > 0) { -%>
38
+ export type <%= ctx.className %>MessageHandler<TPayload, TApi = <%= ctx.className %>PeerApi> = (payload: TPayload, api: TApi) => void | Promise<void>
39
+
40
+ <% } -%>
41
+ export type <%= ctx.className %>ErrorHandler = (error: unknown) => void | Promise<void>
42
+
43
+ export class <%= ctx.className %> {
44
+ static readonly CONFIG = <%= ctx.hostRoleClassName %>.CONFIG
45
+
46
+ readonly name = <%= ctx.className %>.CONFIG.name
47
+ readonly description = <%= ctx.className %>.CONFIG.description
48
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
49
+ <%= remoteRole.apiVarName %>!: <%= remoteRole.scopedApiName %>
50
+ <% } -%>
51
+
52
+ private readonly binder: Fluent.NetworkBinder
53
+ private readonly runtime: Fluent.Runtime
54
+ private readonly fromRole = <%- JSON.stringify(ctx.roleName) %>
55
+ private readonly sendEnvelope: (toRole: string, messageName: string, payload: unknown) => Promise<void>
56
+ private transportUnsubscribe?: Unsubscribe
57
+ private readonly apisByRole: Record<string, ApiProto> = {}
58
+ private readonly apisByMessageName: Record<string, ApiProto> = {}
59
+ private readonly handlersByMessageName: Record<string, (payload: unknown, api: <%= ctx.className %>PeerApi) => Promise<void>> = {}
60
+ private readonly messageErrorHandlers = new Set<<%= ctx.className %>ErrorHandler>()
61
+ private readonly socketErrorHandlers = new Set<<%= ctx.className %>ErrorHandler>()
62
+ <% for (const handler of ctx.handlers) { -%>
63
+ <% const handlerApiType = handler.bindFromRoles.map(fromRole => ctx.remoteRoles.find(remoteRole => remoteRole.roleName === fromRole.roleName)?.scopedApiName).filter(Boolean).join(' | ') || `${ctx.className}PeerApi` -%>
64
+ private readonly <%= handler.listenerFieldName %> = new Set<<%= ctx.className %>MessageHandler<<%= handler.payloadType %>, <%= handlerApiType %>>>()
65
+ <% } -%>
66
+
67
+ constructor(
68
+ protected readonly transport: Transport = new WsTransport()
69
+ ) {
70
+ const HostRole = this.constructor as typeof <%= ctx.className %>
71
+ this.binder = Fluent.bindings(
72
+ WS.network({
73
+ name: <%- JSON.stringify(ctx.networkName) %>,
74
+ description: <%- JSON.stringify(ctx.networkDescription ?? '') %>,
75
+ version: <%- JSON.stringify(ctx.networkVersion ?? '1.0.0') %>,
76
+ roles: [HostRole<% if (ctx.remoteRoles.length > 0) { %>, <%= ctx.remoteRoles.map(remoteRole => remoteRole.roleClassName).join(', ') %><% } %>],
77
+ })
78
+ )
79
+ this.runtime = WS.runtime(this.binder)
80
+ this.sendEnvelope = async (_toRole: string, messageName: string, payload: unknown) => {
81
+ await this.transport.send(encodeEnvelope({ fromRole: this.fromRole, messageName, payload }))
82
+ }
83
+ <% for (const handler of ctx.handlers) { -%>
84
+ <% const handlerApiType = handler.bindFromRoles.map(fromRole => ctx.remoteRoles.find(remoteRole => remoteRole.roleName === fromRole.roleName)?.scopedApiName).filter(Boolean).join(' | ') || `${ctx.className}PeerApi` -%>
85
+ this.handlersByMessageName[<%- JSON.stringify(handler.messageName) %>] = async (payload, api) => {
86
+ await this.<%= handler.dispatchMethodName %>(payload as <%= handler.payloadType %>, api as <%= handlerApiType %>)
87
+ }
88
+ <% for (const fromRole of handler.bindFromRoles) { -%>
89
+ this.binder.fromRoles[<%- JSON.stringify(fromRole.roleName) %>].on(<%- JSON.stringify(handler.messageName) %>, async (payload, api) => {
90
+ await this.<%= handler.dispatchMethodName %>(payload as <%= handler.payloadType %>, api as unknown as <%= handlerApiType %>)
91
+ })
92
+ <% } -%>
93
+ <% } -%>
94
+ if (canBindTransport(transport)) {
95
+ this.bindTransport(transport)
96
+ }
97
+ }
98
+
99
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
100
+ async connect(roleName: <%- JSON.stringify(remoteRole.roleName) %>, endpoint?: OpenWsEndpoint): Promise<<%= remoteRole.scopedApiName %>>
101
+ <% } -%>
102
+ async connect(roleName: string, endpoint?: OpenWsEndpoint): Promise<<%= ctx.className %>PeerApi> {
103
+ switch (roleName) {
104
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
105
+ case <%- JSON.stringify(remoteRole.roleName) %>: {
106
+ const remoteEndpoint = endpoint ?? (<%- remoteRole.endpoints.length > 0 ? JSON.stringify(remoteRole.endpoints[0]) : 'undefined' %> as OpenWsEndpoint | undefined)
107
+ await this.transport.connect?.(roleName, remoteEndpoint)
108
+ if (!this.apisByRole[<%- JSON.stringify(remoteRole.roleName) %>]) {
109
+ const <%= remoteRole.varName %>Api = this.runtime.createApi(<%- JSON.stringify(remoteRole.roleName) %>, this.sendEnvelope)
110
+ this.<%= remoteRole.apiVarName %> = <%= remoteRole.varName %>Api as unknown as <%= remoteRole.scopedApiName %>
111
+ this.apisByRole[<%- JSON.stringify(remoteRole.roleName) %>] = <%= remoteRole.varName %>Api
112
+ <% for (const handler of ctx.handlers) { -%>
113
+ <% const handlerDefaultApi = handler.bindFromRoles.find(fromRole => ctx.remoteRoles.some(remoteRole => remoteRole.roleName === fromRole.roleName)) ?? ctx.remoteRoles[0] -%>
114
+ <% if (handlerDefaultApi?.roleName === remoteRole.roleName) { -%>
115
+ this.apisByMessageName[<%- JSON.stringify(handler.messageName) %>] = <%= remoteRole.varName %>Api
116
+ <% } -%>
117
+ <% } -%>
118
+ }
119
+ return this.<%= remoteRole.apiVarName %>
120
+ }
121
+ <% } -%>
122
+ default:
123
+ throw new Error(`Remote role ${roleName} not found`)
124
+ }
125
+ }
126
+
127
+ async disconnect(roleName: string): Promise<void> {
128
+ await this.transport.disconnect?.(roleName)
129
+ delete this.apisByRole[roleName]
130
+ }
131
+
132
+ bindTransport(transport: Transport = this.transport, options: BindTransportOptions = {}): Unsubscribe {
133
+ this.transportUnsubscribe?.()
134
+ this.transportUnsubscribe = bindTransport(transport, this, options)
135
+ return this.transportUnsubscribe
136
+ }
137
+
138
+ unbindTransport(): void {
139
+ this.transportUnsubscribe?.()
140
+ this.transportUnsubscribe = undefined
141
+ }
142
+
143
+ async messageError(error: unknown): Promise<void> {
144
+ for (const handler of this.messageErrorHandlers) {
145
+ await handler(error)
146
+ }
147
+ }
148
+
149
+ onMessageError(handler: <%= ctx.className %>ErrorHandler): Unsubscribe {
150
+ this.messageErrorHandlers.add(handler)
151
+ return () => {
152
+ this.messageErrorHandlers.delete(handler)
153
+ }
154
+ }
155
+
156
+ async socketError(error: unknown): Promise<void> {
157
+ for (const handler of this.socketErrorHandlers) {
158
+ await handler(error)
159
+ }
160
+ }
161
+
162
+ onSocketError(handler: <%= ctx.className %>ErrorHandler): Unsubscribe {
163
+ this.socketErrorHandlers.add(handler)
164
+ return () => {
165
+ this.socketErrorHandlers.delete(handler)
166
+ }
167
+ }
168
+
169
+ <% for (const handler of ctx.handlers) { -%>
170
+ <% const handlerApiType = handler.bindFromRoles.map(fromRole => ctx.remoteRoles.find(remoteRole => remoteRole.roleName === fromRole.roleName)?.scopedApiName).filter(Boolean).join(' | ') || `${ctx.className}PeerApi` -%>
171
+ async <%= handler.dispatchMethodName %>(payload: <%= handler.payloadType %>, api: <%= handlerApiType %>): Promise<void> {
172
+ for (const handler of this.<%= handler.listenerFieldName %>) {
173
+ await handler(payload, api)
174
+ }
175
+ }
176
+
177
+ <%= handler.onMethodName %>(handler: <%= ctx.className %>MessageHandler<<%= handler.payloadType %>, <%= handlerApiType %>>): Unsubscribe {
178
+ this.<%= handler.listenerFieldName %>.add(handler)
179
+ return () => {
180
+ this.<%= handler.listenerFieldName %>.delete(handler)
181
+ }
182
+ }
183
+
184
+ <% } -%>
185
+ async handleRawMessage(data: string): Promise<void> {
186
+ await this.handleMessage(decodeEnvelope(data))
187
+ }
188
+
189
+ async handleMessage(envelope: OpenWsEnvelope): Promise<void> {
190
+ const remote = this.binder.fromRoles[envelope.fromRole]
191
+ const api = this.apisByRole[envelope.fromRole]
192
+ if (remote && api) {
193
+ await remote.handleMessage(envelope.messageName, envelope.payload, api)
194
+ return
195
+ }
196
+
197
+ const localHandler = this.handlersByMessageName[envelope.messageName]
198
+ const localApi = this.apisByMessageName[envelope.messageName]
199
+ if (envelope.fromRole === this.fromRole && localHandler && localApi) {
200
+ await localHandler(envelope.payload, localApi as unknown as <%= ctx.className %>PeerApi)
201
+ return
202
+ }
203
+
204
+ throw new Error(`Remote role ${envelope.fromRole} not found`)
205
+ }
206
+ }
207
+ <% } else { -%>
208
+ import * as WS from '@polytric/openws/class'
209
+ import * as Fluent from '@polytric/openws/fluent'
210
+ import { WsTransport, bindTransport, canBindTransport, decodeEnvelope, encodeEnvelope } from '<%= networkImport %>'
211
+ <% if (ctx.handlers.length > 0) { -%>
212
+ export * from '<%= modelImportPath %>'
213
+ <% } -%>
214
+ import {
215
+ <%= ctx.hostRoleClassName %>,
216
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
217
+ <%= remoteRole.roleClassName %>,
218
+ <% } -%>
219
+ } from '<%= rolesImport %>'
220
+
221
+ export class <%= ctx.className %> {
222
+ static CONFIG = <%= ctx.hostRoleClassName %>.CONFIG
223
+
224
+ name = <%= ctx.className %>.CONFIG.name
225
+ description = <%= ctx.className %>.CONFIG.description
226
+ runtime
227
+ sendEnvelope
228
+ #transportUnsubscribe
229
+ #apisByRole = {}
230
+ #apisByMessageName = {}
231
+ #handlersByMessageName = {}
232
+ #messageErrorHandlers = new Set()
233
+ #socketErrorHandlers = new Set()
234
+ #fromRole = <%- JSON.stringify(ctx.roleName) %>
235
+ <% for (const handler of ctx.handlers) { -%>
236
+ #<%= handler.listenerFieldName %> = new Set()
237
+ <% } -%>
238
+
239
+ constructor(transport = new WsTransport()) {
240
+ this.transport = transport
241
+ const HostRole = this.constructor
242
+ this.binder = Fluent.bindings(
243
+ WS.network({
244
+ name: <%- JSON.stringify(ctx.networkName) %>,
245
+ description: <%- JSON.stringify(ctx.networkDescription ?? '') %>,
246
+ version: <%- JSON.stringify(ctx.networkVersion ?? '1.0.0') %>,
247
+ roles: [HostRole<% if (ctx.remoteRoles.length > 0) { %>, <%= ctx.remoteRoles.map(remoteRole => remoteRole.roleClassName).join(', ') %><% } %>],
248
+ })
249
+ )
250
+ this.runtime = WS.runtime(this.binder)
251
+ this.sendEnvelope = async (_toRole, messageName, payload) => {
252
+ await this.transport.send(encodeEnvelope({ fromRole: this.#fromRole, messageName, payload }))
253
+ }
254
+ <% for (const handler of ctx.handlers) { -%>
255
+ this.#handlersByMessageName[<%- JSON.stringify(handler.messageName) %>] = async (payload, api) => {
256
+ await this.<%= handler.dispatchMethodName %>(payload, api)
257
+ }
258
+ <% for (const fromRole of handler.bindFromRoles) { -%>
259
+ this.binder.fromRoles[<%- JSON.stringify(fromRole.roleName) %>].on(<%- JSON.stringify(handler.messageName) %>, async (payload, api) => {
260
+ await this.<%= handler.dispatchMethodName %>(payload, api)
261
+ })
262
+ <% } -%>
263
+ <% } -%>
264
+ if (canBindTransport(transport)) {
265
+ this.bindTransport(transport)
266
+ }
267
+ }
268
+
269
+ async connect(roleName, endpoint) {
270
+ switch (roleName) {
271
+ <% for (const remoteRole of ctx.remoteRoles) { -%>
272
+ case <%- JSON.stringify(remoteRole.roleName) %>: {
273
+ const remoteEndpoint = endpoint ?? <%- remoteRole.endpoints.length > 0 ? JSON.stringify(remoteRole.endpoints[0]) : 'undefined' %>
274
+ await this.transport.connect?.(roleName, remoteEndpoint)
275
+ if (!this.#apisByRole[<%- JSON.stringify(remoteRole.roleName) %>]) {
276
+ this.<%= remoteRole.apiVarName %> = this.runtime.createApi(<%- JSON.stringify(remoteRole.roleName) %>, this.sendEnvelope)
277
+ this.#apisByRole[<%- JSON.stringify(remoteRole.roleName) %>] = this.<%= remoteRole.apiVarName %>
278
+ <% for (const handler of ctx.handlers) { -%>
279
+ <% const handlerDefaultApi = handler.bindFromRoles.find(fromRole => ctx.remoteRoles.some(remoteRole => remoteRole.roleName === fromRole.roleName)) ?? ctx.remoteRoles[0] -%>
280
+ <% if (handlerDefaultApi?.roleName === remoteRole.roleName) { -%>
281
+ this.#apisByMessageName[<%- JSON.stringify(handler.messageName) %>] = this.<%= remoteRole.apiVarName %>
282
+ <% } -%>
283
+ <% } -%>
284
+ }
285
+ return this.<%= remoteRole.apiVarName %>
286
+ }
287
+ <% } -%>
288
+ default:
289
+ throw new Error(`Remote role ${roleName} not found`)
290
+ }
291
+ }
292
+
293
+ async disconnect(roleName) {
294
+ await this.transport.disconnect?.(roleName)
295
+ delete this.#apisByRole[roleName]
296
+ }
297
+
298
+ bindTransport(transport = this.transport, options = {}) {
299
+ this.#transportUnsubscribe?.()
300
+ this.#transportUnsubscribe = bindTransport(transport, this, options)
301
+ return this.#transportUnsubscribe
302
+ }
303
+
304
+ unbindTransport() {
305
+ this.#transportUnsubscribe?.()
306
+ this.#transportUnsubscribe = undefined
307
+ }
308
+
309
+ async messageError(error) {
310
+ for (const handler of this.#messageErrorHandlers) {
311
+ await handler(error)
312
+ }
313
+ }
314
+
315
+ onMessageError(handler) {
316
+ this.#messageErrorHandlers.add(handler)
317
+ return () => {
318
+ this.#messageErrorHandlers.delete(handler)
319
+ }
320
+ }
321
+
322
+ async socketError(error) {
323
+ for (const handler of this.#socketErrorHandlers) {
324
+ await handler(error)
325
+ }
326
+ }
327
+
328
+ onSocketError(handler) {
329
+ this.#socketErrorHandlers.add(handler)
330
+ return () => {
331
+ this.#socketErrorHandlers.delete(handler)
332
+ }
333
+ }
334
+
335
+ <% for (const handler of ctx.handlers) { -%>
336
+ async <%= handler.dispatchMethodName %>(payload, api) {
337
+ for (const handler of this.#<%= handler.listenerFieldName %>) {
338
+ await handler(payload, api)
339
+ }
340
+ }
341
+
342
+ <%= handler.onMethodName %>(handler) {
343
+ this.#<%= handler.listenerFieldName %>.add(handler)
344
+ return () => {
345
+ this.#<%= handler.listenerFieldName %>.delete(handler)
346
+ }
347
+ }
348
+
349
+ <% } -%>
350
+ async handleRawMessage(data) {
351
+ await this.handleMessage(decodeEnvelope(data))
352
+ }
353
+
354
+ async handleMessage(envelope) {
355
+ const remote = this.binder.fromRoles[envelope.fromRole]
356
+ const api = this.#apisByRole[envelope.fromRole]
357
+ if (remote && api) {
358
+ await remote.handleMessage(envelope.messageName, envelope.payload, api)
359
+ return
360
+ }
361
+
362
+ const localHandler = this.#handlersByMessageName[envelope.messageName]
363
+ const localApi = this.#apisByMessageName[envelope.messageName]
364
+ if (envelope.fromRole === this.#fromRole && localHandler && localApi) {
365
+ await localHandler(envelope.payload, localApi)
366
+ return
367
+ }
368
+
369
+ throw new Error(`Remote role ${envelope.fromRole} not found`)
370
+ }
371
+ }
372
+ <% } -%>
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "declaration": true,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src"
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ clean: true,
8
+ target: 'es2022',
9
+ outDir: 'dist',
10
+ })
@@ -0,0 +1,115 @@
1
+ import { Spec, Endpoint } from '@polytric/openws-spec/types';
2
+
3
+ interface PipelineContext {
4
+ argv: string[];
5
+ rawInput?: RawInput;
6
+ request?: BuildRequest;
7
+ spec?: Spec;
8
+ ir?: IR;
9
+ plan?: PlanStep[];
10
+ }
11
+ interface RawInput {
12
+ spec: string;
13
+ out: string;
14
+ project: string;
15
+ hostRole: string[];
16
+ language: 'csharp' | 'javascript' | 'typescript';
17
+ environment: 'unity' | 'node' | 'browser';
18
+ frameworks?: string[];
19
+ }
20
+ interface BuildRequest {
21
+ specPath: string;
22
+ outputPath: string;
23
+ project: string;
24
+ hostRoles: string[];
25
+ target: {
26
+ csharp?: {
27
+ environment: 'unity';
28
+ frameworks?: string[];
29
+ };
30
+ javascript?: {
31
+ environment: 'node' | 'browser';
32
+ frameworks?: string[];
33
+ };
34
+ typescript?: {
35
+ environment: 'node' | 'browser';
36
+ frameworks?: string[];
37
+ };
38
+ };
39
+ }
40
+ interface IR {
41
+ package: IRPackage;
42
+ networks: IRNetwork[];
43
+ assemblyName?: string;
44
+ }
45
+ interface IRPackage {
46
+ project: string;
47
+ service: string;
48
+ description?: string;
49
+ version?: string;
50
+ }
51
+ interface IRNetwork {
52
+ name: string;
53
+ description?: string;
54
+ version?: string;
55
+ roles: IRRole[];
56
+ handlers: IRHandler[];
57
+ messages: IRMessage[];
58
+ models: IRModel[];
59
+ }
60
+ interface IRRole {
61
+ name: string;
62
+ description?: string;
63
+ isHost: boolean;
64
+ endpoints: Endpoint[];
65
+ }
66
+ interface IRHandler {
67
+ roleName: string;
68
+ handlerName: string;
69
+ description?: string;
70
+ modelClassName?: string;
71
+ messageName?: string;
72
+ methodName?: string;
73
+ }
74
+ interface IRMessage {
75
+ roleName: string;
76
+ handlerName: string;
77
+ description?: string;
78
+ modelClassName?: string;
79
+ messageName?: string;
80
+ methodName?: string;
81
+ }
82
+ interface IRModel {
83
+ scopeName: string;
84
+ modelName: string;
85
+ type: string;
86
+ description?: string;
87
+ properties?: IRProperty[];
88
+ namespace?: string;
89
+ className?: string;
90
+ }
91
+ interface IRProperty {
92
+ type: string;
93
+ scopeName: string;
94
+ modelName: string;
95
+ description?: string;
96
+ required?: boolean;
97
+ items?: {
98
+ type: string;
99
+ scopeName: string;
100
+ modelName: string;
101
+ description?: string;
102
+ };
103
+ propertyName?: string;
104
+ typeName?: string;
105
+ }
106
+ interface PlanStep {
107
+ name: string;
108
+ command: 'copy' | 'render';
109
+ input?: string;
110
+ output: string;
111
+ template?: string;
112
+ getData?: () => unknown;
113
+ }
114
+
115
+ export type { PipelineContext as P };