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