@tertiumorganum/typespec-amqp-ws 0.0.2 → 0.0.3
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 +0 -4
- package/docs/README.md +48 -0
- package/docs/architecture.md +319 -0
- package/docs/usage.md +281 -0
- package/examples/amqp-consume.tsp +26 -0
- package/examples/amqp-publish.tsp +25 -0
- package/examples/ws-discriminator.tsp +26 -0
- package/examples/ws-reply.tsp +29 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -282,10 +282,6 @@ model M { payload: MPayload; }
|
|
|
282
282
|
- `ws-discriminator.tsp` — WebSocket с literal-дискриминатором.
|
|
283
283
|
- `ws-reply.tsp` — WebSocket с request/reply.
|
|
284
284
|
|
|
285
|
-
## Контрибьюции
|
|
286
|
-
|
|
287
|
-
Pull-requests и issues приветствуются. Это утилита под конкретный воркфлоу; решения по новым фичам принимаются исходя из того, насколько они вписываются в принцип "узкий набор фич для типичных кейсов".
|
|
288
|
-
|
|
289
285
|
## Лицензия
|
|
290
286
|
|
|
291
287
|
[MIT](LICENSE).
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Документация `typespec-amqp-ws`
|
|
2
|
+
|
|
3
|
+
Эмиттер TypeSpec для генерации AsyncAPI 3.0 спецификаций.
|
|
4
|
+
|
|
5
|
+
## Содержание
|
|
6
|
+
|
|
7
|
+
- [usage.md](usage.md) — Установка, конфигурация, синтаксис, поддерживаемые типы, типичные сценарии использования.
|
|
8
|
+
- [architecture.md](architecture.md) — Внутреннее устройство эмиттера: модули, поток данных, принципы преобразования TypeSpec в JSON Schema, особенности реализации.
|
|
9
|
+
|
|
10
|
+
## TL;DR
|
|
11
|
+
|
|
12
|
+
`typespec-amqp-ws` — это TypeSpec-эмиттер, который превращает декларативное описание сервиса на TypeSpec в YAML-файл AsyncAPI 3.0. Поддерживает два транспорта:
|
|
13
|
+
|
|
14
|
+
- **AMQP (RabbitMQ)** — publish в exchange, consume из очереди, exchange-типы `direct` и `fanout`.
|
|
15
|
+
- **WebSocket** — единый канал `/`, дискриминатор сообщений через literal-типы, request/reply, бинарные сообщения через `contentType: application/octet-stream`.
|
|
16
|
+
|
|
17
|
+
Эмиттер задуман с осознанным **ограниченным** объёмом фич: только то, что реально нужно для микросервисов команды. Не Kafka, не MQTT, не security schemes, не topic-exchanges, не numeric enums. Из-за этого его реализация и API проще, чем у универсальных AsyncAPI-эмиттеров.
|
|
18
|
+
|
|
19
|
+
## Минимальный пример
|
|
20
|
+
|
|
21
|
+
```typespec
|
|
22
|
+
import "typespec-amqp-ws";
|
|
23
|
+
|
|
24
|
+
using TspAsyncApi;
|
|
25
|
+
using TspAsyncApi.Amqp;
|
|
26
|
+
|
|
27
|
+
@service(#{ title: "My Service" })
|
|
28
|
+
@info(#{ version: "1.0.0" })
|
|
29
|
+
@server("rabbit", #{ host: "localhost:5672", protocol: "amqp" })
|
|
30
|
+
namespace MyService;
|
|
31
|
+
|
|
32
|
+
model Event {
|
|
33
|
+
id: string;
|
|
34
|
+
payload: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@publish(#{
|
|
38
|
+
routingKey: "events.created",
|
|
39
|
+
exchange: #{ name: "events", type: "direct", durable: true },
|
|
40
|
+
})
|
|
41
|
+
op sendEvent(): Event;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
После `tsp compile .` получите валидный `asyncapi.yaml`, который скармливается стандартным инструментам — `modelina` для генерации Go/TS-моделей, `redocly` для HTML-документации.
|
|
45
|
+
|
|
46
|
+
## Лицензия
|
|
47
|
+
|
|
48
|
+
[MIT](../LICENSE).
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Архитектура `typespec-amqp-ws`
|
|
2
|
+
|
|
3
|
+
Этот документ — для тех, кто разрабатывает или модифицирует эмиттер. Описывает внутреннее устройство, ключевые архитектурные решения и точки расширения.
|
|
4
|
+
|
|
5
|
+
## Общая картина
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
TypeSpec sources (.tsp)
|
|
9
|
+
│
|
|
10
|
+
▼ tsp compile
|
|
11
|
+
TypeSpec compiler AST + decorator state map
|
|
12
|
+
│
|
|
13
|
+
▼ $onEmit($context)
|
|
14
|
+
┌────────────────────────────────────────────────┐
|
|
15
|
+
│ emitAsyncApi(context, target) │
|
|
16
|
+
│ ├─ buildServiceInfo (info + servers) │
|
|
17
|
+
│ ├─ SchemaBuilder (модели/enum/scalars) │
|
|
18
|
+
│ ├─ buildAmqp либо buildWs │
|
|
19
|
+
│ │ (channels + operations + messages) │
|
|
20
|
+
│ └─ serialize → YAML/JSON │
|
|
21
|
+
└────────────────────────────────────────────────┘
|
|
22
|
+
│
|
|
23
|
+
▼ writeFile
|
|
24
|
+
asyncapi.yaml (или .json)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Эмиттер построен на **`@typespec/asset-emitter`** — тот же фреймворк, на котором работают официальные `@typespec/openapi3` и `@typespec/json-schema`. Это даёт нам стандартные механизмы для разрешения `$ref`-ссылок, именования схем, обхода типов компилятора.
|
|
28
|
+
|
|
29
|
+
## Структура пакета
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
asyncapi/
|
|
33
|
+
├── package.json # exports: ".", "./amqp", "./ws", "./testing"
|
|
34
|
+
├── lib/
|
|
35
|
+
│ ├── main.tsp # точка входа (импортируется при `import "typespec-amqp-ws"`)
|
|
36
|
+
│ ├── amqp.tsp # объявления декораторов TspAsyncApi.Amqp
|
|
37
|
+
│ └── ws.tsp # объявления декораторов TspAsyncApi.WebSocket
|
|
38
|
+
├── src/
|
|
39
|
+
│ ├── index.ts # корневой entry: экспортирует $lib
|
|
40
|
+
│ ├── shared/
|
|
41
|
+
│ │ ├── lib.ts # createTypeSpecLibrary, диагностики, опции
|
|
42
|
+
│ │ ├── options.ts # JSON Schema опций эмиттера
|
|
43
|
+
│ │ ├── state.ts # Symbol-ключи для program.stateMap
|
|
44
|
+
│ │ ├── document.ts # типы AsyncApiDoc, AsyncApiOperation и т.п.
|
|
45
|
+
│ │ ├── schema-emitter.ts # SchemaBuilder: TypeSpec → JSON Schema
|
|
46
|
+
│ │ ├── decorators-service.ts # @info, @server (namespace = "TspAsyncApi")
|
|
47
|
+
│ │ ├── asyncapi-emitter.ts # оркестратор emitAsyncApi()
|
|
48
|
+
│ │ └── yaml-writer.ts # сериализация YAML/JSON
|
|
49
|
+
│ ├── amqp/
|
|
50
|
+
│ │ ├── index.ts # $onEmit для AMQP-таргета
|
|
51
|
+
│ │ ├── decorators.ts # @publish, @consume, @message (namespace = "TspAsyncApi.Amqp")
|
|
52
|
+
│ │ └── builder.ts # buildAmqp(program, doc)
|
|
53
|
+
│ └── ws/
|
|
54
|
+
│ ├── index.ts # $onEmit для WS-таргета
|
|
55
|
+
│ ├── decorators.ts # @publish, @consume, @reply, @binary, @message (namespace = "TspAsyncApi.WebSocket")
|
|
56
|
+
│ └── builder.ts # buildWs(program, doc)
|
|
57
|
+
└── test/ # vitest-тесты
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ключевые решения
|
|
61
|
+
|
|
62
|
+
### Один пакет — два emit-таргета
|
|
63
|
+
|
|
64
|
+
`typespec-amqp-ws` экспортирует два независимых эмиттера через **sub-path exports** в `package.json`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
"exports": {
|
|
68
|
+
".": { ... }, // корневые декораторы (@info, @server)
|
|
69
|
+
"./amqp": { ... }, // $onEmit для AMQP
|
|
70
|
+
"./ws": { ... } // $onEmit для WebSocket
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Пользователь подключает один из них в `tspconfig.yaml`:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
emit:
|
|
78
|
+
- "typespec-amqp-ws/amqp" # или "/ws"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Почему так**: каждый сервис должен иметь либо AMQP, либо WebSocket API. Смешивать их в одном `asyncapi.yaml` — концептуально неправильно (разные транспорты, разные паттерны). Два emit-таргета обеспечивают чёткое разделение, при этом весь общий код (схемы, info, servers) живёт в `src/shared/`.
|
|
82
|
+
|
|
83
|
+
Эта возможность (`exports` с sub-paths в TypeSpec-пакете) появилась в TypeSpec 1.0+. До 1.0 пришлось бы делать два отдельных npm-пакета.
|
|
84
|
+
|
|
85
|
+
### Декораторы — три namespace, три JS-модуля
|
|
86
|
+
|
|
87
|
+
| TypeSpec namespace | JS-модуль (с `export const namespace`) |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `TspAsyncApi` | `src/shared/decorators-service.ts` |
|
|
90
|
+
| `TspAsyncApi.Amqp` | `src/amqp/decorators.ts` |
|
|
91
|
+
| `TspAsyncApi.WebSocket` | `src/ws/decorators.ts` |
|
|
92
|
+
|
|
93
|
+
Каждый JS-модуль с декораторами объявляет `export const namespace = "..."`. Это **обязательное соглашение** TypeSpec — компилятор по этому экспорту узнаёт, к какому namespace относятся `$decoratorName`-функции из этого файла.
|
|
94
|
+
|
|
95
|
+
Все три файла импортируются в `lib/main.tsp` через `import "../dist/src/.../decorators.js"`. Когда пользователь пишет `import "typespec-amqp-ws"`, эта цепочка подгружает декораторы.
|
|
96
|
+
|
|
97
|
+
### Декораторы только пишут в state, эмиттер только читает
|
|
98
|
+
|
|
99
|
+
Все декораторы устроены одинаково:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
export function $publish(context: DecoratorContext, target: Operation, config: PublishConfig): void {
|
|
103
|
+
// (опциональная валидация config)
|
|
104
|
+
context.program.stateMap(AmqpPublishKey).set(target, config);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Они **не** делают эмит, не модифицируют типы, не обращаются к другим декораторам. Это правило TypeSpec — порядок выполнения декораторов относительно других файлов не гарантирован, поэтому декоратор должен только сохранять данные.
|
|
109
|
+
|
|
110
|
+
Эмиттер `$onEmit` затем обходит программу через `navigateProgram` и читает state.
|
|
111
|
+
|
|
112
|
+
### State-keys через Symbol.for()
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// src/shared/state.ts
|
|
116
|
+
export const AmqpPublishKey = Symbol.for("typespec-amqp-ws.amqp.publish");
|
|
117
|
+
export const AmqpConsumeKey = Symbol.for("typespec-amqp-ws.amqp.consume");
|
|
118
|
+
// ...
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`Symbol.for(...)` создаёт глобальный symbol — два разных JS-модуля, получающие symbol с одной строкой, получат **тот же** symbol. Это важно, потому что декораторы и эмиттер живут в разных файлах, но обращаются к одному state.
|
|
122
|
+
|
|
123
|
+
### `@typespec/asset-emitter` vs самописный обход
|
|
124
|
+
|
|
125
|
+
Для большинства схемных типов мы используем **самописный обход** через `navigateProgram`. `@typespec/asset-emitter` (TypeEmitter) обеспечивает только общую инфраструктуру — `$ref` resolution мы делаем вручную через имена.
|
|
126
|
+
|
|
127
|
+
**Почему так**: наш набор типов узкий (запрещены циклические зависимости через `Union`, нет `oneOf`/`anyOf`), и простой `navigateProgram + map имён в namedSchemas` справляется. TypeEmitter добавил бы сложности (lifecycle методы, кэширование), которая для нашего объёма не окупается.
|
|
128
|
+
|
|
129
|
+
### Запрет анонимных моделей в полях
|
|
130
|
+
|
|
131
|
+
Принципиальное решение: анонимные `{...}`-объекты в полях запрещены. Только именованные `model X`.
|
|
132
|
+
|
|
133
|
+
```typespec
|
|
134
|
+
// ❌ ошибка эмиттера
|
|
135
|
+
model M {
|
|
136
|
+
payload: { foo: string };
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Почему**: `modelina` (генератор Go/TS моделей) требует осмысленных имён для типов. Авто-генерация имён (`AnonymousSchema1`, и т.п.) ненадёжна и непредсказуема. Запрет на старте проще, чем чинить потом.
|
|
141
|
+
|
|
142
|
+
### `additionalProperties: false` по умолчанию
|
|
143
|
+
|
|
144
|
+
В отличие от `@typespec/openapi3` (где `additionalProperties` по умолчанию не указан, что эквивалентно "разрешены"), у нас на **каждой** модели объекта по умолчанию `additionalProperties: false`.
|
|
145
|
+
|
|
146
|
+
**Почему**: это соответствует рукописной AsyncAPI-конвенции команды. "Зачем мне в структуры произвольно добавлять всякую фигню" (с) — лучше быть строгим по умолчанию.
|
|
147
|
+
|
|
148
|
+
### Узкий белый список типов с диагностиками
|
|
149
|
+
|
|
150
|
+
В `SchemaBuilder.scalarSchema` и связанной логике мы **явно отбрасываем**:
|
|
151
|
+
- Размер-специфичные int (int8/16/32, uint8/16/32) → ошибка `unsupported-sized-int`
|
|
152
|
+
- 64-битные числа → ошибка `unsupported-int64`
|
|
153
|
+
- Float / decimal → ошибка `unsupported-float`
|
|
154
|
+
- Date / time типы → ошибка `unsupported-temporal`
|
|
155
|
+
- URL → ошибка `unsupported-url`
|
|
156
|
+
- `safeint`/`numeric` → ошибка `unsupported-fuzzy-numeric`
|
|
157
|
+
|
|
158
|
+
**Почему**: эмпирически проверено, что modelina и openapi-generator-cli **игнорируют** `format` подсказки и генерируют `int32`/`number`/`string` независимо. Размер-специфичные типы создают ложное ожидание. На границе между языками (C++, Go, TypeScript) переносимо работают только `string`, `boolean`, `integer`, `bytes`. Любой другой числовой тип — мина под кодгеном.
|
|
159
|
+
|
|
160
|
+
### `allOf`-обёртка для $ref с description
|
|
161
|
+
|
|
162
|
+
Если у поля модели есть `@doc`, а тип — именованный (scalar или другая model), то генерируется:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
field:
|
|
166
|
+
allOf:
|
|
167
|
+
- $ref: '#/components/schemas/SomeType'
|
|
168
|
+
description: "пояснение"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Почему**: JSON Schema запрещает рядом с `$ref` иметь другие keywords. `allOf` — стандартный обход этого ограничения. Это копия поведения `@typespec/openapi3` — гарантирует совместимость с тем же `modelina`.
|
|
172
|
+
|
|
173
|
+
### Namespace-префикс через `.`
|
|
174
|
+
|
|
175
|
+
```typespec
|
|
176
|
+
namespace MyService;
|
|
177
|
+
namespace business_event {
|
|
178
|
+
model TokenIssued { ... }
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
→
|
|
183
|
+
```yaml
|
|
184
|
+
components.schemas:
|
|
185
|
+
business_event.TokenIssued: ...
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Корневой service-namespace (`MyService`) **не входит** в префикс — он считается "scope" сервиса. Вложенные namespace — входят через `.`.
|
|
189
|
+
|
|
190
|
+
Это копия поведения `@typespec/openapi3` для имён схем (для operationId openapi3 использует `_`, но у AsyncAPI нет operationId как такового — все ключи в `operations:` объекте).
|
|
191
|
+
|
|
192
|
+
## Поток обработки
|
|
193
|
+
|
|
194
|
+
### 1. Декораторы пишут state
|
|
195
|
+
|
|
196
|
+
При обходе `.tsp`-файлов компилятор вызывает декораторы. Они записывают конфиги в `program.stateMap(<Key>)`:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
@publish(#{...}) → AmqpPublishKey.set(operation, config)
|
|
200
|
+
@consume(#{...}) → AmqpConsumeKey.set(operation, config)
|
|
201
|
+
@reply(M) → WsReplyKey.set(operation, M)
|
|
202
|
+
@binary → WsBinaryKey.set(operation, true)
|
|
203
|
+
@info(#{...}) → InfoKey.set(namespace, config)
|
|
204
|
+
@server(name, ...) → ServerKey.set(namespace, Map<string, ServerConfig>)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 2. `$onEmit(context)` запускается компилятором после AST-парсинга
|
|
208
|
+
|
|
209
|
+
`src/amqp/index.ts` (или `src/ws/index.ts`) экспортирует `$onEmit`, который делегирует в `emitAsyncApi(context, "amqp"|"ws")`.
|
|
210
|
+
|
|
211
|
+
### 3. `emitAsyncApi` собирает документ
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const doc: AsyncApiDoc = emptyDoc(); // { asyncapi: "3.0.0", info: ... }
|
|
215
|
+
buildServiceInfo(context, doc); // info + servers
|
|
216
|
+
new SchemaBuilder(program).collect(); // обход моделей/enums/scalars
|
|
217
|
+
// → components.schemas
|
|
218
|
+
buildAmqp(program, doc); // или buildWs // channels + operations + messages
|
|
219
|
+
serialize(doc, opts); // → YAML или JSON
|
|
220
|
+
host.writeFile(path, text); // mkdirp + write
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 4. SchemaBuilder обход
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
navigateProgram(program, {
|
|
227
|
+
model: m => this.addModel(m),
|
|
228
|
+
enum: e => this.addEnum(e),
|
|
229
|
+
scalar: s => this.addScalar(s),
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Для каждого типа `addX` фильтрует stdlib (`isInLibraryNs`), конструирует JSON Schema-фрагмент, кладёт в `namedSchemas`. Поля модели рекурсивно обрабатываются через `schemaFor(prop.type)`.
|
|
234
|
+
|
|
235
|
+
### 5. buildAmqp / buildWs
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
navigateProgram(program, {
|
|
239
|
+
operation(op) {
|
|
240
|
+
const pub = program.stateMap(AmqpPublishKey).get(op);
|
|
241
|
+
const con = program.stateMap(AmqpConsumeKey).get(op);
|
|
242
|
+
if (pub) attachPublish(...);
|
|
243
|
+
else if (con) attachConsume(...);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`attachPublish` собирает channel + operation + message:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
doc.channels[channelKey] = {
|
|
252
|
+
address: config.routingKey,
|
|
253
|
+
bindings: { amqp: { is: "routingKey", exchange: cleanExchange(config.exchange) } },
|
|
254
|
+
messages: { [messageKey]: { $ref: ... } },
|
|
255
|
+
};
|
|
256
|
+
doc.operations[op.name] = {
|
|
257
|
+
action: "send",
|
|
258
|
+
channel: { $ref: ... },
|
|
259
|
+
messages: [{ $ref: ... }],
|
|
260
|
+
summary, description,
|
|
261
|
+
};
|
|
262
|
+
doc.components.messages[messageKey] = {
|
|
263
|
+
payload: { $ref: `#/components/schemas/${returnTypeName}` },
|
|
264
|
+
};
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`buildWs` устроен похоже, но все операции сворачивает на единый канал `/`, плюс обрабатывает `@reply` и `@binary`.
|
|
268
|
+
|
|
269
|
+
### 6. Сериализация
|
|
270
|
+
|
|
271
|
+
`yaml.dump(doc, { lineWidth: 120, noRefs: true, sortKeys: false })`. `sortKeys: false` сохраняет порядок вставки полей в JS-объекте — поэтому документ читается в логическом порядке (asyncapi → info → servers → channels → operations → components).
|
|
272
|
+
|
|
273
|
+
## Тестирование
|
|
274
|
+
|
|
275
|
+
Тесты на `vitest`. Главный харнесс — `test/utils/test-host.ts`:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const result = await emit(`...TypeSpec код...`, "amqp" | "ws");
|
|
279
|
+
expectNoErrors(result);
|
|
280
|
+
expect(result.doc.components.schemas.X).toEqual({...});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Под капотом `emit()` использует `createTestHost` + `createTestWrapper` из `@typespec/compiler/testing`. Все имеющиеся `tsp`-фикстуры компилируются в виртуальной FS, эмиттер пишет туда `asyncapi.yaml`, мы его парсим и инспектим.
|
|
284
|
+
|
|
285
|
+
Snapshot-тесты на нескольких end-to-end фикстурах в `test/fixtures/` (по одному файлу на типичный сценарий: AMQP send+receive, AMQP fanout, WebSocket с дискриминатором, WebSocket с reply и binary) лежат в `test/__snapshots__/fixtures.test.ts.snap` — это контракт регрессии.
|
|
286
|
+
|
|
287
|
+
## Точки расширения
|
|
288
|
+
|
|
289
|
+
### Добавить новый AsyncAPI binding (например, Kafka)
|
|
290
|
+
|
|
291
|
+
1. Создать `src/kafka/` с `decorators.ts` (namespace = "TspAsyncApi.Kafka") и `builder.ts`
|
|
292
|
+
2. Добавить `lib/kafka.tsp` с extern dec'ами
|
|
293
|
+
3. Импортировать `lib/kafka.tsp` в `lib/main.tsp`
|
|
294
|
+
4. Добавить новый emit-таргет: `"./kafka"` в `package.json` exports, `src/kafka/index.ts` с `$onEmit`
|
|
295
|
+
5. В `emitAsyncApi` (или общем switch) добавить ветку `if (target === "kafka") buildKafka(...)`
|
|
296
|
+
6. Добавить тесты
|
|
297
|
+
|
|
298
|
+
### Поддержать новый TypeSpec-тип в схемах
|
|
299
|
+
|
|
300
|
+
В `src/shared/schema-emitter.ts` метод `schemaFor(type)` — это switch по `type.kind`. Добавить ветку и при необходимости — отдельный `addX(...)` для именованных типов.
|
|
301
|
+
|
|
302
|
+
### Новая диагностика
|
|
303
|
+
|
|
304
|
+
В `src/shared/lib.ts` секция `diagnostics:` — добавить новый код с `severity`, `messages.default` (с `paramMessage` для интерполяции). Использовать в коде через `reportDiagnostic(program, { code, target, format })`.
|
|
305
|
+
|
|
306
|
+
## Совместимость
|
|
307
|
+
|
|
308
|
+
- `@typespec/compiler` ^1.12.0 — API стабилен (1.0+).
|
|
309
|
+
- `@typespec/asset-emitter` ^0.79.0 — пока pre-1.0, может ломаться в будущем.
|
|
310
|
+
- Тестируется на Node.js 22+ и 24+.
|
|
311
|
+
|
|
312
|
+
Запас совместимости с TypeSpec API минимальный — мы используем `@typespec/compiler` напрямую (типы `Type`, `Model`, `Enum`, `Scalar`, `Operation`, `Namespace`, `Union`). Если они мигрируют — придётся обновлять. Сейчас в 1.x намерение Microsoft — держать API стабильным.
|
|
313
|
+
|
|
314
|
+
## Известные ограничения
|
|
315
|
+
|
|
316
|
+
- **Циклические зависимости в Union** не поддерживаются (валится в `unionSchema`). На практике для наших сервисов не используются.
|
|
317
|
+
- **Только `T | null` форма Union**. Прочие union'ы — диагностика `unsupported-union`. Если нужны полиморфные сообщения в будущем — потребуется реализация `oneOf`/`discriminator`.
|
|
318
|
+
- **Реальная FS** — `writeFile` требует чтобы родительская директория существовала (для test-FS она auto-create). Мы делаем `host.mkdirp` перед записью.
|
|
319
|
+
- **Версия AsyncAPI** хардкоднута на `3.0.0`. Для 3.1 нужно поменять одну константу в `document.ts` и проверить, что `modelina`/`redocly` принимают.
|
package/docs/usage.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Использование `@tertiumorganum/typespec-amqp-ws`
|
|
2
|
+
|
|
3
|
+
## Установка
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -D @tertiumorganum/typespec-amqp-ws @typespec/compiler @typespec/asset-emitter
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Требования:
|
|
10
|
+
- Node.js 22+ (рекомендуется 24+ для совместимости с `@asyncapi/cli`)
|
|
11
|
+
- TypeSpec 1.12+
|
|
12
|
+
|
|
13
|
+
## Конфигурация
|
|
14
|
+
|
|
15
|
+
В корне папки с TypeSpec-описанием сервиса (например, `<service>/asyncapi/`) создаётся файл `tspconfig.yaml`. Эмиттер `@tertiumorganum/typespec-amqp-ws` имеет два emit-таргета: `/amqp` и `/ws`. Один проект использует **один** из них.
|
|
16
|
+
|
|
17
|
+
### Конфиг для AMQP-сервиса
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
emit:
|
|
21
|
+
- "@tertiumorganum/typespec-amqp-ws/amqp"
|
|
22
|
+
options:
|
|
23
|
+
"@tertiumorganum/typespec-amqp-ws/amqp":
|
|
24
|
+
file-type: yaml # yaml (default) или json
|
|
25
|
+
output-file: "asyncapi.yaml"
|
|
26
|
+
new-line: "lf" # lf (default) или crlf
|
|
27
|
+
output-dir: "{project-root}/tsp-output"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Конфиг для WebSocket-сервиса
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
emit:
|
|
34
|
+
- "@tertiumorganum/typespec-amqp-ws/ws"
|
|
35
|
+
options:
|
|
36
|
+
"@tertiumorganum/typespec-amqp-ws/ws":
|
|
37
|
+
file-type: yaml
|
|
38
|
+
output-file: "asyncapi.yaml"
|
|
39
|
+
new-line: "lf"
|
|
40
|
+
output-dir: "{project-root}/tsp-output"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
После компиляции (`tsp compile .`) сгенерированный YAML лежит в `tsp-output/@tertiumorganum/typespec-amqp-ws/asyncapi.yaml`. Дальнейший пайплайн (redocly lint, modelina codegen, документация) идёт от этого файла.
|
|
44
|
+
|
|
45
|
+
## Структура проекта
|
|
46
|
+
|
|
47
|
+
Рекомендованная (соответствует тому, как у нас в команде):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
<service>/asyncapi/
|
|
51
|
+
├── main.tsp # @service / @info / @server + операции
|
|
52
|
+
├── tsp-components/
|
|
53
|
+
│ └── models.tsp # scalars + enums + модели
|
|
54
|
+
├── tspconfig.yaml # конфиг эмиттера
|
|
55
|
+
├── package.json # зависимости (typespec, asset-emitter)
|
|
56
|
+
├── redocly.yaml # конфиг линтера
|
|
57
|
+
└── Makefile # include шаблонного build pipeline
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API эмиттера
|
|
61
|
+
|
|
62
|
+
### Декораторы общего назначения (namespace `TspAsyncApi`)
|
|
63
|
+
|
|
64
|
+
| Декоратор | Применяется к | Что делает |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `@service(#{title})` | namespace | Стандартный из `@typespec/compiler`. Маркирует namespace как корневой сервис. |
|
|
67
|
+
| `@info(#{...})` | namespace | Заполняет блок `info:` AsyncAPI. Поля: `version`, `description?`, `contact?{name?, url?, email?}`, `license?{name, url?}`, `externalDocs?{url, description?}` |
|
|
68
|
+
| `@server(name, #{...})` | namespace | Описывает один сервер (брокер). Поля: `host`, `protocol`, `pathname?`, `description?`, `variables?: Record<#{default?, description?, enum?}>` |
|
|
69
|
+
|
|
70
|
+
### Декораторы AMQP (namespace `TspAsyncApi.Amqp`)
|
|
71
|
+
|
|
72
|
+
| Декоратор | Применяется к | Описание |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `@publish(#{...})` | op | Операция-publisher → `action: send`. Поля: `channelName?`, `routingKey?`, `exchange: #{name, type: "direct"\|"fanout", durable?, autoDelete?}` |
|
|
75
|
+
| `@consume(#{...})` | op | Операция-consumer → `action: receive`. Поля: `channelName?`, `routingKey?`, `queue: #{name, durable?, autoDelete?, exclusive?}` |
|
|
76
|
+
| `@message(#{...})` | op | Override параметров сообщения: `name?`, `summary?` |
|
|
77
|
+
|
|
78
|
+
### Декораторы WebSocket (namespace `TspAsyncApi.WebSocket`)
|
|
79
|
+
|
|
80
|
+
| Декоратор | Применяется к | Описание |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `@publish` | op | без аргументов → `action: send` |
|
|
83
|
+
| `@consume` | op | без аргументов → `action: receive` |
|
|
84
|
+
| `@reply(MessageModel)` | op | Модель ответного сообщения (request/reply pattern). Reply-сообщение автоматически добавится в канал и `components.messages`. |
|
|
85
|
+
| `@binary` | op | Помечает сообщение бинарным → `contentType: application/octet-stream` |
|
|
86
|
+
| `@message(#{...})` | op | Override параметров сообщения |
|
|
87
|
+
|
|
88
|
+
### Стандартные TypeSpec-декораторы
|
|
89
|
+
|
|
90
|
+
Из `@typespec/compiler`:
|
|
91
|
+
- `@doc("...")` — длинное описание (попадает в `description` YAML)
|
|
92
|
+
- `@summary("...")` — короткое summary (попадает в `summary` YAML)
|
|
93
|
+
|
|
94
|
+
## Поддерживаемые TypeSpec-типы
|
|
95
|
+
|
|
96
|
+
| TypeSpec | YAML | Go / TS / C++ |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `string` | `{type: string}` | string / string / std::string |
|
|
99
|
+
| `boolean` | `{type: boolean}` | bool / boolean / bool |
|
|
100
|
+
| `integer` | `{type: integer}` | int32 / number / int |
|
|
101
|
+
| `bytes` | `{type: string, format: binary}` | []byte / Uint8Array / std::vector<uint8_t> |
|
|
102
|
+
| `enum X { A, B }` | `{type: string, enum: [A, B]}` | string-typedef с константами |
|
|
103
|
+
| `scalar X extends string` | `{type: string}` в `components.schemas.X` | именованный string-typedef |
|
|
104
|
+
| `model X { ... }` | `{type: object, properties, required, additionalProperties: false}` | сгенерированная структура |
|
|
105
|
+
| `T[]` | `{type: array, items: <T>}` | slice / array |
|
|
106
|
+
| `Record<T>` | `{type: object, additionalProperties: <T>}` | `map[string]T` / `Record<string, T>` |
|
|
107
|
+
| literal `"foo"` (на поле модели) | `{type: string, const: "foo"}` | const-значение |
|
|
108
|
+
| `T \| null` | `{type: [<base>, null]}` | pointer / nullable |
|
|
109
|
+
| `field?: T` | поле не в `required` | optional |
|
|
110
|
+
|
|
111
|
+
## Запрещённые типы (ошибка компиляции)
|
|
112
|
+
|
|
113
|
+
Эмиттер намеренно **запрещает**:
|
|
114
|
+
|
|
115
|
+
| Тип | Почему |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `int8`/`int16`/`int32`, `uint8`/`uint16`/`uint32` | Кодгенераторы (`modelina`, `openapi-generator-cli`) **игнорируют** ширину и signed/unsigned, генерируют `int32`/`number`. Размер-специфичные типы создают ложное ожидание сохранения семантики на границе между языками. |
|
|
118
|
+
| `int64`/`uint64` | 64-битные числа должны передаваться как `string` — JavaScript не умеет точно представлять 64-битные числа в JSON. Поясните формат в `@doc`. |
|
|
119
|
+
| `float32`/`float64`/`decimal`/`decimal128` | Числа с плавающей точкой передавайте через `string` во избежание потерь точности на границе между языками. |
|
|
120
|
+
| `safeint`/`numeric` | Неоднозначно для кодогенерации. Используйте `integer`. |
|
|
121
|
+
| `utcDateTime`/`plainDate`/`plainTime`/`duration` | Дата/время — это `string` в RFC-3339 с пояснением в `@doc`. TypeScript-кодген иначе подставляет `Date`, что ломает разбор в разных локалях. |
|
|
122
|
+
| `url` | URL — это `string`. |
|
|
123
|
+
|
|
124
|
+
Эмиттер также **запрещает анонимные inline-модели в полях**:
|
|
125
|
+
|
|
126
|
+
```typespec
|
|
127
|
+
// ❌ Ошибка эмиттера
|
|
128
|
+
model M {
|
|
129
|
+
payload: { foo: string };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ✅ Только через явно объявленную модель
|
|
133
|
+
model MPayload { foo: string; }
|
|
134
|
+
model M { payload: MPayload; }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Обоснование: детерминированные имена в выводе важнее лаконичности на стороне источника. modelina требует осмысленных имён для генерации Go/TS-типов; авто-генерация ненадёжна.
|
|
138
|
+
|
|
139
|
+
## Полный пример: AMQP-сервис
|
|
140
|
+
|
|
141
|
+
```typespec
|
|
142
|
+
import "@tertiumorganum/typespec-amqp-ws";
|
|
143
|
+
|
|
144
|
+
using TspAsyncApi;
|
|
145
|
+
using TspAsyncApi.Amqp;
|
|
146
|
+
|
|
147
|
+
@service(#{ title: "Notifications" })
|
|
148
|
+
@info(#{
|
|
149
|
+
version: "1.0.0",
|
|
150
|
+
description: "Сервис рассылки уведомлений через RabbitMQ",
|
|
151
|
+
})
|
|
152
|
+
@server("rabbit", #{
|
|
153
|
+
host: "rabbit.example.com:5672",
|
|
154
|
+
pathname: "/notifications",
|
|
155
|
+
protocol: "amqp",
|
|
156
|
+
description: "RabbitMQ-сервер",
|
|
157
|
+
})
|
|
158
|
+
namespace Notifications;
|
|
159
|
+
|
|
160
|
+
@doc("Уведомление пользователю")
|
|
161
|
+
model Notification {
|
|
162
|
+
@doc("Идентификатор уведомления")
|
|
163
|
+
notificationId: string;
|
|
164
|
+
|
|
165
|
+
@doc("Текст уведомления")
|
|
166
|
+
text: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@publish(#{
|
|
170
|
+
routingKey: "notifications.created",
|
|
171
|
+
exchange: #{
|
|
172
|
+
name: "notifications-exchange",
|
|
173
|
+
type: "direct",
|
|
174
|
+
durable: true,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
@summary("Опубликовать новое уведомление")
|
|
178
|
+
op sendNotification(): Notification;
|
|
179
|
+
|
|
180
|
+
@consume(#{
|
|
181
|
+
routingKey: "notifications.acknowledge",
|
|
182
|
+
queue: #{
|
|
183
|
+
name: "notifications-ack-queue",
|
|
184
|
+
durable: true,
|
|
185
|
+
autoDelete: false,
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
@summary("Обработать подтверждение доставки")
|
|
189
|
+
op handleAck(): Notification;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Полный пример: WebSocket с дискриминатором и reply
|
|
193
|
+
|
|
194
|
+
```typespec
|
|
195
|
+
import "@tertiumorganum/typespec-amqp-ws";
|
|
196
|
+
|
|
197
|
+
using TspAsyncApi;
|
|
198
|
+
using TspAsyncApi.WebSocket;
|
|
199
|
+
|
|
200
|
+
@service(#{ title: "Chat WS" })
|
|
201
|
+
@info(#{ version: "1.0.0" })
|
|
202
|
+
@server("public", #{
|
|
203
|
+
host: "localhost:{port}",
|
|
204
|
+
protocol: "ws",
|
|
205
|
+
pathname: "/chat",
|
|
206
|
+
variables: #{
|
|
207
|
+
port: #{ `default`: "8080", description: "Порт WS-сервера" },
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
namespace Chat;
|
|
211
|
+
|
|
212
|
+
// Дискриминатор сообщения — обычное поле literal-типа.
|
|
213
|
+
// Эмиттер выведет {type: "string", const: "userJoined"} в JSON Schema.
|
|
214
|
+
model UserJoined {
|
|
215
|
+
eventType: "userJoined";
|
|
216
|
+
msgUid: string;
|
|
217
|
+
userId: string;
|
|
218
|
+
nickname: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
model SendMessage {
|
|
222
|
+
eventType: "sendMessage";
|
|
223
|
+
msgUid: string;
|
|
224
|
+
text: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
model SendMessageResponse {
|
|
228
|
+
eventType: "sendMessageResponse";
|
|
229
|
+
msgUid: string;
|
|
230
|
+
ok: boolean;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Receive
|
|
234
|
+
@consume
|
|
235
|
+
@summary("Пользователь подключился к чату")
|
|
236
|
+
op userJoined(): UserJoined;
|
|
237
|
+
|
|
238
|
+
// Send + reply (request/reply pattern)
|
|
239
|
+
@publish
|
|
240
|
+
@reply(SendMessageResponse)
|
|
241
|
+
@summary("Отправить сообщение в чат")
|
|
242
|
+
op sendMessage(): SendMessage;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Все WS-операции автоматически складываются на единый канал `/`. Это типичный паттерн WebSocket-API, где дискриминация сообщений происходит через поле `eventType`.
|
|
246
|
+
|
|
247
|
+
## Версионирование AsyncAPI
|
|
248
|
+
|
|
249
|
+
Эмиттер генерирует AsyncAPI 3.0.0. Версия 3.1 backward-совместима, но мы остаёмся на 3.0 ради консервативности и максимальной совместимости с `modelina`/`redocly`.
|
|
250
|
+
|
|
251
|
+
## Дискриминация сообщений в WebSocket
|
|
252
|
+
|
|
253
|
+
В TypeSpec литерал-типы (`"localActions"`) превращаются в JSON Schema `const` — нативный механизм без специальных декораторов. **Любое** поле модели типа `: "значение"` становится `const` в схеме. Поле может называться как угодно — `eventType`, `kind`, `type`, и т.д. Модели без таких полей тоже валидны (например, AMQP-сообщения обычно без дискриминатора).
|
|
254
|
+
|
|
255
|
+
## Диагностики
|
|
256
|
+
|
|
257
|
+
Эмиттер выдаёт следующие коды (все с префиксом `@tertiumorganum/typespec-amqp-ws/`):
|
|
258
|
+
|
|
259
|
+
- `unsupported-sized-int`, `unsupported-int64`, `unsupported-float`, `unsupported-fuzzy-numeric`, `unsupported-temporal`, `unsupported-url` — попытка использовать запрещённый тип.
|
|
260
|
+
- `anonymous-model`, `anonymous-return` — анонимная inline-модель в поле или return type.
|
|
261
|
+
- `non-string-enum`, `invalid-enum-value` — некорректный enum (numeric или не-идентификаторное значение).
|
|
262
|
+
- `unknown-exchange-type` — `topic` или `headers` exchange (вне scope).
|
|
263
|
+
- `unsupported-union` — union, не являющийся `T | null`.
|
|
264
|
+
- `missing-doc` (warning) — модель/enum без `@doc`.
|
|
265
|
+
|
|
266
|
+
## Out of scope (v1)
|
|
267
|
+
|
|
268
|
+
Намеренно **не реализовано** в v1 — добавляется по запросу при реальной потребности:
|
|
269
|
+
|
|
270
|
+
- Транспорты Kafka, MQTT, HTTP/SSE, SNS/SQS.
|
|
271
|
+
- Exchange types `topic`, `headers`.
|
|
272
|
+
- AsyncAPI security schemes.
|
|
273
|
+
- `correlationId`.
|
|
274
|
+
- Traits (`channelTraits`, `operationTraits`, `messageTraits`).
|
|
275
|
+
- AsyncAPI extensions (`x-` properties).
|
|
276
|
+
- Polymorphism: пользовательские `oneOf`/`anyOf`/`allOf` (внутренний `allOf` для $ref+description — используется автоматически).
|
|
277
|
+
- TypeSpec `@versioned` интеграция.
|
|
278
|
+
- Numeric-валуированные enum.
|
|
279
|
+
- `@tag` на operations.
|
|
280
|
+
- AsyncAPI 3.1.
|
|
281
|
+
- JSON output (только YAML).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "typespec-amqp-ws";
|
|
2
|
+
|
|
3
|
+
using TspAsyncApi;
|
|
4
|
+
using TspAsyncApi.Amqp;
|
|
5
|
+
|
|
6
|
+
@service(#{ title: "Notifications Consumer" })
|
|
7
|
+
@info(#{ version: "1.0.0" })
|
|
8
|
+
@server("rabbit", #{ host: "localhost:5672", protocol: "amqp" })
|
|
9
|
+
namespace NotificationsConsumer;
|
|
10
|
+
|
|
11
|
+
@doc("Уведомление пользователю")
|
|
12
|
+
model Notification {
|
|
13
|
+
notificationId: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@consume(#{
|
|
18
|
+
routingKey: "notifications.created",
|
|
19
|
+
queue: #{
|
|
20
|
+
name: "notifications-consumer-queue",
|
|
21
|
+
durable: true,
|
|
22
|
+
autoDelete: false,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
@summary("Обработать уведомление из очереди")
|
|
26
|
+
op handleNotification(): Notification;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import "typespec-amqp-ws";
|
|
2
|
+
|
|
3
|
+
using TspAsyncApi;
|
|
4
|
+
using TspAsyncApi.Amqp;
|
|
5
|
+
|
|
6
|
+
@service(#{ title: "Notifications Producer" })
|
|
7
|
+
@info(#{ version: "1.0.0" })
|
|
8
|
+
@server("rabbit", #{ host: "localhost:5672", protocol: "amqp" })
|
|
9
|
+
namespace NotificationsProducer;
|
|
10
|
+
|
|
11
|
+
@doc("Уведомление пользователю")
|
|
12
|
+
model Notification {
|
|
13
|
+
@doc("Уникальный идентификатор уведомления")
|
|
14
|
+
notificationId: string;
|
|
15
|
+
|
|
16
|
+
@doc("Текст уведомления")
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@publish(#{
|
|
21
|
+
routingKey: "notifications.created",
|
|
22
|
+
exchange: #{ name: "notifications-exchange", type: "direct", durable: true },
|
|
23
|
+
})
|
|
24
|
+
@summary("Опубликовать новое уведомление")
|
|
25
|
+
op sendNotification(): Notification;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "typespec-amqp-ws";
|
|
2
|
+
|
|
3
|
+
using TspAsyncApi;
|
|
4
|
+
using TspAsyncApi.WebSocket;
|
|
5
|
+
|
|
6
|
+
@service(#{ title: "Chat WS" })
|
|
7
|
+
@info(#{ version: "1.0.0" })
|
|
8
|
+
@server("public", #{ host: "localhost:8080", protocol: "ws", pathname: "/ws" })
|
|
9
|
+
namespace Chat;
|
|
10
|
+
|
|
11
|
+
model UserJoinedPayload { userId: string; nickname: string; }
|
|
12
|
+
model MessagePayload { from: string; text: string; }
|
|
13
|
+
|
|
14
|
+
// Дискриминатор через literal-тип — никаких специальных декораторов.
|
|
15
|
+
model UserJoined {
|
|
16
|
+
eventType: "userJoined";
|
|
17
|
+
payload: UserJoinedPayload;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
model NewMessage {
|
|
21
|
+
eventType: "newMessage";
|
|
22
|
+
payload: MessagePayload;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@consume op userJoined(): UserJoined;
|
|
26
|
+
@consume op newMessage(): NewMessage;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "typespec-amqp-ws";
|
|
2
|
+
|
|
3
|
+
using TspAsyncApi;
|
|
4
|
+
using TspAsyncApi.WebSocket;
|
|
5
|
+
|
|
6
|
+
@service(#{ title: "Auth WS" })
|
|
7
|
+
@info(#{ version: "1.0.0" })
|
|
8
|
+
@server("public", #{ host: "localhost:8080", protocol: "ws", pathname: "/ws" })
|
|
9
|
+
namespace Auth;
|
|
10
|
+
|
|
11
|
+
model LoginRequestPayload { username: string; password: string; }
|
|
12
|
+
model LoginResponsePayload { ok: boolean; token?: string; }
|
|
13
|
+
|
|
14
|
+
model LoginRequest {
|
|
15
|
+
eventType: "loginRequest";
|
|
16
|
+
msgUid: string;
|
|
17
|
+
payload: LoginRequestPayload;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
model LoginResponse {
|
|
21
|
+
eventType: "loginResponse";
|
|
22
|
+
msgUid: string;
|
|
23
|
+
payload: LoginResponsePayload;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@publish
|
|
27
|
+
@reply(LoginResponse)
|
|
28
|
+
@summary("Запрос на аутентификацию")
|
|
29
|
+
op login(): LoginRequest;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tertiumorganum/typespec-amqp-ws",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "TypeSpec emitter for AsyncAPI 3.0 covering AMQP (RabbitMQ) and WebSocket transports",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"files": [
|
|
52
52
|
"dist/src/**",
|
|
53
53
|
"lib/**",
|
|
54
|
+
"docs/**",
|
|
55
|
+
"examples/**",
|
|
54
56
|
"README.md",
|
|
55
57
|
"LICENSE"
|
|
56
58
|
]
|