@crossdelta/cloudevents 0.5.6 → 0.6.0
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/README.md +48 -28
- package/dist/index.cjs +1602 -0
- package/dist/index.d.mts +812 -0
- package/dist/index.d.ts +812 -9
- package/dist/index.js +1574 -6
- package/package.json +20 -18
- package/dist/adapters/cloudevents/cloudevents.d.ts +0 -14
- package/dist/adapters/cloudevents/cloudevents.js +0 -58
- package/dist/adapters/cloudevents/index.d.ts +0 -8
- package/dist/adapters/cloudevents/index.js +0 -7
- package/dist/adapters/cloudevents/parsers/binary-mode.d.ts +0 -5
- package/dist/adapters/cloudevents/parsers/binary-mode.js +0 -32
- package/dist/adapters/cloudevents/parsers/pubsub.d.ts +0 -5
- package/dist/adapters/cloudevents/parsers/pubsub.js +0 -54
- package/dist/adapters/cloudevents/parsers/raw-event.d.ts +0 -5
- package/dist/adapters/cloudevents/parsers/raw-event.js +0 -17
- package/dist/adapters/cloudevents/parsers/structured-mode.d.ts +0 -5
- package/dist/adapters/cloudevents/parsers/structured-mode.js +0 -18
- package/dist/adapters/cloudevents/types.d.ts +0 -29
- package/dist/adapters/cloudevents/types.js +0 -1
- package/dist/domain/contract-helper.d.ts +0 -63
- package/dist/domain/contract-helper.js +0 -61
- package/dist/domain/discovery.d.ts +0 -24
- package/dist/domain/discovery.js +0 -201
- package/dist/domain/handler-factory.d.ts +0 -49
- package/dist/domain/handler-factory.js +0 -169
- package/dist/domain/index.d.ts +0 -6
- package/dist/domain/index.js +0 -4
- package/dist/domain/types.d.ts +0 -108
- package/dist/domain/types.js +0 -6
- package/dist/domain/validation.d.ts +0 -37
- package/dist/domain/validation.js +0 -53
- package/dist/infrastructure/errors.d.ts +0 -53
- package/dist/infrastructure/errors.js +0 -54
- package/dist/infrastructure/index.d.ts +0 -4
- package/dist/infrastructure/index.js +0 -2
- package/dist/infrastructure/logging.d.ts +0 -18
- package/dist/infrastructure/logging.js +0 -27
- package/dist/middlewares/cloudevents-middleware.d.ts +0 -171
- package/dist/middlewares/cloudevents-middleware.js +0 -276
- package/dist/middlewares/index.d.ts +0 -1
- package/dist/middlewares/index.js +0 -1
- package/dist/processing/dlq-safe.d.ts +0 -82
- package/dist/processing/dlq-safe.js +0 -108
- package/dist/processing/handler-cache.d.ts +0 -36
- package/dist/processing/handler-cache.js +0 -94
- package/dist/processing/idempotency.d.ts +0 -51
- package/dist/processing/idempotency.js +0 -112
- package/dist/processing/index.d.ts +0 -4
- package/dist/processing/index.js +0 -4
- package/dist/processing/validation.d.ts +0 -41
- package/dist/processing/validation.js +0 -48
- package/dist/publishing/index.d.ts +0 -2
- package/dist/publishing/index.js +0 -2
- package/dist/publishing/nats.publisher.d.ts +0 -19
- package/dist/publishing/nats.publisher.js +0 -115
- package/dist/publishing/pubsub.publisher.d.ts +0 -39
- package/dist/publishing/pubsub.publisher.js +0 -84
- package/dist/transports/nats/base-message-processor.d.ts +0 -44
- package/dist/transports/nats/base-message-processor.js +0 -118
- package/dist/transports/nats/index.d.ts +0 -5
- package/dist/transports/nats/index.js +0 -5
- package/dist/transports/nats/jetstream-consumer.d.ts +0 -217
- package/dist/transports/nats/jetstream-consumer.js +0 -367
- package/dist/transports/nats/jetstream-message-processor.d.ts +0 -9
- package/dist/transports/nats/jetstream-message-processor.js +0 -32
- package/dist/transports/nats/nats-consumer.d.ts +0 -36
- package/dist/transports/nats/nats-consumer.js +0 -84
- package/dist/transports/nats/nats-message-processor.d.ts +0 -11
- package/dist/transports/nats/nats-message-processor.js +0 -32
package/dist/domain/discovery.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
import { logger } from '../infrastructure';
|
|
6
|
-
import { isValidHandler } from './validation';
|
|
7
|
-
/**
|
|
8
|
-
* Gets the search directories for event handler discovery.
|
|
9
|
-
* @returns Array of search directory paths.
|
|
10
|
-
*/
|
|
11
|
-
const getSearchDirectories = () => {
|
|
12
|
-
const directories = new Set();
|
|
13
|
-
directories.add(process.cwd());
|
|
14
|
-
try {
|
|
15
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
directories.add(currentDir);
|
|
17
|
-
// For monorepo tests: also check packages/cloudevents if we're in dist/
|
|
18
|
-
if (currentDir.includes('/dist/')) {
|
|
19
|
-
const pkgRoot = currentDir.split('/dist/')[0];
|
|
20
|
-
if (pkgRoot)
|
|
21
|
-
directories.add(pkgRoot);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// Ignore resolution errors; fallback to process.cwd()
|
|
26
|
-
}
|
|
27
|
-
// Filter out directories that don't exist (e.g., during hot-reload)
|
|
28
|
-
return [...directories].filter((dir) => existsSync(dir));
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Loads and validates handlers from a TypeScript/JavaScript file.
|
|
32
|
-
*/
|
|
33
|
-
const loadHandlers = async (filePath, filter) => {
|
|
34
|
-
try {
|
|
35
|
-
// Check if file exists before import to avoid ENOENT during hot-reload
|
|
36
|
-
if (!existsSync(filePath)) {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
const module = await import(filePath);
|
|
40
|
-
return Object.entries(module)
|
|
41
|
-
.filter(([name, handler]) => isValidHandler(handler) && (!filter || filter(name, handler)))
|
|
42
|
-
.map(([name, handler]) => {
|
|
43
|
-
const HandlerClass = handler;
|
|
44
|
-
// Only override with export name if:
|
|
45
|
-
// 1. Export is named (not 'default')
|
|
46
|
-
// 2. Handler has no meaningful name (empty or generic class names)
|
|
47
|
-
const hasNoMeaningfulName = !HandlerClass.name || HandlerClass.name === '' || HandlerClass.name === 'HandlerClass';
|
|
48
|
-
const isNamedExport = name !== 'default';
|
|
49
|
-
if (hasNoMeaningfulName && isNamedExport) {
|
|
50
|
-
return Object.defineProperty(HandlerClass, 'name', { value: name, configurable: true });
|
|
51
|
-
}
|
|
52
|
-
return HandlerClass;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
// Comprehensive error logging for debugging
|
|
57
|
-
logger?.warn(`Failed to load ${filePath}`);
|
|
58
|
-
logger?.warn(`Error type: ${typeof error}`);
|
|
59
|
-
logger?.warn(`Error constructor: ${error?.constructor?.name}`);
|
|
60
|
-
if (error instanceof Error) {
|
|
61
|
-
logger?.warn(`Error message: ${error.message}`);
|
|
62
|
-
if (error.stack) {
|
|
63
|
-
logger?.warn(`Stack trace:\n${error.stack}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
logger?.warn(`Error value: ${String(error)}`);
|
|
68
|
-
try {
|
|
69
|
-
logger?.warn(`Error JSON: ${JSON.stringify(error, null, 2)}`);
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
logger?.warn('(Error is not JSON-serializable)');
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
/**
|
|
79
|
-
* Deduplicates handlers by class name to avoid loading the same handler from .ts and .js
|
|
80
|
-
* @param handlers - Array of handler constructors
|
|
81
|
-
* @returns Array of unique handlers
|
|
82
|
-
*/
|
|
83
|
-
const deduplicateHandlers = (handlers) => handlers.reduce((acc, handler) => {
|
|
84
|
-
const existing = acc.find((h) => h.name === handler.name);
|
|
85
|
-
if (!existing) {
|
|
86
|
-
acc.push(handler);
|
|
87
|
-
}
|
|
88
|
-
return acc;
|
|
89
|
-
}, []);
|
|
90
|
-
/**
|
|
91
|
-
* Discovers files matching the pattern across multiple search paths
|
|
92
|
-
* @param pattern - Glob pattern to match
|
|
93
|
-
* @param searchPaths - Array of directories to search in
|
|
94
|
-
* @param logger - Logger instance for debug output
|
|
95
|
-
* @param log - Whether logging is enabled
|
|
96
|
-
* @returns Promise resolving to array of unique file paths
|
|
97
|
-
*/
|
|
98
|
-
/**
|
|
99
|
-
* Simple, elegant file discovery using glob patterns
|
|
100
|
-
*/
|
|
101
|
-
const EXTENSION_FALLBACKS = ['js', 'mjs', 'cjs'];
|
|
102
|
-
const expandPatternVariants = (pattern, preferCompiled) => {
|
|
103
|
-
if (!/\.tsx?\b/.test(pattern)) {
|
|
104
|
-
return [pattern];
|
|
105
|
-
}
|
|
106
|
-
const basePattern = pattern;
|
|
107
|
-
const compiledVariants = EXTENSION_FALLBACKS.map((ext) => basePattern.replace(/\.tsx?\b/g, `.${ext}`));
|
|
108
|
-
const ordered = preferCompiled ? [...compiledVariants, basePattern] : [basePattern, ...compiledVariants];
|
|
109
|
-
return [...new Set(ordered)];
|
|
110
|
-
};
|
|
111
|
-
/**
|
|
112
|
-
* Check if a directory path should be scanned (exists and is accessible)
|
|
113
|
-
*/
|
|
114
|
-
const shouldScanDirectory = (basePath, variant) => {
|
|
115
|
-
if (!existsSync(basePath)) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
// For patterns with directory prefixes like "dist/...", check if prefix dir exists
|
|
119
|
-
const dirPrefix = variant.match(/^([^*{[]+)\//)?.[1];
|
|
120
|
-
if (dirPrefix) {
|
|
121
|
-
const fullDirPath = join(basePath, dirPrefix);
|
|
122
|
-
return existsSync(fullDirPath);
|
|
123
|
-
}
|
|
124
|
-
return true;
|
|
125
|
-
};
|
|
126
|
-
const discoverFiles = async (pattern, basePath, preferCompiled) => {
|
|
127
|
-
// Don't add src/dist prefix for test patterns or absolute paths
|
|
128
|
-
const isTestPattern = pattern.startsWith('test/');
|
|
129
|
-
const prefixedPattern = isTestPattern ? pattern : `{src,dist,build,lib,out}/${pattern}`;
|
|
130
|
-
const patterns = preferCompiled ? [prefixedPattern, pattern] : [pattern, prefixedPattern];
|
|
131
|
-
const allFiles = [];
|
|
132
|
-
for (const globPattern of patterns) {
|
|
133
|
-
const variants = expandPatternVariants(globPattern, preferCompiled);
|
|
134
|
-
for (const variant of variants) {
|
|
135
|
-
try {
|
|
136
|
-
// Check if directory should be scanned to avoid scandir errors during hot-reload
|
|
137
|
-
if (!shouldScanDirectory(basePath, variant)) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const files = await glob(variant, {
|
|
141
|
-
cwd: basePath,
|
|
142
|
-
absolute: true,
|
|
143
|
-
nodir: true,
|
|
144
|
-
windowsPathsNoEscape: true,
|
|
145
|
-
});
|
|
146
|
-
// Filter out files that no longer exist (race condition during hot-reload)
|
|
147
|
-
const existingFiles = files.filter((f) => existsSync(f));
|
|
148
|
-
allFiles.push(...existingFiles);
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Ignore errors silently - directory might have been deleted during hot-reload
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return [...new Set(allFiles)]; // Remove duplicates
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* Discovers event handlers from files matching the given glob pattern.
|
|
159
|
-
*
|
|
160
|
-
* @param pattern - Glob pattern to match handler files (e.g., 'events/*.handler.ts')
|
|
161
|
-
* @param options - Configuration options
|
|
162
|
-
* @returns Promise resolving to array of discovered handler constructors
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* ```typescript
|
|
166
|
-
* // Discover all handlers in events directory
|
|
167
|
-
* const handlers = await discoverHandlers('events/*.handler.ts')
|
|
168
|
-
*
|
|
169
|
-
* // With filtering and logging
|
|
170
|
-
* const handlers = await discoverHandlers('events/*.handler.ts', {
|
|
171
|
-
* filter: (name, handler) => name.includes('Customer'),
|
|
172
|
-
* log: true
|
|
173
|
-
* })
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
export const discoverHandlers = async (pattern, options = {}) => {
|
|
177
|
-
const { filter } = options;
|
|
178
|
-
const searchDirectories = getSearchDirectories();
|
|
179
|
-
const preferCompiled = searchDirectories.some((dir) => dir.includes('/dist/') || dir.includes('\\dist\\'));
|
|
180
|
-
try {
|
|
181
|
-
const uniqueFiles = new Set();
|
|
182
|
-
for (const basePath of searchDirectories) {
|
|
183
|
-
const discoveredFiles = await discoverFiles(pattern, basePath, preferCompiled);
|
|
184
|
-
for (const file of discoveredFiles) {
|
|
185
|
-
uniqueFiles.add(file);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (uniqueFiles.size === 0) {
|
|
189
|
-
logger.warn(`No files found matching pattern: ${pattern}`);
|
|
190
|
-
return [];
|
|
191
|
-
}
|
|
192
|
-
const handlers = await Promise.all([...uniqueFiles].map((file) => loadHandlers(file, filter)));
|
|
193
|
-
const flatHandlers = handlers.flat();
|
|
194
|
-
const uniqueHandlers = deduplicateHandlers(flatHandlers);
|
|
195
|
-
return uniqueHandlers;
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
logger.error('Discovery failed:', error);
|
|
199
|
-
return [];
|
|
200
|
-
}
|
|
201
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { type ZodTypeAny, z } from 'zod';
|
|
2
|
-
import type { EventContext } from '../adapters/cloudevents';
|
|
3
|
-
import type { HandleEventOptions, HandlerConstructor } from './types';
|
|
4
|
-
/**
|
|
5
|
-
* Creates an event handler using the handleEvent pattern
|
|
6
|
-
* Compatible with the new middleware system
|
|
7
|
-
*
|
|
8
|
-
* Supports both inline schemas (Basic Mode) and shared contracts (Advanced Mode).
|
|
9
|
-
*
|
|
10
|
-
* @example Basic Mode - Data-only schema inline
|
|
11
|
-
* ```typescript
|
|
12
|
-
* export default handleEvent({
|
|
13
|
-
* type: 'orderboss.orders.created',
|
|
14
|
-
* schema: z.object({ orderId: z.string(), total: z.number() }),
|
|
15
|
-
* }, async (data) => {
|
|
16
|
-
* console.log('Order created:', data.orderId)
|
|
17
|
-
* })
|
|
18
|
-
* ```
|
|
19
|
-
*
|
|
20
|
-
* @example Advanced Mode - With shared contract
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { OrderCreatedContract } from '@orderboss/contracts'
|
|
23
|
-
*
|
|
24
|
-
* export default handleEvent(OrderCreatedContract, async (data) => {
|
|
25
|
-
* console.log('Order created:', data.orderId)
|
|
26
|
-
* })
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @example With tenant filtering
|
|
30
|
-
* ```typescript
|
|
31
|
-
* export default handleEvent({
|
|
32
|
-
* type: 'orderboss.orders.created',
|
|
33
|
-
* schema: OrderSchema,
|
|
34
|
-
* tenantId: ['tenant-a', 'tenant-b'],
|
|
35
|
-
* }, async (data, context) => {
|
|
36
|
-
* console.log(`Order for tenant ${context?.tenantId}:`, data)
|
|
37
|
-
* })
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export declare function handleEvent<TSchema extends ZodTypeAny>(schemaOrOptions: TSchema | HandleEventOptions<TSchema> | HandleEventOptions, handler: (payload: TSchema['_output'], context?: EventContext) => Promise<void> | void, eventType?: string): HandlerConstructor;
|
|
41
|
-
/**
|
|
42
|
-
* Creates an event schema with type inference
|
|
43
|
-
* Automatically enforces the presence of a 'type' field
|
|
44
|
-
*/
|
|
45
|
-
export declare function eventSchema<T extends Record<string, ZodTypeAny>>(schema: T & {
|
|
46
|
-
type: ZodTypeAny;
|
|
47
|
-
}): z.ZodObject<T & {
|
|
48
|
-
type: ZodTypeAny;
|
|
49
|
-
} extends infer T_1 ? { -readonly [P in keyof T_1]: T_1[P]; } : never, z.core.$strip>;
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts type value from Zod v4+ value getter
|
|
4
|
-
*/
|
|
5
|
-
function extractFromValueGetter(typeField) {
|
|
6
|
-
if ('value' in typeField && typeof typeField.value === 'string') {
|
|
7
|
-
return typeField.value;
|
|
8
|
-
}
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Extracts type value from Zod _def (v3 and v4)
|
|
13
|
-
*/
|
|
14
|
-
function extractFromTypeDef(typeField) {
|
|
15
|
-
if (!('_def' in typeField))
|
|
16
|
-
return undefined;
|
|
17
|
-
const typeDef = typeField._def;
|
|
18
|
-
// Try _def.values array (Zod v4)
|
|
19
|
-
if (Array.isArray(typeDef.values) && typeDef.values.length > 0) {
|
|
20
|
-
return String(typeDef.values[0]);
|
|
21
|
-
}
|
|
22
|
-
// Fallback to _def.value (Zod v3)
|
|
23
|
-
if (typeof typeDef.value === 'string') {
|
|
24
|
-
return typeDef.value;
|
|
25
|
-
}
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Extracts event type from a schema's type field
|
|
30
|
-
*/
|
|
31
|
-
function extractFromTypeField(shape) {
|
|
32
|
-
const typeField = shape.type;
|
|
33
|
-
if (!typeField || typeof typeField !== 'object' || typeField === null) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
const field = typeField;
|
|
37
|
-
return extractFromValueGetter(field) ?? extractFromTypeDef(field);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Extracts event type from schema
|
|
41
|
-
* Supports both old Zod (_def.value) and new Zod (_def.values / value getter)
|
|
42
|
-
*/
|
|
43
|
-
function extractEventTypeFromSchema(schema) {
|
|
44
|
-
if (!('shape' in schema) || !schema.shape || typeof schema.shape !== 'object') {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
return extractFromTypeField(schema.shape);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Creates a tenant match function from tenant ID configuration
|
|
51
|
-
*/
|
|
52
|
-
function createTenantMatcher(tenantId) {
|
|
53
|
-
const tenantIds = Array.isArray(tenantId) ? tenantId : [tenantId];
|
|
54
|
-
return (event) => {
|
|
55
|
-
if (!event.tenantId)
|
|
56
|
-
return false;
|
|
57
|
-
return tenantIds.includes(event.tenantId);
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Combines multiple match functions with AND logic
|
|
62
|
-
*/
|
|
63
|
-
function combineMatchers(matchers) {
|
|
64
|
-
return (event) => matchers.every((matcher) => matcher(event));
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Creates an event handler using the handleEvent pattern
|
|
68
|
-
* Compatible with the new middleware system
|
|
69
|
-
*
|
|
70
|
-
* Supports both inline schemas (Basic Mode) and shared contracts (Advanced Mode).
|
|
71
|
-
*
|
|
72
|
-
* @example Basic Mode - Data-only schema inline
|
|
73
|
-
* ```typescript
|
|
74
|
-
* export default handleEvent({
|
|
75
|
-
* type: 'orderboss.orders.created',
|
|
76
|
-
* schema: z.object({ orderId: z.string(), total: z.number() }),
|
|
77
|
-
* }, async (data) => {
|
|
78
|
-
* console.log('Order created:', data.orderId)
|
|
79
|
-
* })
|
|
80
|
-
* ```
|
|
81
|
-
*
|
|
82
|
-
* @example Advanced Mode - With shared contract
|
|
83
|
-
* ```typescript
|
|
84
|
-
* import { OrderCreatedContract } from '@orderboss/contracts'
|
|
85
|
-
*
|
|
86
|
-
* export default handleEvent(OrderCreatedContract, async (data) => {
|
|
87
|
-
* console.log('Order created:', data.orderId)
|
|
88
|
-
* })
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
* @example With tenant filtering
|
|
92
|
-
* ```typescript
|
|
93
|
-
* export default handleEvent({
|
|
94
|
-
* type: 'orderboss.orders.created',
|
|
95
|
-
* schema: OrderSchema,
|
|
96
|
-
* tenantId: ['tenant-a', 'tenant-b'],
|
|
97
|
-
* }, async (data, context) => {
|
|
98
|
-
* console.log(`Order for tenant ${context?.tenantId}:`, data)
|
|
99
|
-
* })
|
|
100
|
-
* ```
|
|
101
|
-
*/
|
|
102
|
-
export function handleEvent(schemaOrOptions, handler, eventType) {
|
|
103
|
-
// Handle both API formats
|
|
104
|
-
let schema;
|
|
105
|
-
let finalEventType = eventType;
|
|
106
|
-
let matchFn;
|
|
107
|
-
let safeParse = false;
|
|
108
|
-
if (schemaOrOptions && typeof schemaOrOptions === 'object' && 'schema' in schemaOrOptions) {
|
|
109
|
-
const options = schemaOrOptions;
|
|
110
|
-
schema = options.schema;
|
|
111
|
-
finalEventType = options.type || eventType;
|
|
112
|
-
safeParse = options.safeParse ?? false;
|
|
113
|
-
// Build match function from options
|
|
114
|
-
const matchers = [];
|
|
115
|
-
if (options.tenantId) {
|
|
116
|
-
matchers.push(createTenantMatcher(options.tenantId));
|
|
117
|
-
}
|
|
118
|
-
if (options.match) {
|
|
119
|
-
matchers.push(options.match);
|
|
120
|
-
}
|
|
121
|
-
if (matchers.length === 1) {
|
|
122
|
-
matchFn = matchers[0];
|
|
123
|
-
}
|
|
124
|
-
else if (matchers.length > 1) {
|
|
125
|
-
matchFn = combineMatchers(matchers);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
schema = schemaOrOptions;
|
|
130
|
-
}
|
|
131
|
-
// Try to extract event type from schema if not provided
|
|
132
|
-
if (!finalEventType) {
|
|
133
|
-
finalEventType = extractEventTypeFromSchema(schema);
|
|
134
|
-
}
|
|
135
|
-
// Fallback to unknown event
|
|
136
|
-
if (!finalEventType) {
|
|
137
|
-
finalEventType = 'unknown.event';
|
|
138
|
-
}
|
|
139
|
-
// Create handler class with proper naming (e.g., "orderboss.orders.created" → "OrdersCreatedHandler")
|
|
140
|
-
const handlerName = `${finalEventType
|
|
141
|
-
.split('.')
|
|
142
|
-
.slice(-2) // Take last 2 segments (e.g., ["orders", "created"])
|
|
143
|
-
.map(s => s.charAt(0).toUpperCase() + s.slice(1)) // Capitalize
|
|
144
|
-
.join('')}Handler`;
|
|
145
|
-
const HandlerClass = class extends Object {
|
|
146
|
-
static __eventarcMetadata = {
|
|
147
|
-
schema,
|
|
148
|
-
declaredType: finalEventType,
|
|
149
|
-
match: matchFn,
|
|
150
|
-
safeParse,
|
|
151
|
-
};
|
|
152
|
-
async handle(payload, context) {
|
|
153
|
-
await Promise.resolve(handler(payload, context));
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
// Set the class name properly
|
|
157
|
-
Object.defineProperty(HandlerClass, 'name', {
|
|
158
|
-
value: handlerName,
|
|
159
|
-
configurable: true,
|
|
160
|
-
});
|
|
161
|
-
return HandlerClass;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Creates an event schema with type inference
|
|
165
|
-
* Automatically enforces the presence of a 'type' field
|
|
166
|
-
*/
|
|
167
|
-
export function eventSchema(schema) {
|
|
168
|
-
return z.object(schema);
|
|
169
|
-
}
|
package/dist/domain/index.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { createContract } from './contract-helper';
|
|
2
|
-
export { discoverHandlers } from './discovery';
|
|
3
|
-
export { eventSchema, handleEvent } from './handler-factory';
|
|
4
|
-
export type { ChannelConfig, ChannelMetadata, EnrichedEvent, EventHandler, HandleEventOptions, HandlerConstructor, HandlerMetadata, IdempotencyStore, InferEventData, MatchFn, RoutingConfig, } from './types';
|
|
5
|
-
export type { HandlerValidationError, ValidationErrorDetail } from './validation';
|
|
6
|
-
export { extractTypeFromSchema, isValidHandler } from './validation';
|
package/dist/domain/index.js
DELETED
package/dist/domain/types.d.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain Types
|
|
3
|
-
*
|
|
4
|
-
* Core business logic types for handlers, events, and domain operations
|
|
5
|
-
*/
|
|
6
|
-
import type { ZodTypeAny } from 'zod';
|
|
7
|
-
import type { EventContext } from '../adapters/cloudevents/types';
|
|
8
|
-
/**
|
|
9
|
-
* Event handler interface
|
|
10
|
-
*/
|
|
11
|
-
export interface EventHandler<T = unknown> {
|
|
12
|
-
handle(payload: T, context?: unknown): Promise<void> | void;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Constructor type for event handler classes that implement the EventHandler interface
|
|
16
|
-
*/
|
|
17
|
-
export type HandlerConstructor<T = unknown> = (new (...args: unknown[]) => EventHandler<T>) & {
|
|
18
|
-
__eventarcMetadata?: {
|
|
19
|
-
schema: ZodTypeAny;
|
|
20
|
-
declaredType?: string;
|
|
21
|
-
match?: (event: EnrichedEvent<T>) => boolean;
|
|
22
|
-
safeParse?: boolean;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Enriched event with both data and context
|
|
27
|
-
*/
|
|
28
|
-
export interface EnrichedEvent<T> extends EventContext {
|
|
29
|
-
data: T;
|
|
30
|
-
/** Tenant identifier for multi-tenant event routing */
|
|
31
|
-
tenantId?: string;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Match function type for custom event matching
|
|
35
|
-
*/
|
|
36
|
-
export type MatchFn<T> = (event: EnrichedEvent<T>) => boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Handler metadata attached to handler constructors
|
|
39
|
-
*/
|
|
40
|
-
export type HandlerMetadata<S extends ZodTypeAny> = {
|
|
41
|
-
schema: S;
|
|
42
|
-
declaredType?: string;
|
|
43
|
-
match?: MatchFn<unknown>;
|
|
44
|
-
safeParse: boolean;
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Channel metadata for NATS JetStream routing
|
|
48
|
-
*/
|
|
49
|
-
export interface ChannelMetadata {
|
|
50
|
-
/** JetStream stream name (e.g., 'ORDERS') */
|
|
51
|
-
stream: string;
|
|
52
|
-
/** NATS subject (defaults to event type if not specified) */
|
|
53
|
-
subject: string;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Channel configuration input (subject is optional, defaults to type)
|
|
57
|
-
*/
|
|
58
|
-
export interface ChannelConfig {
|
|
59
|
-
/** JetStream stream name (e.g., 'ORDERS') */
|
|
60
|
-
stream: string;
|
|
61
|
-
/** NATS subject (optional, defaults to event type) */
|
|
62
|
-
subject?: string;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Options for creating event handlers
|
|
66
|
-
*
|
|
67
|
-
* Note: In Zod v4, we use a more relaxed schema constraint to allow
|
|
68
|
-
* contracts defined in external packages to work correctly.
|
|
69
|
-
*/
|
|
70
|
-
export interface HandleEventOptions<S = ZodTypeAny> {
|
|
71
|
-
schema: S;
|
|
72
|
-
type?: string;
|
|
73
|
-
match?: MatchFn<unknown>;
|
|
74
|
-
safeParse?: boolean;
|
|
75
|
-
/** Filter events by tenant ID(s). If set, only events matching these tenant(s) are processed. */
|
|
76
|
-
tenantId?: string | string[];
|
|
77
|
-
/** Channel metadata for NATS JetStream routing (optional) */
|
|
78
|
-
channel?: ChannelConfig;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Routing configuration for type → subject → stream mapping
|
|
82
|
-
*/
|
|
83
|
-
export interface RoutingConfig {
|
|
84
|
-
/** Map event type prefix to NATS subject prefix, e.g., { 'orderboss.orders': 'orders' } */
|
|
85
|
-
typeToSubjectMap?: Record<string, string>;
|
|
86
|
-
/** Map event type prefix to stream name, e.g., { 'orderboss.orders': 'ORDERS' } */
|
|
87
|
-
typeToStreamMap?: Record<string, string>;
|
|
88
|
-
/** Default subject prefix when no mapping found */
|
|
89
|
-
defaultSubjectPrefix?: string;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Idempotency store interface for deduplication
|
|
93
|
-
*/
|
|
94
|
-
export interface IdempotencyStore {
|
|
95
|
-
/** Check if a message has already been processed */
|
|
96
|
-
has(messageId: string): Promise<boolean> | boolean;
|
|
97
|
-
/** Mark a message as processed */
|
|
98
|
-
add(messageId: string, ttlMs?: number): Promise<void> | void;
|
|
99
|
-
/** Clear the store (useful for testing) */
|
|
100
|
-
clear?(): Promise<void> | void;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Type helper to extract data type from a Zod schema
|
|
104
|
-
* Handles both data-only schemas and full CloudEvent schemas with a 'data' field
|
|
105
|
-
*/
|
|
106
|
-
export type InferEventData<S extends ZodTypeAny> = S['_output'] extends {
|
|
107
|
-
data: infer D;
|
|
108
|
-
} ? D : S['_output'];
|
package/dist/domain/types.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain Validation
|
|
3
|
-
*
|
|
4
|
-
* Core business logic for validating events, handlers, and schemas
|
|
5
|
-
* Contains no infrastructure concerns - pure domain logic
|
|
6
|
-
*/
|
|
7
|
-
import type { ZodTypeAny } from 'zod';
|
|
8
|
-
/**
|
|
9
|
-
* Validation error detail for individual field errors
|
|
10
|
-
*/
|
|
11
|
-
export interface ValidationErrorDetail {
|
|
12
|
-
code?: string;
|
|
13
|
-
message: string;
|
|
14
|
-
path?: (string | number)[];
|
|
15
|
-
expected?: string;
|
|
16
|
-
received?: string;
|
|
17
|
-
validation?: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Handler validation error with handler information
|
|
21
|
-
*/
|
|
22
|
-
export interface HandlerValidationError {
|
|
23
|
-
handlerName: string;
|
|
24
|
-
validationErrors: ValidationErrorDetail[];
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Validates whether an exported value is a valid event handler
|
|
28
|
-
* Checks for either eventarc metadata or a handle method in the prototype
|
|
29
|
-
*/
|
|
30
|
-
export declare function isValidHandler(value: unknown): boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Extracts the event type from a Zod schema by looking for a literal 'type' field
|
|
33
|
-
*
|
|
34
|
-
* @param schema - Zod schema to extract type from
|
|
35
|
-
* @returns Event type string or undefined if not found
|
|
36
|
-
*/
|
|
37
|
-
export declare const extractTypeFromSchema: (schema: ZodTypeAny) => string | undefined;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain Validation
|
|
3
|
-
*
|
|
4
|
-
* Core business logic for validating events, handlers, and schemas
|
|
5
|
-
* Contains no infrastructure concerns - pure domain logic
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Type guards for schema shape analysis
|
|
9
|
-
*/
|
|
10
|
-
const hasShape = (schema) => 'shape' in schema &&
|
|
11
|
-
schema.shape !== null &&
|
|
12
|
-
schema.shape !== undefined &&
|
|
13
|
-
typeof schema.shape === 'object';
|
|
14
|
-
const hasTypeField = (shape) => 'type' in shape &&
|
|
15
|
-
shape.type !== null &&
|
|
16
|
-
shape.type !== undefined &&
|
|
17
|
-
typeof shape.type === 'object' &&
|
|
18
|
-
'value' in shape.type;
|
|
19
|
-
const extractTypeValue = (typeField) => typeof typeField.value === 'string' ? typeField.value : undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Safely extracts type from schema shape
|
|
22
|
-
*/
|
|
23
|
-
const safeExtractType = (schema) => {
|
|
24
|
-
if (!hasShape(schema))
|
|
25
|
-
return undefined;
|
|
26
|
-
if (!hasTypeField(schema.shape))
|
|
27
|
-
return undefined;
|
|
28
|
-
return extractTypeValue(schema.shape.type);
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Validates whether an exported value is a valid event handler
|
|
32
|
-
* Checks for either eventarc metadata or a handle method in the prototype
|
|
33
|
-
*/
|
|
34
|
-
export function isValidHandler(value) {
|
|
35
|
-
if (typeof value !== 'function')
|
|
36
|
-
return false;
|
|
37
|
-
const handler = value;
|
|
38
|
-
return !!(handler.__eventarcMetadata || handler.prototype?.handle);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Extracts the event type from a Zod schema by looking for a literal 'type' field
|
|
42
|
-
*
|
|
43
|
-
* @param schema - Zod schema to extract type from
|
|
44
|
-
* @returns Event type string or undefined if not found
|
|
45
|
-
*/
|
|
46
|
-
export const extractTypeFromSchema = (schema) => {
|
|
47
|
-
try {
|
|
48
|
-
return safeExtractType(schema);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { Context } from 'hono';
|
|
2
|
-
import type { HandlerValidationError } from '../domain';
|
|
3
|
-
export type ErrorResponse = {
|
|
4
|
-
error: {
|
|
5
|
-
type: string;
|
|
6
|
-
message: string;
|
|
7
|
-
timestamp: string;
|
|
8
|
-
details?: unknown;
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
export type SuccessResponse = {
|
|
12
|
-
message: string;
|
|
13
|
-
timestamp: string;
|
|
14
|
-
};
|
|
15
|
-
export type ValidationError = {
|
|
16
|
-
type: 'ValidationError';
|
|
17
|
-
eventType: string;
|
|
18
|
-
message: string;
|
|
19
|
-
details: HandlerValidationError[];
|
|
20
|
-
};
|
|
21
|
-
export type NoHandlerFoundError = {
|
|
22
|
-
type: 'NoHandlerFoundError';
|
|
23
|
-
eventType: string;
|
|
24
|
-
message: string;
|
|
25
|
-
};
|
|
26
|
-
export type CloudEventParseError = {
|
|
27
|
-
type: 'CloudEventParseError';
|
|
28
|
-
message: string;
|
|
29
|
-
cause?: Error;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Handles errors with full context logging
|
|
33
|
-
*/
|
|
34
|
-
export declare const handleErrorWithContext: (error: Error, context: Context, additionalInfo?: Record<string, unknown>) => {
|
|
35
|
-
message: string;
|
|
36
|
-
stack: string | undefined;
|
|
37
|
-
url: string;
|
|
38
|
-
method: string;
|
|
39
|
-
headers: {
|
|
40
|
-
[k: string]: string;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* Converts an error to a standardized response format
|
|
45
|
-
*/
|
|
46
|
-
export declare const errorToResponse: (error: Error, type?: string) => ErrorResponse;
|
|
47
|
-
/**
|
|
48
|
-
* Creates a standardized error response
|
|
49
|
-
*/
|
|
50
|
-
export declare const createErrorResponse: (type: string, message: string, details?: unknown) => ErrorResponse;
|
|
51
|
-
export declare const createValidationError: (eventType: string, details: HandlerValidationError[]) => ValidationError;
|
|
52
|
-
export declare const createNoHandlerFoundError: (eventType: string) => NoHandlerFoundError;
|
|
53
|
-
export declare const createCloudEventParseError: (message: string, cause?: Error) => CloudEventParseError;
|