@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.d.mts +985 -0
- package/dist/index.d.ts +985 -0
- package/dist/index.js +1177 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1138 -0
- package/dist/index.mjs.map +1 -0
- package/dist/prebuild/index.d.mts +65 -0
- package/dist/prebuild/index.d.ts +65 -0
- package/dist/prebuild/index.js +192 -0
- package/dist/prebuild/index.js.map +1 -0
- package/dist/prebuild/index.mjs +162 -0
- package/dist/prebuild/index.mjs.map +1 -0
- package/package.json +64 -0
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
|