@stackwright-services/capabilities 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1365 @@
1
+ // src/registry.ts
2
+ var CapabilityRegistry = class {
3
+ capabilities = /* @__PURE__ */ new Map();
4
+ /**
5
+ * Register a capability. This is a security-relevant operation --
6
+ * every registration should be audited.
7
+ *
8
+ * @throws if a capability with the same name is already registered
9
+ */
10
+ register(definition, handler, inputSchema, outputSchema) {
11
+ if (this.capabilities.has(definition.name)) {
12
+ throw new Error(
13
+ `Capability "${definition.name}" is already registered. Duplicate registration is not allowed -- this is the audit chokepoint.`
14
+ );
15
+ }
16
+ this.capabilities.set(definition.name, {
17
+ definition,
18
+ handler,
19
+ inputSchema,
20
+ outputSchema
21
+ });
22
+ }
23
+ /**
24
+ * Look up a capability by name.
25
+ * @returns the registered capability, or undefined if not found
26
+ */
27
+ lookup(name) {
28
+ return this.capabilities.get(name);
29
+ }
30
+ /**
31
+ * Get a capability by name, throwing if not found.
32
+ * @throws if the capability is not registered
33
+ */
34
+ get(name) {
35
+ const capability = this.capabilities.get(name);
36
+ if (!capability) {
37
+ throw new Error(
38
+ `Capability "${name}" is not registered. Available capabilities: ${this.listNames().join(", ") || "(none)"}`
39
+ );
40
+ }
41
+ return capability;
42
+ }
43
+ /**
44
+ * List all registered capabilities.
45
+ */
46
+ listAll() {
47
+ return Array.from(this.capabilities.values());
48
+ }
49
+ /**
50
+ * List all registered capability names.
51
+ */
52
+ listNames() {
53
+ return Array.from(this.capabilities.keys());
54
+ }
55
+ /**
56
+ * Filter capabilities by kind (transform or effect).
57
+ */
58
+ filterByKind(kind) {
59
+ return this.listAll().filter((cap) => cap.definition.kind === kind);
60
+ }
61
+ /**
62
+ * Check if a capability is registered.
63
+ */
64
+ has(name) {
65
+ return this.capabilities.has(name);
66
+ }
67
+ /**
68
+ * Get the count of registered capabilities.
69
+ */
70
+ get size() {
71
+ return this.capabilities.size;
72
+ }
73
+ /**
74
+ * Clear all registrations. Primarily for testing.
75
+ */
76
+ clear() {
77
+ this.capabilities.clear();
78
+ }
79
+ };
80
+ var defaultRegistry = new CapabilityRegistry();
81
+
82
+ // src/transforms/collection-aggregate.ts
83
+ import { z } from "zod";
84
+ var AggregateOperation = z.enum(["count", "sum", "avg", "min", "max"]);
85
+ var CollectionAggregateInput = z.object({
86
+ items: z.array(z.unknown()),
87
+ field: z.string().min(1),
88
+ operation: AggregateOperation
89
+ });
90
+ var CollectionAggregateOutput = z.object({
91
+ result: z.number()
92
+ });
93
+ function resolveFieldPath(item, path) {
94
+ const parts = path.split(".");
95
+ let current = item;
96
+ for (const part of parts) {
97
+ if (current === null || current === void 0 || typeof current !== "object") {
98
+ return void 0;
99
+ }
100
+ current = current[part];
101
+ }
102
+ return current;
103
+ }
104
+ function extractNumericValues(items, field) {
105
+ return items.map((item, index) => {
106
+ const value = resolveFieldPath(item, field);
107
+ if (typeof value !== "number") {
108
+ throw new Error(
109
+ `Field "${field}" at index ${index} is not a number (got ${typeof value}). Numeric aggregation requires all values to be numbers.`
110
+ );
111
+ }
112
+ return value;
113
+ });
114
+ }
115
+ var collectionAggregateHandler = async (input) => {
116
+ const { items, field, operation } = input;
117
+ if (operation === "count") {
118
+ return { result: items.length };
119
+ }
120
+ if (items.length === 0) {
121
+ throw new Error(`Cannot compute ${operation} on an empty collection`);
122
+ }
123
+ const values = extractNumericValues(items, field);
124
+ let result;
125
+ switch (operation) {
126
+ case "sum":
127
+ result = values.reduce((acc, v) => acc + v, 0);
128
+ break;
129
+ case "avg":
130
+ result = values.reduce((acc, v) => acc + v, 0) / values.length;
131
+ break;
132
+ case "min":
133
+ result = Math.min(...values);
134
+ break;
135
+ case "max":
136
+ result = Math.max(...values);
137
+ break;
138
+ }
139
+ return { result };
140
+ };
141
+ var collectionAggregateDefinition = {
142
+ kind: "transform",
143
+ name: "collection.aggregate",
144
+ description: "Aggregate operations (count, sum, avg, min, max) on a field in an array",
145
+ inputSchema: "CollectionAggregateInput",
146
+ outputSchema: "CollectionAggregateOutput"
147
+ };
148
+
149
+ // src/transforms/collection-filter.ts
150
+ import { z as z2 } from "zod";
151
+ var CollectionFilterInput = z2.object({
152
+ items: z2.array(z2.unknown()),
153
+ conditions: z2.array(
154
+ z2.object({
155
+ field: z2.string().min(1),
156
+ op: z2.string().min(1),
157
+ value: z2.unknown().optional(),
158
+ value_field: z2.string().min(1).optional()
159
+ })
160
+ )
161
+ });
162
+ var CollectionFilterOutput = z2.object({
163
+ items: z2.array(z2.unknown()),
164
+ count: z2.number()
165
+ });
166
+ function resolveFieldPath2(item, path) {
167
+ const parts = path.split(".");
168
+ let current = item;
169
+ for (const part of parts) {
170
+ if (current === null || current === void 0 || typeof current !== "object") {
171
+ return void 0;
172
+ }
173
+ current = current[part];
174
+ }
175
+ return current;
176
+ }
177
+ function evaluatePredicate(item, condition) {
178
+ const fieldValue = resolveFieldPath2(item, condition.field);
179
+ const op = condition.op;
180
+ const target = condition.value;
181
+ switch (op) {
182
+ case "equals":
183
+ return fieldValue === target;
184
+ case "not_equals":
185
+ return fieldValue !== target;
186
+ case "in":
187
+ if (!Array.isArray(target)) return false;
188
+ return target.includes(fieldValue);
189
+ case "not_in":
190
+ if (!Array.isArray(target)) return true;
191
+ return !target.includes(fieldValue);
192
+ case "less_than":
193
+ return typeof fieldValue === "number" && typeof target === "number" ? fieldValue < target : false;
194
+ case "greater_than":
195
+ return typeof fieldValue === "number" && typeof target === "number" ? fieldValue > target : false;
196
+ case "less_than_or_equal":
197
+ return typeof fieldValue === "number" && typeof target === "number" ? fieldValue <= target : false;
198
+ case "greater_than_or_equal":
199
+ return typeof fieldValue === "number" && typeof target === "number" ? fieldValue >= target : false;
200
+ case "matches_prefix":
201
+ return typeof fieldValue === "string" && typeof target === "string" ? fieldValue.startsWith(target) : false;
202
+ case "contains":
203
+ return typeof fieldValue === "string" && typeof target === "string" ? fieldValue.includes(target) : false;
204
+ case "less_than_field": {
205
+ if (!condition.value_field) return false;
206
+ const otherValue = resolveFieldPath2(item, condition.value_field);
207
+ return typeof fieldValue === "number" && typeof otherValue === "number" ? fieldValue < otherValue : false;
208
+ }
209
+ case "greater_than_field": {
210
+ if (!condition.value_field) return false;
211
+ const otherValue = resolveFieldPath2(item, condition.value_field);
212
+ return typeof fieldValue === "number" && typeof otherValue === "number" ? fieldValue > otherValue : false;
213
+ }
214
+ case "equals_field": {
215
+ if (!condition.value_field) return false;
216
+ const otherValue = resolveFieldPath2(item, condition.value_field);
217
+ return fieldValue === otherValue;
218
+ }
219
+ default: {
220
+ const _exhaustive = op;
221
+ throw new Error(`Unsupported predicate operator: ${_exhaustive}`);
222
+ }
223
+ }
224
+ }
225
+ var collectionFilterHandler = async (input) => {
226
+ const { items, conditions } = input;
227
+ const filtered = items.filter(
228
+ (item) => conditions.every((condition) => evaluatePredicate(item, condition))
229
+ );
230
+ return { items: filtered, count: filtered.length };
231
+ };
232
+ var collectionFilterDefinition = {
233
+ kind: "transform",
234
+ name: "collection.filter",
235
+ description: "Filter an array of objects using typed predicate conditions",
236
+ inputSchema: "CollectionFilterInput",
237
+ outputSchema: "CollectionFilterOutput"
238
+ };
239
+
240
+ // src/transforms/collection-join.ts
241
+ import { z as z3 } from "zod";
242
+ var CollectionJoinInput = z3.object({
243
+ /** Left-side items array */
244
+ leftItems: z3.array(z3.unknown()),
245
+ /** Right-side items array */
246
+ rightItems: z3.array(z3.unknown()),
247
+ /** Field path on left items to join on */
248
+ leftField: z3.string().min(1),
249
+ /** Field path on right items to join on */
250
+ rightField: z3.string().min(1)
251
+ });
252
+ var CollectionJoinOutput = z3.object({
253
+ /** Merged items where left[leftField] === right[rightField] */
254
+ items: z3.array(
255
+ z3.object({
256
+ left: z3.unknown(),
257
+ right: z3.unknown()
258
+ })
259
+ ),
260
+ /** Count of matched items */
261
+ count: z3.number()
262
+ });
263
+ function resolveFieldPath3(item, path) {
264
+ const parts = path.split(".");
265
+ let current = item;
266
+ for (const part of parts) {
267
+ if (current === null || current === void 0 || typeof current !== "object") {
268
+ return void 0;
269
+ }
270
+ current = current[part];
271
+ }
272
+ return current;
273
+ }
274
+ var collectionJoinHandler = async (input) => {
275
+ const { leftItems, rightItems, leftField, rightField } = input;
276
+ const rightIndex = /* @__PURE__ */ new Map();
277
+ for (const rightItem of rightItems) {
278
+ const key = resolveFieldPath3(rightItem, rightField);
279
+ if (key !== void 0) {
280
+ const existing = rightIndex.get(key) ?? [];
281
+ existing.push(rightItem);
282
+ rightIndex.set(key, existing);
283
+ }
284
+ }
285
+ const items = [];
286
+ for (const leftItem of leftItems) {
287
+ const key = resolveFieldPath3(leftItem, leftField);
288
+ if (key !== void 0) {
289
+ const matches = rightIndex.get(key);
290
+ if (matches) {
291
+ for (const rightItem of matches) {
292
+ items.push({ left: leftItem, right: rightItem });
293
+ }
294
+ }
295
+ }
296
+ }
297
+ return { items, count: items.length };
298
+ };
299
+ var collectionJoinDefinition = {
300
+ kind: "transform",
301
+ name: "collection.join",
302
+ description: "Join two arrays on matching field values for cross-domain data correlation",
303
+ inputSchema: "CollectionJoinInput",
304
+ outputSchema: "CollectionJoinOutput"
305
+ };
306
+
307
+ // src/transforms/collection-paginate.ts
308
+ import { z as z4 } from "zod";
309
+ var CollectionPaginateInput = z4.object({
310
+ items: z4.array(z4.unknown()),
311
+ page: z4.number().int().min(1),
312
+ pageSize: z4.number().int().min(1).max(1e3)
313
+ });
314
+ var CollectionPaginateOutput = z4.object({
315
+ items: z4.array(z4.unknown()),
316
+ total: z4.number(),
317
+ page: z4.number(),
318
+ pageSize: z4.number(),
319
+ hasNext: z4.boolean(),
320
+ hasPrevious: z4.boolean()
321
+ });
322
+ var collectionPaginateHandler = async (input) => {
323
+ const { items, page, pageSize } = input;
324
+ const total = items.length;
325
+ const startIndex = (page - 1) * pageSize;
326
+ const pageItems = items.slice(startIndex, startIndex + pageSize);
327
+ return {
328
+ items: pageItems,
329
+ total,
330
+ page,
331
+ pageSize,
332
+ hasNext: startIndex + pageSize < total,
333
+ hasPrevious: page > 1
334
+ };
335
+ };
336
+ var collectionPaginateDefinition = {
337
+ kind: "transform",
338
+ name: "collection.paginate",
339
+ description: "Extract a page from an array with pagination metadata",
340
+ inputSchema: "CollectionPaginateInput",
341
+ outputSchema: "CollectionPaginateOutput"
342
+ };
343
+
344
+ // src/transforms/math-compute.ts
345
+ import { z as z5 } from "zod";
346
+ var MathOperation = z5.object({
347
+ op: z5.enum(["add", "subtract", "multiply", "divide", "modulo", "round", "ceil", "floor"]),
348
+ fields: z5.array(z5.string().min(1)).min(1),
349
+ // field paths to read operands from
350
+ result_field: z5.string().min(1)
351
+ // dot-path where the result is written
352
+ });
353
+ var MathComputeInput = z5.object({
354
+ data: z5.record(z5.unknown()),
355
+ operations: z5.array(MathOperation).min(1)
356
+ });
357
+ var MathComputeOutput = z5.object({
358
+ data: z5.record(z5.unknown()),
359
+ computed_fields: z5.array(z5.string())
360
+ });
361
+ function resolveFieldPath4(obj, path) {
362
+ const parts = path.split(".");
363
+ let current = obj;
364
+ for (const part of parts) {
365
+ if (current === null || current === void 0 || typeof current !== "object") {
366
+ return void 0;
367
+ }
368
+ current = current[part];
369
+ }
370
+ return current;
371
+ }
372
+ function setFieldPath(obj, path, value) {
373
+ const parts = path.split(".");
374
+ const lastKey = parts.pop();
375
+ if (lastKey === void 0) return;
376
+ let current = obj;
377
+ for (const key of parts) {
378
+ if (current[key] === null || current[key] === void 0 || typeof current[key] !== "object") {
379
+ current[key] = {};
380
+ }
381
+ current = current[key];
382
+ }
383
+ current[lastKey] = value;
384
+ }
385
+ function resolveNumber(data, field) {
386
+ const value = resolveFieldPath4(data, field);
387
+ return typeof value === "number" ? value : null;
388
+ }
389
+ var mathComputeHandler = async (input) => {
390
+ const { data, operations } = input;
391
+ const result = structuredClone(data);
392
+ const computedFields = [];
393
+ for (const { op, fields, result_field } of operations) {
394
+ const [firstField, secondField] = fields;
395
+ const a = firstField !== void 0 ? resolveNumber(result, firstField) : null;
396
+ let computed = null;
397
+ if (op === "round") {
398
+ computed = a !== null ? Math.round(a) : null;
399
+ } else if (op === "ceil") {
400
+ computed = a !== null ? Math.ceil(a) : null;
401
+ } else if (op === "floor") {
402
+ computed = a !== null ? Math.floor(a) : null;
403
+ } else {
404
+ const b = secondField !== void 0 ? resolveNumber(result, secondField) : null;
405
+ if (a !== null && b !== null) {
406
+ if (op === "add") computed = a + b;
407
+ else if (op === "subtract") computed = a - b;
408
+ else if (op === "multiply") computed = a * b;
409
+ else if (op === "divide") computed = b !== 0 ? a / b : null;
410
+ else if (op === "modulo") computed = b !== 0 ? a % b : null;
411
+ }
412
+ }
413
+ setFieldPath(result, result_field, computed);
414
+ computedFields.push(result_field);
415
+ }
416
+ return { data: result, computed_fields: computedFields };
417
+ };
418
+ var mathComputeDefinition = {
419
+ kind: "transform",
420
+ name: "math.compute",
421
+ description: "Safe arithmetic operations on typed numeric fields. Operations: add, subtract, multiply, divide, modulo, round, ceil, floor.",
422
+ inputSchema: "MathComputeInput",
423
+ outputSchema: "MathComputeOutput"
424
+ };
425
+
426
+ // src/transforms/collection-sort.ts
427
+ import { z as z6 } from "zod";
428
+ var SortKey = z6.object({
429
+ field: z6.string().min(1),
430
+ // Explicit direction required — no silent defaults. Explicit is better than implicit.
431
+ direction: z6.enum(["asc", "desc"])
432
+ });
433
+ var CollectionSortInput = z6.object({
434
+ items: z6.array(z6.unknown()),
435
+ sortBy: z6.array(SortKey).min(1)
436
+ });
437
+ var CollectionSortOutput = z6.object({
438
+ items: z6.array(z6.unknown()),
439
+ count: z6.number()
440
+ });
441
+ function resolveFieldPath5(item, path) {
442
+ const parts = path.split(".");
443
+ let current = item;
444
+ for (const part of parts) {
445
+ if (current === null || current === void 0 || typeof current !== "object") {
446
+ return void 0;
447
+ }
448
+ current = current[part];
449
+ }
450
+ return current;
451
+ }
452
+ function compareNonNilValues(a, b) {
453
+ if (typeof a === "number" && typeof b === "number") {
454
+ return a - b;
455
+ }
456
+ if (typeof a === "string" && typeof b === "string") {
457
+ return a < b ? -1 : a > b ? 1 : 0;
458
+ }
459
+ if (typeof a === "number") return -1;
460
+ if (typeof b === "number") return 1;
461
+ const sa = String(a);
462
+ const sb = String(b);
463
+ return sa < sb ? -1 : sa > sb ? 1 : 0;
464
+ }
465
+ var collectionSortHandler = async (input) => {
466
+ const { items, sortBy } = input;
467
+ const sorted = [...items].sort((a, b) => {
468
+ for (const { field, direction } of sortBy) {
469
+ const aVal = resolveFieldPath5(a, field);
470
+ const bVal = resolveFieldPath5(b, field);
471
+ const aIsNil = aVal === null || aVal === void 0;
472
+ const bIsNil = bVal === null || bVal === void 0;
473
+ if (aIsNil && bIsNil) continue;
474
+ if (aIsNil) return 1;
475
+ if (bIsNil) return -1;
476
+ const cmp = compareNonNilValues(aVal, bVal);
477
+ if (cmp !== 0) {
478
+ return direction === "desc" ? -cmp : cmp;
479
+ }
480
+ }
481
+ return 0;
482
+ });
483
+ return { items: sorted, count: sorted.length };
484
+ };
485
+ var collectionSortDefinition = {
486
+ kind: "transform",
487
+ name: "collection.sort",
488
+ description: "Sort arrays by one or more fields with typed direction (asc/desc)",
489
+ inputSchema: "CollectionSortInput",
490
+ outputSchema: "CollectionSortOutput"
491
+ };
492
+
493
+ // src/transforms/date-shift.ts
494
+ import { z as z7 } from "zod";
495
+ var DateShiftInput = z7.object({
496
+ date: z7.string().min(1),
497
+ duration: z7.string().regex(
498
+ /^P(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+S)?)?$/,
499
+ 'Duration must be ISO 8601 format (e.g. "P3D", "PT1H30M")'
500
+ ),
501
+ direction: z7.enum(["add", "subtract"])
502
+ });
503
+ var DateShiftOutput = z7.object({
504
+ result: z7.string()
505
+ });
506
+ function parseDuration(duration) {
507
+ const result = { days: 0, hours: 0, minutes: 0, seconds: 0 };
508
+ const dayMatch = duration.match(/(\d+)D/);
509
+ if (dayMatch?.[1]) result.days = parseInt(dayMatch[1], 10);
510
+ const timeSection = duration.match(/T(.+)$/);
511
+ if (timeSection?.[1]) {
512
+ const timePart = timeSection[1];
513
+ const hourMatch = timePart.match(/(\d+)H/);
514
+ const minMatch = timePart.match(/(\d+)M/);
515
+ const secMatch = timePart.match(/(\d+)S/);
516
+ if (hourMatch?.[1]) result.hours = parseInt(hourMatch[1], 10);
517
+ if (minMatch?.[1]) result.minutes = parseInt(minMatch[1], 10);
518
+ if (secMatch?.[1]) result.seconds = parseInt(secMatch[1], 10);
519
+ }
520
+ return result;
521
+ }
522
+ var dateShiftHandler = async (input) => {
523
+ const { date, duration, direction } = input;
524
+ const parsed = parseDuration(duration);
525
+ const totalMs = (parsed.days * 86400 + parsed.hours * 3600 + parsed.minutes * 60 + parsed.seconds) * 1e3;
526
+ const dateObj = new Date(date);
527
+ if (isNaN(dateObj.getTime())) {
528
+ throw new Error(`Invalid date: "${date}" is not a valid ISO 8601 date`);
529
+ }
530
+ const shift = direction === "add" ? totalMs : -totalMs;
531
+ const resultDate = new Date(dateObj.getTime() + shift);
532
+ return { result: resultDate.toISOString() };
533
+ };
534
+ var dateShiftDefinition = {
535
+ kind: "transform",
536
+ name: "date.shift",
537
+ description: "Add or subtract an ISO 8601 duration from a date",
538
+ inputSchema: "DateShiftInput",
539
+ outputSchema: "DateShiftOutput"
540
+ };
541
+
542
+ // src/transforms/events-filter.ts
543
+ import { z as z8 } from "zod";
544
+ var EventsFilterInput = z8.object({
545
+ /** The event payload to evaluate */
546
+ event: z8.unknown(),
547
+ /** Typed predicate conditions -- all must pass for the event to pass through */
548
+ conditions: z8.array(
549
+ z8.object({
550
+ field: z8.string().min(1),
551
+ op: z8.string().min(1),
552
+ value: z8.unknown()
553
+ })
554
+ ).min(1)
555
+ });
556
+ var EventsFilterOutput = z8.object({
557
+ /** The event if it passed all conditions, or null if filtered out */
558
+ event: z8.unknown().nullable(),
559
+ /** Whether the event passed all conditions */
560
+ passed: z8.boolean()
561
+ });
562
+ var eventsFilterHandler = async (input) => {
563
+ const { event, conditions } = input;
564
+ const passed = conditions.every(
565
+ (condition) => evaluatePredicate(event, condition)
566
+ );
567
+ return {
568
+ event: passed ? event : null,
569
+ passed
570
+ };
571
+ };
572
+ var eventsFilterDefinition = {
573
+ kind: "transform",
574
+ name: "events.filter",
575
+ description: "Filter a single event payload against typed predicate conditions. Returns the event if all conditions pass, null if filtered out.",
576
+ inputSchema: "EventsFilterInput",
577
+ outputSchema: "EventsFilterOutput"
578
+ };
579
+
580
+ // src/transforms/text-format.ts
581
+ import { z as z9 } from "zod";
582
+ var TextFormatInput = z9.object({
583
+ template: z9.string(),
584
+ params: z9.record(z9.string(), z9.union([z9.string(), z9.number(), z9.boolean()]))
585
+ });
586
+ var TextFormatOutput = z9.object({
587
+ result: z9.string()
588
+ });
589
+ var textFormatHandler = async (input) => {
590
+ const { template, params } = input;
591
+ const result = template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
592
+ const value = params[key];
593
+ if (value === void 0) {
594
+ throw new Error(
595
+ `Missing template parameter: "${key}" (referenced in template but not provided)`
596
+ );
597
+ }
598
+ return String(value);
599
+ });
600
+ return { result };
601
+ };
602
+ var textFormatDefinition = {
603
+ kind: "transform",
604
+ name: "text.format",
605
+ description: "Template interpolation: replaces {{key}} placeholders with typed parameter values",
606
+ inputSchema: "TextFormatInput",
607
+ outputSchema: "TextFormatOutput"
608
+ };
609
+
610
+ // src/transforms/units-convert.ts
611
+ import { z as z10 } from "zod";
612
+ var UnitCategory = z10.enum(["length", "weight", "temperature", "volume"]);
613
+ var UnitsConvertInput = z10.object({
614
+ value: z10.number(),
615
+ from: z10.string().min(1),
616
+ to: z10.string().min(1),
617
+ category: UnitCategory
618
+ });
619
+ var UnitsConvertOutput = z10.object({
620
+ result: z10.number(),
621
+ from: z10.string(),
622
+ to: z10.string()
623
+ });
624
+ var CONVERSION_FACTORS = {
625
+ length: {
626
+ m: 1,
627
+ km: 1e3,
628
+ cm: 0.01,
629
+ mm: 1e-3,
630
+ mi: 1609.344,
631
+ yd: 0.9144,
632
+ ft: 0.3048,
633
+ in: 0.0254,
634
+ nm: 1852
635
+ },
636
+ weight: {
637
+ kg: 1,
638
+ g: 1e-3,
639
+ mg: 1e-6,
640
+ lb: 0.453592,
641
+ oz: 0.0283495,
642
+ ton: 1e3
643
+ },
644
+ volume: {
645
+ l: 1,
646
+ ml: 1e-3,
647
+ gal: 3.78541,
648
+ qt: 0.946353,
649
+ pt: 0.473176,
650
+ cup: 0.236588,
651
+ floz: 0.0295735
652
+ }
653
+ };
654
+ function convertTemperature(value, from, to) {
655
+ let celsius;
656
+ switch (from) {
657
+ case "C":
658
+ celsius = value;
659
+ break;
660
+ case "F":
661
+ celsius = (value - 32) * 5 / 9;
662
+ break;
663
+ case "K":
664
+ celsius = value - 273.15;
665
+ break;
666
+ default:
667
+ throw new Error(`Unknown temperature unit: ${from}`);
668
+ }
669
+ switch (to) {
670
+ case "C":
671
+ return celsius;
672
+ case "F":
673
+ return celsius * 9 / 5 + 32;
674
+ case "K":
675
+ return celsius + 273.15;
676
+ default:
677
+ throw new Error(`Unknown temperature unit: ${to}`);
678
+ }
679
+ }
680
+ var unitsConvertHandler = async (input) => {
681
+ const { value, from, to, category } = input;
682
+ let result;
683
+ if (category === "temperature") {
684
+ result = convertTemperature(value, from, to);
685
+ } else {
686
+ const factors = CONVERSION_FACTORS[category];
687
+ if (!factors) throw new Error(`Unknown unit category: ${category}`);
688
+ const fromFactor = factors[from];
689
+ const toFactor = factors[to];
690
+ if (fromFactor === void 0) throw new Error(`Unknown ${category} unit: ${from}`);
691
+ if (toFactor === void 0) throw new Error(`Unknown ${category} unit: ${to}`);
692
+ result = value * fromFactor / toFactor;
693
+ }
694
+ return { result, from, to };
695
+ };
696
+ var unitsConvertDefinition = {
697
+ kind: "transform",
698
+ name: "units.convert",
699
+ description: "Convert between measurement units (length, weight, temperature, volume)",
700
+ inputSchema: "UnitsConvertInput",
701
+ outputSchema: "UnitsConvertOutput"
702
+ };
703
+
704
+ // src/transforms/validation-check.ts
705
+ import { FIELD_COMPARISON_OPERATORS } from "@stackwright-services/types";
706
+ import { z as z11 } from "zod";
707
+ var ValidationRule = z11.object({
708
+ /** The field path to validate (dot notation supported: "address.zip") */
709
+ field: z11.string().min(1),
710
+ /** Predicate operator to apply */
711
+ op: z11.string(),
712
+ /** Expected value for the predicate (literal value or field name for *_field ops) */
713
+ value: z11.unknown(),
714
+ /** Optional human-readable error message (defaults to auto-generated) */
715
+ message: z11.string().optional()
716
+ });
717
+ var ValidationCheckInput = z11.object({
718
+ /** Data object to validate */
719
+ data: z11.record(z11.unknown()),
720
+ /** Validation rules to apply */
721
+ rules: z11.array(ValidationRule).min(1)
722
+ });
723
+ var ValidationError = z11.object({
724
+ /** Field that failed validation */
725
+ field: z11.string(),
726
+ /** Operator that was applied */
727
+ rule: z11.string(),
728
+ /** Human-readable error message */
729
+ message: z11.string(),
730
+ /** The actual value found at the field */
731
+ actual: z11.unknown().optional()
732
+ });
733
+ var ValidationCheckOutput = z11.object({
734
+ /** Whether all rules passed */
735
+ valid: z11.boolean(),
736
+ /** Number of rules that passed */
737
+ passed_count: z11.number().int().min(0),
738
+ /** Number of rules that failed */
739
+ failed_count: z11.number().int().min(0),
740
+ /** Detailed errors for each failed rule */
741
+ errors: z11.array(ValidationError)
742
+ });
743
+ var validationCheckDefinition = {
744
+ kind: "transform",
745
+ name: "validation.check",
746
+ description: "Run typed predicates against data fields and return structured validation results. Pure transform that composes with all 13 predicate operators. Powers form validation.",
747
+ inputSchema: "ValidationCheckInput",
748
+ outputSchema: "ValidationCheckOutput"
749
+ };
750
+ function getNestedField(obj, path) {
751
+ const parts = path.split(".");
752
+ let current = obj;
753
+ for (const part of parts) {
754
+ if (current === null || current === void 0 || typeof current !== "object") {
755
+ return void 0;
756
+ }
757
+ current = current[part];
758
+ }
759
+ return current;
760
+ }
761
+ function defaultMessage(field, op, value) {
762
+ const valueStr = typeof value === "string" ? `"${value}"` : Array.isArray(value) ? `[${value.map((v) => JSON.stringify(v)).join(", ")}]` : JSON.stringify(value);
763
+ return `Field "${field}" failed ${op} check (expected: ${valueStr})`;
764
+ }
765
+ function toPredicateCondition(rule) {
766
+ const isFieldOp = FIELD_COMPARISON_OPERATORS.includes(rule.op);
767
+ if (isFieldOp) {
768
+ return {
769
+ field: rule.field,
770
+ op: rule.op,
771
+ value_field: String(rule.value)
772
+ };
773
+ }
774
+ return {
775
+ field: rule.field,
776
+ op: rule.op,
777
+ value: rule.value
778
+ };
779
+ }
780
+ var validationCheckHandler = async (input) => {
781
+ const { data, rules } = ValidationCheckInput.parse(input);
782
+ const errors = [];
783
+ for (const rule of rules) {
784
+ const fieldValue = getNestedField(data, rule.field);
785
+ const condition = toPredicateCondition(rule);
786
+ const passed = evaluatePredicate(data, condition);
787
+ if (!passed) {
788
+ errors.push({
789
+ field: rule.field,
790
+ rule: rule.op,
791
+ message: rule.message ?? defaultMessage(rule.field, rule.op, rule.value),
792
+ actual: fieldValue
793
+ });
794
+ }
795
+ }
796
+ return {
797
+ valid: errors.length === 0,
798
+ passed_count: rules.length - errors.length,
799
+ failed_count: errors.length,
800
+ errors
801
+ };
802
+ };
803
+
804
+ // src/transforms/index.ts
805
+ function registerTransforms(registry) {
806
+ registry.register(
807
+ unitsConvertDefinition,
808
+ unitsConvertHandler,
809
+ UnitsConvertInput,
810
+ UnitsConvertOutput
811
+ );
812
+ registry.register(textFormatDefinition, textFormatHandler, TextFormatInput, TextFormatOutput);
813
+ registry.register(
814
+ collectionFilterDefinition,
815
+ collectionFilterHandler,
816
+ CollectionFilterInput,
817
+ CollectionFilterOutput
818
+ );
819
+ registry.register(
820
+ collectionAggregateDefinition,
821
+ collectionAggregateHandler,
822
+ CollectionAggregateInput,
823
+ CollectionAggregateOutput
824
+ );
825
+ registry.register(
826
+ collectionJoinDefinition,
827
+ collectionJoinHandler,
828
+ CollectionJoinInput,
829
+ CollectionJoinOutput
830
+ );
831
+ registry.register(
832
+ collectionPaginateDefinition,
833
+ collectionPaginateHandler,
834
+ CollectionPaginateInput,
835
+ CollectionPaginateOutput
836
+ );
837
+ registry.register(mathComputeDefinition, mathComputeHandler, MathComputeInput, MathComputeOutput);
838
+ registry.register(
839
+ collectionSortDefinition,
840
+ collectionSortHandler,
841
+ CollectionSortInput,
842
+ CollectionSortOutput
843
+ );
844
+ registry.register(dateShiftDefinition, dateShiftHandler, DateShiftInput, DateShiftOutput);
845
+ registry.register(
846
+ eventsFilterDefinition,
847
+ eventsFilterHandler,
848
+ EventsFilterInput,
849
+ EventsFilterOutput
850
+ );
851
+ registry.register(
852
+ validationCheckDefinition,
853
+ validationCheckHandler,
854
+ ValidationCheckInput,
855
+ ValidationCheckOutput
856
+ );
857
+ }
858
+
859
+ // src/effects/service-call.ts
860
+ import { z as z12 } from "zod";
861
+ var ServiceCallInput = z12.object({
862
+ /** The URL to call (raw HTTP fallback — always required so the effect works without a provider) */
863
+ url: z12.string().url(),
864
+ /** HTTP method */
865
+ method: z12.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
866
+ /**
867
+ * Data source ID for provider delegation (e.g. "equipment-api").
868
+ * When provided alongside `operation`, and a DataSourceProvider in context can resolve
869
+ * this source, the call is delegated to the provider instead of raw HTTP.
870
+ * Falls back to `url`+`method` when no provider is installed or source is not known.
871
+ */
872
+ source: z12.string().optional(),
873
+ /**
874
+ * Operation name for provider delegation (e.g. "listUnits").
875
+ * Must be provided together with `source` to trigger provider delegation.
876
+ */
877
+ operation: z12.string().optional(),
878
+ /** Request headers */
879
+ headers: z12.record(z12.string(), z12.string()).optional(),
880
+ /** Request body (for POST/PUT/PATCH, or provider operation params when delegating) */
881
+ body: z12.unknown().optional(),
882
+ /** Expected response type (defaults to 'json') */
883
+ responseType: z12.enum(["json", "text"]).optional(),
884
+ /** Timeout in milliseconds (defaults to 30000) */
885
+ timeout: z12.number().positive().optional()
886
+ });
887
+ var ServiceCallOutput = z12.object({
888
+ /** HTTP status code */
889
+ status: z12.number(),
890
+ /** Response headers */
891
+ headers: z12.record(z12.string(), z12.string()),
892
+ /** Response body */
893
+ body: z12.unknown(),
894
+ /** Whether the call was successful (2xx status) */
895
+ ok: z12.boolean()
896
+ });
897
+ var serviceCallHandler = async (input, context) => {
898
+ const { url, method, headers, body, source, operation } = input;
899
+ const responseType = input.responseType ?? "json";
900
+ const timeout = input.timeout ?? 3e4;
901
+ if (source !== void 0 && operation !== void 0) {
902
+ const provider = context.dataSourceProvider;
903
+ if (provider !== void 0) {
904
+ const knownSources = provider.listSources();
905
+ const canResolve = knownSources.some((s) => s.id === source);
906
+ if (canResolve) {
907
+ context.logger.info(
908
+ `service.call: delegating ${source}/${operation} to DataSourceProvider`,
909
+ { source, operation }
910
+ );
911
+ const result = await provider.execute(source, operation, body);
912
+ return {
913
+ status: 200,
914
+ headers: {},
915
+ body: result.data,
916
+ ok: true
917
+ };
918
+ }
919
+ context.logger.info(
920
+ `service.call: source "${source}" not known to provider, falling back to HTTP`,
921
+ { source, operation, url }
922
+ );
923
+ } else {
924
+ context.logger.info(`service.call: no DataSourceProvider in context, falling back to HTTP`, {
925
+ source,
926
+ operation,
927
+ url
928
+ });
929
+ }
930
+ }
931
+ context.logger.info(`service.call: ${method} ${url}`, { method, url });
932
+ const controller = new AbortController();
933
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
934
+ try {
935
+ const requestHeaders = new Headers(headers);
936
+ const fetchOptions = {
937
+ method,
938
+ headers: requestHeaders,
939
+ signal: controller.signal
940
+ };
941
+ if (body !== void 0 && ["POST", "PUT", "PATCH"].includes(method)) {
942
+ fetchOptions.body = JSON.stringify(body);
943
+ if (!requestHeaders.has("Content-Type")) {
944
+ requestHeaders.set("Content-Type", "application/json");
945
+ }
946
+ }
947
+ const response = await fetch(url, fetchOptions);
948
+ const responseHeaders = {};
949
+ response.headers.forEach((value, key) => {
950
+ responseHeaders[key] = value;
951
+ });
952
+ let responseBody;
953
+ if (responseType === "json") {
954
+ try {
955
+ responseBody = await response.json();
956
+ } catch {
957
+ responseBody = await response.text();
958
+ }
959
+ } else {
960
+ responseBody = await response.text();
961
+ }
962
+ context.logger.info(`service.call: ${method} ${url} -> ${response.status}`, {
963
+ status: response.status,
964
+ ok: response.ok
965
+ });
966
+ return {
967
+ status: response.status,
968
+ headers: responseHeaders,
969
+ body: responseBody,
970
+ ok: response.ok
971
+ };
972
+ } catch (error) {
973
+ if (error instanceof DOMException && error.name === "AbortError") {
974
+ context.logger.error(`service.call: ${method} ${url} timed out after ${timeout}ms`);
975
+ return {
976
+ status: 408,
977
+ headers: {},
978
+ body: { error: `Request timed out after ${timeout}ms` },
979
+ ok: false
980
+ };
981
+ }
982
+ throw error;
983
+ } finally {
984
+ clearTimeout(timeoutId);
985
+ }
986
+ };
987
+ var serviceCallDefinition = {
988
+ kind: "effect",
989
+ name: "service.call",
990
+ description: "Invoke an HTTP endpoint, or delegate to a DataSourceProvider when one is installed. Raw HTTP is the fallback escape hatch when no typed provider can resolve the target.",
991
+ inputSchema: "ServiceCallInput",
992
+ outputSchema: "ServiceCallOutput",
993
+ requiredPermissions: [
994
+ {
995
+ resource: "endpoint:*",
996
+ action: "invoke"
997
+ }
998
+ ]
999
+ };
1000
+
1001
+ // src/effects/events-publish.ts
1002
+ import { z as z13 } from "zod";
1003
+ var EventsPublishInput = z13.object({
1004
+ /** Topic to publish to (e.g. "bus:equipment-status" or "equipment-status") */
1005
+ topic: z13.string().min(1),
1006
+ /** Message payload to publish */
1007
+ payload: z13.unknown(),
1008
+ /** Optional metadata to include with the message */
1009
+ metadata: z13.object({
1010
+ /** Logical source identifier */
1011
+ source: z13.string().optional(),
1012
+ /** Correlation ID for request-scoped grouping */
1013
+ correlationId: z13.string().optional()
1014
+ }).optional()
1015
+ });
1016
+ var EventsPublishOutput = z13.object({
1017
+ /** Provider-assigned message ID */
1018
+ messageId: z13.string(),
1019
+ /** Topic the message was published to (without bus: prefix) */
1020
+ topic: z13.string(),
1021
+ /** Whether publishing succeeded */
1022
+ success: z13.boolean()
1023
+ });
1024
+ function stripBusPrefix(topic) {
1025
+ return topic.startsWith("bus:") ? topic.slice(4) : topic;
1026
+ }
1027
+ var eventsPublishHandler = async (input, context) => {
1028
+ const { payload, metadata } = input;
1029
+ const topic = stripBusPrefix(input.topic);
1030
+ if (!context.messageBus) {
1031
+ throw new Error(
1032
+ "events.publish: No MessageBusProvider available in context. Ensure the runtime injects a MessageBusProvider into CapabilityContext.messageBus. Set MESSAGE_BUS_PROVIDER environment variable or pass a provider at startup."
1033
+ );
1034
+ }
1035
+ context.logger.info(`events.publish: publishing to "${topic}"`, { topic });
1036
+ const result = await context.messageBus.publish(topic, {
1037
+ payload,
1038
+ traceContext: { traceId: context.traceId },
1039
+ metadata: {
1040
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1041
+ ...metadata?.source !== void 0 ? { source: metadata.source } : {},
1042
+ ...metadata?.correlationId !== void 0 ? { correlationId: metadata.correlationId } : {}
1043
+ }
1044
+ });
1045
+ context.logger.info(`events.publish: published to "${topic}" -> ${result.messageId}`, {
1046
+ topic,
1047
+ messageId: result.messageId,
1048
+ success: result.success
1049
+ });
1050
+ return {
1051
+ messageId: result.messageId,
1052
+ topic: result.topic,
1053
+ success: result.success
1054
+ };
1055
+ };
1056
+ var eventsPublishDefinition = {
1057
+ kind: "effect",
1058
+ name: "events.publish",
1059
+ description: "Publish a message to a message bus topic. Declares bus publish permissions for least-privilege derivation.",
1060
+ inputSchema: "EventsPublishInput",
1061
+ outputSchema: "EventsPublishOutput",
1062
+ requiredPermissions: [
1063
+ {
1064
+ resource: "bus:*",
1065
+ action: "publish"
1066
+ }
1067
+ ]
1068
+ };
1069
+
1070
+ // src/effects/notify-user.ts
1071
+ import { z as z14 } from "zod";
1072
+ var NotificationChannel = z14.enum(["email", "sms", "push", "in-app"]);
1073
+ var NotifyUserInput = z14.object({
1074
+ /** Recipient user identifier — the provider resolves this to a delivery address */
1075
+ recipient: z14.string().min(1),
1076
+ /** Delivery channel */
1077
+ channel: NotificationChannel,
1078
+ /** Template name for the notification content */
1079
+ template: z14.string().min(1),
1080
+ /** Typed payload for template interpolation */
1081
+ payload: z14.unknown(),
1082
+ /** Optional subject line (for email, push) */
1083
+ subject: z14.string().optional(),
1084
+ /** Optional metadata to include with the notification */
1085
+ metadata: z14.object({
1086
+ /** Logical source identifier */
1087
+ source: z14.string().optional(),
1088
+ /** Correlation ID for request-scoped grouping */
1089
+ correlationId: z14.string().optional()
1090
+ }).optional()
1091
+ });
1092
+ var NotifyUserOutput = z14.object({
1093
+ /** Provider-assigned notification ID */
1094
+ notificationId: z14.string(),
1095
+ /** Channel the notification was sent through */
1096
+ channel: z14.string(),
1097
+ /** Whether sending succeeded */
1098
+ success: z14.boolean()
1099
+ });
1100
+ var notifyUserHandler = async (input, context) => {
1101
+ const { recipient, channel, template, payload, subject, metadata } = input;
1102
+ if (!context.notificationProvider) {
1103
+ throw new Error(
1104
+ "notify.user: No NotificationProvider available in context. Ensure the runtime injects a NotificationProvider into CapabilityContext.notificationProvider. Set NOTIFICATION_PROVIDER environment variable or pass a provider at startup."
1105
+ );
1106
+ }
1107
+ context.logger.info(`notify.user: sending "${template}" via ${channel} to "${recipient}"`, {
1108
+ recipient,
1109
+ channel,
1110
+ template
1111
+ });
1112
+ const result = await context.notificationProvider.send({
1113
+ recipient,
1114
+ channel,
1115
+ template,
1116
+ payload,
1117
+ ...subject !== void 0 ? { subject } : {},
1118
+ traceContext: { traceId: context.traceId },
1119
+ metadata: {
1120
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1121
+ ...metadata?.source !== void 0 ? { source: metadata.source } : {},
1122
+ ...metadata?.correlationId !== void 0 ? { correlationId: metadata.correlationId } : {}
1123
+ }
1124
+ });
1125
+ context.logger.info(
1126
+ `notify.user: sent "${template}" via ${channel} to "${recipient}" -> ${result.notificationId}`,
1127
+ {
1128
+ notificationId: result.notificationId,
1129
+ channel: result.channel,
1130
+ success: result.success
1131
+ }
1132
+ );
1133
+ return {
1134
+ notificationId: result.notificationId,
1135
+ channel: result.channel,
1136
+ success: result.success
1137
+ };
1138
+ };
1139
+ var notifyUserDefinition = {
1140
+ kind: "effect",
1141
+ name: "notify.user",
1142
+ description: "Send a notification to a user via email, SMS, push, or in-app channel. Template-based with typed payload. Declares notification permissions for least-privilege derivation.",
1143
+ inputSchema: "NotifyUserInput",
1144
+ outputSchema: "NotifyUserOutput",
1145
+ requiredPermissions: [
1146
+ {
1147
+ resource: "notification:*",
1148
+ action: "send"
1149
+ }
1150
+ ]
1151
+ };
1152
+
1153
+ // src/effects/http-webhook.ts
1154
+ import { z as z15 } from "zod";
1155
+ var HttpWebhookInput = z15.object({
1156
+ /** Target URL -- must be declared statically in flow YAML */
1157
+ url: z15.string().url(),
1158
+ /** HTTP method -- only POST or PUT for webhooks (defaults to POST) */
1159
+ method: z15.enum(["POST", "PUT"]).optional(),
1160
+ /** Payload to send -- shape is flow-defined */
1161
+ payload: z15.unknown(),
1162
+ /** Additional headers to include */
1163
+ headers: z15.record(z15.string()).optional(),
1164
+ /** Request timeout in milliseconds (100ms - 30s, defaults to 5000) */
1165
+ timeout_ms: z15.number().int().min(100).max(3e4).optional(),
1166
+ /** Number of retry attempts on failure (0-3, defaults to 1) */
1167
+ retry_count: z15.number().int().min(0).max(3).optional(),
1168
+ /** Optional HMAC-SHA256 secret for request signing */
1169
+ signature_secret: z15.string().optional()
1170
+ });
1171
+ var HttpWebhookOutput = z15.object({
1172
+ /** Whether the webhook was delivered successfully */
1173
+ success: z15.boolean(),
1174
+ /** HTTP status code from the target */
1175
+ status_code: z15.number().int(),
1176
+ /** Response body (truncated to 4KB for safety) */
1177
+ response_body: z15.string().optional(),
1178
+ /** Number of attempts made (1 = first try succeeded) */
1179
+ attempts: z15.number().int().min(1),
1180
+ /** Total elapsed time in milliseconds */
1181
+ elapsed_ms: z15.number()
1182
+ });
1183
+ var httpWebhookDefinition = {
1184
+ kind: "effect",
1185
+ name: "http.webhook",
1186
+ description: "Invoke an outbound webhook with retry, timeout, and optional HMAC signature verification. More constrained than service.call -- URL is static and declared at compile time.",
1187
+ inputSchema: "HttpWebhookInput",
1188
+ outputSchema: "HttpWebhookOutput",
1189
+ requiredPermissions: [{ resource: "webhook:$url", action: "invoke" }]
1190
+ };
1191
+ async function computeHmacSignature(payload, secret) {
1192
+ const encoder = new TextEncoder();
1193
+ const key = await crypto.subtle.importKey(
1194
+ "raw",
1195
+ encoder.encode(secret),
1196
+ { name: "HMAC", hash: "SHA-256" },
1197
+ false,
1198
+ ["sign"]
1199
+ );
1200
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
1201
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
1202
+ }
1203
+ var httpWebhookHandler = async (input, context) => {
1204
+ const parsed = HttpWebhookInput.parse(input);
1205
+ const url = parsed.url;
1206
+ const method = parsed.method ?? "POST";
1207
+ const payload = parsed.payload;
1208
+ const headers = parsed.headers;
1209
+ const timeout_ms = parsed.timeout_ms ?? 5e3;
1210
+ const retry_count = parsed.retry_count ?? 1;
1211
+ const signature_secret = parsed.signature_secret;
1212
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
1213
+ const requestHeaders = {
1214
+ "Content-Type": "application/json",
1215
+ "User-Agent": "stackwright-services/webhook",
1216
+ ...headers
1217
+ };
1218
+ if (signature_secret) {
1219
+ const signature = await computeHmacSignature(body, signature_secret);
1220
+ requestHeaders["X-Webhook-Signature"] = `sha256=${signature}`;
1221
+ }
1222
+ if (context.traceId) {
1223
+ requestHeaders["X-Trace-Id"] = context.traceId;
1224
+ }
1225
+ context.logger.info(`http.webhook: ${method} ${url}`, { method, url });
1226
+ let lastError;
1227
+ let lastStatusCode = 0;
1228
+ let attempts = 0;
1229
+ const startTime = Date.now();
1230
+ for (let attempt = 0; attempt <= retry_count; attempt++) {
1231
+ attempts = attempt + 1;
1232
+ try {
1233
+ const controller = new AbortController();
1234
+ const timeoutId = setTimeout(() => controller.abort(), timeout_ms);
1235
+ const response = await fetch(url, {
1236
+ method,
1237
+ headers: requestHeaders,
1238
+ body,
1239
+ signal: controller.signal
1240
+ });
1241
+ clearTimeout(timeoutId);
1242
+ const elapsed_ms2 = Date.now() - startTime;
1243
+ const responseText = await response.text();
1244
+ const response_body = responseText.length > 4096 ? responseText.slice(0, 4096) + "...[truncated]" : responseText;
1245
+ if (response.ok) {
1246
+ context.logger.info(`http.webhook: delivered successfully`, {
1247
+ status: response.status,
1248
+ attempts,
1249
+ elapsed_ms: elapsed_ms2
1250
+ });
1251
+ return {
1252
+ success: true,
1253
+ status_code: response.status,
1254
+ response_body,
1255
+ attempts,
1256
+ elapsed_ms: elapsed_ms2
1257
+ };
1258
+ }
1259
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
1260
+ context.logger.warn(`http.webhook: non-retryable failure`, {
1261
+ status: response.status,
1262
+ attempts
1263
+ });
1264
+ return {
1265
+ success: false,
1266
+ status_code: response.status,
1267
+ response_body,
1268
+ attempts,
1269
+ elapsed_ms: elapsed_ms2
1270
+ };
1271
+ }
1272
+ lastStatusCode = response.status;
1273
+ lastError = new Error(`HTTP ${response.status}: ${response_body}`);
1274
+ context.logger.warn(`http.webhook: retryable failure (attempt ${attempts})`, {
1275
+ status: response.status
1276
+ });
1277
+ } catch (error) {
1278
+ lastError = error instanceof Error ? error : new Error(String(error));
1279
+ if (lastError.name !== "AbortError" && lastError.name !== "TypeError") {
1280
+ break;
1281
+ }
1282
+ context.logger.warn(`http.webhook: network error (attempt ${attempts})`, {
1283
+ error: lastError.message
1284
+ });
1285
+ }
1286
+ if (attempt < retry_count) {
1287
+ await new Promise((resolve) => setTimeout(resolve, 100 * Math.pow(2, attempt)));
1288
+ }
1289
+ }
1290
+ const elapsed_ms = Date.now() - startTime;
1291
+ context.logger.error(`http.webhook: all retries exhausted`, {
1292
+ attempts,
1293
+ elapsed_ms,
1294
+ lastError: lastError?.message
1295
+ });
1296
+ return {
1297
+ success: false,
1298
+ status_code: lastStatusCode,
1299
+ response_body: lastError?.message ?? "Unknown error",
1300
+ attempts,
1301
+ elapsed_ms
1302
+ };
1303
+ };
1304
+
1305
+ // src/effects/index.ts
1306
+ function registerEffects(registry) {
1307
+ registry.register(serviceCallDefinition, serviceCallHandler, ServiceCallInput, ServiceCallOutput);
1308
+ registry.register(
1309
+ eventsPublishDefinition,
1310
+ eventsPublishHandler,
1311
+ EventsPublishInput,
1312
+ EventsPublishOutput
1313
+ );
1314
+ registry.register(notifyUserDefinition, notifyUserHandler, NotifyUserInput, NotifyUserOutput);
1315
+ registry.register(httpWebhookDefinition, httpWebhookHandler, HttpWebhookInput, HttpWebhookOutput);
1316
+ }
1317
+
1318
+ // src/index.ts
1319
+ function registerAllCapabilities(registry) {
1320
+ registerTransforms(registry);
1321
+ registerEffects(registry);
1322
+ }
1323
+ export {
1324
+ CapabilityRegistry,
1325
+ CollectionAggregateInput,
1326
+ CollectionAggregateOutput,
1327
+ CollectionFilterInput,
1328
+ CollectionFilterOutput,
1329
+ CollectionJoinInput,
1330
+ CollectionJoinOutput,
1331
+ DateShiftInput,
1332
+ DateShiftOutput,
1333
+ EventsFilterInput,
1334
+ EventsFilterOutput,
1335
+ EventsPublishInput,
1336
+ EventsPublishOutput,
1337
+ NotificationChannel,
1338
+ NotifyUserInput,
1339
+ NotifyUserOutput,
1340
+ ServiceCallInput,
1341
+ ServiceCallOutput,
1342
+ TextFormatInput,
1343
+ TextFormatOutput,
1344
+ UnitsConvertInput,
1345
+ UnitsConvertOutput,
1346
+ collectionAggregateHandler,
1347
+ collectionFilterHandler,
1348
+ collectionJoinHandler,
1349
+ dateShiftHandler,
1350
+ defaultRegistry,
1351
+ evaluatePredicate,
1352
+ eventsFilterHandler,
1353
+ eventsPublishDefinition,
1354
+ eventsPublishHandler,
1355
+ notifyUserDefinition,
1356
+ notifyUserHandler,
1357
+ registerAllCapabilities,
1358
+ registerEffects,
1359
+ registerTransforms,
1360
+ serviceCallHandler,
1361
+ stripBusPrefix,
1362
+ textFormatHandler,
1363
+ unitsConvertHandler
1364
+ };
1365
+ //# sourceMappingURL=index.mjs.map