"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { createCacherMiddleware: () => createCacherMiddleware, createCommandBus: () => createCommandBus, createEventBus: () => createEventBus, createFeatureFlagMiddleware: () => createFeatureFlagMiddleware, createInMemoryLockAdapter: () => createInMemoryLockAdapter, createLockMiddleware: () => createLockMiddleware, createLoggerAdapter: () => createLoggerAdapter, createLoggerMiddleware: () => createLoggerMiddleware, createMemoryCacheAdapter: () => createMemoryCacheAdapter, createMockerMiddleware: () => createMockerMiddleware, createQueryBus: () => createQueryBus, createRetryerMiddleware: () => createRetryerMiddleware, createValidatorMiddleware: () => createValidatorMiddleware, createWebhookMiddleware: () => createWebhookMiddleware }); module.exports = __toCommonJS(src_exports); // src/core/envelope.ts function createEnvelope(message) { const stamps = []; return { stamps, message, addStamp: (type, body) => { stamps.push({ type, body, date: /* @__PURE__ */ new Date() }); }, stampsOfType: (type) => { return stamps.filter((stamp) => stamp.type === type); }, firstStamp: (type) => { return stamps.find((stamp) => stamp.type === type); }, lastStamp: (type) => { return [...stamps].reverse().find((stamp) => stamp.type === type); } }; } // src/adapters/console-logger-adapter.ts function createLoggerAdapter({ logger, serializer = JSON.stringify }) { const adapter = { processing: (identity2, message, results, stamps) => logger.log( `[Envelope<${identity2?.body?.id}>](Processing)`, serializer({ message, results, stamps }) ), processed: (identity2, message, results, stamps) => { const timings = stamps.filter((stamp) => stamp.type === "missive:timings")?.[0]; logger.log( `[Envelope<${identity2?.body?.id}>](Processed${timings?.body?.total ? ` in ${(timings.body.total / 1e6).toFixed(4)} ms` : ""})`, serializer({ message, results, stamps }) ); }, error: (identity2, message, results, stamps) => { const timings = stamps.filter((stamp) => stamp.type === "missive:timings")?.[0]; logger.error( `[Envelope<${identity2?.body?.id}>](Errored${timings?.body?.total ? ` in ${(timings.body.total / 1e6).toFixed(4)} ms` : ""}`, serializer({ message, results, stamps }) ); } }; return adapter; } // src/middlewares/logger-middleware.ts function createLoggerMiddleware({ adapter, logger, intents, collect = false, async = false } = {}) { if (!logger) { logger = console; } if (!adapter) { adapter = createLoggerAdapter({ logger }); } const log = async (step, envelope, doAsync) => { const identity2 = envelope.firstStamp("missive:identity"); const results = envelope.stampsOfType("missive:handled"); const promise = adapter[step]( identity2, envelope.message, results, envelope.stamps.filter((stamp) => stamp.type !== "missive:identity" && stamp.type !== "missive:handled") ); if (doAsync) { return promise; } if (!doAsync && promise instanceof Promise) { await promise; } }; const attachNanoTimingStamp = (startTime, envelope) => { const endTime = performance.now(); const duration = (endTime - startTime) * 1e6; envelope.addStamp("missive:timings", { total: duration }); }; return async (envelope, next) => { const startTime = performance.now(); const type = envelope.message.__type; const doCollect = intents?.[type]?.collect ?? collect; const doAsync = intents?.[type]?.async ?? async; if (!doCollect) { await log("processing", envelope, doAsync); try { await next(); attachNanoTimingStamp(startTime, envelope); await log("processed", envelope, doAsync); } catch (error) { attachNanoTimingStamp(startTime, envelope); await log("error", envelope, doAsync); throw error; } return; } const logs = []; logs.push(() => log("processing", envelope, doAsync)); try { await next(); attachNanoTimingStamp(startTime, envelope); logs.push(() => log("processed", envelope, doAsync)); } catch (error) { attachNanoTimingStamp(startTime, envelope); logs.push(() => log("error", envelope, doAsync)); throw error; } finally { const allLogs = Promise.allSettled(logs.map((log2) => log2())); if (!doAsync) { await allLogs; } } }; } // src/adapters/in-memory-cache-adapter.ts function createMemoryCacheAdapter() { const memory = /* @__PURE__ */ new Map(); return { get: async (key) => { const entry = memory.get(key); if (entry && Date.now() < entry.expiresAt) { return entry.value; } memory.delete(key); return null; }, set: async (key, value, ttl) => { const expiresAt = Date.now() + ttl * 1e3; memory.set(key, { value, expiresAt }); } }; } // src/middlewares/cacher-middleware.ts var hashKey = async (data) => { const encoder = new TextEncoder(); const dataBuffer = encoder.encode(data); const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer); return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join(""); }; function createCacherMiddleware({ adapter, intents, bus, cache = "all", defaultTtl = 3600, defaultStaleTtl = 60, shortCircuit = true } = {}) { if (!adapter) { adapter = createMemoryCacheAdapter(); } const inProgressRevalidations = []; return async (envelope, next) => { const type = envelope.message.__type; const key = await hashKey(JSON.stringify(envelope.message)); const now = Date.now() / 1e3; const cachingOptions = intents?.[type] || {}; const ttl = cachingOptions.defaultTtl ?? defaultTtl; const staleWhileRevalidateTtl = cachingOptions.defaultStaleTtl ?? defaultStaleTtl; let stale = false; const reprocessed = envelope.firstStamp("missive:reprocessed"); const cacheEntry = reprocessed ? null : await adapter.get(key); if (cacheEntry) { const age = now - cacheEntry.timestamp; stale = age > ttl; envelope.addStamp("missive:handled", cacheEntry.data); envelope.addStamp("missive:cache:hit", { age, stale }); } if (bus && !reprocessed && stale && staleWhileRevalidateTtl > 0 && !inProgressRevalidations.includes(key)) { inProgressRevalidations.push(key); const newEnvelope = createEnvelope(envelope.message); envelope.stamps.forEach((stamp) => { if (stamp.type !== "missive:cache:hit" && stamp.type !== "missive:handled" && stamp.type !== "missive:identity") { newEnvelope.addStamp(stamp.type, stamp.body); } }); bus.dispatch(newEnvelope).then(() => { inProgressRevalidations.splice(inProgressRevalidations.indexOf(key), 1); }).catch(() => { inProgressRevalidations.splice(inProgressRevalidations.indexOf(key), 1); }); return; } const breakChain = cachingOptions.shortCircuit ?? shortCircuit; if (cacheEntry && breakChain) { return; } await next(); const cacheableStamp = envelope.firstStamp("missive:cacheable"); const caching = cachingOptions.cache ?? cache; if ((caching === "all" || caching === "only-cacheable" && cacheableStamp) && ttl > 0) { const result = envelope.lastStamp("missive:handled"); const ttlInStore = (cacheableStamp?.body?.ttl || ttl) + (cacheableStamp?.body?.staleTtl || staleWhileRevalidateTtl); await adapter.set( key, { timestamp: now, data: result?.body }, ttlInStore ); } }; } // src/utils/sleeper.ts var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3)); function createFibonnaciSleeper(jitter = 0, deps) { const sleepFn = deps?.sleepFn || sleep; let a = 0, b = 1; return { wait: async () => { const w = a + b; a = b; b = w; const max = w * (1 + jitter); const min = w * (1 - jitter); const jitteredDelay = Math.random() * (max - min) + min; await sleepFn(jitteredDelay); }, reset: () => { a = 0; b = 1; } }; } function createExponentialSleeper(multiplier = 1.5, jitter = 0.5, deps) { const sleepFn = deps?.sleepFn || sleep; let currentDelay = 0.5; return { wait: async () => { const max = currentDelay * (1 + jitter); const min = currentDelay * (1 - jitter); const jitteredDelay = Math.random() * (max - min) + min; await sleepFn(jitteredDelay); currentDelay = currentDelay * multiplier; }, reset: () => { currentDelay = 0.5; } }; } function sleeperFactory({ waitingAlgorithm, multiplier, jitter }) { const noneSleeper = () => ({ wait: async () => { }, reset: () => { } }); if (!waitingAlgorithm || waitingAlgorithm === "none") { return noneSleeper(); } if (waitingAlgorithm === "exponential") { return createExponentialSleeper(multiplier, jitter); } return createFibonnaciSleeper(jitter); } function buildSleeper({ waitingAlgorithm = "exponential", multiplier = 1.5, jitter = 0.5 }) { return sleeperFactory({ waitingAlgorithm, multiplier, jitter }); } // src/middlewares/retryer-middleware.ts function createRetryerMiddleware(options = {}) { const defaultSleeper = buildSleeper(options); const sleeperRegistry = {}; return async (envelope, next) => { const type = envelope.message.__type; if (options?.intents?.[type] && !sleeperRegistry[type]) { sleeperRegistry[type] = buildSleeper(options.intents[type]); } const maxAttempts = options.intents?.[type]?.maxAttempts || options.maxAttempts || 3; const sleeper = sleeperRegistry[type] || defaultSleeper; let attempt = 1; sleeper.reset(); let lastError = null; while (attempt <= maxAttempts) { try { const initialErrorStampCount = envelope.stamps.filter((stamp) => stamp.type === "error").length; await next(); const errorStampCount = envelope.stamps.filter((stamp) => stamp.type === "error").length; const newErrorStampCount = errorStampCount - initialErrorStampCount; if (newErrorStampCount === 0) { return; } envelope.addStamp("missive:retried", { attempt, errorMessage: `New error stamp count: ${newErrorStampCount}` }); } catch (error) { lastError = error; envelope.addStamp("missive:retried", { attempt, errorMessage: error instanceof Error ? error.message : String(error) }); } attempt++; if (attempt > maxAttempts) { if (lastError !== null) { throw lastError; } return; } await sleeper.wait(); } }; } // src/middlewares/webhook-middleware.ts function createWebhookMiddleware(options = {}) { const fetchFn = options.fetcher || fetch; const defaultSleeper = buildSleeper(options); const sleeperRegistry = {}; const callEndpoint = async (endpoint, envelope) => { const body = JSON.stringify(envelope); const response = await fetchFn(endpoint.url, { method: endpoint.method, headers: { ...endpoint.headers, ...endpoint.signature && { [endpoint.signatureHeader]: endpoint.signature(body) } }, body }); return { status: response.status, text: await response.text() }; }; const callEndpointsInParallel = async (endpoints, envelope, sleeper, maxAttempts) => { let indexedEndpoints = endpoints.map((endpoint, index) => ({ text: void 0, status: void 0, endpoint, index, attempt: 0 })); let attempt = 1; let results; sleeper.reset(); do { results = await Promise.allSettled( indexedEndpoints.map(async ({ endpoint, index }) => { const { text, status } = await callEndpoint(endpoint, envelope); return { text, status, attempt, index }; }) ); const failedEndpoints = indexedEndpoints.filter((_, idx) => results[idx].status === "rejected"); if (failedEndpoints.length === 0) break; indexedEndpoints = failedEndpoints; attempt++; sleeper.wait(); } while (attempt <= maxAttempts); return endpoints.map((_, i) => { const result = results[i]; return result?.status === "fulfilled" ? result.value : { text: void 0, status: void 0, index: i, attempt: maxAttempts }; }); }; const callEndpointsSequentially = async (endpoints, envelope, sleeper, maxAttempts) => { const indexedEndpoints = endpoints.map((endpoint, index) => ({ text: void 0, status: void 0, endpoint, index, attempt: 0 })); let attempt = 1; sleeper.reset(); for (const { endpoint, index } of indexedEndpoints) { let text; let status; do { try { const response = await callEndpoint(endpoint, envelope); text = response.text; status = response.status; break; } catch { attempt++; sleeper.wait(); continue; } } while (attempt <= maxAttempts); indexedEndpoints[index].attempt = attempt; indexedEndpoints[index].status = status; indexedEndpoints[index].text = text; } return indexedEndpoints; }; return async (envelope, next) => { await next(); const type = envelope.message.__type; if (options?.intents?.[type] && !sleeperRegistry[type]) { sleeperRegistry[type] = buildSleeper(options.intents[type]); } const maxAttempts = options.intents?.[type]?.maxAttempts || options.maxAttempts || 3; const sleeper = sleeperRegistry[type] || defaultSleeper; const parallel = options.intents?.[type]?.parallel ?? options.parallel; const async = options.intents?.[type]?.async ?? options.async; if (options.intents?.[type]) { const endpoints = options.intents[type].endpoints; const results = await (async () => { if (parallel) { if (!async) { return await callEndpointsInParallel(endpoints, envelope, sleeper, maxAttempts); } callEndpointsInParallel(endpoints, envelope, sleeper, maxAttempts); return []; } if (!async) { return await callEndpointsSequentially(endpoints, envelope, sleeper, maxAttempts); } callEndpointsSequentially(endpoints, envelope, sleeper, maxAttempts); return []; })(); for (const result of results) { envelope.addStamp("missive:webhook-called", { attempt: result.attempt, text: result.text, status: result.status }); } } }; } // src/adapters/in-memory-lock-adapter.ts function createInMemoryLockAdapter() { const store = /* @__PURE__ */ new Map(); return { acquire: async (key, ttl) => { if (store.has(key)) { if (store.get(key).expiresAt > Date.now()) { return false; } } const now = Date.now(); const expiresAt = now + ttl; store.set(key, { expiresAt }); return true; }, release: async (key) => { store.delete(key); } }; } // src/middlewares/lock-middleware.ts function createLockMiddleware(options) { const adapter = options.adapter ?? createInMemoryLockAdapter(); return async (envelope, next) => { const type = envelope.message.__type; const ttl = options.intents?.[type]?.ttl ?? options.ttl ?? 500; const tick = options.intents?.[type]?.tick ?? options.tick ?? 100; const getLockKey = options.intents?.[type]?.getLockKey ?? options.getLockKey; const lockKey = await getLockKey(envelope); async function doUnderLock(timeout) { const isAcquired = await adapter.acquire(lockKey, ttl); if (isAcquired) { try { await next(); await adapter.release(lockKey); return; } catch (error) { await adapter.release(lockKey); throw error; } } else { if (Date.now() < timeout) { await new Promise((resolve) => setTimeout(resolve, tick)); return doUnderLock(timeout); } throw new Error("Lock not acquired or timeout"); } } await doUnderLock(Date.now() + (options.intents?.[type]?.timeout ?? options.timeout ?? 5e3)); }; } // src/middlewares/feature-flag-middleware.ts function createFeatureFlagMiddleware({ featureFlagChecker, intents }) { return async (envelope, next) => { const type = envelope.message.__type; const allowed = await featureFlagChecker(type); if (allowed) { await next(); return; } const handler = intents?.[type]?.fallbackHandler; if (typeof handler === "function") { const result = await handler(envelope); envelope.addStamp("missive:handled", result); envelope.addStamp("missive:feature-flag-fallback"); const breakChain = typeof intents?.[type]?.shortCircuit === "boolean" ? intents[type].shortCircuit : true; if (breakChain) { return; } await next(); return; } throw new Error(`Intent ${envelope.message.__type} is not allowed and no fallback handler provided.`); }; } // src/middlewares/mocker-middleware.ts function createMockerMiddleware({ intents }) { return async (envelope, next) => { const type = envelope.message.__type; const handler = intents?.[type]; if (typeof handler === "function") { const result = await handler(envelope); envelope.addStamp("missive:handled", result); } await next(); }; } // src/middlewares/async-middleware.ts function createAsyncMiddleware({ consume, intents, produce, async = true }) { return async (envelope, next) => { const type = envelope.message.__type; const isAsync = intents?.[type]?.async ?? async; if (isAsync && consume === false) { envelope.addStamp("missive:async"); const producer = intents?.[type]?.produce ?? produce; await producer(envelope); return; } await next(); }; } // src/core/errors.ts var MissiveMiddlewareError = class extends Error { constructor(middlewareName, message, envelope, error) { super(`missive.js: [middleware: ${middlewareName}]: ${message}`, { cause: { envelope, error } }); } }; // src/middlewares/validator-middleware.ts function identity() { return true; } function createValidatorMiddleware(input) { return async (envelope, next) => { const intents = input?.intents; const type = envelope.message.__type; const validateInput = intents?.[type]?.input ?? identity; if (!validateInput(envelope.message)) { throw new MissiveMiddlewareError("validator", "Invalid message"); } await next(); const results = envelope.stampsOfType("missive:handled"); for (const result of results) { const validateOutput = intents?.[type]?.output ?? identity; if (!validateOutput(result?.body)) { throw new MissiveMiddlewareError("validator", "Invalid result"); } } }; } // src/core/bus.ts var createBus = (args) => { const randomUUID = async () => `${args?.options?.randomUUID ? args.options.randomUUID() : crypto.randomUUID().toString()}`; const middlewares = args?.middlewares || []; const registry = {}; const useMiddleware = (middleware) => { middlewares.push(middleware); }; const registerHandler = (messageName, handler) => { if (!registry[messageName]) { registry[messageName] = { handlers: [] }; } registry[messageName].handlers.push(handler); }; if (args?.handlers) { for (const { messageName, handler } of args.handlers) { registerHandler(messageName, handler); } } const createMiddlewareChain = (handlers) => { return async (envelope) => { let index = 0; const next = async () => { if (index < middlewares.length) { const middleware = middlewares[index++]; await middleware( envelope, next ); } else { if (envelope.stampsOfType( "missive:handled" ).length > 0) { return; } const results = await Promise.all(handlers.map(async (handler) => await handler(envelope))); results.forEach( (result) => envelope.addStamp( "missive:handled", result ) ); } }; await next(); }; }; function isEnvelope(payload) { return payload && "stamps" in payload && "message" in payload; } const dispatch = async (payload) => { const isEnveloped = isEnvelope(payload); const type = isEnveloped ? payload.message.__type : payload.__type; const entry = registry[type]; if (!entry) { throw new Error(`No handler found for type: ${type}`); } const { handlers } = entry; const chain = createMiddlewareChain(handlers); const envelope = await (async () => { if (!isEnveloped) { const envelope3 = createEnvelope(payload); envelope3.addStamp("missive:identity", { id: await randomUUID() }); return envelope3; } const identity2 = payload.firstStamp("missive:identity"); const stamps = payload.stamps.filter((stamp) => stamp.type !== "missive:identity"); const envelope2 = createEnvelope(payload.message); envelope2.addStamp("missive:identity", { id: identity2?.body?.id || await randomUUID() }); envelope2.addStamp("missive:reprocessed", { stamps }); return envelope2; })(); await chain(envelope); return { envelope, result: envelope.lastStamp("missive:handled")?.body, results: envelope.stampsOfType("missive:handled").map((r) => r?.body) || [] }; }; const createIntent = (type, intent) => { const entry = registry[type]; if (!entry) { throw new Error(`No handler found for type: ${type}`); } return { __type: type, ...intent }; }; return { use: useMiddleware, register: registerHandler, dispatch, createIntent }; }; function createCommandBus(args) { const commandBus = createBus(args); return { use: (middleware) => commandBus.use(middleware), useValidatorMiddleware: (...props) => { commandBus.use(createValidatorMiddleware(...props)); }, useLoggerMiddleware: (...props) => { commandBus.use(createLoggerMiddleware(...props)); }, useLockMiddleware: (...props) => { commandBus.use(createLockMiddleware(...props)); }, useRetryerMiddleware: (...props) => { commandBus.use(createRetryerMiddleware(...props)); }, useWebhookMiddleware: (...props) => { commandBus.use(createWebhookMiddleware(...props)); }, useFeatureFlagMiddleware: (...props) => { commandBus.use(createFeatureFlagMiddleware(...props)); }, useMockerMiddleware: (...props) => { commandBus.use(createMockerMiddleware(...props)); }, useAsyncMiddleware: (...props) => { commandBus.use(createAsyncMiddleware(...props)); }, register: commandBus.register, dispatch: commandBus.dispatch, createCommand: commandBus.createIntent }; } function createQueryBus(args) { const queryBus = createBus(args); const bus = { use: (middleware) => queryBus.use(middleware), useValidatorMiddleware: (...props) => { queryBus.use(createValidatorMiddleware(...props)); }, useLoggerMiddleware: (...props) => { queryBus.use(createLoggerMiddleware(...props)); }, useLockMiddleware: (...props) => { queryBus.use(createLockMiddleware(...props)); }, useRetryerMiddleware: (...props) => { queryBus.use(createRetryerMiddleware(...props)); }, useWebhookMiddleware: (...props) => { queryBus.use(createWebhookMiddleware(...props)); }, useCacherMiddleware: (...props) => { queryBus.use( createCacherMiddleware({ ...props[0], bus }) ); }, useFeatureFlagMiddleware: (...props) => { queryBus.use(createFeatureFlagMiddleware(...props)); }, useMockerMiddleware: (...props) => { queryBus.use(createMockerMiddleware(...props)); }, register: queryBus.register, dispatch: queryBus.dispatch, createQuery: queryBus.createIntent }; return bus; } function createEventBus(args) { const eventBus = createBus(args); return { use: (middleware) => eventBus.use(middleware), useValidatorMiddleware: (...props) => { eventBus.use(createValidatorMiddleware(...props)); }, useLoggerMiddleware: (...props) => { eventBus.use(createLoggerMiddleware(...props)); }, useLockMiddleware: (...props) => { eventBus.use(createLockMiddleware(...props)); }, useRetryerMiddleware: (...props) => { eventBus.use(createRetryerMiddleware(...props)); }, useWebhookMiddleware: (...props) => { eventBus.use(createWebhookMiddleware(...props)); }, useFeatureFlagMiddleware: (...props) => { eventBus.use(createFeatureFlagMiddleware(...props)); }, useMockerMiddleware: (...props) => { eventBus.use(createMockerMiddleware(...props)); }, useAsyncMiddleware: (...props) => { eventBus.use(createAsyncMiddleware(...props)); }, register: eventBus.register, dispatch: eventBus.dispatch, createEvent: eventBus.createIntent }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createCacherMiddleware, createCommandBus, createEventBus, createFeatureFlagMiddleware, createInMemoryLockAdapter, createLockMiddleware, createLoggerAdapter, createLoggerMiddleware, createMemoryCacheAdapter, createMockerMiddleware, createQueryBus, createRetryerMiddleware, createValidatorMiddleware, createWebhookMiddleware });