slapflow 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SPEC-RU.md +43 -49
- package/SPEC.md +43 -49
- package/dist/index.cjs +207 -52
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +202 -50
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ const config = {
|
|
|
162
162
|
- A broad set of [built-in conditions](SPEC.md#built-in-conditions) for comparisons, type checks, collections, and compound logic.
|
|
163
163
|
- Typed PubSub bindings and delegated DOM bindings.
|
|
164
164
|
- `parallel`, `latest`, `queue`, and `drop` concurrency modes with per-entity lanes.
|
|
165
|
-
- A WebSocket
|
|
165
|
+
- A native WebSocket client that proxies socket events into the bus.
|
|
166
166
|
- `core.fetch` with response parsing, cancellation, and retry backoff.
|
|
167
167
|
- Normalized results, execution trace, validation, and lifecycle diagnostics such as `slapflow.run.started` and `slapflow.run.failed`.
|
|
168
168
|
- Runtime variables for configuration values, templates, and expressions.
|
package/SPEC-RU.md
CHANGED
|
@@ -12,23 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
BUILTIN_ACTIONS,
|
|
16
|
+
BUILTIN_CONDITIONS,
|
|
17
17
|
createMemoryTraceSink,
|
|
18
18
|
defineErrorReporter,
|
|
19
19
|
createPubSub,
|
|
20
20
|
PubSub,
|
|
21
21
|
createFlow,
|
|
22
|
-
|
|
22
|
+
createWebSocket,
|
|
23
23
|
catchError,
|
|
24
24
|
} from 'slapflow'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
const flow = createFlow<Context, Patch>(
|
|
29
|
-
{ config: { strategies: {} } },
|
|
30
|
-
{ context: () => ({} as Context) }
|
|
31
|
-
)
|
|
28
|
+
const flow = createFlow<Context, Patch>({ config: { strategies: {} } }, { context: () => ({}) as Context })
|
|
32
29
|
const runner = flow.runner
|
|
33
30
|
|
|
34
31
|
runner.registerAction('jobs.execute', executeJob)
|
|
@@ -48,10 +45,7 @@ type Config = {
|
|
|
48
45
|
guards?: Record<string, ConditionExpression>
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
type ConditionExpression =
|
|
52
|
-
| boolean
|
|
53
|
-
| [operator: string, ...args: unknown[]]
|
|
54
|
-
| ['guard', name: string]
|
|
48
|
+
type ConditionExpression = boolean | [operator: string, ...args: unknown[]] | ['guard', name: string]
|
|
55
49
|
|
|
56
50
|
type Strategy = {
|
|
57
51
|
fn: string
|
|
@@ -85,7 +79,7 @@ const reportError = defineErrorReporter({
|
|
|
85
79
|
|
|
86
80
|
const flow = createFlow(
|
|
87
81
|
{ config: { strategies: {} } },
|
|
88
|
-
{ context: () => ({} as Context
|
|
82
|
+
{ context: () => ({}) as Context, trace: true, onError: reportError }
|
|
89
83
|
)
|
|
90
84
|
```
|
|
91
85
|
|
|
@@ -123,40 +117,34 @@ type ErrorStage = {
|
|
|
123
117
|
|
|
124
118
|
Возвращаемое значение действия нормализуется в один итог. Соответствие:
|
|
125
119
|
|
|
126
|
-
| Возврат | Итог
|
|
127
|
-
| ------------------------------------------------- |
|
|
128
|
-
| `undefined` / `null` | `success`
|
|
129
|
-
| `false` | `skipped`
|
|
130
|
-
| `{ type: 'skip', reason?, data? }` | `skipped` (селектор пробует следующую ветку)
|
|
131
|
-
| `{ type: 'stop', reason?, patch?, events? }` | `stopped` (цепочка останавливается без ошибки)
|
|
132
|
-
| `{ type: 'fail', reason?, data?, error? }` | `failed` (запускается `catch`, затем `onError`)
|
|
120
|
+
| Возврат | Итог |
|
|
121
|
+
| ------------------------------------------------- | ------------------------------------------------------------- |
|
|
122
|
+
| `undefined` / `null` | `success` |
|
|
123
|
+
| `false` | `skipped` |
|
|
124
|
+
| `{ type: 'skip', reason?, data? }` | `skipped` (селектор пробует следующую ветку) |
|
|
125
|
+
| `{ type: 'stop', reason?, patch?, events? }` | `stopped` (цепочка останавливается без ошибки) |
|
|
126
|
+
| `{ type: 'fail', reason?, data?, error? }` | `failed` (запускается `catch`, затем `onError`) |
|
|
133
127
|
| `{ context?, data?, patch?, events?, continue? }` | `success`; `continue: false` прерывает оставшиеся `then`-цели |
|
|
134
128
|
|
|
135
129
|
Брошенное исключение трактуется как `fail`. Возврат `false` и `{ type: 'skip' }` эквивалентны.
|
|
136
130
|
|
|
137
131
|
## Модель реестров
|
|
138
132
|
|
|
139
|
-
|
|
133
|
+
Встроенные элементы живут в двух общих константах — по одной на вид:
|
|
140
134
|
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
actions.ts
|
|
144
|
-
conditions.ts
|
|
135
|
+
```ts
|
|
136
|
+
import { BUILTIN_ACTIONS, BUILTIN_CONDITIONS } from 'slapflow'
|
|
145
137
|
```
|
|
146
138
|
|
|
147
|
-
`
|
|
139
|
+
`BUILTIN_ACTIONS` — это readonly-список `[name, action][]`, предварительно заполненный встроенными действиями; `BUILTIN_CONDITIONS` содержит встроенные условия. Каждый исполнитель получает собственный `new Map(BUILTIN_ACTIONS)` / `new Map(BUILTIN_CONDITIONS)`, поэтому регистрации остаются изолированными по исполнителям.
|
|
148
140
|
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
Каждый исполнитель получает собственную изменяемую копию реестра. Приложения могут переопределить любое встроенное действие или условие:
|
|
141
|
+
Встроенные элементы неизменяемы: `registerAction` и `registerCondition` отклоняют попытку переопределить встроенное имя.
|
|
152
142
|
|
|
153
143
|
```ts
|
|
154
144
|
runner.registerAction('app.setData', customSetData)
|
|
155
|
-
runner.registerCondition('
|
|
145
|
+
runner.registerCondition('hasQueue', hasItems)
|
|
156
146
|
```
|
|
157
147
|
|
|
158
|
-
Таким образом, встроенные элементы являются значениями по умолчанию, а не отдельным неизменяемым слоем.
|
|
159
|
-
|
|
160
148
|
Проверка конфигурации обращается к реестрам через минимальный контракт `has(name)`.
|
|
161
149
|
|
|
162
150
|
## Встроенные действия
|
|
@@ -250,11 +238,11 @@ export const config = {
|
|
|
250
238
|
|
|
251
239
|
Не-`success` итог шага меняет дальнейшее поведение в зависимости от режима:
|
|
252
240
|
|
|
253
|
-
| Итог
|
|
254
|
-
|
|
|
255
|
-
| `skipped
|
|
241
|
+
| Итог | `sequence` | `selector` |
|
|
242
|
+
| --------- | --------------------- | ----------------------- |
|
|
243
|
+
| `skipped` | **прерывает остаток** | пробует следующую ветку |
|
|
256
244
|
|
|
257
|
-
`sequence` — режим по умолчанию, и он прерывает оставшиеся `then`-цели на
|
|
245
|
+
`sequence` — режим по умолчанию, и он прерывает оставшиеся `then`-цели на _любом_ не-`success` (`skipped`, `stopped`, `failed`) — не только на сбое. Условный шаг внутри последовательности — это, таким образом, скрытый ранний выход для всего остатка. Если пропуск шага не должен рвать цепочку, заверните его в селектор с запасным `core.noop`.
|
|
258
246
|
|
|
259
247
|
`terminal: true` останавливает `then`-цепочку после этой стратегии даже при `success`; `continue: false` в `ActionSuccess` даёт тот же эффект.
|
|
260
248
|
|
|
@@ -399,12 +387,12 @@ type BusEvent<TPayload> = {
|
|
|
399
387
|
На тему можно подписаться по шаблону, где `*` соответствует ровно одному сегменту, разделённому точкой. Шаблонный символ не пересекает границу `.`.
|
|
400
388
|
|
|
401
389
|
```ts
|
|
402
|
-
bus.on('hub.user.*', ({ parsed }) => {})
|
|
403
|
-
bus.on('hub.*.created', ({ parsed }) => {})
|
|
404
|
-
bus.on('hub.*.export', ({ parsed }) => {})
|
|
390
|
+
bus.on('hub.user.*', ({ parsed }) => {}) // hub.user.created, hub.user.deleted
|
|
391
|
+
bus.on('hub.*.created', ({ parsed }) => {}) // hub.user.created, hub.team.created
|
|
392
|
+
bus.on('hub.*.export', ({ parsed }) => {}) // НЕ hub.user.audit.export (один сегмент)
|
|
405
393
|
```
|
|
406
394
|
|
|
407
|
-
Подписки с точным именем остаются O(1); шаблоны обрабатываются отдельно, поэтому регистрации без `*` не несут накладных затрат на сравнение. Обработчик шаблона получает `parsed` как `unknown` — сужайте тип перед использованием. Шаблоны работают в `bus.on`/`bus.off
|
|
395
|
+
Подписки с точным именем остаются O(1); шаблоны обрабатываются отдельно, поэтому регистрации без `*` не несут накладных затрат на сравнение. Обработчик шаблона получает `parsed` как `unknown` — сужайте тип перед использованием. Шаблоны работают в `bus.on`/`bus.off`.
|
|
408
396
|
|
|
409
397
|
## Поток
|
|
410
398
|
|
|
@@ -489,24 +477,30 @@ type ConcurrencyOptions<TPayload> = {
|
|
|
489
477
|
|
|
490
478
|
`defaultInput` имеет тип `{ type, value?, dataset, form? }`. `dataset` содержит все атрибуты `data-*` совпавшего элемента в виде ключей camelCase. `form` строится по ближайшему элементу `<form>`; повторяющиеся поля формы превращаются в массивы, а `File` остаётся `File`. Для `submit` значение `preventDefault` по умолчанию равно `true`; для остальных событий оно и `stopPropagation` по умолчанию равны `false`.
|
|
491
479
|
|
|
492
|
-
### WebSocket
|
|
480
|
+
### WebSocket-клиент
|
|
493
481
|
|
|
494
|
-
`
|
|
482
|
+
`createWebSocket` открывает нативный `WebSocket` по `url` и проксирует каждое событие сокета в шину. Формат провода не предполагается — каждое событие отправляется с фиксированной темой и конвертом, где `parsed` содержит сырую полезную нагрузку.
|
|
495
483
|
|
|
496
484
|
```ts
|
|
497
|
-
const
|
|
485
|
+
const socket = createWebSocket({
|
|
486
|
+
url,
|
|
498
487
|
bus,
|
|
499
|
-
|
|
500
|
-
inboundTopics: ['order.created'],
|
|
501
|
-
outboundTopics: ['slapflow.run.finished'],
|
|
502
|
-
origin: 'worker',
|
|
503
|
-
retry: { initialDelay: 500, maxDelay: 10_000, multiplier: 2, jitter: true, maxAttempts: 5 },
|
|
488
|
+
origin: 'client',
|
|
504
489
|
})
|
|
505
490
|
|
|
506
|
-
|
|
491
|
+
socket.start()
|
|
507
492
|
```
|
|
508
493
|
|
|
509
|
-
|
|
494
|
+
```ts
|
|
495
|
+
bus.on('message', ({ parsed }) => {}) // parsed = сырое сообщение (JSON-декодированное, когда возможно)
|
|
496
|
+
bus.on('open', ({ parsed }) => {}) // { url }
|
|
497
|
+
bus.on('close', ({ parsed }) => {}) // { code, reason }
|
|
498
|
+
bus.on('error', ({ parsed }) => {}) // { error }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
События сокета приходят с темой `open`, `message`, `close` или `error`. Полезная нагрузка `message` JSON-декодируется в `parsed`, если валидна, иначе остаётся сырой строкой. Фильтрация тем — забота потребителя: внутри клиента ничего не разрешается и не отклоняется. `reconnect` принимает `initialDelay`, `maxDelay`, `multiplier`, `jitter` и `maxAttempts`; без `maxAttempts` повторы идут бесконечно. `start`, `stop`, `reconnect` и `status` управляют жизненным циклом; статус — одно из `idle`, `connecting`, `connected`, `reconnecting` или `stopped`.
|
|
502
|
+
|
|
503
|
+
`createWS` помечен как deprecated и будет удалён; мигрируйте на `createWebSocket`.
|
|
510
504
|
|
|
511
505
|
## Ограничения безопасности
|
|
512
506
|
|
package/SPEC.md
CHANGED
|
@@ -12,23 +12,20 @@ The package is not coupled to a UI, server framework, scheduler, or domain model
|
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
BUILTIN_ACTIONS,
|
|
16
|
+
BUILTIN_CONDITIONS,
|
|
17
17
|
createMemoryTraceSink,
|
|
18
18
|
defineErrorReporter,
|
|
19
19
|
createPubSub,
|
|
20
20
|
PubSub,
|
|
21
21
|
createFlow,
|
|
22
|
-
|
|
22
|
+
createWebSocket,
|
|
23
23
|
catchError,
|
|
24
24
|
} from 'slapflow'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
const flow = createFlow<Context, Patch>(
|
|
29
|
-
{ config: { strategies: {} } },
|
|
30
|
-
{ context: () => ({} as Context) }
|
|
31
|
-
)
|
|
28
|
+
const flow = createFlow<Context, Patch>({ config: { strategies: {} } }, { context: () => ({}) as Context })
|
|
32
29
|
const runner = flow.runner
|
|
33
30
|
|
|
34
31
|
runner.registerAction('jobs.execute', executeJob)
|
|
@@ -48,10 +45,7 @@ type Config = {
|
|
|
48
45
|
guards?: Record<string, ConditionExpression>
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
type ConditionExpression =
|
|
52
|
-
| boolean
|
|
53
|
-
| [operator: string, ...args: unknown[]]
|
|
54
|
-
| ['guard', name: string]
|
|
48
|
+
type ConditionExpression = boolean | [operator: string, ...args: unknown[]] | ['guard', name: string]
|
|
55
49
|
|
|
56
50
|
type Strategy = {
|
|
57
51
|
fn: string
|
|
@@ -85,7 +79,7 @@ const reportError = defineErrorReporter({
|
|
|
85
79
|
|
|
86
80
|
const flow = createFlow(
|
|
87
81
|
{ config: { strategies: {} } },
|
|
88
|
-
{ context: () => ({} as Context
|
|
82
|
+
{ context: () => ({}) as Context, trace: true, onError: reportError }
|
|
89
83
|
)
|
|
90
84
|
```
|
|
91
85
|
|
|
@@ -123,40 +117,34 @@ If an error is recovered through `catch`, `onError` is still invoked for the ori
|
|
|
123
117
|
|
|
124
118
|
An action's return value is normalized into one outcome. The mapping:
|
|
125
119
|
|
|
126
|
-
| Return | Outcome
|
|
127
|
-
| ------------------------------------------------- |
|
|
128
|
-
| `undefined` / `null` | `success`
|
|
129
|
-
| `false` | `skipped`
|
|
130
|
-
| `{ type: 'skip', reason?, data? }` | `skipped` (a selector tries the next branch)
|
|
131
|
-
| `{ type: 'stop', reason?, patch?, events? }` | `stopped` (the chain halts without error)
|
|
132
|
-
| `{ type: 'fail', reason?, data?, error? }` | `failed` (`catch` runs, then `onError`)
|
|
120
|
+
| Return | Outcome |
|
|
121
|
+
| ------------------------------------------------- | --------------------------------------------------------------- |
|
|
122
|
+
| `undefined` / `null` | `success` |
|
|
123
|
+
| `false` | `skipped` |
|
|
124
|
+
| `{ type: 'skip', reason?, data? }` | `skipped` (a selector tries the next branch) |
|
|
125
|
+
| `{ type: 'stop', reason?, patch?, events? }` | `stopped` (the chain halts without error) |
|
|
126
|
+
| `{ type: 'fail', reason?, data?, error? }` | `failed` (`catch` runs, then `onError`) |
|
|
133
127
|
| `{ context?, data?, patch?, events?, continue? }` | `success`; `continue: false` halts the remaining `then` targets |
|
|
134
128
|
|
|
135
129
|
A thrown exception is treated as `fail`. Returning `false` and `{ type: 'skip' }` are equivalent.
|
|
136
130
|
|
|
137
131
|
## Registry Model
|
|
138
132
|
|
|
139
|
-
|
|
133
|
+
Built-ins live in two shared constants, one per kind:
|
|
140
134
|
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
actions.ts
|
|
144
|
-
conditions.ts
|
|
135
|
+
```ts
|
|
136
|
+
import { BUILTIN_ACTIONS, BUILTIN_CONDITIONS } from 'slapflow'
|
|
145
137
|
```
|
|
146
138
|
|
|
147
|
-
`
|
|
139
|
+
`BUILTIN_ACTIONS` is a readonly `[name, action][]` list prepopulated with built-in actions; `BUILTIN_CONDITIONS` holds built-in conditions. Each runner receives its own `new Map(BUILTIN_ACTIONS)` / `new Map(BUILTIN_CONDITIONS)` seed, so registrations stay isolated per runner.
|
|
148
140
|
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
Each runner receives its own mutable registry copy. Applications can override any built-in action or condition:
|
|
141
|
+
Built-ins are immutable defaults: `registerAction` and `registerCondition` reject an attempt to override a built-in name.
|
|
152
142
|
|
|
153
143
|
```ts
|
|
154
144
|
runner.registerAction('app.setData', customSetData)
|
|
155
|
-
runner.registerCondition('
|
|
145
|
+
runner.registerCondition('hasQueue', hasItems)
|
|
156
146
|
```
|
|
157
147
|
|
|
158
|
-
Built-ins are therefore default values, not a separate immutable layer.
|
|
159
|
-
|
|
160
148
|
Configuration validation accesses registries through the minimal `has(name)` contract.
|
|
161
149
|
|
|
162
150
|
## Built-In Actions
|
|
@@ -250,11 +238,11 @@ export const config = {
|
|
|
250
238
|
|
|
251
239
|
A non-`success` outcome at a step changes what happens next, depending on the mode:
|
|
252
240
|
|
|
253
|
-
| Outcome | `sequence`
|
|
254
|
-
| --------- |
|
|
255
|
-
| `skipped` | **interrupts the rest** | tries the next branch
|
|
241
|
+
| Outcome | `sequence` | `selector` |
|
|
242
|
+
| --------- | ----------------------- | --------------------- |
|
|
243
|
+
| `skipped` | **interrupts the rest** | tries the next branch |
|
|
256
244
|
|
|
257
|
-
`sequence` is the default mode and interrupts the remaining `then` targets on
|
|
245
|
+
`sequence` is the default mode and interrupts the remaining `then` targets on _any_ non-`success` (`skipped`, `stopped`, `failed`) — not only on failure. A conditional step inside a sequence is therefore a hidden early exit for the whole remainder. When skipping a step must not break the chain, wrap it in a selector with a `core.noop` fallback.
|
|
258
246
|
|
|
259
247
|
`terminal: true` stops the `then` chain after that strategy even on `success`; `continue: false` in `ActionSuccess` has the same effect.
|
|
260
248
|
|
|
@@ -399,12 +387,12 @@ type BusEvent<TPayload> = {
|
|
|
399
387
|
A topic may be subscribed by pattern, using `*` to match exactly one dot-delimited segment. A wildcard does not cross a `.` boundary.
|
|
400
388
|
|
|
401
389
|
```ts
|
|
402
|
-
bus.on('hub.user.*', ({ parsed }) => {})
|
|
403
|
-
bus.on('hub.*.created', ({ parsed }) => {})
|
|
404
|
-
bus.on('hub.*.export', ({ parsed }) => {})
|
|
390
|
+
bus.on('hub.user.*', ({ parsed }) => {}) // hub.user.created, hub.user.deleted
|
|
391
|
+
bus.on('hub.*.created', ({ parsed }) => {}) // hub.user.created, hub.team.created
|
|
392
|
+
bus.on('hub.*.export', ({ parsed }) => {}) // NOT hub.user.audit.export (wildcard spans one segment)
|
|
405
393
|
```
|
|
406
394
|
|
|
407
|
-
Exact-name subscriptions stay O(1); wildcard patterns are matched separately, so registrations without `*` pay no matching cost. A wildcard handler receives `parsed` as `unknown` — narrow it before use. Wildcards work in `bus.on`/`bus.off
|
|
395
|
+
Exact-name subscriptions stay O(1); wildcard patterns are matched separately, so registrations without `*` pay no matching cost. A wildcard handler receives `parsed` as `unknown` — narrow it before use. Wildcards work in `bus.on`/`bus.off`.
|
|
408
396
|
|
|
409
397
|
## Flow
|
|
410
398
|
|
|
@@ -489,24 +477,30 @@ A DOM binding key uses the `[dom] <css-selector>:<event>` format. Slapflow insta
|
|
|
489
477
|
|
|
490
478
|
`defaultInput` has type `{ type, value?, dataset, form? }`. `dataset` contains all `data-*` attributes from the matching element as camelCase keys. `form` is built from the nearest `<form>`; repeated form entries become arrays and `File` remains `File`. For `submit`, `preventDefault` defaults to `true`; for other events, it and `stopPropagation` default to `false`.
|
|
491
479
|
|
|
492
|
-
### WebSocket
|
|
480
|
+
### WebSocket Client
|
|
493
481
|
|
|
494
|
-
`
|
|
482
|
+
`createWebSocket` opens a native `WebSocket` to `url` and proxies every socket event into the bus. No wire format is assumed — each event is dispatched with a fixed topic and an envelope whose `parsed` holds the raw payload.
|
|
495
483
|
|
|
496
484
|
```ts
|
|
497
|
-
const
|
|
485
|
+
const socket = createWebSocket({
|
|
486
|
+
url,
|
|
498
487
|
bus,
|
|
499
|
-
|
|
500
|
-
inboundTopics: ['order.created'],
|
|
501
|
-
outboundTopics: ['slapflow.run.finished'],
|
|
502
|
-
origin: 'worker',
|
|
503
|
-
retry: { initialDelay: 500, maxDelay: 10_000, multiplier: 2, jitter: true, maxAttempts: 5 },
|
|
488
|
+
origin: 'client',
|
|
504
489
|
})
|
|
505
490
|
|
|
506
|
-
|
|
491
|
+
socket.start()
|
|
507
492
|
```
|
|
508
493
|
|
|
509
|
-
|
|
494
|
+
```ts
|
|
495
|
+
bus.on('message', ({ parsed }) => {}) // parsed = the raw message (JSON-decoded when possible)
|
|
496
|
+
bus.on('open', ({ parsed }) => {}) // { url }
|
|
497
|
+
bus.on('close', ({ parsed }) => {}) // { code, reason }
|
|
498
|
+
bus.on('error', ({ parsed }) => {}) // { error }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Socket events are forwarded with the topic `open`, `message`, `close`, or `error`. `message` payloads are JSON-decoded into `parsed` when valid, otherwise kept as the raw string. Filtering topics is the consumer's responsibility — nothing is allow-listed or rejected inside the client. `reconnect` accepts `initialDelay`, `maxDelay`, `multiplier`, `jitter`, and `maxAttempts`; omitting `maxAttempts` retries indefinitely. `start`, `stop`, `reconnect`, and `status` manage the lifecycle; status is one of `idle`, `connecting`, `connected`, `reconnecting`, or `stopped`.
|
|
502
|
+
|
|
503
|
+
`createWS` is deprecated and will be removed; migrate to `createWebSocket`.
|
|
510
504
|
|
|
511
505
|
## Safety Limits
|
|
512
506
|
|