slapflow 1.1.0 → 1.2.0
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 +174 -52
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +169 -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
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,14 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
BUILTIN_ACTIONS: () => BUILTIN_ACTIONS,
|
|
24
|
+
BUILTIN_ACTION_NAMES: () => BUILTIN_ACTION_NAMES,
|
|
25
|
+
BUILTIN_CONDITIONS: () => BUILTIN_CONDITIONS,
|
|
26
|
+
BUILTIN_CONDITION_NAMES: () => BUILTIN_CONDITION_NAMES,
|
|
23
27
|
PubSub: () => PubSub,
|
|
24
28
|
catchError: () => catchError,
|
|
25
|
-
createActionsRegistry: () => createActionsRegistry,
|
|
26
|
-
createConditionsRegistry: () => createConditionsRegistry,
|
|
27
29
|
createFlow: () => createFlow,
|
|
28
30
|
createMemoryTraceSink: () => createMemoryTraceSink,
|
|
29
31
|
createPubSub: () => createPubSub,
|
|
30
32
|
createWS: () => createWS,
|
|
33
|
+
createWebSocket: () => createWebSocket,
|
|
31
34
|
defineConfig: () => defineConfig,
|
|
32
35
|
defineErrorReporter: () => defineErrorReporter
|
|
33
36
|
});
|
|
@@ -36,9 +39,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
36
39
|
// src/helpers/config/defineConfig.ts
|
|
37
40
|
var defineConfig = (config) => config;
|
|
38
41
|
|
|
39
|
-
// src/helpers/trace/cloneData.ts
|
|
40
|
-
var cloneData = (data) => ({ ...data });
|
|
41
|
-
|
|
42
42
|
// src/helpers/trace/createMemoryTraceSink.ts
|
|
43
43
|
var createMemoryTraceSink = () => {
|
|
44
44
|
const items = [];
|
|
@@ -50,22 +50,9 @@ var createMemoryTraceSink = () => {
|
|
|
50
50
|
};
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
// src/helpers/errors/slapError.ts
|
|
54
|
-
var slapError = (code, message, extras = {}) => ({ code, message, ...extras });
|
|
55
|
-
|
|
56
53
|
// src/helpers/errors/defineErrorReporter.ts
|
|
57
54
|
var defineErrorReporter = (handlers) => typeof handlers === "function" ? handlers : handlers.report;
|
|
58
55
|
|
|
59
|
-
// src/errors.ts
|
|
60
|
-
var SyncAsyncError = class extends Error {
|
|
61
|
-
slapError;
|
|
62
|
-
constructor(error) {
|
|
63
|
-
super(error.message);
|
|
64
|
-
this.name = "SyncAsyncError";
|
|
65
|
-
this.slapError = error;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
56
|
// src/helpers/ids/createId.ts
|
|
70
57
|
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
71
58
|
var idLength = 12;
|
|
@@ -105,7 +92,7 @@ var serializeError = (error) => ({
|
|
|
105
92
|
error: error instanceof Error ? error.message : String(error)
|
|
106
93
|
});
|
|
107
94
|
|
|
108
|
-
// src/
|
|
95
|
+
// src/createPubSub.ts
|
|
109
96
|
var createPubSub = (options = {}) => {
|
|
110
97
|
const subscribers = /* @__PURE__ */ new Map();
|
|
111
98
|
const wildcardSubscribers = /* @__PURE__ */ new Map();
|
|
@@ -203,6 +190,16 @@ var createPubSub = (options = {}) => {
|
|
|
203
190
|
};
|
|
204
191
|
var PubSub = createPubSub();
|
|
205
192
|
|
|
193
|
+
// src/helpers/errors/syncAsyncError.ts
|
|
194
|
+
var SyncAsyncError = class extends Error {
|
|
195
|
+
slapError;
|
|
196
|
+
constructor(error) {
|
|
197
|
+
super(error.message);
|
|
198
|
+
this.name = "SyncAsyncError";
|
|
199
|
+
this.slapError = error;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
206
203
|
// src/helpers/actions/coreDelay.ts
|
|
207
204
|
var coreDelay = async ({
|
|
208
205
|
props,
|
|
@@ -239,10 +236,7 @@ var coreEmit = ({
|
|
|
239
236
|
};
|
|
240
237
|
|
|
241
238
|
// src/helpers/actions/coreFail.ts
|
|
242
|
-
var coreFail = ({
|
|
243
|
-
props,
|
|
244
|
-
runtime
|
|
245
|
-
}) => runtime.fail(String(props.reason ?? "failed"), props.data);
|
|
239
|
+
var coreFail = ({ props, runtime }) => runtime.fail(String(props.reason ?? "failed"), props.data);
|
|
246
240
|
|
|
247
241
|
// src/helpers/retry/getRetryDelay.ts
|
|
248
242
|
var getRetryDelay = (attempt, options) => {
|
|
@@ -449,10 +443,7 @@ var corePatch = ({
|
|
|
449
443
|
};
|
|
450
444
|
|
|
451
445
|
// src/helpers/actions/coreSet.ts
|
|
452
|
-
var coreSet = ({
|
|
453
|
-
props,
|
|
454
|
-
runtime
|
|
455
|
-
}) => {
|
|
446
|
+
var coreSet = ({ props, runtime }) => {
|
|
456
447
|
const path = props.path;
|
|
457
448
|
if (typeof path === "string") {
|
|
458
449
|
runtime.set(path, props.value);
|
|
@@ -473,13 +464,10 @@ var coreSetData = ({
|
|
|
473
464
|
};
|
|
474
465
|
|
|
475
466
|
// src/helpers/actions/coreStop.ts
|
|
476
|
-
var coreStop = ({
|
|
477
|
-
props,
|
|
478
|
-
runtime
|
|
479
|
-
}) => runtime.stop(String(props.reason ?? "stopped"));
|
|
467
|
+
var coreStop = ({ props, runtime }) => runtime.stop(String(props.reason ?? "stopped"));
|
|
480
468
|
|
|
481
|
-
// src/
|
|
482
|
-
var
|
|
469
|
+
// src/helpers/actions/index.ts
|
|
470
|
+
var BUILTIN_ACTIONS = [
|
|
483
471
|
["core.noop", coreNoop],
|
|
484
472
|
["core.stop", coreStop],
|
|
485
473
|
["core.fail", coreFail],
|
|
@@ -493,7 +481,8 @@ var createActionsRegistry = () => /* @__PURE__ */ new Map([
|
|
|
493
481
|
["core.emit", coreEmit],
|
|
494
482
|
["core.patch", corePatch],
|
|
495
483
|
["core.delay", coreDelay]
|
|
496
|
-
]
|
|
484
|
+
];
|
|
485
|
+
var BUILTIN_ACTION_NAMES = new Set(BUILTIN_ACTIONS.map(([name]) => name));
|
|
497
486
|
|
|
498
487
|
// src/helpers/conditions/changedCondition.ts
|
|
499
488
|
var changedCondition = (_args, current, previous) => !Object.is(current, previous);
|
|
@@ -587,8 +576,8 @@ var typeIsCondition = (_args, value, expected) => {
|
|
|
587
576
|
return expected === "string" || expected === "number" || expected === "boolean" ? typeof value === expected : false;
|
|
588
577
|
};
|
|
589
578
|
|
|
590
|
-
// src/
|
|
591
|
-
var
|
|
579
|
+
// src/helpers/conditions/index.ts
|
|
580
|
+
var BUILTIN_CONDITIONS = [
|
|
592
581
|
["eq", eqCondition],
|
|
593
582
|
["neq", neqCondition],
|
|
594
583
|
["gt", gtCondition],
|
|
@@ -605,7 +594,8 @@ var createConditionsRegistry = () => /* @__PURE__ */ new Map([
|
|
|
605
594
|
["typeIs", typeIsCondition],
|
|
606
595
|
["changed", changedCondition],
|
|
607
596
|
["cooldownReady", cooldownReadyCondition]
|
|
608
|
-
]
|
|
597
|
+
];
|
|
598
|
+
var BUILTIN_CONDITION_NAMES = new Set(BUILTIN_CONDITIONS.map(([name]) => name));
|
|
609
599
|
|
|
610
600
|
// src/helpers/runner/applyResult.ts
|
|
611
601
|
var applyResult = (result, state, mergeData) => {
|
|
@@ -619,6 +609,9 @@ var applyResult = (result, state, mergeData) => {
|
|
|
619
609
|
state.events.push(...result.events);
|
|
620
610
|
};
|
|
621
611
|
|
|
612
|
+
// src/helpers/errors/slapError.ts
|
|
613
|
+
var slapError = (code, message, extras = {}) => ({ code, message, ...extras });
|
|
614
|
+
|
|
622
615
|
// src/helpers/path/resolveValue.ts
|
|
623
616
|
var import_objwalk3 = require("objwalk");
|
|
624
617
|
|
|
@@ -1057,7 +1050,7 @@ var executeNext = (item, depth, state, environment) => {
|
|
|
1057
1050
|
const id = typeof item === "string" ? item : item.strategy;
|
|
1058
1051
|
if (typeof item !== "string" && item.when) {
|
|
1059
1052
|
const runtime = createRuntime(state);
|
|
1060
|
-
const condition = evaluateCondition(item.when, environment.
|
|
1053
|
+
const condition = evaluateCondition(item.when, environment.registry.conditions, { ...state, runtime, strategy: id });
|
|
1061
1054
|
if (!condition.ok) {
|
|
1062
1055
|
return { status: "failed", error: condition.error, patches: [], events: [] };
|
|
1063
1056
|
}
|
|
@@ -1309,6 +1302,9 @@ var normalizeActionResult = (raw) => {
|
|
|
1309
1302
|
};
|
|
1310
1303
|
};
|
|
1311
1304
|
|
|
1305
|
+
// src/helpers/trace/cloneData.ts
|
|
1306
|
+
var cloneData = (data) => ({ ...data });
|
|
1307
|
+
|
|
1312
1308
|
// src/helpers/runner/pushTrace.ts
|
|
1313
1309
|
var pushTrace = (state, step, depth, strategyId, strategy, status, props, dataBefore, startedAt, reason) => {
|
|
1314
1310
|
state.traceSink?.push({
|
|
@@ -1491,7 +1487,7 @@ var executeStrategy = (id, extraProps, depth, state, environment) => {
|
|
|
1491
1487
|
events: []
|
|
1492
1488
|
};
|
|
1493
1489
|
}
|
|
1494
|
-
const action = environment.
|
|
1490
|
+
const action = environment.registry.actions.get(strategy.fn);
|
|
1495
1491
|
if (!action) {
|
|
1496
1492
|
return {
|
|
1497
1493
|
status: "failed",
|
|
@@ -1546,7 +1542,7 @@ var executeStrategy = (id, extraProps, depth, state, environment) => {
|
|
|
1546
1542
|
const dataBefore = cloneData(state.data);
|
|
1547
1543
|
const traceStep = state.stepCounter.current + 1;
|
|
1548
1544
|
const startedAt = Date.now();
|
|
1549
|
-
const condition = evaluateCondition(strategy.when, environment.
|
|
1545
|
+
const condition = evaluateCondition(strategy.when, environment.registry.conditions, {
|
|
1550
1546
|
...state,
|
|
1551
1547
|
runtime,
|
|
1552
1548
|
strategy: id
|
|
@@ -1860,7 +1856,12 @@ var validateCondition = (expression, strategy, path, conditionsRegistry, errors)
|
|
|
1860
1856
|
const [operator, ...args] = expression;
|
|
1861
1857
|
if (operator === "guard") {
|
|
1862
1858
|
if (args.length !== 1 || typeof args[0] !== "string") {
|
|
1863
|
-
errors.push({
|
|
1859
|
+
errors.push({
|
|
1860
|
+
code: "CONDITION_INVALID",
|
|
1861
|
+
message: "Guard reference must be a single string name",
|
|
1862
|
+
strategy,
|
|
1863
|
+
path
|
|
1864
|
+
});
|
|
1864
1865
|
}
|
|
1865
1866
|
return;
|
|
1866
1867
|
}
|
|
@@ -1913,7 +1914,9 @@ var validateNextList = (config, list, path, strategy, conditionsRegistry, errors
|
|
|
1913
1914
|
var isGuardRef = (expression) => Array.isArray(expression) && expression.length === 2 && expression[0] === "guard" && typeof expression[1] === "string";
|
|
1914
1915
|
var resolveRef = (config, name, visiting, path) => {
|
|
1915
1916
|
if (visiting.includes(name)) {
|
|
1916
|
-
return {
|
|
1917
|
+
return {
|
|
1918
|
+
issue: { code: "GUARD_CYCLE", message: `Guard "${name}" is part of a reference cycle`, guard: name, path }
|
|
1919
|
+
};
|
|
1917
1920
|
}
|
|
1918
1921
|
const guard = config.guards?.[name];
|
|
1919
1922
|
if (guard === void 0) {
|
|
@@ -2136,10 +2139,10 @@ var createRunCancellation = (source) => {
|
|
|
2136
2139
|
};
|
|
2137
2140
|
};
|
|
2138
2141
|
|
|
2139
|
-
// src/
|
|
2142
|
+
// src/createRunner.ts
|
|
2140
2143
|
var createRunner = (options = {}) => {
|
|
2141
|
-
const actionsRegistry =
|
|
2142
|
-
const conditionsRegistry =
|
|
2144
|
+
const actionsRegistry = new Map(BUILTIN_ACTIONS);
|
|
2145
|
+
const conditionsRegistry = new Map(BUILTIN_CONDITIONS);
|
|
2143
2146
|
const configRef = {};
|
|
2144
2147
|
const timeout = options.timeout ?? options.timeoutMs;
|
|
2145
2148
|
const runnerOptions = timeout === void 0 ? options : { ...options, timeout };
|
|
@@ -2149,19 +2152,24 @@ var createRunner = (options = {}) => {
|
|
|
2149
2152
|
console.warn("timeoutMs is deprecated; use timeout. It will be removed in a future major release.");
|
|
2150
2153
|
}
|
|
2151
2154
|
const environment = {
|
|
2152
|
-
actionsRegistry,
|
|
2153
|
-
conditionsRegistry,
|
|
2155
|
+
registry: { actions: actionsRegistry, conditions: conditionsRegistry },
|
|
2154
2156
|
configRef,
|
|
2155
2157
|
options: runnerOptions,
|
|
2156
2158
|
mergeData
|
|
2157
2159
|
};
|
|
2158
2160
|
const registerAction = (name, action) => {
|
|
2161
|
+
if (BUILTIN_ACTION_NAMES.has(name)) {
|
|
2162
|
+
throw new Error(`Cannot override built-in action "${name}"`);
|
|
2163
|
+
}
|
|
2159
2164
|
actionsRegistry.set(name, action);
|
|
2160
2165
|
};
|
|
2161
2166
|
const registerActions = (items) => {
|
|
2162
2167
|
Object.entries(items).forEach(([name, action]) => registerAction(name, action));
|
|
2163
2168
|
};
|
|
2164
2169
|
const registerCondition = (name, condition) => {
|
|
2170
|
+
if (BUILTIN_CONDITION_NAMES.has(name)) {
|
|
2171
|
+
throw new Error(`Cannot override built-in condition "${name}"`);
|
|
2172
|
+
}
|
|
2165
2173
|
conditionsRegistry.set(name, condition);
|
|
2166
2174
|
};
|
|
2167
2175
|
const registerConditions = (items) => {
|
|
@@ -2258,7 +2266,7 @@ var parseDomBinding = (binding, prefix) => {
|
|
|
2258
2266
|
return separator <= 0 || separator === source.length - 1 ? void 0 : { selector: source.slice(0, separator), eventType: source.slice(separator + 1) };
|
|
2259
2267
|
};
|
|
2260
2268
|
|
|
2261
|
-
// src/
|
|
2269
|
+
// src/createFlow.ts
|
|
2262
2270
|
var busBindingPrefix = "[bus] ";
|
|
2263
2271
|
var domBindingPrefix = "[dom] ";
|
|
2264
2272
|
var defaultMaxQueueSize = 50;
|
|
@@ -2499,10 +2507,11 @@ var createFlow = (definition, options) => {
|
|
|
2499
2507
|
return { runner, start, stop };
|
|
2500
2508
|
};
|
|
2501
2509
|
|
|
2502
|
-
// src/
|
|
2510
|
+
// src/createWS.ts
|
|
2503
2511
|
var openState = 1;
|
|
2504
2512
|
var maxSeenEvents = 1e3;
|
|
2505
2513
|
var createWS = (options) => {
|
|
2514
|
+
console.warn("[slapflow] createWS is deprecated and will be removed soon. Use createWebSocket instead.");
|
|
2506
2515
|
const inboundTopics = new Set(options.inboundTopics ?? []);
|
|
2507
2516
|
const outboundTopics = new Set(options.outboundTopics ?? []);
|
|
2508
2517
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
@@ -2666,7 +2675,117 @@ var createWS = (options) => {
|
|
|
2666
2675
|
return { start, stop, reconnect, status: () => currentStatus };
|
|
2667
2676
|
};
|
|
2668
2677
|
|
|
2669
|
-
// src/
|
|
2678
|
+
// src/createWebSocket.ts
|
|
2679
|
+
var createWebSocket = (options) => {
|
|
2680
|
+
let socket;
|
|
2681
|
+
let retryTimer;
|
|
2682
|
+
let retryAttempt = 0;
|
|
2683
|
+
let started = false;
|
|
2684
|
+
let currentStatus = "idle";
|
|
2685
|
+
const emitSocketEvent = (topic, payload) => {
|
|
2686
|
+
options.bus.dispatch({
|
|
2687
|
+
id: createId(),
|
|
2688
|
+
topic,
|
|
2689
|
+
occurredAt: Date.now(),
|
|
2690
|
+
...options.origin ? { origin: options.origin } : {},
|
|
2691
|
+
parsed: payload,
|
|
2692
|
+
serialized: JSON.stringify(payload)
|
|
2693
|
+
});
|
|
2694
|
+
};
|
|
2695
|
+
const scheduleRetry = () => {
|
|
2696
|
+
const delay = getRetryDelay(retryAttempt, options.reconnect ?? {});
|
|
2697
|
+
const attempt = retryAttempt + 1;
|
|
2698
|
+
currentStatus = "reconnecting";
|
|
2699
|
+
retryAttempt = attempt;
|
|
2700
|
+
retryTimer = setTimeout(() => {
|
|
2701
|
+
retryTimer = void 0;
|
|
2702
|
+
connect();
|
|
2703
|
+
}, delay);
|
|
2704
|
+
};
|
|
2705
|
+
const connect = () => {
|
|
2706
|
+
if (!started || socket) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
currentStatus = "connecting";
|
|
2710
|
+
try {
|
|
2711
|
+
const current = new WebSocket(options.url, options.protocols ?? []);
|
|
2712
|
+
socket = current;
|
|
2713
|
+
current.addEventListener("open", () => {
|
|
2714
|
+
if (socket === current) {
|
|
2715
|
+
retryAttempt = 0;
|
|
2716
|
+
currentStatus = "connected";
|
|
2717
|
+
emitSocketEvent("open", { url: options.url });
|
|
2718
|
+
}
|
|
2719
|
+
});
|
|
2720
|
+
current.addEventListener("message", (event) => {
|
|
2721
|
+
if (socket !== current) {
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
let parsed = event.data;
|
|
2725
|
+
if (typeof parsed === "string") {
|
|
2726
|
+
try {
|
|
2727
|
+
parsed = JSON.parse(parsed);
|
|
2728
|
+
} catch {
|
|
2729
|
+
parsed = event.data;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
emitSocketEvent("message", parsed);
|
|
2733
|
+
});
|
|
2734
|
+
current.addEventListener("close", (event) => {
|
|
2735
|
+
if (socket !== current) {
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
socket = void 0;
|
|
2739
|
+
emitSocketEvent("close", { code: event.code, reason: event.reason });
|
|
2740
|
+
if (started) {
|
|
2741
|
+
scheduleRetry();
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
current.addEventListener("error", (event) => {
|
|
2745
|
+
emitSocketEvent("error", { error: event });
|
|
2746
|
+
});
|
|
2747
|
+
} catch (error) {
|
|
2748
|
+
socket = void 0;
|
|
2749
|
+
emitSocketEvent("error", { error });
|
|
2750
|
+
if (started) {
|
|
2751
|
+
scheduleRetry();
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
const start = () => {
|
|
2756
|
+
if (!started) {
|
|
2757
|
+
started = true;
|
|
2758
|
+
connect();
|
|
2759
|
+
}
|
|
2760
|
+
};
|
|
2761
|
+
const stop = () => {
|
|
2762
|
+
started = false;
|
|
2763
|
+
if (retryTimer) {
|
|
2764
|
+
clearTimeout(retryTimer);
|
|
2765
|
+
}
|
|
2766
|
+
retryTimer = void 0;
|
|
2767
|
+
const current = socket;
|
|
2768
|
+
socket = void 0;
|
|
2769
|
+
current?.close();
|
|
2770
|
+
currentStatus = "stopped";
|
|
2771
|
+
};
|
|
2772
|
+
const reconnect = () => {
|
|
2773
|
+
if (!started) {
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
if (retryTimer) {
|
|
2777
|
+
clearTimeout(retryTimer);
|
|
2778
|
+
retryTimer = void 0;
|
|
2779
|
+
}
|
|
2780
|
+
const current = socket;
|
|
2781
|
+
socket = void 0;
|
|
2782
|
+
current?.close();
|
|
2783
|
+
connect();
|
|
2784
|
+
};
|
|
2785
|
+
return { start, stop, reconnect, status: () => currentStatus };
|
|
2786
|
+
};
|
|
2787
|
+
|
|
2788
|
+
// src/helpers/catchError.ts
|
|
2670
2789
|
var catchError = (callback) => new Promise((resolve, reject) => {
|
|
2671
2790
|
try {
|
|
2672
2791
|
resolve(callback());
|
|
@@ -2676,14 +2795,17 @@ var catchError = (callback) => new Promise((resolve, reject) => {
|
|
|
2676
2795
|
});
|
|
2677
2796
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2678
2797
|
0 && (module.exports = {
|
|
2798
|
+
BUILTIN_ACTIONS,
|
|
2799
|
+
BUILTIN_ACTION_NAMES,
|
|
2800
|
+
BUILTIN_CONDITIONS,
|
|
2801
|
+
BUILTIN_CONDITION_NAMES,
|
|
2679
2802
|
PubSub,
|
|
2680
2803
|
catchError,
|
|
2681
|
-
createActionsRegistry,
|
|
2682
|
-
createConditionsRegistry,
|
|
2683
2804
|
createFlow,
|
|
2684
2805
|
createMemoryTraceSink,
|
|
2685
2806
|
createPubSub,
|
|
2686
2807
|
createWS,
|
|
2808
|
+
createWebSocket,
|
|
2687
2809
|
defineConfig,
|
|
2688
2810
|
defineErrorReporter
|
|
2689
2811
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -148,6 +148,21 @@ type WS = {
|
|
|
148
148
|
reconnect(): void;
|
|
149
149
|
status(): WSStatus;
|
|
150
150
|
};
|
|
151
|
+
type SocketEventTopic = 'open' | 'message' | 'close' | 'error';
|
|
152
|
+
type WebSocketOptions<TEvents extends object = EventMap> = {
|
|
153
|
+
url: string;
|
|
154
|
+
bus: Bus<TEvents>;
|
|
155
|
+
origin?: string;
|
|
156
|
+
protocols?: string | string[];
|
|
157
|
+
reconnect?: RetryOptions;
|
|
158
|
+
};
|
|
159
|
+
type WebSocketStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'stopped';
|
|
160
|
+
type WSClient = {
|
|
161
|
+
start(): void;
|
|
162
|
+
stop(): void;
|
|
163
|
+
reconnect(): void;
|
|
164
|
+
status(): WebSocketStatus;
|
|
165
|
+
};
|
|
151
166
|
type BindingEventMap = Record<string, Input>;
|
|
152
167
|
type BusBindingKey<TEvents extends object> = {
|
|
153
168
|
[TEvent in EventName<TEvents>]: TEvents[TEvent] extends Input ? `[bus] ${TEvent}` : never;
|
|
@@ -374,14 +389,21 @@ declare const PubSub: Bus<EventMap>;
|
|
|
374
389
|
|
|
375
390
|
declare const createFlow: <TContext, TPatch = unknown, TEvents extends object = BindingEventMap>(definition: FlowDefinition<TContext, TPatch, TEvents>, options: FlowOptions<TContext, TPatch, TEvents>) => Flow<TContext, TPatch>;
|
|
376
391
|
|
|
392
|
+
/**
|
|
393
|
+
* @deprecated Use `createWebSocket` instead. This adapter will be removed in a future release.
|
|
394
|
+
*/
|
|
377
395
|
declare const createWS: <TEvents extends object = EventMap>(options: WSOptions<TEvents>) => WS;
|
|
378
396
|
|
|
397
|
+
declare const createWebSocket: <TEvents extends object = EventMap>(options: WebSocketOptions<TEvents>) => WSClient;
|
|
398
|
+
|
|
379
399
|
declare const catchError: <T>(callback: () => T | PromiseLike<T>) => Promise<T>;
|
|
380
400
|
|
|
381
401
|
type ActionsRegistry<TContext, TPatch> = Map<string, Action<TContext, TPatch>>;
|
|
382
|
-
declare const
|
|
402
|
+
declare const BUILTIN_ACTIONS: readonly [name: string, action: Action<unknown, unknown>][];
|
|
403
|
+
declare const BUILTIN_ACTION_NAMES: Set<string>;
|
|
383
404
|
|
|
384
405
|
type ConditionsRegistry<TContext> = Map<string, ConditionFn<TContext>>;
|
|
385
|
-
declare const
|
|
406
|
+
declare const BUILTIN_CONDITIONS: readonly [name: string, condition: ConditionFn<unknown>][];
|
|
407
|
+
declare const BUILTIN_CONDITION_NAMES: Set<string>;
|
|
386
408
|
|
|
387
|
-
export { type Action, type ActionArgs, type ActionFail, type ActionResult, type ActionSkip, type ActionStop, type ActionSuccess, type ActionsRegistry, type BindingEventMap, type Bus, type BusBinding, type BusBindingKey, type BusBindings, type BusEmitOptions, type BusErrorEvent, type BusEvent, type BusOptions, type ConcurrencyMode, type ConcurrencyOptions, type ConditionExpression, type ConditionFn, type ConditionsRegistry, type Config, type DomBinding, type DomBindingKey, type DomBindings, type DomForm, type DomInput, type ErrorReporter, type ErrorReporterHandlers, type ErrorStage, type EventHandler, type EventMap, type EventName, type EventPattern, type ExpressionOperator, type FetchResponseType, type Flow, type FlowDefinition, type FlowOptions, type InactiveBinding, type Input, type Mode, type Next, type Props, PubSub, type QueueOverflow, type RetryOptions, type RunOptions, type RunResult, type Runner, type RunnerErrorEvent, type RunnerOptions, type Runtime, type SlapError, type SlapErrorEvent, type SlapEvent, type StartResult, type Strategy, type TraceEntry, type TraceSink, type ValidationIssue, type ValidationResult, type VariableValue, type Variables, type WS, type WSOptions, type WSRetryOptions, type WSSocket, type WSStatus,
|
|
409
|
+
export { type Action, type ActionArgs, type ActionFail, type ActionResult, type ActionSkip, type ActionStop, type ActionSuccess, type ActionsRegistry, BUILTIN_ACTIONS, BUILTIN_ACTION_NAMES, BUILTIN_CONDITIONS, BUILTIN_CONDITION_NAMES, type BindingEventMap, type Bus, type BusBinding, type BusBindingKey, type BusBindings, type BusEmitOptions, type BusErrorEvent, type BusEvent, type BusOptions, type ConcurrencyMode, type ConcurrencyOptions, type ConditionExpression, type ConditionFn, type ConditionsRegistry, type Config, type DomBinding, type DomBindingKey, type DomBindings, type DomForm, type DomInput, type ErrorReporter, type ErrorReporterHandlers, type ErrorStage, type EventHandler, type EventMap, type EventName, type EventPattern, type ExpressionOperator, type FetchResponseType, type Flow, type FlowDefinition, type FlowOptions, type InactiveBinding, type Input, type Mode, type Next, type Props, PubSub, type QueueOverflow, type RetryOptions, type RunOptions, type RunResult, type Runner, type RunnerErrorEvent, type RunnerOptions, type Runtime, type SlapError, type SlapErrorEvent, type SlapEvent, type SocketEventTopic, type StartResult, type Strategy, type TraceEntry, type TraceSink, type ValidationIssue, type ValidationResult, type VariableValue, type Variables, type WS, type WSClient, type WSOptions, type WSRetryOptions, type WSSocket, type WSStatus, type WebSocketOptions, type WebSocketStatus, catchError, createFlow, createMemoryTraceSink, createPubSub, createWS, createWebSocket, defineConfig, defineErrorReporter };
|
package/dist/index.d.ts
CHANGED
|
@@ -148,6 +148,21 @@ type WS = {
|
|
|
148
148
|
reconnect(): void;
|
|
149
149
|
status(): WSStatus;
|
|
150
150
|
};
|
|
151
|
+
type SocketEventTopic = 'open' | 'message' | 'close' | 'error';
|
|
152
|
+
type WebSocketOptions<TEvents extends object = EventMap> = {
|
|
153
|
+
url: string;
|
|
154
|
+
bus: Bus<TEvents>;
|
|
155
|
+
origin?: string;
|
|
156
|
+
protocols?: string | string[];
|
|
157
|
+
reconnect?: RetryOptions;
|
|
158
|
+
};
|
|
159
|
+
type WebSocketStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'stopped';
|
|
160
|
+
type WSClient = {
|
|
161
|
+
start(): void;
|
|
162
|
+
stop(): void;
|
|
163
|
+
reconnect(): void;
|
|
164
|
+
status(): WebSocketStatus;
|
|
165
|
+
};
|
|
151
166
|
type BindingEventMap = Record<string, Input>;
|
|
152
167
|
type BusBindingKey<TEvents extends object> = {
|
|
153
168
|
[TEvent in EventName<TEvents>]: TEvents[TEvent] extends Input ? `[bus] ${TEvent}` : never;
|
|
@@ -374,14 +389,21 @@ declare const PubSub: Bus<EventMap>;
|
|
|
374
389
|
|
|
375
390
|
declare const createFlow: <TContext, TPatch = unknown, TEvents extends object = BindingEventMap>(definition: FlowDefinition<TContext, TPatch, TEvents>, options: FlowOptions<TContext, TPatch, TEvents>) => Flow<TContext, TPatch>;
|
|
376
391
|
|
|
392
|
+
/**
|
|
393
|
+
* @deprecated Use `createWebSocket` instead. This adapter will be removed in a future release.
|
|
394
|
+
*/
|
|
377
395
|
declare const createWS: <TEvents extends object = EventMap>(options: WSOptions<TEvents>) => WS;
|
|
378
396
|
|
|
397
|
+
declare const createWebSocket: <TEvents extends object = EventMap>(options: WebSocketOptions<TEvents>) => WSClient;
|
|
398
|
+
|
|
379
399
|
declare const catchError: <T>(callback: () => T | PromiseLike<T>) => Promise<T>;
|
|
380
400
|
|
|
381
401
|
type ActionsRegistry<TContext, TPatch> = Map<string, Action<TContext, TPatch>>;
|
|
382
|
-
declare const
|
|
402
|
+
declare const BUILTIN_ACTIONS: readonly [name: string, action: Action<unknown, unknown>][];
|
|
403
|
+
declare const BUILTIN_ACTION_NAMES: Set<string>;
|
|
383
404
|
|
|
384
405
|
type ConditionsRegistry<TContext> = Map<string, ConditionFn<TContext>>;
|
|
385
|
-
declare const
|
|
406
|
+
declare const BUILTIN_CONDITIONS: readonly [name: string, condition: ConditionFn<unknown>][];
|
|
407
|
+
declare const BUILTIN_CONDITION_NAMES: Set<string>;
|
|
386
408
|
|
|
387
|
-
export { type Action, type ActionArgs, type ActionFail, type ActionResult, type ActionSkip, type ActionStop, type ActionSuccess, type ActionsRegistry, type BindingEventMap, type Bus, type BusBinding, type BusBindingKey, type BusBindings, type BusEmitOptions, type BusErrorEvent, type BusEvent, type BusOptions, type ConcurrencyMode, type ConcurrencyOptions, type ConditionExpression, type ConditionFn, type ConditionsRegistry, type Config, type DomBinding, type DomBindingKey, type DomBindings, type DomForm, type DomInput, type ErrorReporter, type ErrorReporterHandlers, type ErrorStage, type EventHandler, type EventMap, type EventName, type EventPattern, type ExpressionOperator, type FetchResponseType, type Flow, type FlowDefinition, type FlowOptions, type InactiveBinding, type Input, type Mode, type Next, type Props, PubSub, type QueueOverflow, type RetryOptions, type RunOptions, type RunResult, type Runner, type RunnerErrorEvent, type RunnerOptions, type Runtime, type SlapError, type SlapErrorEvent, type SlapEvent, type StartResult, type Strategy, type TraceEntry, type TraceSink, type ValidationIssue, type ValidationResult, type VariableValue, type Variables, type WS, type WSOptions, type WSRetryOptions, type WSSocket, type WSStatus,
|
|
409
|
+
export { type Action, type ActionArgs, type ActionFail, type ActionResult, type ActionSkip, type ActionStop, type ActionSuccess, type ActionsRegistry, BUILTIN_ACTIONS, BUILTIN_ACTION_NAMES, BUILTIN_CONDITIONS, BUILTIN_CONDITION_NAMES, type BindingEventMap, type Bus, type BusBinding, type BusBindingKey, type BusBindings, type BusEmitOptions, type BusErrorEvent, type BusEvent, type BusOptions, type ConcurrencyMode, type ConcurrencyOptions, type ConditionExpression, type ConditionFn, type ConditionsRegistry, type Config, type DomBinding, type DomBindingKey, type DomBindings, type DomForm, type DomInput, type ErrorReporter, type ErrorReporterHandlers, type ErrorStage, type EventHandler, type EventMap, type EventName, type EventPattern, type ExpressionOperator, type FetchResponseType, type Flow, type FlowDefinition, type FlowOptions, type InactiveBinding, type Input, type Mode, type Next, type Props, PubSub, type QueueOverflow, type RetryOptions, type RunOptions, type RunResult, type Runner, type RunnerErrorEvent, type RunnerOptions, type Runtime, type SlapError, type SlapErrorEvent, type SlapEvent, type SocketEventTopic, type StartResult, type Strategy, type TraceEntry, type TraceSink, type ValidationIssue, type ValidationResult, type VariableValue, type Variables, type WS, type WSClient, type WSOptions, type WSRetryOptions, type WSSocket, type WSStatus, type WebSocketOptions, type WebSocketStatus, catchError, createFlow, createMemoryTraceSink, createPubSub, createWS, createWebSocket, defineConfig, defineErrorReporter };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// src/helpers/config/defineConfig.ts
|
|
2
2
|
var defineConfig = (config) => config;
|
|
3
3
|
|
|
4
|
-
// src/helpers/trace/cloneData.ts
|
|
5
|
-
var cloneData = (data) => ({ ...data });
|
|
6
|
-
|
|
7
4
|
// src/helpers/trace/createMemoryTraceSink.ts
|
|
8
5
|
var createMemoryTraceSink = () => {
|
|
9
6
|
const items = [];
|
|
@@ -15,22 +12,9 @@ var createMemoryTraceSink = () => {
|
|
|
15
12
|
};
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
// src/helpers/errors/slapError.ts
|
|
19
|
-
var slapError = (code, message, extras = {}) => ({ code, message, ...extras });
|
|
20
|
-
|
|
21
15
|
// src/helpers/errors/defineErrorReporter.ts
|
|
22
16
|
var defineErrorReporter = (handlers) => typeof handlers === "function" ? handlers : handlers.report;
|
|
23
17
|
|
|
24
|
-
// src/errors.ts
|
|
25
|
-
var SyncAsyncError = class extends Error {
|
|
26
|
-
slapError;
|
|
27
|
-
constructor(error) {
|
|
28
|
-
super(error.message);
|
|
29
|
-
this.name = "SyncAsyncError";
|
|
30
|
-
this.slapError = error;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
18
|
// src/helpers/ids/createId.ts
|
|
35
19
|
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
36
20
|
var idLength = 12;
|
|
@@ -70,7 +54,7 @@ var serializeError = (error) => ({
|
|
|
70
54
|
error: error instanceof Error ? error.message : String(error)
|
|
71
55
|
});
|
|
72
56
|
|
|
73
|
-
// src/
|
|
57
|
+
// src/createPubSub.ts
|
|
74
58
|
var createPubSub = (options = {}) => {
|
|
75
59
|
const subscribers = /* @__PURE__ */ new Map();
|
|
76
60
|
const wildcardSubscribers = /* @__PURE__ */ new Map();
|
|
@@ -168,6 +152,16 @@ var createPubSub = (options = {}) => {
|
|
|
168
152
|
};
|
|
169
153
|
var PubSub = createPubSub();
|
|
170
154
|
|
|
155
|
+
// src/helpers/errors/syncAsyncError.ts
|
|
156
|
+
var SyncAsyncError = class extends Error {
|
|
157
|
+
slapError;
|
|
158
|
+
constructor(error) {
|
|
159
|
+
super(error.message);
|
|
160
|
+
this.name = "SyncAsyncError";
|
|
161
|
+
this.slapError = error;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
171
165
|
// src/helpers/actions/coreDelay.ts
|
|
172
166
|
var coreDelay = async ({
|
|
173
167
|
props,
|
|
@@ -204,10 +198,7 @@ var coreEmit = ({
|
|
|
204
198
|
};
|
|
205
199
|
|
|
206
200
|
// src/helpers/actions/coreFail.ts
|
|
207
|
-
var coreFail = ({
|
|
208
|
-
props,
|
|
209
|
-
runtime
|
|
210
|
-
}) => runtime.fail(String(props.reason ?? "failed"), props.data);
|
|
201
|
+
var coreFail = ({ props, runtime }) => runtime.fail(String(props.reason ?? "failed"), props.data);
|
|
211
202
|
|
|
212
203
|
// src/helpers/retry/getRetryDelay.ts
|
|
213
204
|
var getRetryDelay = (attempt, options) => {
|
|
@@ -414,10 +405,7 @@ var corePatch = ({
|
|
|
414
405
|
};
|
|
415
406
|
|
|
416
407
|
// src/helpers/actions/coreSet.ts
|
|
417
|
-
var coreSet = ({
|
|
418
|
-
props,
|
|
419
|
-
runtime
|
|
420
|
-
}) => {
|
|
408
|
+
var coreSet = ({ props, runtime }) => {
|
|
421
409
|
const path = props.path;
|
|
422
410
|
if (typeof path === "string") {
|
|
423
411
|
runtime.set(path, props.value);
|
|
@@ -438,13 +426,10 @@ var coreSetData = ({
|
|
|
438
426
|
};
|
|
439
427
|
|
|
440
428
|
// src/helpers/actions/coreStop.ts
|
|
441
|
-
var coreStop = ({
|
|
442
|
-
props,
|
|
443
|
-
runtime
|
|
444
|
-
}) => runtime.stop(String(props.reason ?? "stopped"));
|
|
429
|
+
var coreStop = ({ props, runtime }) => runtime.stop(String(props.reason ?? "stopped"));
|
|
445
430
|
|
|
446
|
-
// src/
|
|
447
|
-
var
|
|
431
|
+
// src/helpers/actions/index.ts
|
|
432
|
+
var BUILTIN_ACTIONS = [
|
|
448
433
|
["core.noop", coreNoop],
|
|
449
434
|
["core.stop", coreStop],
|
|
450
435
|
["core.fail", coreFail],
|
|
@@ -458,7 +443,8 @@ var createActionsRegistry = () => /* @__PURE__ */ new Map([
|
|
|
458
443
|
["core.emit", coreEmit],
|
|
459
444
|
["core.patch", corePatch],
|
|
460
445
|
["core.delay", coreDelay]
|
|
461
|
-
]
|
|
446
|
+
];
|
|
447
|
+
var BUILTIN_ACTION_NAMES = new Set(BUILTIN_ACTIONS.map(([name]) => name));
|
|
462
448
|
|
|
463
449
|
// src/helpers/conditions/changedCondition.ts
|
|
464
450
|
var changedCondition = (_args, current, previous) => !Object.is(current, previous);
|
|
@@ -552,8 +538,8 @@ var typeIsCondition = (_args, value, expected) => {
|
|
|
552
538
|
return expected === "string" || expected === "number" || expected === "boolean" ? typeof value === expected : false;
|
|
553
539
|
};
|
|
554
540
|
|
|
555
|
-
// src/
|
|
556
|
-
var
|
|
541
|
+
// src/helpers/conditions/index.ts
|
|
542
|
+
var BUILTIN_CONDITIONS = [
|
|
557
543
|
["eq", eqCondition],
|
|
558
544
|
["neq", neqCondition],
|
|
559
545
|
["gt", gtCondition],
|
|
@@ -570,7 +556,8 @@ var createConditionsRegistry = () => /* @__PURE__ */ new Map([
|
|
|
570
556
|
["typeIs", typeIsCondition],
|
|
571
557
|
["changed", changedCondition],
|
|
572
558
|
["cooldownReady", cooldownReadyCondition]
|
|
573
|
-
]
|
|
559
|
+
];
|
|
560
|
+
var BUILTIN_CONDITION_NAMES = new Set(BUILTIN_CONDITIONS.map(([name]) => name));
|
|
574
561
|
|
|
575
562
|
// src/helpers/runner/applyResult.ts
|
|
576
563
|
var applyResult = (result, state, mergeData) => {
|
|
@@ -584,6 +571,9 @@ var applyResult = (result, state, mergeData) => {
|
|
|
584
571
|
state.events.push(...result.events);
|
|
585
572
|
};
|
|
586
573
|
|
|
574
|
+
// src/helpers/errors/slapError.ts
|
|
575
|
+
var slapError = (code, message, extras = {}) => ({ code, message, ...extras });
|
|
576
|
+
|
|
587
577
|
// src/helpers/path/resolveValue.ts
|
|
588
578
|
import { pick as pick3 } from "objwalk";
|
|
589
579
|
|
|
@@ -1022,7 +1012,7 @@ var executeNext = (item, depth, state, environment) => {
|
|
|
1022
1012
|
const id = typeof item === "string" ? item : item.strategy;
|
|
1023
1013
|
if (typeof item !== "string" && item.when) {
|
|
1024
1014
|
const runtime = createRuntime(state);
|
|
1025
|
-
const condition = evaluateCondition(item.when, environment.
|
|
1015
|
+
const condition = evaluateCondition(item.when, environment.registry.conditions, { ...state, runtime, strategy: id });
|
|
1026
1016
|
if (!condition.ok) {
|
|
1027
1017
|
return { status: "failed", error: condition.error, patches: [], events: [] };
|
|
1028
1018
|
}
|
|
@@ -1274,6 +1264,9 @@ var normalizeActionResult = (raw) => {
|
|
|
1274
1264
|
};
|
|
1275
1265
|
};
|
|
1276
1266
|
|
|
1267
|
+
// src/helpers/trace/cloneData.ts
|
|
1268
|
+
var cloneData = (data) => ({ ...data });
|
|
1269
|
+
|
|
1277
1270
|
// src/helpers/runner/pushTrace.ts
|
|
1278
1271
|
var pushTrace = (state, step, depth, strategyId, strategy, status, props, dataBefore, startedAt, reason) => {
|
|
1279
1272
|
state.traceSink?.push({
|
|
@@ -1456,7 +1449,7 @@ var executeStrategy = (id, extraProps, depth, state, environment) => {
|
|
|
1456
1449
|
events: []
|
|
1457
1450
|
};
|
|
1458
1451
|
}
|
|
1459
|
-
const action = environment.
|
|
1452
|
+
const action = environment.registry.actions.get(strategy.fn);
|
|
1460
1453
|
if (!action) {
|
|
1461
1454
|
return {
|
|
1462
1455
|
status: "failed",
|
|
@@ -1511,7 +1504,7 @@ var executeStrategy = (id, extraProps, depth, state, environment) => {
|
|
|
1511
1504
|
const dataBefore = cloneData(state.data);
|
|
1512
1505
|
const traceStep = state.stepCounter.current + 1;
|
|
1513
1506
|
const startedAt = Date.now();
|
|
1514
|
-
const condition = evaluateCondition(strategy.when, environment.
|
|
1507
|
+
const condition = evaluateCondition(strategy.when, environment.registry.conditions, {
|
|
1515
1508
|
...state,
|
|
1516
1509
|
runtime,
|
|
1517
1510
|
strategy: id
|
|
@@ -1825,7 +1818,12 @@ var validateCondition = (expression, strategy, path, conditionsRegistry, errors)
|
|
|
1825
1818
|
const [operator, ...args] = expression;
|
|
1826
1819
|
if (operator === "guard") {
|
|
1827
1820
|
if (args.length !== 1 || typeof args[0] !== "string") {
|
|
1828
|
-
errors.push({
|
|
1821
|
+
errors.push({
|
|
1822
|
+
code: "CONDITION_INVALID",
|
|
1823
|
+
message: "Guard reference must be a single string name",
|
|
1824
|
+
strategy,
|
|
1825
|
+
path
|
|
1826
|
+
});
|
|
1829
1827
|
}
|
|
1830
1828
|
return;
|
|
1831
1829
|
}
|
|
@@ -1878,7 +1876,9 @@ var validateNextList = (config, list, path, strategy, conditionsRegistry, errors
|
|
|
1878
1876
|
var isGuardRef = (expression) => Array.isArray(expression) && expression.length === 2 && expression[0] === "guard" && typeof expression[1] === "string";
|
|
1879
1877
|
var resolveRef = (config, name, visiting, path) => {
|
|
1880
1878
|
if (visiting.includes(name)) {
|
|
1881
|
-
return {
|
|
1879
|
+
return {
|
|
1880
|
+
issue: { code: "GUARD_CYCLE", message: `Guard "${name}" is part of a reference cycle`, guard: name, path }
|
|
1881
|
+
};
|
|
1882
1882
|
}
|
|
1883
1883
|
const guard = config.guards?.[name];
|
|
1884
1884
|
if (guard === void 0) {
|
|
@@ -2101,10 +2101,10 @@ var createRunCancellation = (source) => {
|
|
|
2101
2101
|
};
|
|
2102
2102
|
};
|
|
2103
2103
|
|
|
2104
|
-
// src/
|
|
2104
|
+
// src/createRunner.ts
|
|
2105
2105
|
var createRunner = (options = {}) => {
|
|
2106
|
-
const actionsRegistry =
|
|
2107
|
-
const conditionsRegistry =
|
|
2106
|
+
const actionsRegistry = new Map(BUILTIN_ACTIONS);
|
|
2107
|
+
const conditionsRegistry = new Map(BUILTIN_CONDITIONS);
|
|
2108
2108
|
const configRef = {};
|
|
2109
2109
|
const timeout = options.timeout ?? options.timeoutMs;
|
|
2110
2110
|
const runnerOptions = timeout === void 0 ? options : { ...options, timeout };
|
|
@@ -2114,19 +2114,24 @@ var createRunner = (options = {}) => {
|
|
|
2114
2114
|
console.warn("timeoutMs is deprecated; use timeout. It will be removed in a future major release.");
|
|
2115
2115
|
}
|
|
2116
2116
|
const environment = {
|
|
2117
|
-
actionsRegistry,
|
|
2118
|
-
conditionsRegistry,
|
|
2117
|
+
registry: { actions: actionsRegistry, conditions: conditionsRegistry },
|
|
2119
2118
|
configRef,
|
|
2120
2119
|
options: runnerOptions,
|
|
2121
2120
|
mergeData
|
|
2122
2121
|
};
|
|
2123
2122
|
const registerAction = (name, action) => {
|
|
2123
|
+
if (BUILTIN_ACTION_NAMES.has(name)) {
|
|
2124
|
+
throw new Error(`Cannot override built-in action "${name}"`);
|
|
2125
|
+
}
|
|
2124
2126
|
actionsRegistry.set(name, action);
|
|
2125
2127
|
};
|
|
2126
2128
|
const registerActions = (items) => {
|
|
2127
2129
|
Object.entries(items).forEach(([name, action]) => registerAction(name, action));
|
|
2128
2130
|
};
|
|
2129
2131
|
const registerCondition = (name, condition) => {
|
|
2132
|
+
if (BUILTIN_CONDITION_NAMES.has(name)) {
|
|
2133
|
+
throw new Error(`Cannot override built-in condition "${name}"`);
|
|
2134
|
+
}
|
|
2130
2135
|
conditionsRegistry.set(name, condition);
|
|
2131
2136
|
};
|
|
2132
2137
|
const registerConditions = (items) => {
|
|
@@ -2223,7 +2228,7 @@ var parseDomBinding = (binding, prefix) => {
|
|
|
2223
2228
|
return separator <= 0 || separator === source.length - 1 ? void 0 : { selector: source.slice(0, separator), eventType: source.slice(separator + 1) };
|
|
2224
2229
|
};
|
|
2225
2230
|
|
|
2226
|
-
// src/
|
|
2231
|
+
// src/createFlow.ts
|
|
2227
2232
|
var busBindingPrefix = "[bus] ";
|
|
2228
2233
|
var domBindingPrefix = "[dom] ";
|
|
2229
2234
|
var defaultMaxQueueSize = 50;
|
|
@@ -2464,10 +2469,11 @@ var createFlow = (definition, options) => {
|
|
|
2464
2469
|
return { runner, start, stop };
|
|
2465
2470
|
};
|
|
2466
2471
|
|
|
2467
|
-
// src/
|
|
2472
|
+
// src/createWS.ts
|
|
2468
2473
|
var openState = 1;
|
|
2469
2474
|
var maxSeenEvents = 1e3;
|
|
2470
2475
|
var createWS = (options) => {
|
|
2476
|
+
console.warn("[slapflow] createWS is deprecated and will be removed soon. Use createWebSocket instead.");
|
|
2471
2477
|
const inboundTopics = new Set(options.inboundTopics ?? []);
|
|
2472
2478
|
const outboundTopics = new Set(options.outboundTopics ?? []);
|
|
2473
2479
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
@@ -2631,7 +2637,117 @@ var createWS = (options) => {
|
|
|
2631
2637
|
return { start, stop, reconnect, status: () => currentStatus };
|
|
2632
2638
|
};
|
|
2633
2639
|
|
|
2634
|
-
// src/
|
|
2640
|
+
// src/createWebSocket.ts
|
|
2641
|
+
var createWebSocket = (options) => {
|
|
2642
|
+
let socket;
|
|
2643
|
+
let retryTimer;
|
|
2644
|
+
let retryAttempt = 0;
|
|
2645
|
+
let started = false;
|
|
2646
|
+
let currentStatus = "idle";
|
|
2647
|
+
const emitSocketEvent = (topic, payload) => {
|
|
2648
|
+
options.bus.dispatch({
|
|
2649
|
+
id: createId(),
|
|
2650
|
+
topic,
|
|
2651
|
+
occurredAt: Date.now(),
|
|
2652
|
+
...options.origin ? { origin: options.origin } : {},
|
|
2653
|
+
parsed: payload,
|
|
2654
|
+
serialized: JSON.stringify(payload)
|
|
2655
|
+
});
|
|
2656
|
+
};
|
|
2657
|
+
const scheduleRetry = () => {
|
|
2658
|
+
const delay = getRetryDelay(retryAttempt, options.reconnect ?? {});
|
|
2659
|
+
const attempt = retryAttempt + 1;
|
|
2660
|
+
currentStatus = "reconnecting";
|
|
2661
|
+
retryAttempt = attempt;
|
|
2662
|
+
retryTimer = setTimeout(() => {
|
|
2663
|
+
retryTimer = void 0;
|
|
2664
|
+
connect();
|
|
2665
|
+
}, delay);
|
|
2666
|
+
};
|
|
2667
|
+
const connect = () => {
|
|
2668
|
+
if (!started || socket) {
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
currentStatus = "connecting";
|
|
2672
|
+
try {
|
|
2673
|
+
const current = new WebSocket(options.url, options.protocols ?? []);
|
|
2674
|
+
socket = current;
|
|
2675
|
+
current.addEventListener("open", () => {
|
|
2676
|
+
if (socket === current) {
|
|
2677
|
+
retryAttempt = 0;
|
|
2678
|
+
currentStatus = "connected";
|
|
2679
|
+
emitSocketEvent("open", { url: options.url });
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
current.addEventListener("message", (event) => {
|
|
2683
|
+
if (socket !== current) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
let parsed = event.data;
|
|
2687
|
+
if (typeof parsed === "string") {
|
|
2688
|
+
try {
|
|
2689
|
+
parsed = JSON.parse(parsed);
|
|
2690
|
+
} catch {
|
|
2691
|
+
parsed = event.data;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
emitSocketEvent("message", parsed);
|
|
2695
|
+
});
|
|
2696
|
+
current.addEventListener("close", (event) => {
|
|
2697
|
+
if (socket !== current) {
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
socket = void 0;
|
|
2701
|
+
emitSocketEvent("close", { code: event.code, reason: event.reason });
|
|
2702
|
+
if (started) {
|
|
2703
|
+
scheduleRetry();
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
current.addEventListener("error", (event) => {
|
|
2707
|
+
emitSocketEvent("error", { error: event });
|
|
2708
|
+
});
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
socket = void 0;
|
|
2711
|
+
emitSocketEvent("error", { error });
|
|
2712
|
+
if (started) {
|
|
2713
|
+
scheduleRetry();
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
const start = () => {
|
|
2718
|
+
if (!started) {
|
|
2719
|
+
started = true;
|
|
2720
|
+
connect();
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
const stop = () => {
|
|
2724
|
+
started = false;
|
|
2725
|
+
if (retryTimer) {
|
|
2726
|
+
clearTimeout(retryTimer);
|
|
2727
|
+
}
|
|
2728
|
+
retryTimer = void 0;
|
|
2729
|
+
const current = socket;
|
|
2730
|
+
socket = void 0;
|
|
2731
|
+
current?.close();
|
|
2732
|
+
currentStatus = "stopped";
|
|
2733
|
+
};
|
|
2734
|
+
const reconnect = () => {
|
|
2735
|
+
if (!started) {
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
if (retryTimer) {
|
|
2739
|
+
clearTimeout(retryTimer);
|
|
2740
|
+
retryTimer = void 0;
|
|
2741
|
+
}
|
|
2742
|
+
const current = socket;
|
|
2743
|
+
socket = void 0;
|
|
2744
|
+
current?.close();
|
|
2745
|
+
connect();
|
|
2746
|
+
};
|
|
2747
|
+
return { start, stop, reconnect, status: () => currentStatus };
|
|
2748
|
+
};
|
|
2749
|
+
|
|
2750
|
+
// src/helpers/catchError.ts
|
|
2635
2751
|
var catchError = (callback) => new Promise((resolve, reject) => {
|
|
2636
2752
|
try {
|
|
2637
2753
|
resolve(callback());
|
|
@@ -2640,14 +2756,17 @@ var catchError = (callback) => new Promise((resolve, reject) => {
|
|
|
2640
2756
|
}
|
|
2641
2757
|
});
|
|
2642
2758
|
export {
|
|
2759
|
+
BUILTIN_ACTIONS,
|
|
2760
|
+
BUILTIN_ACTION_NAMES,
|
|
2761
|
+
BUILTIN_CONDITIONS,
|
|
2762
|
+
BUILTIN_CONDITION_NAMES,
|
|
2643
2763
|
PubSub,
|
|
2644
2764
|
catchError,
|
|
2645
|
-
createActionsRegistry,
|
|
2646
|
-
createConditionsRegistry,
|
|
2647
2765
|
createFlow,
|
|
2648
2766
|
createMemoryTraceSink,
|
|
2649
2767
|
createPubSub,
|
|
2650
2768
|
createWS,
|
|
2769
|
+
createWebSocket,
|
|
2651
2770
|
defineConfig,
|
|
2652
2771
|
defineErrorReporter
|
|
2653
2772
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slapflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Chain actions behavior runtime",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Sergey Khalilov",
|
|
@@ -57,7 +57,9 @@
|
|
|
57
57
|
"test": "npm run typecheck && vitest run",
|
|
58
58
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
59
59
|
"pack:check": "npm pack --dry-run",
|
|
60
|
-
"prepublishOnly": "npm test && npm run build"
|
|
60
|
+
"prepublishOnly": "npm test && npm run build",
|
|
61
|
+
"format": "prettier --write .",
|
|
62
|
+
"check": "prettier --check ."
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
65
|
"@types/node": "^26.1.0",
|