@polytric/openws-sdkgen 0.0.12 → 0.0.14

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/README.md CHANGED
@@ -41,7 +41,7 @@ The CLI exposes the following options:
41
41
  - `--out` (string): Output directory for generated code.
42
42
  - `--project` (string): Project/namespace prefix for generated code.
43
43
  - `--network` (string): Network name to generate.
44
- - `--hostRole` (string): Participant role name that represents the host side.
44
+ - `--hostRole` (string): Peer role name that represents the host side.
45
45
  - `--language` (string): Target language (`csharp`, `javascript`, or `typescript`).
46
46
  - `--environment` (string|array): Target environment (`unity`, `node`, `browser`).
47
47
 
package/dist/main.cjs CHANGED
@@ -457,7 +457,7 @@ function parseInput(ctx) {
457
457
  }).option("hostRole", {
458
458
  type: "array",
459
459
  string: true,
460
- description: "The target participant roles that use the generated code",
460
+ description: "The target peer roles that use the generated code",
461
461
  demandOption: true
462
462
  }).option("language", {
463
463
  type: "string",
package/dist/main.js CHANGED
@@ -434,7 +434,7 @@ function parseInput(ctx) {
434
434
  }).option("hostRole", {
435
435
  type: "array",
436
436
  string: true,
437
- description: "The target participant roles that use the generated code",
437
+ description: "The target peer roles that use the generated code",
438
438
  demandOption: true
439
439
  }).option("language", {
440
440
  type: "string",
@@ -216,7 +216,7 @@ function createPlan(ctx) {
216
216
  const remoteRoles = getPeerRoles(networkSpec, rolesByName, hostRole.roleName).map(
217
217
  (remoteRole) => ({
218
218
  ...remoteRole,
219
- scopedApiName: `${hostRole.className}${remoteRole.className}Api`,
219
+ scopedPeerName: `${hostRole.className}${remoteRole.className}Peer`,
220
220
  allowedMethodNames: getAllowedMessageMethodNames(
221
221
  networkSpec,
222
222
  remoteRole.roleName,
@@ -342,9 +342,9 @@ function toRoleInfo(role) {
342
342
  className,
343
343
  roleClassName: className,
344
344
  hostRoleClassName: `${className}Host`,
345
- apiName: `${className}Api`,
345
+ peerName: `${className}Peer`,
346
346
  varName: camelCase(role.name),
347
- apiVarName: `${camelCase(role.name)}Api`,
347
+ peerVarName: `${camelCase(role.name)}Peer`,
348
348
  fileName,
349
349
  roleFileName: `${fileName}-role`,
350
350
  description: role.description || "",
@@ -176,7 +176,7 @@ function createPlan(ctx) {
176
176
  const remoteRoles = getPeerRoles(networkSpec, rolesByName, hostRole.roleName).map(
177
177
  (remoteRole) => ({
178
178
  ...remoteRole,
179
- scopedApiName: `${hostRole.className}${remoteRole.className}Api`,
179
+ scopedPeerName: `${hostRole.className}${remoteRole.className}Peer`,
180
180
  allowedMethodNames: getAllowedMessageMethodNames(
181
181
  networkSpec,
182
182
  remoteRole.roleName,
@@ -302,9 +302,9 @@ function toRoleInfo(role) {
302
302
  className,
303
303
  roleClassName: className,
304
304
  hostRoleClassName: `${className}Host`,
305
- apiName: `${className}Api`,
305
+ peerName: `${className}Peer`,
306
306
  varName: camelCase(role.name),
307
- apiVarName: `${camelCase(role.name)}Api`,
307
+ peerVarName: `${camelCase(role.name)}Peer`,
308
308
  fileName,
309
309
  roleFileName: `${fileName}-role`,
310
310
  description: role.description || "",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "files": ["dist"],
25
25
  "dependencies": {
26
- "@polytric/openws": "^0.0.6"
26
+ "@polytric/openws": "^0.0.7"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsup",
@@ -9,12 +9,18 @@ export const endpoints = <%- JSON.stringify(
9
9
  ) %>
10
10
 
11
11
  <% if (ctx.isTypeScript) { -%>
12
+ /**
13
+ * Wire-format message envelope used by this OpenWS SDK.
14
+ */
12
15
  export interface OpenWsEnvelope<Payload = unknown> {
13
16
  fromRole: string
14
17
  messageName: string
15
18
  payload: Payload
16
19
  }
17
20
 
21
+ /**
22
+ * Connection endpoint metadata from the OpenWS spec.
23
+ */
18
24
  export interface OpenWsEndpoint {
19
25
  scheme: string
20
26
  host?: string
@@ -23,9 +29,15 @@ export interface OpenWsEndpoint {
23
29
  }
24
30
 
25
31
  export type Unsubscribe = () => void
26
- export type TransportEvent = 'message' | 'error'
32
+ export type TransportEvent = 'message' | 'error' | 'close'
27
33
  export type TransportHandler = (data: unknown) => void | Promise<void>
28
34
 
35
+ /**
36
+ * Minimal transport contract used by generated SDK clients.
37
+ *
38
+ * Implement this interface to plug in a custom socket, browser WebSocket,
39
+ * server-side WebSocket client, or test transport.
40
+ */
29
41
  export interface Transport {
30
42
  send(data: string): void | Promise<void>
31
43
  on?(event: TransportEvent, handler: TransportHandler): unknown
@@ -38,24 +50,47 @@ export interface BindTransportOptions {
38
50
  closeOnError?: boolean
39
51
  }
40
52
 
53
+ /**
54
+ * Callback surface used by `bindTransport`.
55
+ *
56
+ * `handle...` methods are framework entrypoints called by transport glue.
57
+ * Generated clients expose matching `on...` methods for application callbacks.
58
+ */
41
59
  export interface RawMessageHandler {
42
60
  handleRawMessage(data: string): void | Promise<void>
43
- messageError?(error: unknown): void | Promise<void>
44
- socketError?(error: unknown): void | Promise<void>
61
+ handleMessageError?(error: unknown): void | Promise<void>
62
+ handleSocketError?(error: unknown): void | Promise<void>
63
+ handleSocketClose?(event: unknown): void | Promise<void>
45
64
  }
46
65
 
66
+ /**
67
+ * Encodes an OpenWS envelope for transport.
68
+ */
47
69
  export function encodeEnvelope(envelope: OpenWsEnvelope): string {
48
70
  return JSON.stringify(envelope)
49
71
  }
50
72
 
73
+ /**
74
+ * Decodes raw transport data into an OpenWS envelope.
75
+ */
51
76
  export function decodeEnvelope(data: string): OpenWsEnvelope {
52
77
  return JSON.parse(data) as OpenWsEnvelope
53
78
  }
54
79
 
80
+ /**
81
+ * Returns true when a transport can be bound to generated client callbacks.
82
+ */
55
83
  export function canBindTransport(transport: Transport): boolean {
56
84
  return typeof transport.on === 'function'
57
85
  }
58
86
 
87
+ /**
88
+ * Binds transport events to a generated client or compatible raw message handler.
89
+ *
90
+ * Transport `message` events call `handleRawMessage`, message handling failures
91
+ * call `handleMessageError`, transport `error` events call `handleSocketError`,
92
+ * and transport `close` events call `handleSocketClose`.
93
+ */
59
94
  export function bindTransport(
60
95
  transport: Transport,
61
96
  handler: RawMessageHandler,
@@ -65,14 +100,17 @@ export function bindTransport(
65
100
  try {
66
101
  await handler.handleRawMessage(await normalizeMessageData(data))
67
102
  } catch (error) {
68
- await handler.messageError?.(error)
103
+ await handler.handleMessageError?.(error)
69
104
  if (options.closeOnError) {
70
105
  transport.close?.()
71
106
  }
72
107
  }
73
108
  }
74
109
  const handleSocketError = async (error: unknown) => {
75
- await handler.socketError?.(error)
110
+ await handler.handleSocketError?.(error)
111
+ }
112
+ const handleSocketClose = async (event: unknown) => {
113
+ await handler.handleSocketClose?.(event)
76
114
  }
77
115
 
78
116
  const nodeHandler = (data: unknown, ..._args: unknown[]) => {
@@ -81,6 +119,9 @@ export function bindTransport(
81
119
  const nodeErrorHandler = (error: unknown, ..._args: unknown[]) => {
82
120
  void handleSocketError(error)
83
121
  }
122
+ const nodeCloseHandler = (event: unknown, ..._args: unknown[]) => {
123
+ void handleSocketClose(event)
124
+ }
84
125
 
85
126
  if (typeof transport.on !== 'function') {
86
127
  throw new Error('Transport must support on("message")')
@@ -88,12 +129,17 @@ export function bindTransport(
88
129
 
89
130
  const messageUnsubscribe = transport.on('message', nodeHandler)
90
131
  const errorUnsubscribe = transport.on('error', nodeErrorHandler)
132
+ const closeUnsubscribe = transport.on('close', nodeCloseHandler)
91
133
  return () => {
92
134
  if (typeof messageUnsubscribe === 'function') messageUnsubscribe()
93
135
  if (typeof errorUnsubscribe === 'function') errorUnsubscribe()
136
+ if (typeof closeUnsubscribe === 'function') closeUnsubscribe()
94
137
  }
95
138
  }
96
139
 
140
+ /**
141
+ * WebSocket-backed transport implementation for generated SDK clients.
142
+ */
97
143
  export class WsTransport implements Transport {
98
144
  private socket?: unknown
99
145
  private socketUnsubscribe?: Unsubscribe
@@ -101,6 +147,7 @@ export class WsTransport implements Transport {
101
147
  private readonly listeners: Record<TransportEvent, Set<TransportHandler>> = {
102
148
  message: new Set(),
103
149
  error: new Set(),
150
+ close: new Set(),
104
151
  }
105
152
 
106
153
  constructor(socket?: unknown) {
@@ -109,6 +156,9 @@ export class WsTransport implements Transport {
109
156
  }
110
157
  }
111
158
 
159
+ /**
160
+ * Opens the underlying WebSocket when needed and waits until it is ready.
161
+ */
112
162
  async connect(_roleName: string, endpoint?: OpenWsEndpoint): Promise<void> {
113
163
  if (!this.socket) {
114
164
  if (!endpoint) {
@@ -119,10 +169,16 @@ export class WsTransport implements Transport {
119
169
  await this.waitForOpen()
120
170
  }
121
171
 
172
+ /**
173
+ * Closes the underlying WebSocket connection.
174
+ */
122
175
  async disconnect(): Promise<void> {
123
176
  this.close()
124
177
  }
125
178
 
179
+ /**
180
+ * Sends already-encoded OpenWS envelope data.
181
+ */
126
182
  async send(data: string): Promise<void> {
127
183
  await this.waitForOpen()
128
184
  const socket = this.requireSocket() as { send?: (data: string) => void | Promise<void> }
@@ -132,6 +188,9 @@ export class WsTransport implements Transport {
132
188
  await socket.send(data)
133
189
  }
134
190
 
191
+ /**
192
+ * Registers a transport event callback.
193
+ */
135
194
  on(event: TransportEvent, handler: TransportHandler): Unsubscribe {
136
195
  this.listeners[event].add(handler)
137
196
  return () => {
@@ -139,17 +198,17 @@ export class WsTransport implements Transport {
139
198
  }
140
199
  }
141
200
 
201
+ /**
202
+ * Closes and clears the current socket.
203
+ */
142
204
  close(): void {
143
205
  const socket = this.socket as { close?: () => void } | undefined
144
206
  socket?.close?.()
145
- this.socketUnsubscribe?.()
146
- this.socket = undefined
147
- this.socketUnsubscribe = undefined
148
- this.openPromise = undefined
207
+ this.clearSocket()
149
208
  }
150
209
 
151
210
  private bindSocket(socket: unknown): void {
152
- this.socketUnsubscribe?.()
211
+ this.clearSocket()
153
212
  this.socket = socket
154
213
  this.openPromise = undefined
155
214
 
@@ -159,12 +218,31 @@ export class WsTransport implements Transport {
159
218
  const unsubscribeError = addSocketListener(socket, 'error', error => {
160
219
  void this.emit('error', error)
161
220
  })
221
+ const unsubscribeClose = addSocketListener(socket, 'close', event => {
222
+ void this.handleSocketClose(socket, event)
223
+ })
162
224
  this.socketUnsubscribe = () => {
163
225
  unsubscribeMessage()
164
226
  unsubscribeError()
227
+ unsubscribeClose()
165
228
  }
166
229
  }
167
230
 
231
+ private async handleSocketClose(socket: unknown, event: unknown): Promise<void> {
232
+ if (this.socket !== socket) {
233
+ return
234
+ }
235
+ this.clearSocket()
236
+ await this.emit('close', event)
237
+ }
238
+
239
+ private clearSocket(): void {
240
+ this.socketUnsubscribe?.()
241
+ this.socket = undefined
242
+ this.socketUnsubscribe = undefined
243
+ this.openPromise = undefined
244
+ }
245
+
168
246
  private async emit(event: TransportEvent, data: unknown): Promise<void> {
169
247
  for (const handler of this.listeners[event]) {
170
248
  await handler(data)
@@ -196,9 +274,14 @@ export class WsTransport implements Transport {
196
274
  cleanup()
197
275
  reject(error)
198
276
  })
277
+ const unsubscribeClose = addSocketListener(socket, 'close', event => {
278
+ cleanup()
279
+ reject(event instanceof Error ? event : new Error('WebSocket closed before opening'))
280
+ })
199
281
  cleanup = () => {
200
282
  unsubscribeOpen()
201
283
  unsubscribeError()
284
+ unsubscribeClose()
202
285
  }
203
286
  })
204
287
  await this.openPromise
@@ -243,7 +326,7 @@ function getReadyState(socket: unknown): number | undefined {
243
326
 
244
327
  function addSocketListener(
245
328
  socket: unknown,
246
- event: 'open' | 'message' | 'error',
329
+ event: 'open' | 'message' | 'error' | 'close',
247
330
  handler: (...args: unknown[]) => void
248
331
  ): Unsubscribe {
249
332
  const target = socket as {
@@ -289,31 +372,50 @@ function addSocketListener(
289
372
  }
290
373
  }
291
374
  <% } else { -%>
375
+ /**
376
+ * Encodes an OpenWS envelope for transport.
377
+ */
292
378
  export function encodeEnvelope(envelope) {
293
379
  return JSON.stringify(envelope)
294
380
  }
295
381
 
382
+ /**
383
+ * Decodes raw transport data into an OpenWS envelope.
384
+ */
296
385
  export function decodeEnvelope(data) {
297
386
  return JSON.parse(data)
298
387
  }
299
388
 
389
+ /**
390
+ * Returns true when a transport can be bound to generated client callbacks.
391
+ */
300
392
  export function canBindTransport(transport) {
301
393
  return typeof transport.on === 'function'
302
394
  }
303
395
 
396
+ /**
397
+ * Binds transport events to a generated client or compatible raw message handler.
398
+ *
399
+ * Transport `message` events call `handleRawMessage`, message handling failures
400
+ * call `handleMessageError`, transport `error` events call `handleSocketError`,
401
+ * and transport `close` events call `handleSocketClose`.
402
+ */
304
403
  export function bindTransport(transport, handler, options = {}) {
305
404
  const handleData = async data => {
306
405
  try {
307
406
  await handler.handleRawMessage(await normalizeMessageData(data))
308
407
  } catch (error) {
309
- await handler.messageError?.(error)
408
+ await handler.handleMessageError?.(error)
310
409
  if (options.closeOnError) {
311
410
  transport.close?.()
312
411
  }
313
412
  }
314
413
  }
315
414
  const handleSocketError = async error => {
316
- await handler.socketError?.(error)
415
+ await handler.handleSocketError?.(error)
416
+ }
417
+ const handleSocketClose = async event => {
418
+ await handler.handleSocketClose?.(event)
317
419
  }
318
420
 
319
421
  const nodeHandler = data => {
@@ -322,6 +424,9 @@ export function bindTransport(transport, handler, options = {}) {
322
424
  const nodeErrorHandler = error => {
323
425
  void handleSocketError(error)
324
426
  }
427
+ const nodeCloseHandler = event => {
428
+ void handleSocketClose(event)
429
+ }
325
430
 
326
431
  if (typeof transport.on !== 'function') {
327
432
  throw new Error('Transport must support on("message")')
@@ -329,12 +434,17 @@ export function bindTransport(transport, handler, options = {}) {
329
434
 
330
435
  const messageUnsubscribe = transport.on('message', nodeHandler)
331
436
  const errorUnsubscribe = transport.on('error', nodeErrorHandler)
437
+ const closeUnsubscribe = transport.on('close', nodeCloseHandler)
332
438
  return () => {
333
439
  if (typeof messageUnsubscribe === 'function') messageUnsubscribe()
334
440
  if (typeof errorUnsubscribe === 'function') errorUnsubscribe()
441
+ if (typeof closeUnsubscribe === 'function') closeUnsubscribe()
335
442
  }
336
443
  }
337
444
 
445
+ /**
446
+ * WebSocket-backed transport implementation for generated SDK clients.
447
+ */
338
448
  export class WsTransport {
339
449
  socket
340
450
  socketUnsubscribe
@@ -342,6 +452,7 @@ export class WsTransport {
342
452
  listeners = {
343
453
  message: new Set(),
344
454
  error: new Set(),
455
+ close: new Set(),
345
456
  }
346
457
 
347
458
  constructor(socket) {
@@ -350,6 +461,9 @@ export class WsTransport {
350
461
  }
351
462
  }
352
463
 
464
+ /**
465
+ * Opens the underlying WebSocket when needed and waits until it is ready.
466
+ */
353
467
  async connect(_roleName, endpoint) {
354
468
  if (!this.socket) {
355
469
  if (!endpoint) {
@@ -360,10 +474,16 @@ export class WsTransport {
360
474
  await this.waitForOpen()
361
475
  }
362
476
 
477
+ /**
478
+ * Closes the underlying WebSocket connection.
479
+ */
363
480
  async disconnect() {
364
481
  this.close()
365
482
  }
366
483
 
484
+ /**
485
+ * Sends already-encoded OpenWS envelope data.
486
+ */
367
487
  async send(data) {
368
488
  await this.waitForOpen()
369
489
  const socket = this.requireSocket()
@@ -373,6 +493,9 @@ export class WsTransport {
373
493
  await socket.send(data)
374
494
  }
375
495
 
496
+ /**
497
+ * Registers a transport event callback.
498
+ */
376
499
  on(event, handler) {
377
500
  this.listeners[event].add(handler)
378
501
  return () => {
@@ -380,16 +503,16 @@ export class WsTransport {
380
503
  }
381
504
  }
382
505
 
506
+ /**
507
+ * Closes and clears the current socket.
508
+ */
383
509
  close() {
384
510
  this.socket?.close?.()
385
- this.socketUnsubscribe?.()
386
- this.socket = undefined
387
- this.socketUnsubscribe = undefined
388
- this.openPromise = undefined
511
+ this.clearSocket()
389
512
  }
390
513
 
391
514
  bindSocket(socket) {
392
- this.socketUnsubscribe?.()
515
+ this.clearSocket()
393
516
  this.socket = socket
394
517
  this.openPromise = undefined
395
518
 
@@ -399,12 +522,31 @@ export class WsTransport {
399
522
  const unsubscribeError = addSocketListener(socket, 'error', error => {
400
523
  void this.emit('error', error)
401
524
  })
525
+ const unsubscribeClose = addSocketListener(socket, 'close', event => {
526
+ void this.handleSocketClose(socket, event)
527
+ })
402
528
  this.socketUnsubscribe = () => {
403
529
  unsubscribeMessage()
404
530
  unsubscribeError()
531
+ unsubscribeClose()
405
532
  }
406
533
  }
407
534
 
535
+ async handleSocketClose(socket, event) {
536
+ if (this.socket !== socket) {
537
+ return
538
+ }
539
+ this.clearSocket()
540
+ await this.emit('close', event)
541
+ }
542
+
543
+ clearSocket() {
544
+ this.socketUnsubscribe?.()
545
+ this.socket = undefined
546
+ this.socketUnsubscribe = undefined
547
+ this.openPromise = undefined
548
+ }
549
+
408
550
  async emit(event, data) {
409
551
  for (const handler of this.listeners[event]) {
410
552
  await handler(data)
@@ -436,9 +578,14 @@ export class WsTransport {
436
578
  cleanup()
437
579
  reject(error)
438
580
  })
581
+ const unsubscribeClose = addSocketListener(socket, 'close', event => {
582
+ cleanup()
583
+ reject(event instanceof Error ? event : new Error('WebSocket closed before opening'))
584
+ })
439
585
  cleanup = () => {
440
586
  unsubscribeOpen()
441
587
  unsubscribeError()
588
+ unsubscribeClose()
442
589
  }
443
590
  })
444
591
  await this.openPromise
@@ -10,7 +10,7 @@ import type { <%= payloadImports.join(', ') %> } from '<%= modelImportPath %>'
10
10
  import { <%= peerRoleImports.join(', ') %> } from './index'
11
11
  <% } -%>
12
12
 
13
- export interface <%= ctx.apiName %> {
13
+ export interface <%= ctx.peerName %> {
14
14
  <% for (const message of ctx.messages) { -%>
15
15
  <%= message.methodName %>(payload: <%= message.payloadType %> | <%= message.payloadType %>Init): Promise<void>
16
16
  <% } -%>