@stackwright-services/runtime 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,1138 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+
28
+ // src/messaging/sqs-sns.ts
29
+ var sqs_sns_exports = {};
30
+ __export(sqs_sns_exports, {
31
+ SqsSnsMessageBusProvider: () => SqsSnsMessageBusProvider,
32
+ toUpperSnake: () => toUpperSnake
33
+ });
34
+ import {
35
+ SNSClient,
36
+ PublishCommand
37
+ } from "@aws-sdk/client-sns";
38
+ import {
39
+ SQSClient,
40
+ ReceiveMessageCommand,
41
+ DeleteMessageCommand
42
+ } from "@aws-sdk/client-sqs";
43
+ function toUpperSnake(name) {
44
+ return name.replace(/[-.]/g, "_").toUpperCase();
45
+ }
46
+ var LOG_PREFIX, SqsSnsMessageBusProvider;
47
+ var init_sqs_sns = __esm({
48
+ "src/messaging/sqs-sns.ts"() {
49
+ "use strict";
50
+ LOG_PREFIX = "stackwright-services:messaging";
51
+ SqsSnsMessageBusProvider = class {
52
+ snsClient;
53
+ sqsClient;
54
+ topicArns;
55
+ queueUrls;
56
+ waitTimeSeconds;
57
+ maxMessages;
58
+ pollIntervalMs;
59
+ pollingStates = /* @__PURE__ */ new Map();
60
+ constructor(config = {}) {
61
+ const region = config.region ?? process.env["AWS_REGION"] ?? "us-east-1";
62
+ this.snsClient = config.snsClient ?? new SNSClient({ region });
63
+ this.sqsClient = config.sqsClient ?? new SQSClient({ region });
64
+ this.topicArns = config.topicArns ?? {};
65
+ this.queueUrls = config.queueUrls ?? {};
66
+ this.waitTimeSeconds = config.waitTimeSeconds ?? 20;
67
+ this.maxMessages = config.maxMessages ?? 10;
68
+ this.pollIntervalMs = config.pollIntervalMs ?? 100;
69
+ }
70
+ async publish(topic, message) {
71
+ const topicArn = this.resolveTopicArn(topic);
72
+ const attributes = this.buildSnsAttributes(message);
73
+ const input = {
74
+ TopicArn: topicArn,
75
+ Message: JSON.stringify(message.payload),
76
+ MessageAttributes: attributes
77
+ };
78
+ const response = await this.snsClient.send(new PublishCommand(input));
79
+ const messageId = response.MessageId ?? `sns-${Date.now()}`;
80
+ return { messageId, topic, success: true };
81
+ }
82
+ async subscribe(topic, handler) {
83
+ const queueUrl = this.resolveQueueUrl(topic);
84
+ const id = `sqs-sub-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
85
+ const state = { active: true };
86
+ this.pollingStates.set(id, state);
87
+ this.poll(queueUrl, handler, state);
88
+ const cancel = async () => {
89
+ await this.unsubscribe({ id, topic, cancel: async () => {
90
+ } });
91
+ };
92
+ return { id, topic, cancel };
93
+ }
94
+ async unsubscribe(subscription) {
95
+ const state = this.pollingStates.get(subscription.id);
96
+ if (state) {
97
+ state.active = false;
98
+ this.pollingStates.delete(subscription.id);
99
+ }
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Private helpers
103
+ // ---------------------------------------------------------------------------
104
+ resolveTopicArn(topic) {
105
+ const fromConfig = this.topicArns[topic];
106
+ if (fromConfig) return fromConfig;
107
+ const envKey = `BUS_TOPIC_${toUpperSnake(topic)}_ARN`;
108
+ const fromEnv = process.env[envKey];
109
+ if (fromEnv) return fromEnv;
110
+ throw new Error(
111
+ `${LOG_PREFIX} No SNS Topic ARN found for "${topic}". Set config.topicArns["${topic}"] or env var ${envKey}.`
112
+ );
113
+ }
114
+ resolveQueueUrl(topic) {
115
+ const fromConfig = this.queueUrls[topic];
116
+ if (fromConfig) return fromConfig;
117
+ const envKey = `BUS_QUEUE_${toUpperSnake(topic)}_URL`;
118
+ const fromEnv = process.env[envKey];
119
+ if (fromEnv) return fromEnv;
120
+ throw new Error(
121
+ `${LOG_PREFIX} No SQS Queue URL found for "${topic}". Set config.queueUrls["${topic}"] or env var ${envKey}.`
122
+ );
123
+ }
124
+ buildSnsAttributes(message) {
125
+ const attrs = {};
126
+ if (message.traceContext) {
127
+ for (const [key, value] of Object.entries(message.traceContext)) {
128
+ attrs[`trace.${key}`] = { DataType: "String", StringValue: value };
129
+ }
130
+ }
131
+ if (message.metadata) {
132
+ const { timestamp, source, correlationId } = message.metadata;
133
+ if (timestamp) {
134
+ attrs["meta.timestamp"] = { DataType: "String", StringValue: timestamp };
135
+ }
136
+ if (source) {
137
+ attrs["meta.source"] = { DataType: "String", StringValue: source };
138
+ }
139
+ if (correlationId) {
140
+ attrs["meta.correlationId"] = { DataType: "String", StringValue: correlationId };
141
+ }
142
+ }
143
+ return attrs;
144
+ }
145
+ async poll(queueUrl, handler, state) {
146
+ while (state.active) {
147
+ await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
148
+ if (!state.active) break;
149
+ try {
150
+ const response = await this.sqsClient.send(
151
+ new ReceiveMessageCommand({
152
+ QueueUrl: queueUrl,
153
+ WaitTimeSeconds: this.waitTimeSeconds,
154
+ MaxNumberOfMessages: this.maxMessages,
155
+ MessageAttributeNames: ["All"]
156
+ })
157
+ );
158
+ for (const sqsMessage of response.Messages ?? []) {
159
+ if (!state.active) break;
160
+ const busMessage = this.parseSqsMessage(sqsMessage);
161
+ try {
162
+ await handler(busMessage);
163
+ } catch (handlerError) {
164
+ console.error(
165
+ JSON.stringify({
166
+ prefix: LOG_PREFIX,
167
+ event: "handler_error",
168
+ error: handlerError instanceof Error ? handlerError.message : String(handlerError),
169
+ queueUrl
170
+ })
171
+ );
172
+ continue;
173
+ }
174
+ if (sqsMessage.ReceiptHandle) {
175
+ await this.sqsClient.send(
176
+ new DeleteMessageCommand({
177
+ QueueUrl: queueUrl,
178
+ ReceiptHandle: sqsMessage.ReceiptHandle
179
+ })
180
+ );
181
+ }
182
+ }
183
+ } catch (pollError) {
184
+ console.error(
185
+ JSON.stringify({
186
+ prefix: LOG_PREFIX,
187
+ event: "poll_error",
188
+ error: pollError instanceof Error ? pollError.message : String(pollError),
189
+ queueUrl
190
+ })
191
+ );
192
+ }
193
+ }
194
+ }
195
+ parseSqsMessage(sqsMessage) {
196
+ const payload = sqsMessage.Body ? JSON.parse(sqsMessage.Body) : null;
197
+ const traceContext = this.extractTraceContext(sqsMessage.MessageAttributes);
198
+ const metadata = this.extractMetadata(sqsMessage.MessageAttributes);
199
+ return {
200
+ payload,
201
+ ...traceContext !== void 0 && { traceContext },
202
+ ...metadata !== void 0 && { metadata }
203
+ };
204
+ }
205
+ extractTraceContext(attrs) {
206
+ if (!attrs) return void 0;
207
+ const trace = {};
208
+ for (const [key, value] of Object.entries(attrs)) {
209
+ if (key.startsWith("trace.") && value.StringValue) {
210
+ trace[key.slice("trace.".length)] = value.StringValue;
211
+ }
212
+ }
213
+ return Object.keys(trace).length > 0 ? trace : void 0;
214
+ }
215
+ extractMetadata(attrs) {
216
+ if (!attrs) return void 0;
217
+ const timestamp = attrs["meta.timestamp"]?.StringValue;
218
+ const source = attrs["meta.source"]?.StringValue;
219
+ const correlationId = attrs["meta.correlationId"]?.StringValue;
220
+ if (!timestamp && !source && !correlationId) return void 0;
221
+ return {
222
+ ...timestamp && { timestamp },
223
+ ...source && { source },
224
+ ...correlationId && { correlationId }
225
+ };
226
+ }
227
+ };
228
+ }
229
+ });
230
+
231
+ // src/triggers/http.ts
232
+ import { Hono } from "hono";
233
+ function createHttpTrigger(options) {
234
+ const { trigger, handler, inputSchema, outputSchema } = options;
235
+ const app = new Hono();
236
+ app.get("/health", (c) => c.json({ status: "ok", trigger: "http" }));
237
+ const method = trigger.method.toLowerCase();
238
+ app[method](trigger.path, async (c) => {
239
+ let input;
240
+ if (trigger.method === "GET") {
241
+ input = Object.fromEntries(new URL(c.req.url).searchParams);
242
+ } else {
243
+ try {
244
+ input = await c.req.json();
245
+ } catch {
246
+ return c.json(
247
+ { error: "Invalid JSON body", code: "INVALID_REQUEST_BODY" },
248
+ 400
249
+ );
250
+ }
251
+ }
252
+ if (inputSchema) {
253
+ const result = inputSchema.safeParse(input);
254
+ if (!result.success) {
255
+ return c.json(
256
+ {
257
+ error: "Input validation failed",
258
+ code: "VALIDATION_ERROR",
259
+ details: result.error.issues
260
+ },
261
+ 400
262
+ );
263
+ }
264
+ input = result.data;
265
+ }
266
+ const context = {
267
+ traceId: c.req.header("x-trace-id") ?? c.req.header("x-request-id") ?? crypto.randomUUID(),
268
+ logger: createLogger()
269
+ };
270
+ try {
271
+ const result = await handler(input, context);
272
+ if (outputSchema) {
273
+ const outputResult = outputSchema.safeParse(result);
274
+ if (!outputResult.success) {
275
+ return c.json(
276
+ { error: "Output validation failed", code: "OUTPUT_VALIDATION_ERROR" },
277
+ 500
278
+ );
279
+ }
280
+ return c.json(outputResult.data);
281
+ }
282
+ return c.json(result);
283
+ } catch (error) {
284
+ const message = error instanceof Error ? error.message : "Unknown error";
285
+ return c.json({ error: message, code: "FLOW_EXECUTION_ERROR" }, 500);
286
+ }
287
+ });
288
+ return app;
289
+ }
290
+ function createLogger() {
291
+ return {
292
+ debug: (msg, data) => console.debug(JSON.stringify({ level: "debug", msg, ...data })),
293
+ info: (msg, data) => console.info(JSON.stringify({ level: "info", msg, ...data })),
294
+ warn: (msg, data) => console.warn(JSON.stringify({ level: "warn", msg, ...data })),
295
+ error: (msg, data) => console.error(JSON.stringify({ level: "error", msg, ...data }))
296
+ };
297
+ }
298
+
299
+ // src/triggers/event.ts
300
+ var EventTriggerAdapter = class {
301
+ type = "event";
302
+ topic;
303
+ messageBus;
304
+ handler;
305
+ inputSchema;
306
+ subscription;
307
+ constructor(options) {
308
+ this.topic = options.trigger.source.replace(/^bus:/, "");
309
+ this.messageBus = options.messageBus;
310
+ this.handler = options.handler;
311
+ this.inputSchema = options.inputSchema;
312
+ }
313
+ async start() {
314
+ this.subscription = await this.messageBus.subscribe(
315
+ this.topic,
316
+ (message) => this.handleMessage(message)
317
+ );
318
+ }
319
+ async stop() {
320
+ if (this.subscription) {
321
+ await this.subscription.cancel();
322
+ this.subscription = void 0;
323
+ }
324
+ }
325
+ async handleMessage(message) {
326
+ let payload = message.payload;
327
+ if (this.inputSchema) {
328
+ const result = this.inputSchema.safeParse(payload);
329
+ if (!result.success) {
330
+ const logger = createLogger2();
331
+ logger.warn("Event payload validation failed", {
332
+ topic: this.topic,
333
+ errors: result.error.issues
334
+ });
335
+ return;
336
+ }
337
+ payload = result.data;
338
+ }
339
+ const traceId = message.traceContext?.["traceId"] ?? crypto.randomUUID();
340
+ const context = {
341
+ traceId,
342
+ logger: createLogger2()
343
+ };
344
+ try {
345
+ await this.handler(payload, context);
346
+ } catch (error) {
347
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
348
+ context.logger.error("Event handler failed", {
349
+ topic: this.topic,
350
+ error: errorMessage
351
+ });
352
+ }
353
+ }
354
+ };
355
+ function createLogger2() {
356
+ return {
357
+ debug: (msg, data) => console.debug(JSON.stringify({ level: "debug", msg, ...data })),
358
+ info: (msg, data) => console.info(JSON.stringify({ level: "info", msg, ...data })),
359
+ warn: (msg, data) => console.warn(JSON.stringify({ level: "warn", msg, ...data })),
360
+ error: (msg, data) => console.error(JSON.stringify({ level: "error", msg, ...data }))
361
+ };
362
+ }
363
+
364
+ // src/triggers/schedule.ts
365
+ var DEFAULT_INTERVAL_MS = 6e4;
366
+ var ScheduleTriggerAdapter = class {
367
+ type = "schedule";
368
+ cron;
369
+ handler;
370
+ inputSchema;
371
+ intervalMs;
372
+ timer;
373
+ constructor(options) {
374
+ this.cron = options.trigger.cron;
375
+ this.handler = options.handler;
376
+ this.inputSchema = options.inputSchema;
377
+ this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
378
+ }
379
+ async start() {
380
+ if (this.intervalMs === DEFAULT_INTERVAL_MS) {
381
+ const logger = createLogger3();
382
+ logger.warn("Cron parsing not built-in; using default 60s interval for local dev", {
383
+ cron: this.cron,
384
+ intervalMs: this.intervalMs
385
+ });
386
+ }
387
+ this.timer = setInterval(() => {
388
+ void this.tick();
389
+ }, this.intervalMs);
390
+ }
391
+ async stop() {
392
+ if (this.timer !== void 0) {
393
+ clearInterval(this.timer);
394
+ this.timer = void 0;
395
+ }
396
+ }
397
+ async tick() {
398
+ const traceId = crypto.randomUUID();
399
+ const context = {
400
+ traceId,
401
+ logger: createLogger3()
402
+ };
403
+ try {
404
+ await this.handler({}, context);
405
+ } catch (error) {
406
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
407
+ context.logger.error("Schedule handler failed", {
408
+ cron: this.cron,
409
+ error: errorMessage
410
+ });
411
+ }
412
+ }
413
+ };
414
+ function createLogger3() {
415
+ return {
416
+ debug: (msg, data) => console.debug(JSON.stringify({ level: "debug", msg, ...data })),
417
+ info: (msg, data) => console.info(JSON.stringify({ level: "info", msg, ...data })),
418
+ warn: (msg, data) => console.warn(JSON.stringify({ level: "warn", msg, ...data })),
419
+ error: (msg, data) => console.error(JSON.stringify({ level: "error", msg, ...data }))
420
+ };
421
+ }
422
+
423
+ // src/triggers/queue.ts
424
+ var QueueTriggerAdapter = class {
425
+ type = "queue";
426
+ queueName;
427
+ messageBus;
428
+ handler;
429
+ inputSchema;
430
+ subscription;
431
+ constructor(options) {
432
+ this.queueName = options.trigger.name;
433
+ this.messageBus = options.messageBus;
434
+ this.handler = options.handler;
435
+ this.inputSchema = options.inputSchema;
436
+ }
437
+ async start() {
438
+ this.subscription = await this.messageBus.subscribe(
439
+ this.queueName,
440
+ (message) => this.handleMessage(message)
441
+ );
442
+ }
443
+ async stop() {
444
+ if (this.subscription) {
445
+ await this.subscription.cancel();
446
+ this.subscription = void 0;
447
+ }
448
+ }
449
+ async handleMessage(message) {
450
+ let payload = message.payload;
451
+ if (this.inputSchema) {
452
+ const result = this.inputSchema.safeParse(payload);
453
+ if (!result.success) {
454
+ const logger = createLogger4();
455
+ logger.warn("Queue payload validation failed", {
456
+ queueName: this.queueName,
457
+ errors: result.error.issues
458
+ });
459
+ return;
460
+ }
461
+ payload = result.data;
462
+ }
463
+ const traceId = message.traceContext?.["traceId"] ?? crypto.randomUUID();
464
+ const context = {
465
+ traceId,
466
+ logger: createLogger4()
467
+ };
468
+ try {
469
+ await this.handler(payload, context);
470
+ } catch (error) {
471
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
472
+ context.logger.error("Queue handler failed", {
473
+ queueName: this.queueName,
474
+ error: errorMessage
475
+ });
476
+ }
477
+ }
478
+ };
479
+ function createLogger4() {
480
+ return {
481
+ debug: (msg, data) => console.debug(JSON.stringify({ level: "debug", msg, ...data })),
482
+ info: (msg, data) => console.info(JSON.stringify({ level: "info", msg, ...data })),
483
+ warn: (msg, data) => console.warn(JSON.stringify({ level: "warn", msg, ...data })),
484
+ error: (msg, data) => console.error(JSON.stringify({ level: "error", msg, ...data }))
485
+ };
486
+ }
487
+
488
+ // src/observability/noop.ts
489
+ var NOOP_SPAN = {
490
+ setAttribute: () => {
491
+ },
492
+ setError: () => {
493
+ },
494
+ end: () => {
495
+ }
496
+ };
497
+ var NoopObservabilityProvider = class {
498
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
499
+ startSpan(name, attributes) {
500
+ return NOOP_SPAN;
501
+ }
502
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
503
+ recordMetric(name, value, tags) {
504
+ }
505
+ };
506
+ var noopProvider = new NoopObservabilityProvider();
507
+
508
+ // src/observability/otel.ts
509
+ var OtelObservabilityProvider = class {
510
+ serviceName;
511
+ constructor(serviceName = "stackwright-service") {
512
+ this.serviceName = serviceName;
513
+ }
514
+ startSpan(name, attributes) {
515
+ const spanId = crypto.randomUUID().slice(0, 16);
516
+ const startTime = Date.now();
517
+ console.info(
518
+ JSON.stringify({
519
+ type: "span_start",
520
+ spanId,
521
+ name,
522
+ service: this.serviceName,
523
+ attributes,
524
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
525
+ })
526
+ );
527
+ const spanAttributes = { ...attributes };
528
+ return {
529
+ setAttribute(key, value) {
530
+ spanAttributes[key] = value;
531
+ },
532
+ setError(error) {
533
+ spanAttributes["error"] = true;
534
+ spanAttributes["error.message"] = error.message;
535
+ console.error(
536
+ JSON.stringify({
537
+ type: "span_error",
538
+ spanId,
539
+ name,
540
+ error: error.message,
541
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
542
+ })
543
+ );
544
+ },
545
+ end() {
546
+ const duration = Date.now() - startTime;
547
+ console.info(
548
+ JSON.stringify({
549
+ type: "span_end",
550
+ spanId,
551
+ name,
552
+ duration,
553
+ attributes: spanAttributes,
554
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
555
+ })
556
+ );
557
+ }
558
+ };
559
+ }
560
+ recordMetric(name, value, tags) {
561
+ console.info(
562
+ JSON.stringify({
563
+ type: "metric",
564
+ name,
565
+ value,
566
+ tags,
567
+ service: this.serviceName,
568
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
569
+ })
570
+ );
571
+ }
572
+ };
573
+
574
+ // src/observability/factory.ts
575
+ function createObservabilityProvider(options) {
576
+ const otelEnabled = process.env["OTEL_ENABLED"];
577
+ if (otelEnabled === "true" || otelEnabled === "1") {
578
+ return new OtelObservabilityProvider(options?.serviceName);
579
+ }
580
+ return noopProvider;
581
+ }
582
+
583
+ // src/observability/audit-noop.ts
584
+ var NoopAuditProvider = class {
585
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
586
+ record(_event) {
587
+ }
588
+ async flush() {
589
+ }
590
+ };
591
+ var noopAuditProvider = new NoopAuditProvider();
592
+
593
+ // src/observability/audit-console.ts
594
+ var ConsoleAuditProvider = class {
595
+ record(event) {
596
+ process.stderr.write(JSON.stringify(event) + "\n");
597
+ }
598
+ async flush() {
599
+ }
600
+ };
601
+
602
+ // src/observability/audit-factory.ts
603
+ function createAuditProvider() {
604
+ const auditEnabled = process.env["STACKWRIGHT_AUDIT"];
605
+ if (auditEnabled === "true" || auditEnabled === "1") {
606
+ return new ConsoleAuditProvider();
607
+ }
608
+ return noopAuditProvider;
609
+ }
610
+
611
+ // src/observability/audit-hash.ts
612
+ import { createHash } from "crypto";
613
+ function auditHash(value) {
614
+ const serialized = JSON.stringify(value, (_key, val) => {
615
+ if (typeof val === "bigint") return val.toString();
616
+ if (val instanceof Error) return { message: val.message, name: val.name };
617
+ if (typeof val === "function") return "[function]";
618
+ if (typeof val === "symbol") return val.toString();
619
+ if (val === void 0) return null;
620
+ return val;
621
+ });
622
+ return createHash("sha256").update(serialized ?? "undefined").digest("hex").slice(0, 16);
623
+ }
624
+
625
+ // src/messaging/noop.ts
626
+ var InMemoryMessageBusProvider = class {
627
+ subscriptions = /* @__PURE__ */ new Map();
628
+ nextId = 0;
629
+ async publish(topic, message) {
630
+ const messageId = message.metadata?.messageId ?? `mem-${++this.nextId}`;
631
+ const topicHandlers = this.subscriptions.get(topic);
632
+ if (topicHandlers) {
633
+ const enrichedMessage = {
634
+ ...message,
635
+ metadata: {
636
+ ...message.metadata,
637
+ messageId,
638
+ timestamp: message.metadata?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
639
+ }
640
+ };
641
+ const handlers = Array.from(topicHandlers.values());
642
+ await Promise.all(handlers.map((handler) => handler(enrichedMessage)));
643
+ }
644
+ return { messageId, topic, success: true };
645
+ }
646
+ async subscribe(topic, handler) {
647
+ const id = `sub-${++this.nextId}`;
648
+ let topicHandlers = this.subscriptions.get(topic);
649
+ if (!topicHandlers) {
650
+ topicHandlers = /* @__PURE__ */ new Map();
651
+ this.subscriptions.set(topic, topicHandlers);
652
+ }
653
+ topicHandlers.set(id, handler);
654
+ const cancel = async () => {
655
+ await this.unsubscribe({ id, topic, cancel: async () => {
656
+ } });
657
+ };
658
+ return { id, topic, cancel };
659
+ }
660
+ async unsubscribe(subscription) {
661
+ const topicHandlers = this.subscriptions.get(subscription.topic);
662
+ if (topicHandlers) {
663
+ topicHandlers.delete(subscription.id);
664
+ if (topicHandlers.size === 0) {
665
+ this.subscriptions.delete(subscription.topic);
666
+ }
667
+ }
668
+ }
669
+ /** Helper for testing: get count of active subscriptions for a topic */
670
+ getSubscriptionCount(topic) {
671
+ return this.subscriptions.get(topic)?.size ?? 0;
672
+ }
673
+ /** Helper for testing: clear all subscriptions */
674
+ clear() {
675
+ this.subscriptions.clear();
676
+ }
677
+ };
678
+ var inMemoryBusProvider = new InMemoryMessageBusProvider();
679
+
680
+ // src/messaging/index.ts
681
+ init_sqs_sns();
682
+
683
+ // src/messaging/factory.ts
684
+ function createMessageBusProvider(options) {
685
+ const providerType = options?.provider ?? process.env["MESSAGE_BUS_PROVIDER"] ?? "memory";
686
+ switch (providerType) {
687
+ case "memory":
688
+ return inMemoryBusProvider;
689
+ case "sqs": {
690
+ const { SqsSnsMessageBusProvider: SqsSnsMessageBusProvider2 } = (init_sqs_sns(), __toCommonJS(sqs_sns_exports));
691
+ return new SqsSnsMessageBusProvider2(options?.config);
692
+ }
693
+ default:
694
+ console.warn(
695
+ `stackwright-services:messaging Unknown MESSAGE_BUS_PROVIDER "${providerType}", falling back to in-memory`
696
+ );
697
+ return inMemoryBusProvider;
698
+ }
699
+ }
700
+
701
+ // src/notifications/noop.ts
702
+ var InMemoryNotificationProvider = class {
703
+ notifications = [];
704
+ nextId = 0;
705
+ async send(notification) {
706
+ const notificationId = `notif-${++this.nextId}`;
707
+ this.notifications.push({
708
+ ...notification,
709
+ notificationId,
710
+ metadata: {
711
+ ...notification.metadata,
712
+ timestamp: notification.metadata?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
713
+ }
714
+ });
715
+ return {
716
+ notificationId,
717
+ channel: notification.channel,
718
+ success: true
719
+ };
720
+ }
721
+ /** Helper for testing: get all sent notifications */
722
+ getSentNotifications() {
723
+ return [...this.notifications];
724
+ }
725
+ /** Helper for testing: get notifications filtered by channel */
726
+ getByChannel(channel) {
727
+ return this.notifications.filter((n) => n.channel === channel);
728
+ }
729
+ /** Helper for testing: get notifications filtered by recipient */
730
+ getByRecipient(recipient) {
731
+ return this.notifications.filter((n) => n.recipient === recipient);
732
+ }
733
+ /** Helper for testing: get count of sent notifications */
734
+ get sentCount() {
735
+ return this.notifications.length;
736
+ }
737
+ /** Helper for testing: clear all recorded notifications */
738
+ clear() {
739
+ this.notifications = [];
740
+ this.nextId = 0;
741
+ }
742
+ };
743
+ var inMemoryNotificationProvider = new InMemoryNotificationProvider();
744
+
745
+ // src/notifications/factory.ts
746
+ function createNotificationProvider(options) {
747
+ const providerType = options?.provider ?? process.env["NOTIFICATION_PROVIDER"] ?? "memory";
748
+ switch (providerType) {
749
+ case "memory":
750
+ return inMemoryNotificationProvider;
751
+ // Future: case 'sendgrid' -> SendGrid adapter
752
+ // Future: case 'twilio' -> Twilio/SNS adapter
753
+ // Future: case 'fcm' -> Firebase Cloud Messaging adapter
754
+ default:
755
+ console.warn(
756
+ `stackwright-services:notifications Unknown NOTIFICATION_PROVIDER "${providerType}", falling back to in-memory`
757
+ );
758
+ return inMemoryNotificationProvider;
759
+ }
760
+ }
761
+
762
+ // src/auth/noop.ts
763
+ var NoopAuthProvider = class {
764
+ name = "noop";
765
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
766
+ async validateToken(token) {
767
+ return { valid: false, error: "No auth provider configured" };
768
+ }
769
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
770
+ async extractClaims(token) {
771
+ throw new Error("No auth provider configured");
772
+ }
773
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
774
+ checkRole(claims, role) {
775
+ return false;
776
+ }
777
+ };
778
+ var noopAuthProvider = new NoopAuthProvider();
779
+
780
+ // src/auth/factory.ts
781
+ function createAuthProvider(type) {
782
+ switch (type) {
783
+ case "oidc":
784
+ throw new Error(
785
+ "OIDC auth provider not yet implemented. Install @stackwright-pro/auth when available."
786
+ );
787
+ case "cac":
788
+ throw new Error(
789
+ "CAC/PKI auth provider not yet implemented. Install @stackwright-pro/auth when available."
790
+ );
791
+ case "noop":
792
+ default:
793
+ return noopAuthProvider;
794
+ }
795
+ }
796
+
797
+ // src/datasource/noop.ts
798
+ var NoopDataSourceProvider = class {
799
+ name = "noop";
800
+ listSources() {
801
+ return [];
802
+ }
803
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
804
+ execute(_source, _operation, _params) {
805
+ return Promise.reject(
806
+ new Error(
807
+ "No DataSourceProvider is configured. Install @stackwright-pro/openapi or @stackwright-pro/pulse and register an adapter, or set DATA_SOURCE_PROVIDER in your environment."
808
+ )
809
+ );
810
+ }
811
+ };
812
+ var noopDataSourceProvider = new NoopDataSourceProvider();
813
+
814
+ // src/datasource/openapi-adapter.ts
815
+ var LOG_PREFIX2 = "stackwright-services:datasource:openapi";
816
+ var OpenApiDataSourceProvider = class {
817
+ constructor(config) {
818
+ this.config = config;
819
+ this.sourcesMap = new Map(config.sources.map((s) => [s.id, s]));
820
+ }
821
+ name = "openapi";
822
+ sourcesMap;
823
+ listSources() {
824
+ return Array.from(this.sourcesMap.values()).map((s) => ({
825
+ id: s.id,
826
+ transport: "openapi",
827
+ operations: Object.keys(s.operations),
828
+ ...s.description !== void 0 && { description: s.description }
829
+ }));
830
+ }
831
+ async execute(source, operation, params) {
832
+ const registration = this.sourcesMap.get(source);
833
+ if (!registration) {
834
+ const knownSources = Array.from(this.sourcesMap.keys());
835
+ return Promise.reject(
836
+ new Error(
837
+ `${LOG_PREFIX2} Unknown source "${source}". ` + (knownSources.length > 0 ? `Known sources: ${knownSources.join(", ")}.` : "No sources are registered. Pass sources in OpenApiAdapterConfig.")
838
+ )
839
+ );
840
+ }
841
+ const handler = registration.operations[operation];
842
+ if (!handler) {
843
+ const knownOps = Object.keys(registration.operations);
844
+ return Promise.reject(
845
+ new Error(
846
+ `${LOG_PREFIX2} Unknown operation "${operation}" on source "${source}". ` + (knownOps.length > 0 ? `Known operations: ${knownOps.join(", ")}.` : "No operations are registered for this source.")
847
+ )
848
+ );
849
+ }
850
+ const data = await handler(params);
851
+ return { data };
852
+ }
853
+ };
854
+ function createOpenApiDataSourceProvider(config) {
855
+ return new OpenApiDataSourceProvider(config);
856
+ }
857
+ function listDataSources() {
858
+ return [];
859
+ }
860
+
861
+ // src/datasource/pulse-adapter.ts
862
+ var LOG_PREFIX3 = "stackwright-services:datasource:pulse";
863
+ var PulseDataSourceProvider = class {
864
+ constructor(config) {
865
+ this.config = config;
866
+ this.sourcesMap = new Map(config.sources.map((s) => [s.id, s]));
867
+ }
868
+ name = "pulse";
869
+ sourcesMap;
870
+ listSources() {
871
+ return Array.from(this.sourcesMap.values()).map((s) => ({
872
+ id: s.id,
873
+ transport: "pulse",
874
+ operations: Object.keys(s.operations),
875
+ ...s.description !== void 0 && { description: s.description }
876
+ }));
877
+ }
878
+ async execute(source, operation, params) {
879
+ const registration = this.sourcesMap.get(source);
880
+ if (!registration) {
881
+ const knownSources = Array.from(this.sourcesMap.keys());
882
+ return Promise.reject(
883
+ new Error(
884
+ `${LOG_PREFIX3} Unknown source "${source}". ` + (knownSources.length > 0 ? `Known sources: ${knownSources.join(", ")}.` : "No sources are registered. Pass sources in PulseAdapterConfig.")
885
+ )
886
+ );
887
+ }
888
+ const handler = registration.operations[operation];
889
+ if (!handler) {
890
+ const knownOps = Object.keys(registration.operations);
891
+ return Promise.reject(
892
+ new Error(
893
+ `${LOG_PREFIX3} Unknown operation "${operation}" on source "${source}". ` + (knownOps.length > 0 ? `Known operations: ${knownOps.join(", ")}.` : "No operations are registered for this source.")
894
+ )
895
+ );
896
+ }
897
+ const data = await handler(params);
898
+ return { data };
899
+ }
900
+ };
901
+ function createPulseDataSourceProvider(config) {
902
+ return new PulseDataSourceProvider(config);
903
+ }
904
+ function listDataSources2() {
905
+ return [];
906
+ }
907
+
908
+ // src/datasource/factory.ts
909
+ function createDataSourceProvider(options) {
910
+ const providerType = options?.provider ?? process.env["DATA_SOURCE_PROVIDER"] ?? _autoDiscover();
911
+ switch (providerType) {
912
+ case "noop":
913
+ return noopDataSourceProvider;
914
+ case "openapi": {
915
+ return createOpenApiDataSourceProvider(
916
+ options?.config ?? { sources: [] }
917
+ );
918
+ }
919
+ case "pulse": {
920
+ return createPulseDataSourceProvider(
921
+ options?.config ?? { sources: [] }
922
+ );
923
+ }
924
+ default:
925
+ if (providerType !== "noop") {
926
+ console.warn(
927
+ `stackwright-services:datasource Unknown DATA_SOURCE_PROVIDER "${providerType}", falling back to noop`
928
+ );
929
+ }
930
+ return noopDataSourceProvider;
931
+ }
932
+ }
933
+ function _autoDiscover() {
934
+ const candidates = [
935
+ // @stackwright-pro/openapi ships its own datasource-adapter entry point that
936
+ // auto-registers all generated clients — when that package is installed, 'openapi'
937
+ // is returned here. The local OpenApiDataSourceProvider requires explicit source
938
+ // registration and does NOT trigger auto-discovery.
939
+ { pkg: "@stackwright-pro/openapi/datasource-adapter", providerType: "openapi" },
940
+ // @stackwright-pro/pulse follows the same pattern for Pulse-backed data sources.
941
+ { pkg: "@stackwright-pro/pulse/datasource-adapter", providerType: "pulse" }
942
+ ];
943
+ for (const { pkg, providerType } of candidates) {
944
+ try {
945
+ __require.resolve(pkg);
946
+ return providerType;
947
+ } catch {
948
+ }
949
+ }
950
+ return "noop";
951
+ }
952
+
953
+ // src/datasource/scan.ts
954
+ function scanDataSources() {
955
+ const sources = [];
956
+ const detectedProviders = [];
957
+ try {
958
+ __require.resolve("@stackwright-pro/openapi/datasource-adapter");
959
+ detectedProviders.push("@stackwright-pro/openapi");
960
+ try {
961
+ const mod = __require("@stackwright-pro/openapi/datasource-adapter");
962
+ if (typeof mod.listDataSources === "function") {
963
+ sources.push(...mod.listDataSources());
964
+ }
965
+ } catch (err) {
966
+ console.warn(
967
+ "stackwright-services:datasource Failed to enumerate @stackwright-pro/openapi sources:",
968
+ err
969
+ );
970
+ }
971
+ } catch {
972
+ }
973
+ try {
974
+ __require.resolve("@stackwright-pro/pulse/datasource-adapter");
975
+ detectedProviders.push("@stackwright-pro/pulse");
976
+ try {
977
+ const mod = __require("@stackwright-pro/pulse/datasource-adapter");
978
+ if (typeof mod.listDataSources === "function") {
979
+ sources.push(...mod.listDataSources());
980
+ }
981
+ } catch (err) {
982
+ console.warn(
983
+ "stackwright-services:datasource Failed to enumerate @stackwright-pro/pulse sources:",
984
+ err
985
+ );
986
+ }
987
+ } catch {
988
+ }
989
+ return { sources, detectedProviders };
990
+ }
991
+
992
+ // src/prebuild/register.ts
993
+ import { CapabilityRegistry, registerAllCapabilities } from "@stackwright-services/capabilities";
994
+ var PACKAGE_VERSION = "0.0.1";
995
+ function enumerateTriggers() {
996
+ return [
997
+ {
998
+ type: "http",
999
+ description: "HTTP request/response trigger \u2014 becomes API Gateway route (Lambda) or Ingress (k8s)",
1000
+ inputSchema: {
1001
+ type: "object",
1002
+ properties: {
1003
+ method: { type: "string", enum: ["GET", "POST", "PUT", "DELETE", "PATCH"] },
1004
+ path: { type: "string", pattern: "^/" },
1005
+ input_schema: { type: "string" }
1006
+ },
1007
+ required: ["method", "path"]
1008
+ },
1009
+ example: {
1010
+ type: "http",
1011
+ method: "POST",
1012
+ path: "/api/patients/at-risk",
1013
+ input_schema: "AtRiskRequest"
1014
+ }
1015
+ },
1016
+ {
1017
+ type: "event",
1018
+ description: "Pub/sub event trigger \u2014 subscribes to a message bus topic",
1019
+ inputSchema: {
1020
+ type: "object",
1021
+ properties: {
1022
+ source: { type: "string", pattern: "^bus:" },
1023
+ input_schema: { type: "string" }
1024
+ },
1025
+ required: ["source"]
1026
+ },
1027
+ example: {
1028
+ type: "event",
1029
+ source: "bus:equipment-status",
1030
+ input_schema: "EquipmentStatusEvent"
1031
+ }
1032
+ },
1033
+ {
1034
+ type: "schedule",
1035
+ description: "Cron-style scheduled trigger \u2014 periodic invocation",
1036
+ inputSchema: {
1037
+ type: "object",
1038
+ properties: {
1039
+ cron: { type: "string" },
1040
+ input_schema: { type: "string" }
1041
+ },
1042
+ required: ["cron"]
1043
+ },
1044
+ example: { type: "schedule", cron: "0 */6 * * *", input_schema: "ScheduledCheckInput" }
1045
+ },
1046
+ {
1047
+ type: "queue",
1048
+ description: "Durable point-to-point queue trigger \u2014 processes messages sequentially",
1049
+ inputSchema: {
1050
+ type: "object",
1051
+ properties: {
1052
+ source: { type: "string", pattern: "^queue:" },
1053
+ batch_size: { type: "number", minimum: 1, maximum: 10 },
1054
+ input_schema: { type: "string" }
1055
+ },
1056
+ required: ["source"]
1057
+ },
1058
+ example: {
1059
+ type: "queue",
1060
+ source: "queue:approvals",
1061
+ batch_size: 1,
1062
+ input_schema: "ApprovalMessage"
1063
+ }
1064
+ }
1065
+ ];
1066
+ }
1067
+ function enumerateCapabilities() {
1068
+ const registry = new CapabilityRegistry();
1069
+ registerAllCapabilities(registry);
1070
+ return registry.listAll().map((cap) => {
1071
+ const descriptor = {
1072
+ name: cap.definition.name,
1073
+ kind: cap.definition.kind,
1074
+ description: cap.definition.description,
1075
+ inputSchema: cap.definition.inputSchema,
1076
+ outputSchema: cap.definition.outputSchema
1077
+ };
1078
+ if (cap.definition.kind === "effect") {
1079
+ descriptor.requiredPermissions = cap.definition.requiredPermissions;
1080
+ }
1081
+ return descriptor;
1082
+ });
1083
+ }
1084
+ function enumerateDataSources() {
1085
+ const scanResult = scanDataSources();
1086
+ return scanResult.sources.map((source) => ({
1087
+ id: source.id,
1088
+ provider: source.transport,
1089
+ operations: source.operations
1090
+ }));
1091
+ }
1092
+ function registerServices() {
1093
+ return {
1094
+ pluginId: "@stackwright-services/runtime",
1095
+ version: PACKAGE_VERSION,
1096
+ triggers: enumerateTriggers(),
1097
+ capabilities: enumerateCapabilities(),
1098
+ dataSources: enumerateDataSources(),
1099
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
1100
+ };
1101
+ }
1102
+ export {
1103
+ ConsoleAuditProvider,
1104
+ EventTriggerAdapter,
1105
+ InMemoryMessageBusProvider,
1106
+ InMemoryNotificationProvider,
1107
+ NoopAuditProvider,
1108
+ NoopAuthProvider,
1109
+ NoopDataSourceProvider,
1110
+ NoopObservabilityProvider,
1111
+ OpenApiDataSourceProvider,
1112
+ OtelObservabilityProvider,
1113
+ PulseDataSourceProvider,
1114
+ QueueTriggerAdapter,
1115
+ ScheduleTriggerAdapter,
1116
+ SqsSnsMessageBusProvider,
1117
+ auditHash,
1118
+ createAuditProvider,
1119
+ createAuthProvider,
1120
+ createDataSourceProvider,
1121
+ createHttpTrigger,
1122
+ createMessageBusProvider,
1123
+ createNotificationProvider,
1124
+ createObservabilityProvider,
1125
+ createOpenApiDataSourceProvider,
1126
+ createPulseDataSourceProvider,
1127
+ inMemoryBusProvider,
1128
+ inMemoryNotificationProvider,
1129
+ listDataSources as listOpenApiDataSources,
1130
+ listDataSources2 as listPulseDataSources,
1131
+ noopAuditProvider,
1132
+ noopAuthProvider,
1133
+ noopDataSourceProvider,
1134
+ noopProvider,
1135
+ registerServices,
1136
+ scanDataSources
1137
+ };
1138
+ //# sourceMappingURL=index.mjs.map