slapflow 1.0.2

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/dist/index.js ADDED
@@ -0,0 +1,2524 @@
1
+ // src/helpers/config/defineConfig.ts
2
+ var defineConfig = (config) => config;
3
+
4
+ // src/helpers/trace/cloneData.ts
5
+ var cloneData = (data) => ({ ...data });
6
+
7
+ // src/helpers/trace/createMemoryTraceSink.ts
8
+ var createMemoryTraceSink = () => {
9
+ const items = [];
10
+ return {
11
+ push: (entry) => {
12
+ items.push(entry);
13
+ },
14
+ entries: () => [...items]
15
+ };
16
+ };
17
+
18
+ // src/helpers/errors/slapError.ts
19
+ var slapError = (code, message, extras = {}) => ({ code, message, ...extras });
20
+
21
+ // src/helpers/errors/defineErrorReporter.ts
22
+ var defineErrorReporter = (handlers) => typeof handlers === "function" ? handlers : handlers.report;
23
+
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
+ // src/helpers/ids/createId.ts
35
+ var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
36
+ var idLength = 12;
37
+ var createId = () => {
38
+ const { crypto } = globalThis;
39
+ const randomValues = crypto?.getRandomValues ? crypto.getRandomValues(new Uint32Array(idLength)) : void 0;
40
+ return Array.from(
41
+ { length: idLength },
42
+ (_, index) => alphabet[Math.floor((randomValues?.[index] ?? Math.random() * 2 ** 32) / 2 ** 32 * alphabet.length)]
43
+ ).join("");
44
+ };
45
+
46
+ // src/helpers/pubSub/isBusEvent.ts
47
+ var isBusEvent = (event) => {
48
+ if (!event || typeof event !== "object") {
49
+ return false;
50
+ }
51
+ const candidate = event;
52
+ return typeof candidate.id === "string" && typeof candidate.topic === "string" && typeof candidate.occurredAt === "number" && typeof candidate.serialized === "string" && "parsed" in candidate && (candidate.origin === void 0 || typeof candidate.origin === "string");
53
+ };
54
+
55
+ // src/helpers/pubSub/serializeError.ts
56
+ var serializeError = (error) => ({
57
+ error: error instanceof Error ? error.message : String(error)
58
+ });
59
+
60
+ // src/pubSub.ts
61
+ var createPubSub = (options = {}) => {
62
+ const subscribers = /* @__PURE__ */ new Map();
63
+ const dispatch = (event) => {
64
+ let dispatchedEvent;
65
+ if (!isBusEvent(event)) {
66
+ options.onError?.({
67
+ type: "serialization",
68
+ topic: "unknown",
69
+ payload: event,
70
+ error: new Error("Invalid bus event")
71
+ });
72
+ } else {
73
+ const handlers = subscribers.get(event.topic);
74
+ if (handlers) {
75
+ for (const handler of [...handlers]) {
76
+ try {
77
+ handler(event);
78
+ } catch (error) {
79
+ options.onError?.({ type: "subscriber", event, error });
80
+ }
81
+ }
82
+ }
83
+ dispatchedEvent = event;
84
+ }
85
+ return dispatchedEvent;
86
+ };
87
+ const on = (event, handler) => {
88
+ const handlers = subscribers.get(event) ?? /* @__PURE__ */ new Set();
89
+ const listener = handler;
90
+ handlers.add(listener);
91
+ subscribers.set(event, handlers);
92
+ return () => off(event, handler);
93
+ };
94
+ const off = (event, handler) => {
95
+ if (handler) {
96
+ const handlers = subscribers.get(event);
97
+ if (handlers) {
98
+ handlers.delete(handler);
99
+ if (handlers.size === 0) {
100
+ subscribers.delete(event);
101
+ }
102
+ }
103
+ } else {
104
+ subscribers.delete(event);
105
+ }
106
+ };
107
+ const emit = (topic, payload, emitOptions = {}) => {
108
+ let event;
109
+ try {
110
+ const serialized = JSON.stringify(payload);
111
+ if (typeof serialized !== "string") {
112
+ throw new Error("Payload cannot be serialized");
113
+ }
114
+ event = {
115
+ id: createId(),
116
+ topic,
117
+ occurredAt: Date.now(),
118
+ ...emitOptions.origin ? { origin: emitOptions.origin } : {},
119
+ parsed: payload,
120
+ serialized
121
+ };
122
+ } catch (error) {
123
+ const parsed = serializeError(error);
124
+ event = {
125
+ id: createId(),
126
+ topic,
127
+ occurredAt: Date.now(),
128
+ ...emitOptions.origin ? { origin: emitOptions.origin } : {},
129
+ parsed,
130
+ serialized: JSON.stringify(parsed)
131
+ };
132
+ options.onError?.({
133
+ type: "serialization",
134
+ topic,
135
+ payload,
136
+ ...emitOptions.origin ? { origin: emitOptions.origin } : {},
137
+ error
138
+ });
139
+ }
140
+ return dispatch(event);
141
+ };
142
+ return { on, off, emit, dispatch };
143
+ };
144
+ var PubSub = createPubSub();
145
+
146
+ // src/helpers/actions/coreDelay.ts
147
+ var coreDelay = async ({
148
+ props,
149
+ signal
150
+ }) => {
151
+ const ms = Math.max(0, Number(props.ms ?? 0));
152
+ await new Promise((resolve) => {
153
+ let timer;
154
+ const finish = () => {
155
+ if (timer) {
156
+ clearTimeout(timer);
157
+ }
158
+ signal.removeEventListener("abort", finish);
159
+ resolve();
160
+ };
161
+ if (signal.aborted) {
162
+ finish();
163
+ } else {
164
+ timer = setTimeout(finish, ms);
165
+ signal.addEventListener("abort", finish, { once: true });
166
+ }
167
+ });
168
+ };
169
+
170
+ // src/helpers/actions/coreEmit.ts
171
+ var coreEmit = ({
172
+ props,
173
+ runtime
174
+ }) => {
175
+ const type = props.type;
176
+ if (typeof type === "string") {
177
+ runtime.emit({ type, payload: props.payload });
178
+ }
179
+ };
180
+
181
+ // src/helpers/actions/coreFail.ts
182
+ var coreFail = ({
183
+ props,
184
+ runtime
185
+ }) => runtime.fail(String(props.reason ?? "failed"), props.data);
186
+
187
+ // src/helpers/retry/getRetryDelay.ts
188
+ var getRetryDelay = (attempt, options) => {
189
+ const { initialDelay = 500, maxDelay = 1e4, multiplier = 2, jitter } = options;
190
+ const delay = Math.min(initialDelay * multiplier ** attempt, maxDelay);
191
+ return jitter === false ? delay : Math.round(delay * (0.5 + Math.random() * 0.5));
192
+ };
193
+
194
+ // src/helpers/retry/waitForRetry.ts
195
+ var waitForRetry = (delay, signal) => new Promise((resolve, reject) => {
196
+ const timer = setTimeout(resolve, delay);
197
+ const abort = () => {
198
+ clearTimeout(timer);
199
+ reject(signal.reason);
200
+ };
201
+ signal.addEventListener("abort", abort, { once: true });
202
+ });
203
+
204
+ // src/helpers/actions/coreFetch.ts
205
+ var defaultMaxAttempts = 2;
206
+ var retryableStatuses = /* @__PURE__ */ new Set([408, 425, 429]);
207
+ var credentialsValues = /* @__PURE__ */ new Set(["include", "same-origin", "omit"]);
208
+ var responseReaders = {
209
+ json: (response) => response.json(),
210
+ text: (response) => response.text(),
211
+ blob: (response) => response.blob(),
212
+ arrayBuffer: (response) => response.arrayBuffer(),
213
+ none: async () => void 0
214
+ };
215
+ var coreFetch = async ({
216
+ props,
217
+ runtime,
218
+ signal
219
+ }) => {
220
+ const {
221
+ acceptStatuses,
222
+ body,
223
+ contextPath,
224
+ credentials,
225
+ dataPath,
226
+ headers,
227
+ method,
228
+ response,
229
+ retry: retryProps,
230
+ retryStatuses,
231
+ url
232
+ } = props;
233
+ const responseType = typeof response === "string" ? response : "json";
234
+ const retry = retryProps ?? {};
235
+ const maxAttempts = Math.max(0, Number(retry.maxAttempts ?? defaultMaxAttempts));
236
+ const acceptedStatusSet = Array.isArray(acceptStatuses) ? new Set(acceptStatuses) : void 0;
237
+ const retryStatusSet = Array.isArray(retryStatuses) ? new Set(retryStatuses) : void 0;
238
+ const requestCredentials = typeof credentials === "string" ? credentials : void 0;
239
+ if (typeof url !== "string" || !url) {
240
+ return runtime.fail("core.fetch requires a non-empty url");
241
+ }
242
+ if (!Object.prototype.hasOwnProperty.call(responseReaders, responseType)) {
243
+ return runtime.fail("core.fetch response must be json, text, blob, arrayBuffer, or none");
244
+ }
245
+ if (credentials !== void 0 && (typeof credentials !== "string" || !credentialsValues.has(credentials))) {
246
+ return runtime.fail("core.fetch credentials must be include, same-origin, or omit");
247
+ }
248
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
249
+ try {
250
+ const request = {
251
+ signal,
252
+ ...typeof method === "string" ? { method } : {},
253
+ ...requestCredentials ? { credentials: requestCredentials } : {},
254
+ ...headers ? { headers } : {},
255
+ ...body === void 0 ? {} : { body }
256
+ };
257
+ const response2 = await fetch(url, request);
258
+ const accepted = acceptedStatusSet?.has(response2.status) ?? response2.ok;
259
+ if (accepted) {
260
+ let responseBody;
261
+ try {
262
+ responseBody = await responseReaders[responseType](response2);
263
+ } catch (cause) {
264
+ return runtime.fail("Fetch response could not be parsed", { cause });
265
+ }
266
+ const result = {
267
+ status: response2.status,
268
+ ok: response2.ok,
269
+ headers: Object.fromEntries(response2.headers),
270
+ body: responseBody
271
+ };
272
+ if (typeof dataPath === "string") {
273
+ runtime.data.set(dataPath, result);
274
+ }
275
+ if (typeof contextPath === "string") {
276
+ runtime.set(contextPath, result);
277
+ }
278
+ return;
279
+ }
280
+ const retryableStatus = retryStatusSet?.has(response2.status) ?? (retryableStatuses.has(response2.status) || response2.status >= 500);
281
+ if (attempt === maxAttempts || !retryableStatus) {
282
+ return runtime.fail(`Fetch request failed with status ${response2.status}`, { status: response2.status });
283
+ }
284
+ } catch (cause) {
285
+ if (signal.aborted) {
286
+ return false;
287
+ }
288
+ if (attempt === maxAttempts) {
289
+ return runtime.fail("Fetch request failed", { cause });
290
+ }
291
+ }
292
+ try {
293
+ await waitForRetry(getRetryDelay(attempt, retry), signal);
294
+ } catch (cause) {
295
+ return signal.aborted ? false : runtime.fail("Fetch retry was interrupted", { cause });
296
+ }
297
+ }
298
+ };
299
+
300
+ // src/helpers/runner/stopResult.ts
301
+ var stopResult = (reason) => reason ? { type: "stop", reason } : { type: "stop" };
302
+
303
+ // src/helpers/actions/coreLoop.ts
304
+ var defaultMax = 999;
305
+ var coreLoop = ({
306
+ props,
307
+ signal,
308
+ runtime
309
+ }) => new Promise((resolve) => {
310
+ const configuredDuration = Number(props.duration ?? 0);
311
+ const duration = Number.isFinite(configuredDuration) ? Math.max(1, configuredDuration) : 1;
312
+ const configuredMax = Number(props.max ?? defaultMax);
313
+ const max = configuredMax === -1 ? Number.POSITIVE_INFINITY : Number.isFinite(configuredMax) && configuredMax >= 1 ? Math.floor(configuredMax) : defaultMax;
314
+ const immediate = props.immediate === true;
315
+ let iterationCount = 0;
316
+ let running = false;
317
+ let stopped = false;
318
+ const finish = (result) => {
319
+ if (stopped) {
320
+ return;
321
+ }
322
+ stopped = true;
323
+ clearInterval(interval);
324
+ signal.removeEventListener("abort", abort);
325
+ resolve(result);
326
+ };
327
+ const abort = () => {
328
+ clearInterval(interval);
329
+ finish({ continue: false });
330
+ };
331
+ const tick = async () => {
332
+ if (running || signal.aborted) {
333
+ return;
334
+ }
335
+ running = true;
336
+ iterationCount += 1;
337
+ try {
338
+ const result = await runtime.executeThen();
339
+ if (result.status === "failed") {
340
+ const recovered = await runtime.executeCatch();
341
+ if (recovered?.status === "failed") {
342
+ finish({ type: "fail", reason: recovered.error.message, error: recovered.error, handled: true });
343
+ } else if (recovered?.status === "stopped") {
344
+ finish(stopResult(recovered.reason));
345
+ } else if (!recovered) {
346
+ finish({ type: "fail", reason: result.error.message, error: result.error });
347
+ }
348
+ } else if (result.status === "stopped") {
349
+ finish(stopResult(result.reason));
350
+ }
351
+ if (!stopped && iterationCount >= max) {
352
+ finish({ continue: false });
353
+ }
354
+ } finally {
355
+ running = false;
356
+ if (signal.aborted) {
357
+ finish({ continue: false });
358
+ }
359
+ }
360
+ };
361
+ const interval = setInterval(() => {
362
+ void tick().catch((error) => {
363
+ finish({ type: "fail", reason: "core.loop tick failed", error });
364
+ });
365
+ }, duration);
366
+ signal.addEventListener("abort", abort, { once: true });
367
+ if (signal.aborted) {
368
+ abort();
369
+ return;
370
+ }
371
+ if (immediate) {
372
+ void tick().catch((error) => {
373
+ finish({ type: "fail", reason: "core.loop tick failed", error });
374
+ });
375
+ }
376
+ });
377
+
378
+ // src/helpers/actions/coreNoop.ts
379
+ var coreNoop = () => void 0;
380
+
381
+ // src/helpers/actions/corePatch.ts
382
+ var corePatch = ({
383
+ props,
384
+ runtime
385
+ }) => {
386
+ if ("patch" in props) {
387
+ runtime.patch(props.patch);
388
+ }
389
+ };
390
+
391
+ // src/helpers/actions/coreSet.ts
392
+ var coreSet = ({
393
+ props,
394
+ runtime
395
+ }) => {
396
+ const path = props.path;
397
+ if (typeof path === "string") {
398
+ runtime.set(path, props.value);
399
+ }
400
+ return props.data ? { data: props.data } : void 0;
401
+ };
402
+
403
+ // src/helpers/actions/coreSetData.ts
404
+ var coreSetData = ({
405
+ props,
406
+ runtime
407
+ }) => {
408
+ const path = props.path;
409
+ if (typeof path === "string") {
410
+ runtime.data.set(path, props.value);
411
+ }
412
+ return props.data ? { data: props.data } : void 0;
413
+ };
414
+
415
+ // src/helpers/actions/coreStop.ts
416
+ var coreStop = ({
417
+ props,
418
+ runtime
419
+ }) => runtime.stop(String(props.reason ?? "stopped"));
420
+
421
+ // src/registry/actions.ts
422
+ var createActionsRegistry = () => /* @__PURE__ */ new Map([
423
+ ["core.noop", coreNoop],
424
+ ["core.stop", coreStop],
425
+ ["core.fail", coreFail],
426
+ ["core.fetch", coreFetch],
427
+ ["core.loop", coreLoop],
428
+ ["core.sequence", coreNoop],
429
+ ["core.selector", coreNoop],
430
+ ["core.parallel", coreNoop],
431
+ ["core.set", coreSet],
432
+ ["core.setData", coreSetData],
433
+ ["core.emit", coreEmit],
434
+ ["core.patch", corePatch],
435
+ ["core.delay", coreDelay]
436
+ ]);
437
+
438
+ // src/helpers/conditions/changedCondition.ts
439
+ var changedCondition = (_args, current, previous) => !Object.is(current, previous);
440
+
441
+ // src/helpers/conditions/cooldownReadyCondition.ts
442
+ var cooldownReadyCondition = (_args, now, lastAt, cooldownMs) => {
443
+ if (lastAt == null) {
444
+ return true;
445
+ }
446
+ return Number(now) - Number(lastAt) >= Number(cooldownMs ?? 0);
447
+ };
448
+
449
+ // src/helpers/conditions/sizeOf.ts
450
+ var sizeOf = (value) => {
451
+ if (value == null) {
452
+ return 0;
453
+ }
454
+ if (typeof value === "string" || Array.isArray(value)) {
455
+ return value.length;
456
+ }
457
+ if (value instanceof Map || value instanceof Set) {
458
+ return value.size;
459
+ }
460
+ if (typeof value === "object") {
461
+ return Object.keys(value).length;
462
+ }
463
+ return 0;
464
+ };
465
+
466
+ // src/helpers/conditions/emptyCondition.ts
467
+ var emptyCondition = (_args, value) => sizeOf(value) === 0;
468
+
469
+ // src/helpers/conditions/eqCondition.ts
470
+ var eqCondition = (_args, left, right) => Object.is(left, right);
471
+
472
+ // src/helpers/conditions/existsCondition.ts
473
+ var existsCondition = (_args, value) => value !== void 0 && value !== null;
474
+
475
+ // src/helpers/conditions/falsyCondition.ts
476
+ var falsyCondition = (_args, value) => !value;
477
+
478
+ // src/helpers/conditions/gtCondition.ts
479
+ var gtCondition = (_args, left, right) => Number(left) > Number(right);
480
+
481
+ // src/helpers/conditions/gteCondition.ts
482
+ var gteCondition = (_args, left, right) => Number(left) >= Number(right);
483
+
484
+ // src/helpers/conditions/includesCondition.ts
485
+ var includesCondition = (_args, collection, value) => {
486
+ if (typeof collection === "string") {
487
+ return collection.includes(String(value));
488
+ }
489
+ if (Array.isArray(collection)) {
490
+ return collection.includes(value);
491
+ }
492
+ if (collection instanceof Set) {
493
+ return collection.has(value);
494
+ }
495
+ return false;
496
+ };
497
+
498
+ // src/helpers/conditions/ltCondition.ts
499
+ var ltCondition = (_args, left, right) => Number(left) < Number(right);
500
+
501
+ // src/helpers/conditions/lteCondition.ts
502
+ var lteCondition = (_args, left, right) => Number(left) <= Number(right);
503
+
504
+ // src/helpers/conditions/missingCondition.ts
505
+ var missingCondition = (_args, value) => value === void 0 || value === null;
506
+
507
+ // src/helpers/conditions/neqCondition.ts
508
+ var neqCondition = (_args, left, right) => !Object.is(left, right);
509
+
510
+ // src/helpers/conditions/notEmptyCondition.ts
511
+ var notEmptyCondition = (_args, value) => sizeOf(value) > 0;
512
+
513
+ // src/helpers/conditions/truthyCondition.ts
514
+ var truthyCondition = (_args, value) => Boolean(value);
515
+
516
+ // src/helpers/conditions/typeIsCondition.ts
517
+ var typeIsCondition = (_args, value, expected) => {
518
+ if (expected === "finite-number") {
519
+ return typeof value === "number" && Number.isFinite(value);
520
+ }
521
+ if (expected === "array") {
522
+ return Array.isArray(value);
523
+ }
524
+ if (expected === "record") {
525
+ return typeof value === "object" && value !== null && !Array.isArray(value);
526
+ }
527
+ return expected === "string" || expected === "number" || expected === "boolean" ? typeof value === expected : false;
528
+ };
529
+
530
+ // src/registry/conditions.ts
531
+ var createConditionsRegistry = () => /* @__PURE__ */ new Map([
532
+ ["eq", eqCondition],
533
+ ["neq", neqCondition],
534
+ ["gt", gtCondition],
535
+ ["gte", gteCondition],
536
+ ["lt", ltCondition],
537
+ ["lte", lteCondition],
538
+ ["truthy", truthyCondition],
539
+ ["falsy", falsyCondition],
540
+ ["exists", existsCondition],
541
+ ["missing", missingCondition],
542
+ ["empty", emptyCondition],
543
+ ["notEmpty", notEmptyCondition],
544
+ ["includes", includesCondition],
545
+ ["typeIs", typeIsCondition],
546
+ ["changed", changedCondition],
547
+ ["cooldownReady", cooldownReadyCondition]
548
+ ]);
549
+
550
+ // src/helpers/runner/applyResult.ts
551
+ var applyResult = (result, state, mergeData) => {
552
+ if (result.status === "success" && result.context !== void 0) {
553
+ state.context = result.context;
554
+ }
555
+ if ("data" in result && result.data) {
556
+ state.data = mergeData(state.data, result.data);
557
+ }
558
+ state.patches.push(...result.patches);
559
+ state.events.push(...result.events);
560
+ };
561
+
562
+ // src/helpers/path/resolveValue.ts
563
+ import { pick as pick3 } from "objwalk";
564
+
565
+ // src/helpers/path/childPath.ts
566
+ var childPath = (path, key) => typeof key === "number" ? `${path}[${key}]` : path ? `${path}.${key}` : key;
567
+
568
+ // src/helpers/path/ResolutionError.ts
569
+ var ResolutionError = class extends Error {
570
+ slapError;
571
+ constructor(slapError2) {
572
+ super(slapError2.message);
573
+ this.name = "ResolutionError";
574
+ this.slapError = slapError2;
575
+ }
576
+ };
577
+
578
+ // src/helpers/path/createResolutionError.ts
579
+ var createResolutionError = (code, message, scope, path) => new ResolutionError(
580
+ slapError(code, message, {
581
+ ...scope.strategy ? { strategy: scope.strategy } : {},
582
+ path
583
+ })
584
+ );
585
+
586
+ // src/helpers/path/evaluateExpression.ts
587
+ import { pick as pick2 } from "objwalk";
588
+
589
+ // src/helpers/path/failExpression.ts
590
+ var failExpression = (code, message, details) => {
591
+ throw new ResolutionError(slapError(code, message, details));
592
+ };
593
+
594
+ // src/helpers/path/finiteNumbers.ts
595
+ var finiteNumbers = (operator, args, count, details) => {
596
+ const [minimum, maximum] = typeof count === "number" ? [count, count] : count;
597
+ if (args.length < minimum || args.length > maximum || args.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
598
+ failExpression("EXPRESSION_INVALID_ARGUMENT", `Expression "${operator}" requires finite number arguments`, details);
599
+ }
600
+ return args;
601
+ };
602
+
603
+ // src/helpers/path/propertyValue.ts
604
+ import { pick } from "objwalk";
605
+
606
+ // src/helpers/path/protectedPickOptions.ts
607
+ var protectedPickOptions = {
608
+ inherited: false,
609
+ ignore: ["__proto__", "prototype", "constructor"]
610
+ };
611
+
612
+ // src/helpers/path/propertyValue.ts
613
+ var propertyValue = (target, key, details) => {
614
+ if (typeof target !== "object" && typeof target !== "function" || target === null || typeof key !== "string") {
615
+ failExpression("EXPRESSION_PATH_NOT_FOUND", "Expression property was not found", details);
616
+ }
617
+ const resolved = pick(target, key, protectedPickOptions);
618
+ if (resolved === void 0) {
619
+ failExpression("EXPRESSION_PATH_NOT_FOUND", "Expression property was not found", details);
620
+ }
621
+ return resolved;
622
+ };
623
+
624
+ // src/helpers/path/evaluateExpression.ts
625
+ var evaluateExpression = (operator, args, custom, details) => {
626
+ if (operator === "add") {
627
+ return finiteNumbers(operator, args, [2, Infinity], details).reduce((a, b) => a + b);
628
+ }
629
+ if (operator === "subtract") {
630
+ const numbers = finiteNumbers(operator, args, 2, details);
631
+ const a = numbers[0];
632
+ const b = numbers[1];
633
+ return a - b;
634
+ }
635
+ if (operator === "multiply") {
636
+ return finiteNumbers(operator, args, [2, Infinity], details).reduce((a, b) => a * b);
637
+ }
638
+ if (operator === "divide" || operator === "modulo") {
639
+ const numbers = finiteNumbers(operator, args, 2, details);
640
+ const a = numbers[0];
641
+ const b = numbers[1];
642
+ if (b === 0) {
643
+ failExpression("EXPRESSION_DIVISION_BY_ZERO", `Expression "${operator}" cannot divide by zero`, details);
644
+ }
645
+ return operator === "divide" ? a / b : a % b;
646
+ }
647
+ if (operator === "min" || operator === "max") {
648
+ return Math[operator](...finiteNumbers(operator, args, [1, Infinity], details));
649
+ }
650
+ if (["abs", "round", "floor", "ceil"].includes(operator)) {
651
+ const value = finiteNumbers(operator, args, 1, details)[0];
652
+ return Math[operator](value);
653
+ }
654
+ if (operator === "clamp") {
655
+ const numbers = finiteNumbers(operator, args, 3, details);
656
+ const value = numbers[0];
657
+ const minimum = numbers[1];
658
+ const maximum = numbers[2];
659
+ if (minimum > maximum) {
660
+ failExpression("EXPRESSION_INVALID_ARGUMENT", 'Expression "clamp" minimum exceeds maximum', details);
661
+ }
662
+ return Math.min(Math.max(value, minimum), maximum);
663
+ }
664
+ if (operator === "at") {
665
+ if (args.length !== 2) {
666
+ failExpression("EXPRESSION_INVALID_ARGUMENT", 'Expression "at" requires two arguments', details);
667
+ }
668
+ const target = args[0];
669
+ const index = args[1];
670
+ if (!Array.isArray(target) || typeof index !== "number" || !Number.isInteger(index) || index < 0) {
671
+ failExpression(
672
+ "EXPRESSION_INVALID_ARGUMENT",
673
+ 'Expression "at" requires an array and a non-negative integer',
674
+ details
675
+ );
676
+ }
677
+ const resolved = pick2(target, String(index), protectedPickOptions);
678
+ if (resolved === void 0) {
679
+ failExpression("EXPRESSION_PATH_NOT_FOUND", "Expression array index was not found", details);
680
+ }
681
+ return resolved;
682
+ }
683
+ if (operator === "property") {
684
+ if (args.length !== 2) {
685
+ failExpression("EXPRESSION_INVALID_ARGUMENT", 'Expression "property" requires two arguments', details);
686
+ }
687
+ return propertyValue(args[0], args[1], details);
688
+ }
689
+ if (operator === "get") {
690
+ if (args.length !== 2 || typeof args[1] !== "string") {
691
+ failExpression("EXPRESSION_INVALID_ARGUMENT", 'Expression "get" requires an object and a path string', details);
692
+ }
693
+ const resolved = pick2(args[0], args[1], protectedPickOptions);
694
+ if (resolved === void 0) {
695
+ return failExpression("EXPRESSION_PATH_NOT_FOUND", "Expression path was not found", details);
696
+ }
697
+ return resolved;
698
+ }
699
+ if (operator === "concat") {
700
+ if (args.some((value) => !["string", "number", "boolean", "bigint"].includes(typeof value))) {
701
+ failExpression(
702
+ "EXPRESSION_INVALID_ARGUMENT",
703
+ 'Expression "concat" requires primitive string-compatible arguments',
704
+ details
705
+ );
706
+ }
707
+ return args.map(String).join("");
708
+ }
709
+ if (!Object.prototype.hasOwnProperty.call(custom, operator)) {
710
+ failExpression("EXPRESSION_OPERATOR_NOT_FOUND", `Expression operator "${operator}" is not registered`, details);
711
+ }
712
+ const customOperator = custom[operator];
713
+ try {
714
+ return customOperator(args);
715
+ } catch (cause) {
716
+ const causeType = cause instanceof Error ? "Error" : typeof cause;
717
+ throw new ResolutionError(
718
+ slapError("EXPRESSION_INVALID_ARGUMENT", `Expression operator "${operator}" rejected its arguments`, {
719
+ ...details,
720
+ cause: { type: causeType }
721
+ })
722
+ );
723
+ }
724
+ };
725
+
726
+ // src/helpers/path/pathReferenceRegex.ts
727
+ var pathReferenceRegex = /^\$(context|data|input|variables)(?:\.([A-Za-z0-9_$.[\]-]+))?$/;
728
+
729
+ // src/helpers/path/parseTemplate.ts
730
+ var variablePattern = /^([A-Za-z_$][A-Za-z0-9_$]*)(?::-([^}]*))?$/;
731
+ var dataPathPattern = /^[A-Za-z0-9_$.[\]-]+$/;
732
+ var parseTemplate = (template) => {
733
+ const parts = [];
734
+ let literal = "";
735
+ let offset = 0;
736
+ const flushLiteral = () => {
737
+ if (literal) {
738
+ parts.push({ type: "literal", value: literal });
739
+ literal = "";
740
+ }
741
+ };
742
+ while (offset < template.length) {
743
+ if (template[offset] === "\\") {
744
+ let end = offset;
745
+ while (template[end] === "\\") {
746
+ end += 1;
747
+ }
748
+ const slashCount = end - offset;
749
+ const delimiter = template.startsWith("${", end) ? "${" : template.startsWith("{{", end) ? "{{" : void 0;
750
+ if (delimiter) {
751
+ literal += "\\".repeat(Math.floor(slashCount / 2));
752
+ if (slashCount % 2 === 1) {
753
+ literal += delimiter;
754
+ offset = end + delimiter.length;
755
+ continue;
756
+ }
757
+ offset = end;
758
+ } else {
759
+ literal += "\\".repeat(Math.ceil(slashCount / 2));
760
+ offset = end;
761
+ continue;
762
+ }
763
+ }
764
+ if (template.startsWith("${", offset)) {
765
+ const end = template.indexOf("}", offset + 2);
766
+ if (end < 0) {
767
+ return { ok: false };
768
+ }
769
+ const match = template.slice(offset + 2, end).match(variablePattern);
770
+ if (!match) {
771
+ return { ok: false };
772
+ }
773
+ flushLiteral();
774
+ parts.push({
775
+ type: "variable",
776
+ name: match[1],
777
+ ...match[2] === void 0 ? {} : { fallback: match[2] }
778
+ });
779
+ offset = end + 1;
780
+ continue;
781
+ }
782
+ if (template.startsWith("{{", offset)) {
783
+ const end = template.indexOf("}}", offset + 2);
784
+ if (end < 0) {
785
+ return { ok: false };
786
+ }
787
+ const path = template.slice(offset + 2, end).trim();
788
+ if (!dataPathPattern.test(path)) {
789
+ return { ok: false };
790
+ }
791
+ flushLiteral();
792
+ parts.push({ type: "data", path });
793
+ offset = end + 2;
794
+ continue;
795
+ }
796
+ literal += template[offset];
797
+ offset += 1;
798
+ }
799
+ if (literal || parts.length === 0) {
800
+ parts.push({ type: "literal", value: literal });
801
+ }
802
+ return { ok: true, parts };
803
+ };
804
+
805
+ // src/helpers/path/resolveValue.ts
806
+ var resolveValue = (value, scope, path = scope.configPath ?? "") => {
807
+ if (typeof value === "string") {
808
+ const match = value.match(pathReferenceRegex);
809
+ if (!match) {
810
+ return value;
811
+ }
812
+ const root = match[1];
813
+ const nestedPath = match[2] ?? "";
814
+ if (root !== "variables") {
815
+ const source2 = scope[root];
816
+ if (!nestedPath) {
817
+ return source2;
818
+ }
819
+ if (!source2 || typeof source2 !== "object") {
820
+ return void 0;
821
+ }
822
+ return pick3(source2, nestedPath);
823
+ }
824
+ const source = scope.variables ?? {};
825
+ if (!nestedPath) {
826
+ return source;
827
+ }
828
+ const resolved = pick3(source, nestedPath, protectedPickOptions);
829
+ if (resolved === void 0) {
830
+ throw createResolutionError("VARIABLE_NOT_FOUND", "Variable reference was not found", scope, path);
831
+ }
832
+ return resolved;
833
+ }
834
+ if (Array.isArray(value)) {
835
+ return value.map((item, index) => resolveValue(item, scope, childPath(path, index)));
836
+ }
837
+ if (value && typeof value === "object") {
838
+ const record = value;
839
+ if (Object.prototype.hasOwnProperty.call(record, "$expression")) {
840
+ if (!Array.isArray(record.$expression) || typeof record.$expression[0] !== "string") {
841
+ throw createResolutionError("EXPRESSION_INVALID_ARGUMENT", "Expression must contain an operator", scope, path);
842
+ }
843
+ const [operator, ...rawArgs] = record.$expression;
844
+ const args = rawArgs.map(
845
+ (argument, index) => resolveValue(argument, scope, childPath(childPath(path, "$expression"), index + 1))
846
+ );
847
+ return evaluateExpression(operator, args, scope.expressions ?? {}, {
848
+ ...scope.strategy ? { strategy: scope.strategy } : {},
849
+ path
850
+ });
851
+ }
852
+ if (typeof record.$template === "string") {
853
+ const parsed = parseTemplate(record.$template);
854
+ if (!parsed.ok) {
855
+ throw createResolutionError("TEMPLATE_INVALID", "Template syntax is invalid", scope, path);
856
+ }
857
+ return parsed.parts.map((part) => {
858
+ if (part.type === "literal") {
859
+ return part.value;
860
+ }
861
+ if (part.type === "data") {
862
+ const scopedPath = part.path.match(/^(context|data|input)\.(.+)$/);
863
+ const source = scopedPath ? scope[scopedPath[1]] : scope.data;
864
+ const resolved2 = pick3(source, scopedPath?.[2] ?? part.path);
865
+ return resolved2 == null ? "" : String(resolved2);
866
+ }
867
+ const resolved = pick3(scope.variables ?? {}, part.name, protectedPickOptions);
868
+ if (resolved === void 0 || resolved === null || part.fallback !== void 0 && resolved === "") {
869
+ if (part.fallback !== void 0) {
870
+ return part.fallback;
871
+ }
872
+ throw createResolutionError("VARIABLE_NOT_FOUND", "Template variable was not found", scope, path);
873
+ }
874
+ return String(resolved);
875
+ }).join("");
876
+ }
877
+ return Object.fromEntries(
878
+ Object.entries(record).map(([key, item]) => [key, resolveValue(item, scope, childPath(path, key))])
879
+ );
880
+ }
881
+ return value;
882
+ };
883
+
884
+ // src/helpers/runner/evaluateCondition.ts
885
+ var evaluateCondition = (expression, registry, scope) => {
886
+ if (expression === void 0) {
887
+ return { ok: true, matched: true };
888
+ }
889
+ if (typeof expression === "boolean") {
890
+ return { ok: true, matched: expression };
891
+ }
892
+ const [operator, ...rawArgs] = expression;
893
+ if (operator === "and") {
894
+ for (const item of rawArgs) {
895
+ const result = evaluateCondition(item, registry, scope);
896
+ if (!result.ok || !result.matched) {
897
+ return result;
898
+ }
899
+ }
900
+ return { ok: true, matched: true };
901
+ }
902
+ if (operator === "or") {
903
+ for (const item of rawArgs) {
904
+ const result = evaluateCondition(item, registry, scope);
905
+ if (!result.ok) {
906
+ return result;
907
+ }
908
+ if (result.matched) {
909
+ return { ok: true, matched: true };
910
+ }
911
+ }
912
+ return { ok: true, matched: false };
913
+ }
914
+ if (operator === "not") {
915
+ const result = evaluateCondition(rawArgs[0], registry, scope);
916
+ return result.ok ? { ok: true, matched: !result.matched } : result;
917
+ }
918
+ const condition = registry.get(operator);
919
+ if (!condition) {
920
+ return {
921
+ ok: false,
922
+ error: slapError("CONDITION_NOT_FOUND", `Condition "${operator}" is not registered`, {
923
+ ...scope.strategy ? { strategy: scope.strategy } : {}
924
+ })
925
+ };
926
+ }
927
+ try {
928
+ const configPath = scope.strategy ? `strategies.${scope.strategy}.when` : "when";
929
+ const args = rawArgs.map((arg, index) => resolveValue(arg, { ...scope, configPath: `${configPath}[${index + 1}]` }));
930
+ return { ok: true, matched: Boolean(condition(scope, ...args)) };
931
+ } catch (cause) {
932
+ if (cause instanceof ResolutionError) {
933
+ return { ok: false, error: cause.slapError };
934
+ }
935
+ throw cause;
936
+ }
937
+ };
938
+
939
+ // src/helpers/runner/createRuntime.ts
940
+ import { pick as pick4, set } from "objwalk";
941
+ var createRuntime = (state, branches = {
942
+ executeThen: async () => ({ status: "success" }),
943
+ executeCatch: async () => void 0
944
+ }) => {
945
+ const data = {
946
+ get: (path) => pick4(state.data, path),
947
+ set: (path, value) => {
948
+ if (!state.closed) {
949
+ set(state.data, path, value);
950
+ }
951
+ }
952
+ };
953
+ return {
954
+ get: (path) => pick4(state.context, path),
955
+ set: (path, value) => {
956
+ if (!state.closed) {
957
+ set(state.context, path, value);
958
+ }
959
+ },
960
+ data,
961
+ variables: {
962
+ get: (path) => pick4(state.variables, path, protectedPickOptions)
963
+ },
964
+ getData: (path) => {
965
+ console.warn("runtime.getData() is deprecated; use runtime.data.get() instead");
966
+ return data.get(path);
967
+ },
968
+ setData: (path, value) => {
969
+ console.warn("runtime.setData() is deprecated; use runtime.data.set() instead");
970
+ data.set(path, value);
971
+ },
972
+ resolve: (value) => resolveValue(value, state),
973
+ signal: state.signal,
974
+ executeThen: async () => state.closed ? { status: "stopped", reason: "Run is already finished" } : branches.executeThen(),
975
+ executeCatch: async () => state.closed ? void 0 : branches.executeCatch(),
976
+ emit: (event) => {
977
+ if (!state.closed) {
978
+ state.events.push(event);
979
+ }
980
+ },
981
+ patch: (patch) => {
982
+ if (!state.closed) {
983
+ state.patches.push(patch);
984
+ }
985
+ },
986
+ stop: (reason) => stopResult(reason),
987
+ fail: (reason, failureData) => ({
988
+ type: "fail",
989
+ ...reason ? { reason } : {},
990
+ ...failureData ? { data: failureData } : {}
991
+ })
992
+ };
993
+ };
994
+
995
+ // src/helpers/runner/executeNext.ts
996
+ var executeNext = (item, depth, state, environment) => {
997
+ const id = typeof item === "string" ? item : item.strategy;
998
+ if (typeof item !== "string" && item.when) {
999
+ const runtime = createRuntime(state);
1000
+ const condition = evaluateCondition(item.when, environment.conditionsRegistry, { ...state, runtime, strategy: id });
1001
+ if (!condition.ok) {
1002
+ return { status: "failed", error: condition.error, patches: [], events: [] };
1003
+ }
1004
+ if (!condition.matched) {
1005
+ return {
1006
+ status: "skipped",
1007
+ reason: "next condition did not match",
1008
+ patches: [],
1009
+ events: []
1010
+ };
1011
+ }
1012
+ }
1013
+ const extraProps = typeof item === "string" ? {} : item.props ?? {};
1014
+ return executeStrategy(id, extraProps, depth, state, environment);
1015
+ };
1016
+
1017
+ // src/helpers/runner/cloneParallelValue.ts
1018
+ var cloneParallelValue = (value, seen = /* @__PURE__ */ new WeakMap()) => {
1019
+ if (!value || typeof value !== "object") {
1020
+ return value;
1021
+ }
1022
+ const existing = seen.get(value);
1023
+ if (existing) {
1024
+ return existing;
1025
+ }
1026
+ if (Array.isArray(value)) {
1027
+ const clone2 = [];
1028
+ seen.set(value, clone2);
1029
+ for (const item of value) {
1030
+ clone2.push(cloneParallelValue(item, seen));
1031
+ }
1032
+ return clone2;
1033
+ }
1034
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
1035
+ return value;
1036
+ }
1037
+ const clone = {};
1038
+ seen.set(value, clone);
1039
+ for (const [key, item] of Object.entries(value)) {
1040
+ clone[key] = cloneParallelValue(item, seen);
1041
+ }
1042
+ return clone;
1043
+ };
1044
+
1045
+ // src/helpers/runner/isPromiseLike.ts
1046
+ var isPromiseLike = (value) => Boolean(value && typeof value === "object" && "then" in value && typeof value.then === "function");
1047
+
1048
+ // src/helpers/runner/executeSequence.ts
1049
+ var executeSequence = (items, depth, state, environment) => {
1050
+ let index = 0;
1051
+ const loop = () => {
1052
+ if (index >= items.length) {
1053
+ return { status: "success", patches: [], events: [] };
1054
+ }
1055
+ const result = executeNext(items[index++], depth + 1, state, environment);
1056
+ const next = (value) => {
1057
+ if (value.status !== "success") {
1058
+ return value;
1059
+ }
1060
+ return loop();
1061
+ };
1062
+ return isPromiseLike(result) ? result.then(next) : next(result);
1063
+ };
1064
+ return loop();
1065
+ };
1066
+
1067
+ // src/helpers/runner/skippedResult.ts
1068
+ var skippedResult = (reason, data) => ({
1069
+ status: "skipped",
1070
+ patches: [],
1071
+ events: [],
1072
+ ...reason ? { reason } : {},
1073
+ ...data ? { data } : {}
1074
+ });
1075
+
1076
+ // src/helpers/runner/successResult.ts
1077
+ var successResult = () => ({
1078
+ status: "success",
1079
+ patches: [],
1080
+ events: []
1081
+ });
1082
+
1083
+ // src/helpers/runner/executeParallel.ts
1084
+ var executeParallel = (items, depth, state, environment) => {
1085
+ if (state.sync) {
1086
+ return executeSequence(items, depth, state, environment);
1087
+ }
1088
+ const snapshots = items.map((item) => {
1089
+ const child = {
1090
+ ...state,
1091
+ context: cloneParallelValue(state.context),
1092
+ patches: [],
1093
+ events: [],
1094
+ data: cloneParallelValue(state.data)
1095
+ };
1096
+ return Promise.resolve(executeNext(item, depth + 1, child, environment)).then((result) => ({ result, child }));
1097
+ });
1098
+ return Promise.all(snapshots).then((results) => {
1099
+ for (const { result } of results) {
1100
+ if (result.status === "failed" || result.status === "stopped") {
1101
+ return result;
1102
+ }
1103
+ }
1104
+ for (const { child } of results) {
1105
+ state.patches.push(...child.patches);
1106
+ state.events.push(...child.events);
1107
+ state.data = environment.mergeData(state.data, child.data);
1108
+ }
1109
+ return results.some(({ result }) => result.status === "success") ? successResult() : skippedResult("All parallel branches skipped");
1110
+ });
1111
+ };
1112
+
1113
+ // src/helpers/runner/executeSelector.ts
1114
+ var executeSelector = (items, depth, state, environment) => {
1115
+ let index = 0;
1116
+ const loop = () => {
1117
+ if (index >= items.length) {
1118
+ return { status: "skipped", reason: "No selector branch matched", patches: [], events: [] };
1119
+ }
1120
+ const result = executeNext(items[index++], depth + 1, state, environment);
1121
+ const next = (value) => {
1122
+ if (value.status === "skipped") {
1123
+ return loop();
1124
+ }
1125
+ return value;
1126
+ };
1127
+ return isPromiseLike(result) ? result.then(next) : next(result);
1128
+ };
1129
+ return loop();
1130
+ };
1131
+
1132
+ // src/helpers/runner/executeThen.ts
1133
+ var executeThen = (strategy, depth, state, environment) => {
1134
+ const items = strategy.then ?? [];
1135
+ if (items.length === 0) {
1136
+ return { status: "success", patches: [], events: [] };
1137
+ }
1138
+ const mode = strategy.mode ?? "sequence";
1139
+ if (mode === "selector") {
1140
+ return executeSelector(items, depth, state, environment);
1141
+ }
1142
+ if (mode === "parallel") {
1143
+ return executeParallel(items, depth, state, environment);
1144
+ }
1145
+ return executeSequence(items, depth, state, environment);
1146
+ };
1147
+
1148
+ // src/helpers/errors/compactErrorStage.ts
1149
+ var compactErrorStage = (stage) => Object.fromEntries(Object.entries(stage).filter(([, value]) => value !== void 0));
1150
+
1151
+ // src/helpers/errors/withErrorStage.ts
1152
+ var withErrorStage = (error, stage) => {
1153
+ const nextError = {
1154
+ ...error,
1155
+ stage: compactErrorStage({
1156
+ ...stage,
1157
+ ...error.stage
1158
+ })
1159
+ };
1160
+ const { strategy: errorStrategy, fn: errorFn } = error;
1161
+ const { strategy: stageStrategy, fn: stageFn } = stage;
1162
+ const strategy = errorStrategy ?? stageStrategy;
1163
+ const fn = errorFn ?? stageFn;
1164
+ if (strategy) {
1165
+ nextError.strategy = strategy;
1166
+ }
1167
+ if (fn) {
1168
+ nextError.fn = fn;
1169
+ }
1170
+ return nextError;
1171
+ };
1172
+
1173
+ // src/helpers/runner/handleFailure.ts
1174
+ var handleFailure = (error, strategy, depth, state, environment) => {
1175
+ const stagedError = withErrorStage(error, {
1176
+ phase: error.stage?.phase ?? "action",
1177
+ strategy: error.strategy,
1178
+ fn: error.fn ?? strategy.fn,
1179
+ mode: strategy.mode,
1180
+ depth,
1181
+ step: state.stepCounter.current
1182
+ });
1183
+ environment.options.onError?.({
1184
+ error: stagedError,
1185
+ context: state.context,
1186
+ input: state.input,
1187
+ data: state.data,
1188
+ patches: state.patches,
1189
+ events: state.events,
1190
+ ...state.traceSink?.entries ? { trace: state.traceSink.entries() } : {}
1191
+ });
1192
+ state.reportedErrors.push(stagedError);
1193
+ if (strategy.catch?.length) {
1194
+ const caught = executeSequence(strategy.catch, depth, state, environment);
1195
+ const stageCatchFailure = (result) => {
1196
+ if (result.status === "failed") {
1197
+ return {
1198
+ ...result,
1199
+ error: {
1200
+ ...result.error,
1201
+ stage: {
1202
+ ...result.error.stage,
1203
+ phase: "catch"
1204
+ }
1205
+ }
1206
+ };
1207
+ }
1208
+ return result;
1209
+ };
1210
+ return isPromiseLike(caught) ? caught.then(stageCatchFailure) : stageCatchFailure(caught);
1211
+ }
1212
+ return { status: "failed", error: stagedError, patches: [], events: [] };
1213
+ };
1214
+
1215
+ // src/helpers/runner/normalizeActionResult.ts
1216
+ var normalizeActionResult = (raw) => {
1217
+ if (raw === false) {
1218
+ return skippedResult();
1219
+ }
1220
+ if (raw == null) {
1221
+ return successResult();
1222
+ }
1223
+ const patches = "patch" in raw && raw.patch !== void 0 ? [].concat(raw.patch) : [];
1224
+ const events = "events" in raw && raw.events ? raw.events : [];
1225
+ if (raw.type === "skip") {
1226
+ return skippedResult(raw.reason, raw.data);
1227
+ }
1228
+ if (raw.type === "stop") {
1229
+ return { status: "stopped", patches, events, ...raw.reason ? { reason: raw.reason } : {} };
1230
+ }
1231
+ if (raw.type === "fail") {
1232
+ const suppliedError = raw.error && typeof raw.error === "object" && "code" in raw.error && "message" in raw.error ? raw.error : void 0;
1233
+ return {
1234
+ status: "failed",
1235
+ error: suppliedError ?? slapError("ACTION_THROWN", raw.reason ?? "Action failed", { cause: raw.error }),
1236
+ patches: [],
1237
+ events: [],
1238
+ ...raw.data ? { data: raw.data } : {},
1239
+ ...raw.handled ? { handled: true } : {}
1240
+ };
1241
+ }
1242
+ return {
1243
+ status: "success",
1244
+ patches,
1245
+ events,
1246
+ ...raw.context !== void 0 ? { context: raw.context } : {},
1247
+ ...raw.data ? { data: raw.data } : {},
1248
+ ...raw.continue !== void 0 ? { continue: raw.continue } : {}
1249
+ };
1250
+ };
1251
+
1252
+ // src/helpers/runner/pushTrace.ts
1253
+ var pushTrace = (state, step, depth, strategyId, strategy, status, props, dataBefore, startedAt, reason) => {
1254
+ state.traceSink?.push({
1255
+ step,
1256
+ depth,
1257
+ strategy: strategyId,
1258
+ fn: strategy.fn,
1259
+ mode: strategy.mode,
1260
+ status,
1261
+ input: state.input,
1262
+ props,
1263
+ dataBefore,
1264
+ dataAfter: cloneData(state.data),
1265
+ durationMs: Date.now() - startedAt,
1266
+ ...reason ? { reason } : {}
1267
+ });
1268
+ };
1269
+
1270
+ // src/helpers/runner/traceReason.ts
1271
+ var traceReason = (result) => {
1272
+ if (result.status === "failed") {
1273
+ return result.error.message;
1274
+ }
1275
+ if (result.status === "success") {
1276
+ return void 0;
1277
+ }
1278
+ return result.reason;
1279
+ };
1280
+
1281
+ // src/helpers/runner/afterAction.ts
1282
+ var afterAction = (raw, id, strategy, depth, state, props, dataBefore, traceStep, startedAt, environment) => {
1283
+ const result = normalizeActionResult(raw);
1284
+ const reason = traceReason(result);
1285
+ if (result.status === "failed") {
1286
+ result.error = withErrorStage(result.error, {
1287
+ phase: "action",
1288
+ strategy: id,
1289
+ fn: strategy.fn,
1290
+ mode: strategy.mode,
1291
+ depth,
1292
+ step: traceStep
1293
+ });
1294
+ }
1295
+ applyResult(result, state, environment.mergeData);
1296
+ pushTrace(
1297
+ state,
1298
+ traceStep,
1299
+ depth,
1300
+ id,
1301
+ strategy,
1302
+ result.status === "success" ? "success" : result.status,
1303
+ props,
1304
+ dataBefore,
1305
+ startedAt,
1306
+ reason
1307
+ );
1308
+ if (result.status === "failed") {
1309
+ return result.handled ? result : handleFailure(result.error, strategy, depth, state, environment);
1310
+ }
1311
+ if (result.status === "skipped" || result.status === "stopped" || strategy.terminal || result.continue === false) {
1312
+ return result;
1313
+ }
1314
+ return executeThen(strategy, depth, state, environment);
1315
+ };
1316
+
1317
+ // src/helpers/runner/runnerDefaults.ts
1318
+ var defaultMaxStepCount = 1e3;
1319
+ var defaultMaxDepth = 32;
1320
+
1321
+ // src/helpers/runner/failLimit.ts
1322
+ var failLimit = (code, message, id) => ({
1323
+ status: "failed",
1324
+ error: slapError(code, message, { strategy: id, stage: { phase: "limit", strategy: id } }),
1325
+ patches: [],
1326
+ events: []
1327
+ });
1328
+
1329
+ // src/helpers/trace/dependsOnVariables.ts
1330
+ var dependsOnVariables = (value) => {
1331
+ if (typeof value === "string") {
1332
+ return value === "$variables" || value.startsWith("$variables.");
1333
+ }
1334
+ if (Array.isArray(value)) {
1335
+ return value.some(dependsOnVariables);
1336
+ }
1337
+ if (value && typeof value === "object") {
1338
+ const record = value;
1339
+ if (typeof record.$template === "string") {
1340
+ const parsed = parseTemplate(record.$template);
1341
+ return parsed.ok && parsed.parts.some((part) => part.type === "variable");
1342
+ }
1343
+ return Object.values(record).some(dependsOnVariables);
1344
+ }
1345
+ return false;
1346
+ };
1347
+
1348
+ // src/helpers/trace/redactVariableProps.ts
1349
+ var redactVariableProps = (raw, resolved) => {
1350
+ if (typeof raw === "string" && (raw === "$variables" || raw.startsWith("$variables.")) || raw && typeof raw === "object" && !Array.isArray(raw) && (typeof raw.$template === "string" && dependsOnVariables(raw) || Object.prototype.hasOwnProperty.call(raw, "$expression") && dependsOnVariables(raw))) {
1351
+ return "[REDACTED]";
1352
+ }
1353
+ if (Array.isArray(raw) && Array.isArray(resolved)) {
1354
+ return raw.map((item, index) => redactVariableProps(item, resolved[index]));
1355
+ }
1356
+ if (raw && resolved && typeof raw === "object" && typeof resolved === "object") {
1357
+ return Object.fromEntries(
1358
+ Object.entries(raw).map(([key, item]) => [
1359
+ key,
1360
+ redactVariableProps(item, resolved[key])
1361
+ ])
1362
+ );
1363
+ }
1364
+ return resolved;
1365
+ };
1366
+
1367
+ // src/helpers/runner/toRuntimeResult.ts
1368
+ var toRuntimeResult = (result) => {
1369
+ if (result.status === "failed") {
1370
+ return { status: "failed", error: result.error };
1371
+ }
1372
+ if (result.status === "stopped") {
1373
+ return { status: "stopped", ..."reason" in result && result.reason ? { reason: result.reason } : {} };
1374
+ }
1375
+ if (result.status === "skipped") {
1376
+ return { status: "skipped", ...result.reason ? { reason: result.reason } : {} };
1377
+ }
1378
+ return { status: "success" };
1379
+ };
1380
+
1381
+ // src/helpers/runner/isTimedOut.ts
1382
+ var isTimedOut = (startedAt, timeout) => timeout !== void 0 && timeout > 0 && Date.now() - startedAt > timeout;
1383
+
1384
+ // src/helpers/runner/raceTimeout.ts
1385
+ var raceTimeout = (promise, timeout, startedAt, onTimeout) => new Promise((resolve, reject) => {
1386
+ const remaining = Math.max(timeout - (Date.now() - startedAt), 0);
1387
+ const timer = setTimeout(() => resolve(onTimeout()), remaining);
1388
+ void promise.then((result) => {
1389
+ clearTimeout(timer);
1390
+ resolve(result);
1391
+ }).catch((cause) => {
1392
+ clearTimeout(timer);
1393
+ reject(cause);
1394
+ });
1395
+ });
1396
+
1397
+ // src/helpers/runner/timeoutResult.ts
1398
+ var timeoutResult = (timeout, strategy) => failLimit("TIMEOUT", `Slapflow run timed out after ${timeout}ms`, strategy);
1399
+
1400
+ // src/helpers/runner/executeStrategy.ts
1401
+ var executeStrategy = (id, extraProps, depth, state, environment) => {
1402
+ const config = environment.configRef.current;
1403
+ if (!config) {
1404
+ return {
1405
+ status: "failed",
1406
+ error: slapError("CONFIG_INVALID", "No config loaded", { stage: { phase: "entrypoint" } }),
1407
+ patches: [],
1408
+ events: []
1409
+ };
1410
+ }
1411
+ const maxDepth = environment.options.maxDepth ?? defaultMaxDepth;
1412
+ if (maxDepth !== -1 && depth > maxDepth) {
1413
+ return failLimit("MAX_DEPTH", `Max depth exceeded at strategy "${id}"`, id);
1414
+ }
1415
+ const maxStepCount = environment.options.maxStepCount ?? environment.options.maxSteps ?? defaultMaxStepCount;
1416
+ if (maxStepCount !== -1 && state.stepCounter.current >= maxStepCount) {
1417
+ return failLimit("MAX_STEPS", `Max steps exceeded at strategy "${id}"`, id);
1418
+ }
1419
+ if (isTimedOut(state.startedAt, environment.options.timeout)) {
1420
+ return timeoutResult(environment.options.timeout, id);
1421
+ }
1422
+ const strategy = config.strategies[id];
1423
+ if (!strategy) {
1424
+ return {
1425
+ status: "failed",
1426
+ error: slapError("STRATEGY_NOT_FOUND", `Strategy "${id}" is not defined`, {
1427
+ strategy: id,
1428
+ stage: { phase: "entrypoint", strategy: id, depth }
1429
+ }),
1430
+ patches: [],
1431
+ events: []
1432
+ };
1433
+ }
1434
+ const action = environment.actionsRegistry.get(strategy.fn);
1435
+ if (!action) {
1436
+ return {
1437
+ status: "failed",
1438
+ error: slapError("ACTION_NOT_FOUND", `Action "${strategy.fn}" is not registered`, {
1439
+ strategy: id,
1440
+ fn: strategy.fn,
1441
+ stage: {
1442
+ phase: "action",
1443
+ strategy: id,
1444
+ fn: strategy.fn,
1445
+ mode: strategy.mode,
1446
+ depth,
1447
+ step: state.stepCounter.current + 1
1448
+ }
1449
+ }),
1450
+ patches: [],
1451
+ events: []
1452
+ };
1453
+ }
1454
+ const rawProps = { ...strategy.props ?? {}, ...extraProps };
1455
+ let props;
1456
+ try {
1457
+ props = resolveValue(rawProps, {
1458
+ ...state,
1459
+ strategy: id,
1460
+ configPath: `strategies.${id}.props`
1461
+ });
1462
+ } catch (cause) {
1463
+ if (!(cause instanceof ResolutionError)) {
1464
+ throw cause;
1465
+ }
1466
+ return handleFailure(
1467
+ withErrorStage(cause.slapError, {
1468
+ phase: "action",
1469
+ strategy: id,
1470
+ fn: strategy.fn,
1471
+ mode: strategy.mode,
1472
+ depth,
1473
+ step: state.stepCounter.current + 1
1474
+ }),
1475
+ strategy,
1476
+ depth,
1477
+ state,
1478
+ environment
1479
+ );
1480
+ }
1481
+ const traceProps = redactVariableProps(rawProps, props);
1482
+ const runtime = createRuntime(state, {
1483
+ executeThen: async () => toRuntimeResult(await executeThen(strategy, depth, state, environment)),
1484
+ executeCatch: strategy.catch?.length ? async () => toRuntimeResult(await executeSequence(strategy.catch, depth, state, environment)) : async () => void 0
1485
+ });
1486
+ const dataBefore = cloneData(state.data);
1487
+ const traceStep = state.stepCounter.current + 1;
1488
+ const startedAt = Date.now();
1489
+ const condition = evaluateCondition(strategy.when, environment.conditionsRegistry, {
1490
+ ...state,
1491
+ runtime,
1492
+ strategy: id
1493
+ });
1494
+ if (!condition.ok) {
1495
+ return handleFailure(
1496
+ withErrorStage(condition.error, {
1497
+ phase: "condition",
1498
+ strategy: id,
1499
+ fn: strategy.fn,
1500
+ mode: strategy.mode,
1501
+ depth,
1502
+ step: traceStep
1503
+ }),
1504
+ strategy,
1505
+ depth,
1506
+ state,
1507
+ environment
1508
+ );
1509
+ }
1510
+ if (!condition.matched) {
1511
+ pushTrace(state, traceStep, depth, id, strategy, "skipped", traceProps, dataBefore, startedAt);
1512
+ return { status: "skipped", reason: "when condition did not match", patches: [], events: [] };
1513
+ }
1514
+ state.stepCounter.current += 1;
1515
+ const invoke = () => action({ context: state.context, props, input: state.input, signal: state.signal, runtime });
1516
+ const actionThrown = (cause) => {
1517
+ if (isTimedOut(state.startedAt, environment.options.timeout)) {
1518
+ return timeoutResult(environment.options.timeout, id);
1519
+ }
1520
+ return handleFailure(
1521
+ cause instanceof ResolutionError ? withErrorStage(cause.slapError, {
1522
+ phase: "action",
1523
+ strategy: id,
1524
+ fn: strategy.fn,
1525
+ mode: strategy.mode,
1526
+ depth,
1527
+ step: traceStep
1528
+ }) : slapError("ACTION_THROWN", `Action "${strategy.fn}" threw`, {
1529
+ strategy: id,
1530
+ fn: strategy.fn,
1531
+ cause,
1532
+ stage: { phase: "action", strategy: id, fn: strategy.fn, mode: strategy.mode, depth, step: traceStep }
1533
+ }),
1534
+ strategy,
1535
+ depth,
1536
+ state,
1537
+ environment
1538
+ );
1539
+ };
1540
+ try {
1541
+ const raw = invoke();
1542
+ if (isPromiseLike(raw)) {
1543
+ if (state.sync) {
1544
+ throw new SyncAsyncError(
1545
+ slapError("ASYNC_IN_SYNC_RUN", `Strategy "${id}" returned a Promise`, {
1546
+ strategy: id,
1547
+ fn: strategy.fn,
1548
+ stage: { phase: "action", strategy: id, fn: strategy.fn, mode: strategy.mode, depth, step: traceStep }
1549
+ })
1550
+ );
1551
+ }
1552
+ let timedOut = false;
1553
+ const complete = raw.then((value) => {
1554
+ if (timedOut || isTimedOut(state.startedAt, environment.options.timeout)) {
1555
+ return timeoutResult(environment.options.timeout, id);
1556
+ }
1557
+ return afterAction(
1558
+ value,
1559
+ id,
1560
+ strategy,
1561
+ depth,
1562
+ state,
1563
+ traceProps,
1564
+ dataBefore,
1565
+ traceStep,
1566
+ startedAt,
1567
+ environment
1568
+ );
1569
+ }).catch((cause) => {
1570
+ if (timedOut || isTimedOut(state.startedAt, environment.options.timeout)) {
1571
+ return timeoutResult(environment.options.timeout, id);
1572
+ }
1573
+ return actionThrown(cause);
1574
+ });
1575
+ const timeout = environment.options.timeout;
1576
+ if (timeout !== void 0 && timeout > 0) {
1577
+ return raceTimeout(complete, timeout, state.startedAt, () => {
1578
+ timedOut = true;
1579
+ state.closed = true;
1580
+ state.abort();
1581
+ return timeoutResult(timeout, id);
1582
+ });
1583
+ }
1584
+ return complete;
1585
+ }
1586
+ if (isTimedOut(state.startedAt, environment.options.timeout)) {
1587
+ return timeoutResult(environment.options.timeout, id);
1588
+ }
1589
+ return afterAction(raw, id, strategy, depth, state, traceProps, dataBefore, traceStep, startedAt, environment);
1590
+ } catch (cause) {
1591
+ if (cause instanceof SyncAsyncError) {
1592
+ throw cause;
1593
+ }
1594
+ return actionThrown(cause);
1595
+ }
1596
+ };
1597
+
1598
+ // src/helpers/runner/finishRunResult.ts
1599
+ var finishRunResult = (result, state, traceSink) => {
1600
+ const runResult = {
1601
+ status: result.status,
1602
+ context: state.context,
1603
+ data: state.data,
1604
+ patches: state.patches,
1605
+ events: state.events,
1606
+ steps: state.stepCounter.current
1607
+ };
1608
+ if (result.status === "failed") {
1609
+ runResult.error = result.error;
1610
+ }
1611
+ const entries = traceSink?.entries?.();
1612
+ if (entries) {
1613
+ runResult.trace = entries;
1614
+ }
1615
+ return runResult;
1616
+ };
1617
+
1618
+ // src/helpers/runner/resolveEntrypoint.ts
1619
+ var resolveEntrypoint = (entrypoint, environment) => {
1620
+ const config = environment.configRef.current;
1621
+ if (!config) {
1622
+ return { error: slapError("CONFIG_INVALID", "No config loaded", { stage: { phase: "entrypoint" } }) };
1623
+ }
1624
+ const id = config.entrypoints?.[entrypoint] ?? entrypoint;
1625
+ if (!config.strategies[id]) {
1626
+ return {
1627
+ error: slapError("STRATEGY_NOT_FOUND", `Strategy "${id}" is not defined`, {
1628
+ strategy: id,
1629
+ stage: { phase: "entrypoint", entrypoint, strategy: id }
1630
+ })
1631
+ };
1632
+ }
1633
+ return { id };
1634
+ };
1635
+
1636
+ // src/helpers/validation/runnerLimitWarnings.ts
1637
+ var runnerLimitWarnings = (options) => {
1638
+ const warnings = [];
1639
+ const maxStepCount = options.maxStepCount ?? options.maxSteps;
1640
+ if (maxStepCount === -1) {
1641
+ warnings.push({
1642
+ code: "LIMIT_DISABLED",
1643
+ message: "maxStepCount is disabled; cycles or unexpectedly long runs may execute indefinitely",
1644
+ path: "options.maxStepCount"
1645
+ });
1646
+ }
1647
+ if (options.maxDepth === -1) {
1648
+ warnings.push({
1649
+ code: "LIMIT_DISABLED",
1650
+ message: "maxDepth is disabled; deeply nested strategies may exhaust the call stack",
1651
+ path: "options.maxDepth"
1652
+ });
1653
+ }
1654
+ return warnings;
1655
+ };
1656
+
1657
+ // src/helpers/validation/getNextTarget.ts
1658
+ var getNextTarget = (next) => {
1659
+ if (typeof next === "string") {
1660
+ return next;
1661
+ }
1662
+ if (next && typeof next === "object" && "strategy" in next && typeof next.strategy === "string") {
1663
+ return next.strategy;
1664
+ }
1665
+ return void 0;
1666
+ };
1667
+
1668
+ // src/helpers/validation/detectCycles.ts
1669
+ var detectCycles = (config, errors, warnings) => {
1670
+ const visiting = /* @__PURE__ */ new Set();
1671
+ const visited = /* @__PURE__ */ new Set();
1672
+ const visit = (id, path) => {
1673
+ if (visiting.has(id)) {
1674
+ const cycle = [...path, id];
1675
+ const hasTerminal = cycle.some((item) => config.strategies[item]?.terminal);
1676
+ const issue = {
1677
+ code: "CYCLE_DETECTED",
1678
+ message: `Cycle detected: ${cycle.join(" -> ")}`,
1679
+ strategy: id
1680
+ };
1681
+ (hasTerminal ? warnings : errors).push(issue);
1682
+ return true;
1683
+ }
1684
+ if (visited.has(id)) {
1685
+ return false;
1686
+ }
1687
+ visiting.add(id);
1688
+ for (const branch of ["then", "catch"]) {
1689
+ const nextItems = config.strategies[id]?.[branch];
1690
+ for (const next of Array.isArray(nextItems) ? nextItems : []) {
1691
+ const target = getNextTarget(next);
1692
+ if (target && config.strategies[target]) {
1693
+ visit(target, [...path, id]);
1694
+ }
1695
+ }
1696
+ }
1697
+ visiting.delete(id);
1698
+ visited.add(id);
1699
+ return false;
1700
+ };
1701
+ Object.keys(config.strategies ?? {}).forEach((id) => visit(id, []));
1702
+ };
1703
+
1704
+ // src/helpers/validation/getNextItems.ts
1705
+ var getNextItems = (value) => Array.isArray(value) ? value : [];
1706
+
1707
+ // src/helpers/validation/detectNestedLoops.ts
1708
+ var detectNestedLoops = (config, errors) => {
1709
+ for (const [outerId, outer] of Object.entries(config.strategies)) {
1710
+ if (outer.fn !== "core.loop") {
1711
+ continue;
1712
+ }
1713
+ const visited = /* @__PURE__ */ new Set([outerId]);
1714
+ const visit = (id, path) => {
1715
+ const strategy = config.strategies[id];
1716
+ if (!strategy) {
1717
+ return;
1718
+ }
1719
+ if (strategy.fn === "core.loop") {
1720
+ errors.push({
1721
+ code: "NESTED_LOOP",
1722
+ message: `Strategy "${id}" cannot run inside loop "${outerId}". Nested core.loop strategies are not supported.`,
1723
+ strategy: id,
1724
+ path: [...path, id].join(" -> ")
1725
+ });
1726
+ return;
1727
+ }
1728
+ if (visited.has(id)) {
1729
+ return;
1730
+ }
1731
+ visited.add(id);
1732
+ for (const branch of ["then", "catch"]) {
1733
+ for (const next of getNextItems(strategy[branch])) {
1734
+ const target = getNextTarget(next);
1735
+ if (target) {
1736
+ visit(target, [...path, `${id}.${branch}`]);
1737
+ }
1738
+ }
1739
+ }
1740
+ };
1741
+ for (const branch of ["then", "catch"]) {
1742
+ for (const next of getNextItems(outer[branch])) {
1743
+ const target = getNextTarget(next);
1744
+ if (target) {
1745
+ visit(target, [`${outerId}.${branch}`]);
1746
+ }
1747
+ }
1748
+ }
1749
+ }
1750
+ };
1751
+
1752
+ // src/helpers/validation/validationConstants.ts
1753
+ var validModes = /* @__PURE__ */ new Set(["sequence", "selector", "parallel"]);
1754
+ var controlConditions = /* @__PURE__ */ new Set(["and", "or", "not"]);
1755
+
1756
+ // src/helpers/path/isPathReference.ts
1757
+ var isPathReference = (value) => typeof value === "string" && value.startsWith("$");
1758
+
1759
+ // src/helpers/path/isValidPathReference.ts
1760
+ var isValidPathReference = (value) => pathReferenceRegex.test(value);
1761
+
1762
+ // src/helpers/validation/validateRefs.ts
1763
+ var validateRefs = (value, strategy, path, errors) => {
1764
+ if (typeof value === "string") {
1765
+ if (isPathReference(value) && !isValidPathReference(value)) {
1766
+ errors.push({ code: "PATH_INVALID", message: `Invalid path reference "${value}"`, strategy, path });
1767
+ }
1768
+ return;
1769
+ }
1770
+ if (Array.isArray(value)) {
1771
+ value.forEach((item, index) => validateRefs(item, strategy, `${path}.${index}`, errors));
1772
+ }
1773
+ if (value && typeof value === "object") {
1774
+ Object.entries(value).forEach(([key, item]) => {
1775
+ if (key === "$template" && typeof item === "string") {
1776
+ if (!parseTemplate(item).ok) {
1777
+ errors.push({
1778
+ code: "TEMPLATE_INVALID",
1779
+ message: "Template syntax is invalid",
1780
+ strategy,
1781
+ path: `${path}.${key}`
1782
+ });
1783
+ }
1784
+ } else {
1785
+ validateRefs(item, strategy, `${path}.${key}`, errors);
1786
+ }
1787
+ });
1788
+ }
1789
+ };
1790
+
1791
+ // src/helpers/validation/validateCondition.ts
1792
+ var validateCondition = (expression, strategy, path, conditionsRegistry, errors) => {
1793
+ if (expression === void 0 || typeof expression === "boolean") {
1794
+ return;
1795
+ }
1796
+ if (!Array.isArray(expression) || typeof expression[0] !== "string") {
1797
+ errors.push({ code: "CONDITION_INVALID", message: "Condition expression is invalid", strategy, path });
1798
+ return;
1799
+ }
1800
+ const [operator, ...args] = expression;
1801
+ if (operator === "and" || operator === "or") {
1802
+ args.forEach((arg, index) => validateCondition(arg, strategy, `${path}.${index + 1}`, conditionsRegistry, errors));
1803
+ return;
1804
+ }
1805
+ if (operator === "not") {
1806
+ validateCondition(args[0], strategy, `${path}.1`, conditionsRegistry, errors);
1807
+ return;
1808
+ }
1809
+ if (!conditionsRegistry.has(operator) && !controlConditions.has(operator)) {
1810
+ errors.push({
1811
+ code: "CONDITION_NOT_FOUND",
1812
+ message: `Condition "${operator}" is not registered`,
1813
+ strategy,
1814
+ path
1815
+ });
1816
+ }
1817
+ args.forEach((arg, index) => validateRefs(arg, strategy, `${path}.${index + 1}`, errors));
1818
+ };
1819
+
1820
+ // src/helpers/validation/validateNextList.ts
1821
+ var validateNextList = (config, list, path, strategy, conditionsRegistry, errors) => {
1822
+ if (list === void 0) {
1823
+ return;
1824
+ }
1825
+ if (!Array.isArray(list)) {
1826
+ errors.push({ code: "NEXT_INVALID", message: "then/catch must be arrays", strategy, path });
1827
+ return;
1828
+ }
1829
+ list.forEach((item, index) => {
1830
+ const target = typeof item === "string" ? item : item && typeof item === "object" ? item.strategy : void 0;
1831
+ if (typeof target !== "string" || !config.strategies[target]) {
1832
+ errors.push({
1833
+ code: "STRATEGY_NOT_FOUND",
1834
+ message: `Next strategy "${String(target)}" is not defined`,
1835
+ strategy,
1836
+ path: `${path}.${index}`
1837
+ });
1838
+ } else if (item && typeof item === "object") {
1839
+ const { props, when } = item;
1840
+ validateCondition(when, strategy, `${path}.${index}.when`, conditionsRegistry, errors);
1841
+ validateRefs(props, strategy, `${path}.${index}.props`, errors);
1842
+ }
1843
+ });
1844
+ };
1845
+
1846
+ // src/helpers/validation/validateConfig.ts
1847
+ var validateConfig = (config, actionsRegistry, conditionsRegistry) => {
1848
+ const errors = [];
1849
+ const warnings = [];
1850
+ if (!config || typeof config !== "object") {
1851
+ return {
1852
+ ok: false,
1853
+ errors: [{ code: "CONFIG_INVALID", message: "Config must be an object" }],
1854
+ warnings
1855
+ };
1856
+ }
1857
+ if (!config.strategies || typeof config.strategies !== "object" || Array.isArray(config.strategies)) {
1858
+ return {
1859
+ ok: false,
1860
+ errors: [{ code: "CONFIG_INVALID", message: "Config must include a strategies object", path: "strategies" }],
1861
+ warnings
1862
+ };
1863
+ }
1864
+ for (const [id, strategy] of Object.entries(config.strategies ?? {})) {
1865
+ if (!strategy || typeof strategy !== "object") {
1866
+ errors.push({ code: "STRATEGY_INVALID", message: "Strategy must be an object", strategy: id });
1867
+ continue;
1868
+ }
1869
+ if (!strategy.fn || typeof strategy.fn !== "string") {
1870
+ errors.push({ code: "FN_MISSING", message: "Strategy fn is required", strategy: id, path: `${id}.fn` });
1871
+ } else if (!actionsRegistry.has(strategy.fn)) {
1872
+ errors.push({
1873
+ code: "ACTION_NOT_FOUND",
1874
+ message: `Action "${strategy.fn}" is not registered`,
1875
+ strategy: id,
1876
+ path: `${id}.fn`
1877
+ });
1878
+ }
1879
+ if (strategy.mode && !validModes.has(strategy.mode)) {
1880
+ errors.push({ code: "MODE_INVALID", message: `Mode "${strategy.mode}" is invalid`, strategy: id });
1881
+ }
1882
+ validateNextList(config, strategy.then, `${id}.then`, id, conditionsRegistry, errors);
1883
+ validateNextList(config, strategy.catch, `${id}.catch`, id, conditionsRegistry, errors);
1884
+ validateCondition(strategy.when, id, `${id}.when`, conditionsRegistry, errors);
1885
+ validateRefs(strategy.props, id, `${id}.props`, errors);
1886
+ }
1887
+ for (const [name, target] of Object.entries(config.entrypoints ?? {})) {
1888
+ if (!config.strategies[target]) {
1889
+ errors.push({
1890
+ code: "STRATEGY_NOT_FOUND",
1891
+ message: `Entrypoint "${name}" references missing strategy "${target}"`,
1892
+ path: `entrypoints.${name}`
1893
+ });
1894
+ }
1895
+ }
1896
+ detectCycles(config, errors, warnings);
1897
+ detectNestedLoops(config, errors);
1898
+ return { ok: errors.length === 0, errors, warnings };
1899
+ };
1900
+
1901
+ // src/helpers/runner/unsupportedVariablesError.ts
1902
+ var unsupportedVariablesError = () => new TypeError("Runtime variables support only primitives, arrays, and plain objects");
1903
+
1904
+ // src/helpers/runner/cloneRuntimeVariableValue.ts
1905
+ var cloneRuntimeVariableValue = (value, seen) => {
1906
+ if (typeof value === "function" || typeof value === "symbol" || typeof value === "undefined") {
1907
+ throw unsupportedVariablesError();
1908
+ }
1909
+ if (!value || typeof value !== "object") {
1910
+ return value;
1911
+ }
1912
+ const existing = seen.get(value);
1913
+ if (existing) {
1914
+ return existing;
1915
+ }
1916
+ if (Array.isArray(value)) {
1917
+ if (Object.getOwnPropertySymbols(value).length > 0) {
1918
+ throw unsupportedVariablesError();
1919
+ }
1920
+ const clone2 = new Array(value.length);
1921
+ seen.set(value, clone2);
1922
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
1923
+ if (key === "length") {
1924
+ continue;
1925
+ }
1926
+ const index = Number(key);
1927
+ if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) {
1928
+ throw unsupportedVariablesError();
1929
+ }
1930
+ if ("get" in descriptor || "set" in descriptor) {
1931
+ throw unsupportedVariablesError();
1932
+ }
1933
+ Object.defineProperty(clone2, key, {
1934
+ value: cloneRuntimeVariableValue(descriptor.value, seen),
1935
+ enumerable: descriptor.enumerable ?? false,
1936
+ configurable: false,
1937
+ writable: false
1938
+ });
1939
+ }
1940
+ return Object.freeze(clone2);
1941
+ }
1942
+ const prototype = Object.getPrototypeOf(value);
1943
+ if (prototype !== Object.prototype && prototype !== null) {
1944
+ throw unsupportedVariablesError();
1945
+ }
1946
+ if (Object.getOwnPropertySymbols(value).length > 0) {
1947
+ throw unsupportedVariablesError();
1948
+ }
1949
+ const clone = Object.create(prototype);
1950
+ seen.set(value, clone);
1951
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
1952
+ if ("get" in descriptor || "set" in descriptor) {
1953
+ throw unsupportedVariablesError();
1954
+ }
1955
+ Object.defineProperty(clone, key, {
1956
+ value: cloneRuntimeVariableValue(descriptor.value, seen),
1957
+ enumerable: descriptor.enumerable ?? false,
1958
+ configurable: false,
1959
+ writable: false
1960
+ });
1961
+ }
1962
+ return Object.freeze(clone);
1963
+ };
1964
+
1965
+ // src/helpers/runner/cloneRuntimeVariables.ts
1966
+ var cloneRuntimeVariables = (variables, seen = /* @__PURE__ */ new WeakMap()) => cloneRuntimeVariableValue(variables, seen);
1967
+
1968
+ // src/helpers/runner/createRunCancellation.ts
1969
+ var createRunCancellation = (source) => {
1970
+ const controller = new AbortController();
1971
+ const abort = () => controller.abort();
1972
+ if (source?.aborted) {
1973
+ abort();
1974
+ } else {
1975
+ source?.addEventListener("abort", abort, { once: true });
1976
+ }
1977
+ return {
1978
+ controller,
1979
+ dispose: () => source?.removeEventListener("abort", abort)
1980
+ };
1981
+ };
1982
+
1983
+ // src/helpers/runner/createRunner.ts
1984
+ var createRunner = (options = {}) => {
1985
+ const actionsRegistry = createActionsRegistry();
1986
+ const conditionsRegistry = createConditionsRegistry();
1987
+ const configRef = {};
1988
+ const timeout = options.timeout ?? options.timeoutMs;
1989
+ const runnerOptions = timeout === void 0 ? options : { ...options, timeout };
1990
+ const mergeData = options.mergeData ?? ((current, next) => ({ ...current, ...next }));
1991
+ const variables = cloneRuntimeVariables(options.variables ?? {});
1992
+ if (options.timeoutMs !== void 0) {
1993
+ console.warn("timeoutMs is deprecated; use timeout. It will be removed in a future major release.");
1994
+ }
1995
+ const environment = {
1996
+ actionsRegistry,
1997
+ conditionsRegistry,
1998
+ configRef,
1999
+ options: runnerOptions,
2000
+ mergeData
2001
+ };
2002
+ const registerAction = (name, action) => {
2003
+ actionsRegistry.set(name, action);
2004
+ };
2005
+ const registerActions = (items) => {
2006
+ Object.entries(items).forEach(([name, action]) => registerAction(name, action));
2007
+ };
2008
+ const registerCondition = (name, condition) => {
2009
+ conditionsRegistry.set(name, condition);
2010
+ };
2011
+ const registerConditions = (items) => {
2012
+ Object.entries(items).forEach(([name, condition]) => registerCondition(name, condition));
2013
+ };
2014
+ const validateConfig2 = (target = configRef.current) => {
2015
+ const result = validateConfig(target, actionsRegistry, conditionsRegistry);
2016
+ return { ...result, warnings: [...result.warnings, ...runnerLimitWarnings(runnerOptions)] };
2017
+ };
2018
+ const loadConfig = (nextConfig) => {
2019
+ configRef.current = nextConfig;
2020
+ return validateConfig2(nextConfig);
2021
+ };
2022
+ const runInternal = (entrypoint, context, input, sync, runOptions) => {
2023
+ const traceSink = options.trace === true ? createMemoryTraceSink() : options.trace || void 0;
2024
+ const cancellation = createRunCancellation(runOptions.signal);
2025
+ const state = {
2026
+ context,
2027
+ input,
2028
+ data: {},
2029
+ patches: [],
2030
+ events: [],
2031
+ stepCounter: { current: 0 },
2032
+ startedAt: Date.now(),
2033
+ sync,
2034
+ signal: cancellation.controller.signal,
2035
+ abort: () => cancellation.controller.abort(),
2036
+ closed: false,
2037
+ reportedErrors: [],
2038
+ variables,
2039
+ expressions: options.expressions ?? {},
2040
+ ...traceSink ? { traceSink } : {}
2041
+ };
2042
+ const reportError = (result) => {
2043
+ if (result.status !== "failed" || state.reportedErrors.includes(result.error)) {
2044
+ return;
2045
+ }
2046
+ state.reportedErrors.push(result.error);
2047
+ options.onError?.({
2048
+ error: result.error,
2049
+ context: state.context,
2050
+ input: state.input,
2051
+ data: state.data,
2052
+ patches: state.patches,
2053
+ events: state.events,
2054
+ ...traceSink?.entries ? { trace: traceSink.entries() } : {}
2055
+ });
2056
+ };
2057
+ const finish = (result) => {
2058
+ state.closed = true;
2059
+ cancellation.dispose();
2060
+ return finishRunResult(result, state, traceSink || void 0);
2061
+ };
2062
+ const start = resolveEntrypoint(entrypoint, environment);
2063
+ if ("error" in start) {
2064
+ const result = { status: "failed", error: start.error, patches: [], events: [] };
2065
+ reportError(result);
2066
+ return finish(result);
2067
+ }
2068
+ const executed = executeStrategy(start.id, {}, 0, state, environment);
2069
+ const done = (result) => {
2070
+ reportError(result);
2071
+ return finish(result);
2072
+ };
2073
+ return isPromiseLike(executed) ? executed.then(done) : done(executed);
2074
+ };
2075
+ const run = async (entrypoint, context, input = {}, runOptions = {}) => runInternal(entrypoint, context, input, false, runOptions);
2076
+ const runSync = (entrypoint, context, input = {}, runOptions = {}) => {
2077
+ const result = runInternal(entrypoint, context, input, true, runOptions);
2078
+ if (isPromiseLike(result)) {
2079
+ throw new SyncAsyncError(slapError("ASYNC_IN_SYNC_RUN", "runSync encountered an async action"));
2080
+ }
2081
+ return result;
2082
+ };
2083
+ return {
2084
+ registerAction,
2085
+ registerActions,
2086
+ registerCondition,
2087
+ registerConditions,
2088
+ loadConfig,
2089
+ validateConfig: validateConfig2,
2090
+ run,
2091
+ runSync
2092
+ };
2093
+ };
2094
+
2095
+ // src/helpers/chain/isInput.ts
2096
+ var isInput = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2097
+
2098
+ // src/helpers/chain/parseDomBinding.ts
2099
+ var parseDomBinding = (binding, prefix) => {
2100
+ const source = binding.slice(prefix.length);
2101
+ const separator = source.lastIndexOf(":");
2102
+ return separator <= 0 || separator === source.length - 1 ? void 0 : { selector: source.slice(0, separator), eventType: source.slice(separator + 1) };
2103
+ };
2104
+
2105
+ // src/flow.ts
2106
+ var busBindingPrefix = "[bus] ";
2107
+ var domBindingPrefix = "[dom] ";
2108
+ var defaultMaxQueueSize = 50;
2109
+ var createFlow = (definition, options) => {
2110
+ const runner = createRunner(options);
2111
+ const bus = options.bus ?? PubSub;
2112
+ const unsubscribers = /* @__PURE__ */ new Set();
2113
+ const lanes = /* @__PURE__ */ new Map();
2114
+ const activeRuns = /* @__PURE__ */ new Set();
2115
+ let runCount = 0;
2116
+ runner.registerActions(definition.actions ?? {});
2117
+ runner.registerConditions(definition.conditions ?? {});
2118
+ const emitDiagnostic = (event, payload) => {
2119
+ ;
2120
+ bus.emit(event, payload);
2121
+ };
2122
+ const clearQueuedRuns = () => {
2123
+ for (const [binding, lane] of lanes) {
2124
+ for (const input of lane.queue) {
2125
+ emitDiagnostic("slapflow.run.dropped", { binding, input, reason: "chain-stopped" });
2126
+ }
2127
+ lane.queue = [];
2128
+ if (!lane.active) {
2129
+ lanes.delete(binding);
2130
+ }
2131
+ }
2132
+ };
2133
+ const stop = (stopOptions = {}) => {
2134
+ for (const unsubscribe of unsubscribers) {
2135
+ unsubscribe();
2136
+ }
2137
+ unsubscribers.clear();
2138
+ clearQueuedRuns();
2139
+ if (stopOptions.force) {
2140
+ for (const run of activeRuns) {
2141
+ run.controller.abort();
2142
+ }
2143
+ }
2144
+ };
2145
+ const getContext = () => typeof options.context === "function" ? options.context() : options.context;
2146
+ const getConcurrency = (target) => target.options?.concurrency ?? options.concurrency ?? {};
2147
+ const startRun = (binding, target, input, key, lane, laneKey) => {
2148
+ const controller = new AbortController();
2149
+ const run = { controller, id: `run-${++runCount}` };
2150
+ activeRuns.add(run);
2151
+ if (lane) {
2152
+ lane.active = run;
2153
+ }
2154
+ emitDiagnostic("slapflow.run.started", { binding, entrypoint: target.entrypoint, key, runId: run.id });
2155
+ void runner.run(target.entrypoint, getContext(), input, { signal: controller.signal }).then((result) => {
2156
+ const payload = { binding, entrypoint: target.entrypoint, key, runId: run.id };
2157
+ if (controller.signal.aborted) {
2158
+ emitDiagnostic("slapflow.run.cancelled", payload);
2159
+ } else if (result.status === "failed") {
2160
+ emitDiagnostic("slapflow.run.failed", { ...payload, error: result.error });
2161
+ options.onRunnerError?.({
2162
+ error: result.error,
2163
+ result,
2164
+ binding,
2165
+ entrypoint: target.entrypoint,
2166
+ runId: run.id,
2167
+ ...key === void 0 ? {} : { key }
2168
+ });
2169
+ } else {
2170
+ emitDiagnostic("slapflow.run.finished", { ...payload, status: result.status });
2171
+ }
2172
+ }).catch((error) => {
2173
+ const payload = { binding, entrypoint: target.entrypoint, key, runId: run.id };
2174
+ emitDiagnostic(controller.signal.aborted ? "slapflow.run.cancelled" : "slapflow.run.failed", {
2175
+ ...payload,
2176
+ ...controller.signal.aborted ? {} : { error }
2177
+ });
2178
+ }).finally(() => {
2179
+ activeRuns.delete(run);
2180
+ if (lane && lane.active === run) {
2181
+ const nextInput = lane.queue.shift();
2182
+ lane.active = void 0;
2183
+ if (nextInput) {
2184
+ startRun(binding, target, nextInput, key, lane, laneKey);
2185
+ } else {
2186
+ lanes.delete(laneKey);
2187
+ }
2188
+ }
2189
+ });
2190
+ };
2191
+ const scheduleRun = (binding, target, input) => {
2192
+ const concurrency = getConcurrency(target);
2193
+ const mode = concurrency.mode ?? "parallel";
2194
+ if (mode === "parallel") {
2195
+ startRun(binding, target, input);
2196
+ } else {
2197
+ const key = concurrency.key?.(input) ?? "";
2198
+ const laneKey = `${binding}:${key}`;
2199
+ const lane = lanes.get(laneKey) ?? { queue: [] };
2200
+ lanes.set(laneKey, lane);
2201
+ if (!lane.active) {
2202
+ startRun(binding, target, input, key, lane, laneKey);
2203
+ } else if (mode === "latest") {
2204
+ lane.active.controller.abort();
2205
+ startRun(binding, target, input, key, lane, laneKey);
2206
+ } else if (mode === "drop") {
2207
+ emitDiagnostic("slapflow.run.dropped", { binding, entrypoint: target.entrypoint, key, reason: "run-active" });
2208
+ } else {
2209
+ const maxQueueSize = concurrency.maxQueueSize ?? defaultMaxQueueSize;
2210
+ const queueIsFull = lane.queue.length >= maxQueueSize;
2211
+ const dropsOldest = concurrency.overflow === "drop-oldest";
2212
+ if (queueIsFull) {
2213
+ emitDiagnostic("slapflow.queue.overflow", { binding, entrypoint: target.entrypoint, key, maxQueueSize });
2214
+ if (dropsOldest) {
2215
+ const dropped = lane.queue.shift();
2216
+ emitDiagnostic("slapflow.run.dropped", {
2217
+ binding,
2218
+ entrypoint: target.entrypoint,
2219
+ key,
2220
+ reason: "queue-overflow",
2221
+ ...dropped ? { input: dropped } : {}
2222
+ });
2223
+ } else {
2224
+ emitDiagnostic("slapflow.run.dropped", {
2225
+ binding,
2226
+ entrypoint: target.entrypoint,
2227
+ key,
2228
+ reason: "queue-overflow"
2229
+ });
2230
+ }
2231
+ }
2232
+ if (!queueIsFull || dropsOldest) {
2233
+ lane.queue.push(input);
2234
+ }
2235
+ }
2236
+ }
2237
+ };
2238
+ const subscribeBusBinding = (binding, target) => {
2239
+ const event = binding.slice(busBindingPrefix.length);
2240
+ const unsubscribe = bus.on(event, (busEvent) => {
2241
+ if (isInput(busEvent.parsed)) {
2242
+ scheduleRun(binding, target, busEvent.parsed);
2243
+ } else {
2244
+ emitDiagnostic("slapflow.run.dropped", {
2245
+ binding,
2246
+ entrypoint: target.entrypoint,
2247
+ reason: "input-not-object"
2248
+ });
2249
+ }
2250
+ });
2251
+ unsubscribers.add(unsubscribe);
2252
+ };
2253
+ const collectForm = (element) => {
2254
+ const form = typeof HTMLFormElement !== "undefined" && element instanceof HTMLFormElement ? element : element.closest("form");
2255
+ if (!form) {
2256
+ return void 0;
2257
+ }
2258
+ const values = {};
2259
+ for (const [name, value] of new FormData(form)) {
2260
+ const current = values[name];
2261
+ values[name] = current === void 0 ? value : Array.isArray(current) ? [...current, value] : [current, value];
2262
+ }
2263
+ return values;
2264
+ };
2265
+ const createDomInput = (event, element) => {
2266
+ const value = "value" in element && typeof element.value === "string" ? element.value : void 0;
2267
+ const form = collectForm(element);
2268
+ const dataset = {};
2269
+ if (element instanceof HTMLElement) {
2270
+ for (const [key, item] of Object.entries(element.dataset)) {
2271
+ if (item !== void 0) {
2272
+ dataset[key] = item;
2273
+ }
2274
+ }
2275
+ }
2276
+ return {
2277
+ type: event.type,
2278
+ ...value === void 0 ? {} : { value },
2279
+ dataset,
2280
+ ...form ? { form } : {}
2281
+ };
2282
+ };
2283
+ const subscribeDomBinding = (binding, target) => {
2284
+ const parsed = parseDomBinding(binding, domBindingPrefix);
2285
+ const root = options.root ?? (typeof document === "undefined" ? void 0 : document);
2286
+ let active = false;
2287
+ if (parsed && root) {
2288
+ let unsubscribe = () => void 0;
2289
+ const listener = (event) => {
2290
+ const eventTarget = event.target;
2291
+ if (typeof Element !== "undefined" && eventTarget instanceof Element) {
2292
+ const element = eventTarget.closest(parsed.selector);
2293
+ const belongsToRoot = !element || typeof Element === "undefined" || !(root instanceof Element) || root.contains(element);
2294
+ if (element && belongsToRoot) {
2295
+ const preventDefault = target.options?.preventDefault ?? event.type === "submit";
2296
+ if (preventDefault) {
2297
+ event.preventDefault();
2298
+ }
2299
+ if (target.options?.stopPropagation) {
2300
+ event.stopPropagation();
2301
+ }
2302
+ const defaultInput = createDomInput(event, element);
2303
+ const input = target.options?.input?.({ event, element, defaultInput }) ?? defaultInput;
2304
+ scheduleRun(binding, target, input);
2305
+ if (target.options?.once) {
2306
+ unsubscribe();
2307
+ }
2308
+ }
2309
+ }
2310
+ };
2311
+ const listenerOptions = target.options?.capture === void 0 ? void 0 : { capture: target.options.capture };
2312
+ root.addEventListener(parsed.eventType, listener, listenerOptions);
2313
+ unsubscribe = () => root.removeEventListener(parsed.eventType, listener, listenerOptions);
2314
+ unsubscribers.add(unsubscribe);
2315
+ active = true;
2316
+ }
2317
+ return active;
2318
+ };
2319
+ const start = () => {
2320
+ stop();
2321
+ const validation = runner.loadConfig(definition.config);
2322
+ const active = [];
2323
+ const inactive = [];
2324
+ if (validation.ok) {
2325
+ const bindings = Object.entries(definition.events ?? {});
2326
+ for (const [binding, target] of bindings) {
2327
+ if (binding.startsWith(busBindingPrefix)) {
2328
+ subscribeBusBinding(binding, target);
2329
+ active.push(binding);
2330
+ } else if (binding.startsWith(domBindingPrefix)) {
2331
+ if (subscribeDomBinding(binding, target)) {
2332
+ active.push(binding);
2333
+ } else {
2334
+ inactive.push({ binding, reason: "dom-unavailable" });
2335
+ }
2336
+ } else {
2337
+ inactive.push({ binding, reason: "unsupported-source" });
2338
+ }
2339
+ }
2340
+ }
2341
+ return { active, inactive, validation };
2342
+ };
2343
+ return { runner, start, stop };
2344
+ };
2345
+
2346
+ // src/ws.ts
2347
+ var openState = 1;
2348
+ var maxSeenEvents = 1e3;
2349
+ var createWS = (options) => {
2350
+ const inboundTopics = new Set(options.inboundTopics ?? []);
2351
+ const outboundTopics = new Set(options.outboundTopics ?? []);
2352
+ const seenEventIds = /* @__PURE__ */ new Set();
2353
+ const outboundUnsubscribers = /* @__PURE__ */ new Set();
2354
+ const socketUnsubscribers = /* @__PURE__ */ new Set();
2355
+ let socket;
2356
+ let retryTimer;
2357
+ let retryAttempt = 0;
2358
+ let started = false;
2359
+ let currentStatus = "idle";
2360
+ const diagnosticsBus = options.bus;
2361
+ const emitDiagnostic = (topic, payload) => {
2362
+ diagnosticsBus.emit(topic, payload, {
2363
+ ...options.origin ? { origin: options.origin } : {}
2364
+ });
2365
+ };
2366
+ const rememberEvent = (id) => {
2367
+ seenEventIds.add(id);
2368
+ if (seenEventIds.size > maxSeenEvents) {
2369
+ const oldest = seenEventIds.values().next();
2370
+ if (!oldest.done) {
2371
+ seenEventIds.delete(oldest.value);
2372
+ }
2373
+ }
2374
+ };
2375
+ const clearSocket = () => {
2376
+ for (const unsubscribe of socketUnsubscribers) {
2377
+ unsubscribe();
2378
+ }
2379
+ socketUnsubscribers.clear();
2380
+ socket = void 0;
2381
+ };
2382
+ const scheduleRetry = (reason, error) => {
2383
+ const maxAttempts = options.retry?.maxAttempts;
2384
+ if (maxAttempts !== void 0 && retryAttempt >= maxAttempts) {
2385
+ currentStatus = "stopped";
2386
+ emitDiagnostic("slapflow.ws.disconnected", { reason: "retry-limit-reached", attempt: retryAttempt });
2387
+ } else {
2388
+ const delay = getRetryDelay(retryAttempt, options.retry ?? {});
2389
+ const attempt = retryAttempt + 1;
2390
+ const retry = () => {
2391
+ retryTimer = void 0;
2392
+ connect();
2393
+ };
2394
+ currentStatus = "retrying";
2395
+ retryAttempt = attempt;
2396
+ retryTimer = setTimeout(retry, delay);
2397
+ emitDiagnostic("slapflow.ws.retrying", {
2398
+ reason,
2399
+ delay,
2400
+ attempt,
2401
+ ...error === void 0 ? {} : { error: String(error) }
2402
+ });
2403
+ }
2404
+ };
2405
+ const connect = () => {
2406
+ if (started && !socket) {
2407
+ currentStatus = "connecting";
2408
+ emitDiagnostic("slapflow.ws.connecting", { attempt: retryAttempt });
2409
+ try {
2410
+ const current = options.createSocket();
2411
+ socket = current;
2412
+ const listen = (type, listener) => {
2413
+ current.addEventListener(type, listener);
2414
+ socketUnsubscribers.add(() => current.removeEventListener(type, listener));
2415
+ };
2416
+ const disconnect = (reason) => {
2417
+ if (socket === current) {
2418
+ clearSocket();
2419
+ if (started) {
2420
+ scheduleRetry(reason);
2421
+ }
2422
+ }
2423
+ };
2424
+ listen("open", () => {
2425
+ if (socket === current) {
2426
+ retryAttempt = 0;
2427
+ currentStatus = "connected";
2428
+ emitDiagnostic("slapflow.ws.connected", {});
2429
+ }
2430
+ });
2431
+ listen("close", () => disconnect("close"));
2432
+ listen("error", () => disconnect("error"));
2433
+ listen("message", (event) => {
2434
+ const data = event.data;
2435
+ if (typeof data === "string") {
2436
+ try {
2437
+ const busEvent = JSON.parse(data);
2438
+ if (busEvent.topic && inboundTopics.has(busEvent.topic)) {
2439
+ if (busEvent.id) {
2440
+ rememberEvent(busEvent.id);
2441
+ }
2442
+ if (!options.bus.dispatch(busEvent)) {
2443
+ emitDiagnostic("slapflow.ws.message.rejected", { reason: "invalid-envelope", topic: busEvent.topic });
2444
+ }
2445
+ } else {
2446
+ emitDiagnostic("slapflow.ws.message.rejected", { reason: "topic-not-allowed", topic: busEvent.topic });
2447
+ }
2448
+ } catch (error) {
2449
+ emitDiagnostic("slapflow.ws.message.rejected", { reason: "message-parse-failed", error: String(error) });
2450
+ }
2451
+ } else {
2452
+ emitDiagnostic("slapflow.ws.message.rejected", { reason: "message-not-string" });
2453
+ }
2454
+ });
2455
+ } catch (error) {
2456
+ scheduleRetry("socket-create-failed", error);
2457
+ }
2458
+ }
2459
+ };
2460
+ const start = () => {
2461
+ if (!started) {
2462
+ started = true;
2463
+ for (const topic of outboundTopics) {
2464
+ const unsubscribe = options.bus.on(topic, (event) => {
2465
+ if (!seenEventIds.delete(event.id) && event.origin !== options.origin && socket?.readyState === openState) {
2466
+ socket.send(JSON.stringify(event));
2467
+ }
2468
+ });
2469
+ outboundUnsubscribers.add(unsubscribe);
2470
+ }
2471
+ connect();
2472
+ }
2473
+ };
2474
+ const stop = () => {
2475
+ started = false;
2476
+ if (retryTimer) {
2477
+ clearTimeout(retryTimer);
2478
+ }
2479
+ retryTimer = void 0;
2480
+ for (const unsubscribe of outboundUnsubscribers) {
2481
+ unsubscribe();
2482
+ }
2483
+ outboundUnsubscribers.clear();
2484
+ const current = socket;
2485
+ clearSocket();
2486
+ current?.close();
2487
+ currentStatus = "stopped";
2488
+ emitDiagnostic("slapflow.ws.disconnected", { reason: "stopped" });
2489
+ };
2490
+ const reconnect = () => {
2491
+ if (started) {
2492
+ if (retryTimer) {
2493
+ clearTimeout(retryTimer);
2494
+ }
2495
+ const current = socket;
2496
+ retryTimer = void 0;
2497
+ clearSocket();
2498
+ current?.close();
2499
+ connect();
2500
+ }
2501
+ };
2502
+ return { start, stop, reconnect, status: () => currentStatus };
2503
+ };
2504
+
2505
+ // src/catchError.ts
2506
+ var catchError = (callback) => new Promise((resolve, reject) => {
2507
+ try {
2508
+ resolve(callback());
2509
+ } catch (error) {
2510
+ reject(error);
2511
+ }
2512
+ });
2513
+ export {
2514
+ PubSub,
2515
+ catchError,
2516
+ createActionsRegistry,
2517
+ createConditionsRegistry,
2518
+ createFlow,
2519
+ createMemoryTraceSink,
2520
+ createPubSub,
2521
+ createWS,
2522
+ defineConfig,
2523
+ defineErrorReporter
2524
+ };