@nimbus-cqrs/core 2.1.1 → 2.2.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 CHANGED
@@ -13,7 +13,7 @@ Refer to the [Nimbus main repository](https://github.com/overlap-dev/Nimbus) or
13
13
 
14
14
  ```bash
15
15
  # Deno
16
- deno add jsr:@nimbus-cqrs/core
16
+ deno add npm:@nimbus-cqrs/core
17
17
 
18
18
  # NPM
19
19
  npm install @nimbus-cqrs/core
@@ -30,17 +30,13 @@ For detailed documentation, please refer to the [Nimbus documentation](https://n
30
30
 
31
31
  ## Command
32
32
 
33
- A **Command** asks the system to *do* something (a write). You declare a [Zod](https://zod.dev/) schema that extends Nimbus' built-in `commandSchema`, write a handler, and register both on the router. Incoming messages are validated against the schema before they reach your handler.
33
+ A **Command** asks the system to _do_ something (a write). You declare a [Zod](https://zod.dev/) schema that extends Nimbus' built-in `commandSchema`, write a handler, and register both on the router. Incoming messages are validated against the schema before they reach your handler.
34
34
 
35
35
  ```typescript
36
- import {
37
- commandSchema,
38
- createCommand,
39
- getRouter,
40
- } from '@nimbus-cqrs/core';
41
- import { z } from 'zod';
36
+ import { commandSchema, createCommand, getRouter } from "@nimbus-cqrs/core";
37
+ import { z } from "zod";
42
38
 
43
- const ADD_TODO = 'com.example.todo.add';
39
+ const ADD_TODO = "com.example.todo.add";
44
40
 
45
41
  const addTodoSchema = commandSchema.extend({
46
42
  type: z.literal(ADD_TODO),
@@ -52,7 +48,7 @@ type AddTodoCommand = z.infer<typeof addTodoSchema>;
52
48
 
53
49
  const handleAddTodo = async (cmd: AddTodoCommand) => {
54
50
  // ...persist the todo here...
55
- return { id: 'todo-1', title: cmd.data.title, status: 'open' };
51
+ return { id: "todo-1", title: cmd.data.title, status: "open" };
56
52
  };
57
53
 
58
54
  const router = getRouter();
@@ -61,9 +57,9 @@ router.register(ADD_TODO, handleAddTodo, addTodoSchema);
61
57
  const result = await router.route(
62
58
  createCommand<AddTodoCommand>({
63
59
  type: ADD_TODO,
64
- source: 'https://app.example.com',
65
- data: { title: 'Write the README' },
66
- }),
60
+ source: "https://app.example.com",
61
+ data: { title: "Write the README" },
62
+ })
67
63
  );
68
64
 
69
65
  console.log(result);
@@ -74,13 +70,13 @@ console.log(result);
74
70
 
75
71
  ## Query
76
72
 
77
- A **Query** asks the system to *read* something. Mechanically it is identical to a Command — same router, same validation, same shape — only the intent differs.
73
+ A **Query** asks the system to _read_ something. Mechanically it is identical to a Command — same router, same validation, same shape — only the intent differs.
78
74
 
79
75
  ```typescript
80
- import { createQuery, getRouter, querySchema } from '@nimbus-cqrs/core';
81
- import { z } from 'zod';
76
+ import { createQuery, getRouter, querySchema } from "@nimbus-cqrs/core";
77
+ import { z } from "zod";
82
78
 
83
- const GET_TODO = 'com.example.todo.get';
79
+ const GET_TODO = "com.example.todo.get";
84
80
 
85
81
  const getTodoSchema = querySchema.extend({
86
82
  type: z.literal(GET_TODO),
@@ -90,7 +86,7 @@ type GetTodoQuery = z.infer<typeof getTodoSchema>;
90
86
 
91
87
  const handleGetTodo = async (q: GetTodoQuery) => {
92
88
  // ...load the todo from your read model...
93
- return { id: q.data.id, title: 'Write the README', status: 'open' };
89
+ return { id: q.data.id, title: "Write the README", status: "open" };
94
90
  };
95
91
 
96
92
  const router = getRouter();
@@ -99,25 +95,21 @@ router.register(GET_TODO, handleGetTodo, getTodoSchema);
99
95
  const todo = await router.route(
100
96
  createQuery<GetTodoQuery>({
101
97
  type: GET_TODO,
102
- source: 'https://app.example.com',
103
- data: { id: 'todo-1' },
104
- }),
98
+ source: "https://app.example.com",
99
+ data: { id: "todo-1" },
100
+ })
105
101
  );
106
102
  ```
107
103
 
108
104
  ## Event
109
105
 
110
- An **Event** announces that something *has happened*. Events are published to the in-process EventBus, which delivers them to every matching subscriber asynchronously, with exponential-backoff retries on handler errors and built-in OpenTelemetry traces and metrics.
106
+ An **Event** announces that something _has happened_. Events are published to the in-process EventBus, which delivers them to every matching subscriber asynchronously, with exponential-backoff retries on handler errors and built-in OpenTelemetry traces and metrics.
111
107
 
112
108
  ```typescript
113
- import {
114
- createEvent,
115
- eventSchema,
116
- getEventBus,
117
- } from '@nimbus-cqrs/core';
118
- import { z } from 'zod';
109
+ import { createEvent, eventSchema, getEventBus } from "@nimbus-cqrs/core";
110
+ import { z } from "zod";
119
111
 
120
- const TODO_ADDED = 'com.example.todo.added';
112
+ const TODO_ADDED = "com.example.todo.added";
121
113
 
122
114
  const todoAddedSchema = eventSchema.extend({
123
115
  type: z.literal(TODO_ADDED),
@@ -143,10 +135,10 @@ eventBus.subscribeEvent<TodoAddedEvent>({
143
135
  eventBus.putEvent(
144
136
  createEvent<TodoAddedEvent>({
145
137
  type: TODO_ADDED,
146
- source: 'https://app.example.com',
147
- subject: '/todos/todo-1',
148
- data: { id: 'todo-1', title: 'Write the README' },
149
- }),
138
+ source: "https://app.example.com",
139
+ subject: "/todos/todo-1",
140
+ data: { id: "todo-1", title: "Write the README" },
141
+ })
150
142
  );
151
143
  ```
152
144
 
@@ -157,27 +149,27 @@ A typical flow is to publish an event from inside a command handler once the wri
157
149
  A typical app configures a single named router at startup with cross-cutting concerns (logging, correlation IDs, …) and then resolves it from anywhere via `getRouter()`.
158
150
 
159
151
  ```typescript
160
- import { getLogger, getRouter, setupRouter } from '@nimbus-cqrs/core';
152
+ import { getLogger, getRouter, setupRouter } from "@nimbus-cqrs/core";
161
153
 
162
- setupRouter('default', {
154
+ setupRouter("default", {
163
155
  logInput: (input) => {
164
156
  getLogger().debug({
165
- category: 'Router',
157
+ category: "Router",
166
158
  message: `Routing ${input.type}`,
167
159
  correlationId: input.correlationid,
168
160
  });
169
161
  },
170
162
  logOutput: (output) => {
171
163
  getLogger().debug({
172
- category: 'Router',
173
- message: 'Handler completed',
164
+ category: "Router",
165
+ message: "Handler completed",
174
166
  data: { output },
175
167
  });
176
168
  },
177
169
  });
178
170
 
179
171
  // Anywhere else in your app:
180
- const router = getRouter('default');
172
+ const router = getRouter("default");
181
173
  router.register(/* type, handler, schema */);
182
174
  await router.route(/* command or query */);
183
175
  ```
@@ -5,6 +5,6 @@ export declare class Exception extends Error {
5
5
  details?: Record<string, unknown>;
6
6
  statusCode?: number;
7
7
  constructor(name: string, message: string, details?: Record<string, unknown>, statusCode?: number);
8
- fromError(error: Error): Exception;
8
+ fromError(error: Error): this;
9
9
  }
10
10
  //# sourceMappingURL=exception.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/exception.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM;IAmBhB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS;CAM5C"}
1
+ {"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/exception.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM;IAmBhB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAMvC"}
@@ -13,6 +13,6 @@ export declare class InvalidInputException extends Exception {
13
13
  *
14
14
  * @returns {InvalidInputException} The InvalidInputException.
15
15
  */
16
- fromZodError(error: ZodError): InvalidInputException;
16
+ fromZodError(error: ZodError): this;
17
17
  }
18
18
  //# sourceMappingURL=invalidInputException.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"invalidInputException.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/invalidInputException.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;gBACpC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAS/D;;;;;;;OAOG;IACI,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,qBAAqB;CAa9D"}
1
+ {"version":3,"file":"invalidInputException.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/invalidInputException.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;gBACpC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAS/D;;;;;;;OAOG;IACI,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAa7C"}
@@ -20,6 +20,12 @@ import { z } from 'zod';
20
20
  * @property {string} datacontenttype - A MIME type that indicates the format that the data is in (optional).
21
21
  * @property {string} dataschema - An absolute URL to the schema that the data adheres to (optional).
22
22
  *
23
+ * Commands may include additional CloudEvents extension attributes such as
24
+ * `metadata` or `authcontext`. Define them on a command-specific schema via
25
+ * {@link commandSchema.extend} or as an intersection type.
26
+ *
27
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
28
+ *
23
29
  * @template TData - The type of the data.
24
30
  *
25
31
  * @example
@@ -49,6 +55,10 @@ export type Command<TData = unknown> = {
49
55
  datacontenttype?: string;
50
56
  dataschema?: string;
51
57
  };
58
+ /**
59
+ * Known command properties handled by {@link createCommand}.
60
+ */
61
+ type CommandKnownProperty = 'specversion' | 'id' | 'correlationid' | 'time' | 'source' | 'type' | 'subject' | 'data' | 'datacontenttype' | 'dataschema';
52
62
  /**
53
63
  * Type alias for the command data field schema.
54
64
  */
@@ -90,7 +100,7 @@ export declare const commandSchema: CommandSchemaType;
90
100
  * the input is validated against that type. This means fields like
91
101
  * `type` and `data` must match the narrower types of `TCommand`.
92
102
  */
93
- export type CreateCommandInput<TCommand extends Command = Command> = Partial<Pick<TCommand, 'id' | 'correlationid' | 'time' | 'subject' | 'datacontenttype' | 'dataschema'>> & Pick<TCommand, 'type' | 'source' | 'data'>;
103
+ export type CreateCommandInput<TCommand extends Command = Command> = Partial<Pick<TCommand, 'id' | 'correlationid' | 'time' | 'subject' | 'datacontenttype' | 'dataschema'>> & Pick<TCommand, 'type' | 'source' | 'data'> & Partial<Omit<TCommand, CommandKnownProperty>>;
94
104
  /**
95
105
  * Creates a command based on input data with the convenience
96
106
  * to skip properties and use the defaults for the rest.
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/lib/message/command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,IAAI;IACnC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,iBAAiB,GAAG,CAAC,CAAC,QAAQ,CAC/B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,SAAS,CAAC;IACxC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,iBAiB1B,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,OAAO,GAAG,OAAO,IAC3D,OAAO,CACL,IAAI,CACA,QAAQ,EACN,IAAI,GACJ,eAAe,GACf,MAAM,GACN,SAAS,GACT,iBAAiB,GACjB,YAAY,CACjB,CACJ,GACC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,SAAS,OAAO,EAClD,OAAO,kBAAkB,CAAC,QAAQ,CAAC,KACpC,QAeF,CAAC"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/lib/message/command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,IAAI;IACnC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,oBAAoB,GACnB,aAAa,GACb,IAAI,GACJ,eAAe,GACf,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,GACT,MAAM,GACN,iBAAiB,GACjB,YAAY,CAAC;AAEnB;;GAEG;AACH,KAAK,iBAAiB,GAAG,CAAC,CAAC,QAAQ,CAC/B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,SAAS,CAAC;IACxC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,iBAiBL,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,OAAO,GAAG,OAAO,IAC3D,OAAO,CACL,IAAI,CACA,QAAQ,EACN,IAAI,GACJ,eAAe,GACf,MAAM,GACN,SAAS,GACT,iBAAiB,GACjB,YAAY,CACjB,CACJ,GACC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC;AAEpD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,SAAS,OAAO,EAClD,OAAO,kBAAkB,CAAC,QAAQ,CAAC,KACpC,QAuCF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { ulid } from '../../deps/jsr.io/@std/ulid/1.0.0/mod.js';
2
2
  import { z } from 'zod';
3
+ import { warnOnInvalidExtensionAttributeNames } from './message.js';
3
4
  /**
4
5
  * The Zod schema matching the Command type.
5
6
  *
@@ -8,7 +9,7 @@ import { z } from 'zod';
8
9
  * We do not infer the Command type from this schema because of
9
10
  * slow type issues see https://jsr.io/docs/about-slow-types for more details.
10
11
  */
11
- export const commandSchema = z.object({
12
+ export const commandSchema = z.looseObject({
12
13
  specversion: z.literal('1.0'),
13
14
  id: z.string(),
14
15
  correlationid: z.string(),
@@ -31,17 +32,23 @@ export const commandSchema = z.object({
31
32
  * to skip properties and use the defaults for the rest.
32
33
  */
33
34
  export const createCommand = (input) => {
35
+ const { id, correlationid, time, source, type, subject, data, datacontenttype, dataschema, ...extensions } = input;
36
+ const correlationIdWithFallback = correlationid ?? ulid();
37
+ if (Object.keys(extensions).length > 0) {
38
+ warnOnInvalidExtensionAttributeNames(extensions, correlationIdWithFallback, createCommand);
39
+ }
34
40
  const command = {
35
41
  specversion: '1.0',
36
- id: input.id ?? ulid(),
37
- correlationid: input.correlationid ?? ulid(),
38
- time: input.time ?? new Date().toISOString(),
39
- source: input.source,
40
- type: input.type,
41
- ...(input.subject && { subject: input.subject }),
42
- data: input.data,
43
- datacontenttype: input.datacontenttype ?? 'application/json',
44
- ...(input.dataschema && { dataschema: input.dataschema }),
42
+ id: id ?? ulid(),
43
+ correlationid: correlationIdWithFallback,
44
+ time: time ?? new Date().toISOString(),
45
+ source,
46
+ type,
47
+ ...(subject && { subject }),
48
+ data,
49
+ datacontenttype: datacontenttype ?? 'application/json',
50
+ ...(dataschema && { dataschema }),
51
+ ...extensions,
45
52
  };
46
53
  return command;
47
54
  };
@@ -20,6 +20,12 @@ import { z } from 'zod';
20
20
  * @property {string} datacontenttype - A MIME type that indicates the format that the data is in (optional).
21
21
  * @property {string} dataschema - An absolute URL to the schema that the data adheres to (optional).
22
22
  *
23
+ * Events may include additional CloudEvents extension attributes such as
24
+ * `metadata` or `authcontext`. Define them on an event-specific schema via
25
+ * {@link eventSchema.extend} or as an intersection type.
26
+ *
27
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
28
+ *
23
29
  * @template TData - The type of the data.
24
30
  *
25
31
  * @example
@@ -52,6 +58,10 @@ export type Event<TData = unknown> = {
52
58
  datacontenttype?: string;
53
59
  dataschema?: string;
54
60
  };
61
+ /**
62
+ * Known event properties handled by {@link createEvent}.
63
+ */
64
+ type EventKnownProperty = 'specversion' | 'id' | 'correlationid' | 'time' | 'source' | 'type' | 'subject' | 'data' | 'datacontenttype' | 'dataschema';
55
65
  /**
56
66
  * Type alias for the event data field schema.
57
67
  */
@@ -93,7 +103,7 @@ export declare const eventSchema: EventSchemaType;
93
103
  * the input is validated against that type. This means fields like
94
104
  * `type` and `data` must match the narrower types of `TEvent`.
95
105
  */
96
- export type CreateEventInput<TEvent extends Event = Event> = Partial<Pick<TEvent, 'id' | 'correlationid' | 'time' | 'datacontenttype' | 'dataschema'>> & Pick<TEvent, 'type' | 'source' | 'subject' | 'data'>;
106
+ export type CreateEventInput<TEvent extends Event = Event> = Partial<Pick<TEvent, 'id' | 'correlationid' | 'time' | 'datacontenttype' | 'dataschema'>> & Pick<TEvent, 'type' | 'source' | 'subject' | 'data'> & Partial<Omit<TEvent, EventKnownProperty>>;
97
107
  /**
98
108
  * Creates an event based on input data with the convenience
99
109
  * to skip properties and use the defaults for the rest.
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/lib/message/event.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,MAAM,KAAK,CAAC,KAAK,GAAG,OAAO,IAAI;IACjC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,eAAe,GAAG,CAAC,CAAC,QAAQ,CAC7B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,IAAI,EAAE,eAAe,CAAC;IACtB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,eAiBxB,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,KAAK,GAAG,KAAK,IACnD,OAAO,CACL,IAAI,CACA,MAAM,EACN,IAAI,GAAG,eAAe,GAAG,MAAM,GAAG,iBAAiB,GAAG,YAAY,CACrE,CACJ,GACC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAK,EAC5C,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAChC,MAeF,CAAC"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/lib/message/event.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,MAAM,KAAK,CAAC,KAAK,GAAG,OAAO,IAAI;IACjC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,kBAAkB,GACjB,aAAa,GACb,IAAI,GACJ,eAAe,GACf,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,GACT,MAAM,GACN,iBAAiB,GACjB,YAAY,CAAC;AAEnB;;GAEG;AACH,KAAK,eAAe,GAAG,CAAC,CAAC,QAAQ,CAC7B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,IAAI,EAAE,eAAe,CAAC;IACtB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,eAiBL,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,KAAK,GAAG,KAAK,IACnD,OAAO,CACL,IAAI,CACA,MAAM,EACN,IAAI,GAAG,eAAe,GAAG,MAAM,GAAG,iBAAiB,GAAG,YAAY,CACrE,CACJ,GACC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC,GACpD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAK,EAC5C,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAChC,MAuCF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { ulid } from '../../deps/jsr.io/@std/ulid/1.0.0/mod.js';
2
2
  import { z } from 'zod';
3
+ import { warnOnInvalidExtensionAttributeNames } from './message.js';
3
4
  /**
4
5
  * The Zod schema matching the Event type.
5
6
  *
@@ -8,7 +9,7 @@ import { z } from 'zod';
8
9
  * We do not infer the Event type from this schema because of
9
10
  * slow type issues see https://jsr.io/docs/about-slow-types for more details.
10
11
  */
11
- export const eventSchema = z.object({
12
+ export const eventSchema = z.looseObject({
12
13
  specversion: z.literal('1.0'),
13
14
  id: z.string(),
14
15
  correlationid: z.string(),
@@ -31,17 +32,23 @@ export const eventSchema = z.object({
31
32
  * to skip properties and use the defaults for the rest.
32
33
  */
33
34
  export const createEvent = (input) => {
35
+ const { id, correlationid, time, source, type, subject, data, datacontenttype, dataschema, ...extensions } = input;
36
+ const correlationIdWithFallback = correlationid ?? ulid();
37
+ if (Object.keys(extensions).length > 0) {
38
+ warnOnInvalidExtensionAttributeNames(extensions, correlationIdWithFallback, createEvent);
39
+ }
34
40
  const event = {
35
41
  specversion: '1.0',
36
- id: input.id ?? ulid(),
37
- correlationid: input.correlationid ?? ulid(),
38
- time: input.time ?? new Date().toISOString(),
39
- source: input.source,
40
- type: input.type,
41
- subject: input.subject,
42
- data: input.data,
43
- datacontenttype: input.datacontenttype ?? 'application/json',
44
- ...(input.dataschema && { dataschema: input.dataschema }),
42
+ id: id ?? ulid(),
43
+ correlationid: correlationIdWithFallback,
44
+ time: time ?? new Date().toISOString(),
45
+ source,
46
+ type,
47
+ subject,
48
+ data,
49
+ datacontenttype: datacontenttype ?? 'application/json',
50
+ ...(dataschema && { dataschema }),
51
+ ...extensions,
45
52
  };
46
53
  return event;
47
54
  };
@@ -15,4 +15,18 @@ import type { Query } from './query.js';
15
15
  * @template TData - The type of the data.
16
16
  */
17
17
  export type Message<TData = unknown> = Command<TData> | Event<TData> | Query<TData>;
18
+ /**
19
+ * Validates CloudEvents extension attribute names and logs a warning when invalid.
20
+ *
21
+ * Called by {@link createCommand}, {@link createEvent}, and {@link createQuery}
22
+ * to check extension attributes against the CloudEvents naming rules. Invalid
23
+ * names are logged with a stack trace anchored at the caller function.
24
+ *
25
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
26
+ *
27
+ * @param {Record<string, unknown>} extensions - Extension attributes to validate, excluding known CloudEvents context attributes.
28
+ * @param {string | undefined} correlationId - Optional correlation ID included in the warning log for tracing.
29
+ * @param {(...args: never[]) => unknown} stackTraceAnchor - Function used to anchor the warning stack trace at the message factory caller (for example {@link createCommand}).
30
+ */
31
+ export declare const warnOnInvalidExtensionAttributeNames: (extensions: Record<string, unknown>, correlationId: string | undefined, stackTraceAnchor: (...args: never[]) => unknown) => void;
18
32
  //# sourceMappingURL=message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/lib/message/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,IAC7B,OAAO,CAAC,KAAK,CAAC,GACd,KAAK,CAAC,KAAK,CAAC,GACZ,KAAK,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/lib/message/message.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,IAC7B,OAAO,CAAC,KAAK,CAAC,GACd,KAAK,CAAC,KAAK,CAAC,GACZ,KAAK,CAAC,KAAK,CAAC,CAAC;AAsCnB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oCAAoC,GAC7C,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,eAAe,MAAM,GAAG,SAAS,EACjC,kBAAkB,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,KAChD,IAkBF,CAAC"}
@@ -1 +1,47 @@
1
- export {};
1
+ import { getLogger } from '../log/logger.js';
2
+ /**
3
+ * CloudEvents extension context attribute names must consist of lowercase
4
+ * ASCII letters or digits.
5
+ *
6
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
7
+ */
8
+ const getInvalidExtensionAttributeNames = (extensions) => {
9
+ const EXTENSION_ATTRIBUTE_NAME_PATTERN = /^[a-z0-9]+$/;
10
+ return Object.keys(extensions).filter((name) => !EXTENSION_ATTRIBUTE_NAME_PATTERN.test(name));
11
+ };
12
+ /**
13
+ * Create an error object to capture the stack trace for better debugging.
14
+ */
15
+ const createInvalidExtensionAttributeNamesError = (invalidNames, stackTraceAnchor) => {
16
+ const error = new Error(`Invalid CloudEvents extension attribute names: ${invalidNames.join(', ')}`);
17
+ if (typeof Error.captureStackTrace === 'function') {
18
+ Error.captureStackTrace(error, stackTraceAnchor);
19
+ }
20
+ return error;
21
+ };
22
+ /**
23
+ * Validates CloudEvents extension attribute names and logs a warning when invalid.
24
+ *
25
+ * Called by {@link createCommand}, {@link createEvent}, and {@link createQuery}
26
+ * to check extension attributes against the CloudEvents naming rules. Invalid
27
+ * names are logged with a stack trace anchored at the caller function.
28
+ *
29
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
30
+ *
31
+ * @param {Record<string, unknown>} extensions - Extension attributes to validate, excluding known CloudEvents context attributes.
32
+ * @param {string | undefined} correlationId - Optional correlation ID included in the warning log for tracing.
33
+ * @param {(...args: never[]) => unknown} stackTraceAnchor - Function used to anchor the warning stack trace at the message factory caller (for example {@link createCommand}).
34
+ */
35
+ export const warnOnInvalidExtensionAttributeNames = (extensions, correlationId, stackTraceAnchor) => {
36
+ const invalidNames = getInvalidExtensionAttributeNames(extensions);
37
+ if (invalidNames.length === 0) {
38
+ return;
39
+ }
40
+ getLogger().warn({
41
+ category: 'Nimbus',
42
+ message: 'Extension attribute names must use lowercase ASCII letters or digits only per CloudEvents specification.',
43
+ data: { invalidAttributeNames: invalidNames },
44
+ error: createInvalidExtensionAttributeNamesError(invalidNames, stackTraceAnchor),
45
+ ...(correlationId && { correlationId }),
46
+ });
47
+ };
@@ -18,6 +18,12 @@ import { z } from 'zod';
18
18
  * @property {string} datacontenttype - A MIME type that indicates the format that the data is in (optional).
19
19
  * @property {string} dataschema - An absolute URL to the schema that the data adheres to (optional).
20
20
  *
21
+ * Queries may include additional CloudEvents extension attributes such as
22
+ * `metadata` or `authcontext`. Define them on a query-specific schema via
23
+ * {@link querySchema.extend} or as an intersection type.
24
+ *
25
+ * @see https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#extension-context-attributes
26
+ *
21
27
  * @template TData - The type of the data.
22
28
  *
23
29
  * @example
@@ -45,6 +51,10 @@ export type Query<TData = unknown> = {
45
51
  datacontenttype?: string;
46
52
  dataschema?: string;
47
53
  };
54
+ /**
55
+ * Known query properties handled by {@link createQuery}.
56
+ */
57
+ type QueryKnownProperty = 'specversion' | 'id' | 'correlationid' | 'time' | 'source' | 'type' | 'data' | 'datacontenttype' | 'dataschema';
48
58
  /**
49
59
  * Type alias for the query data field schema.
50
60
  */
@@ -85,7 +95,7 @@ export declare const querySchema: QuerySchemaType;
85
95
  * the input is validated against that type. This means fields like
86
96
  * `type` and `data` must match the narrower types of `TQuery`.
87
97
  */
88
- export type CreateQueryInput<TQuery extends Query = Query> = Partial<Pick<TQuery, 'id' | 'correlationid' | 'time' | 'datacontenttype' | 'dataschema'>> & Pick<TQuery, 'type' | 'source' | 'data'>;
98
+ export type CreateQueryInput<TQuery extends Query = Query> = Partial<Pick<TQuery, 'id' | 'correlationid' | 'time' | 'datacontenttype' | 'dataschema'>> & Pick<TQuery, 'type' | 'source' | 'data'> & Partial<Omit<TQuery, QueryKnownProperty>>;
89
99
  /**
90
100
  * Creates a query based on input data with the convenience
91
101
  * to skip properties and use the defaults for the rest.
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/lib/message/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,KAAK,CAAC,KAAK,GAAG,OAAO,IAAI;IACjC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,eAAe,GAAG,CAAC,CAAC,QAAQ,CAC7B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,IAAI,EAAE,eAAe,CAAC;IACtB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,eAgBxB,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,KAAK,GAAG,KAAK,IACnD,OAAO,CACL,IAAI,CACA,MAAM,EACN,IAAI,GAAG,eAAe,GAAG,MAAM,GAAG,iBAAiB,GAAG,YAAY,CACrE,CACJ,GACC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAK,EAC5C,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAChC,MAcF,CAAC"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/lib/message/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,MAAM,KAAK,CAAC,KAAK,GAAG,OAAO,IAAI;IACjC,WAAW,EAAE,KAAK,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;GAEG;AACH,KAAK,kBAAkB,GACjB,aAAa,GACb,IAAI,GACJ,eAAe,GACf,MAAM,GACN,QAAQ,GACR,MAAM,GACN,MAAM,GACN,iBAAiB,GACjB,YAAY,CAAC;AAEnB;;GAEG;AACH,KAAK,eAAe,GAAG,CAAC,CAAC,QAAQ,CAC7B;IACI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;IACtC,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IACxB,CAAC,CAAC,UAAU;CACf,CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;IACtC,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;IAChB,aAAa,EAAE,CAAC,CAAC,SAAS,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC;IAClB,IAAI,EAAE,eAAe,CAAC;IACtB,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,eAgBL,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,CAAC,MAAM,SAAS,KAAK,GAAG,KAAK,IACnD,OAAO,CACL,IAAI,CACA,MAAM,EACN,IAAI,GAAG,eAAe,GAAG,MAAM,GAAG,iBAAiB,GAAG,YAAY,CACrE,CACJ,GACC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAK,EAC5C,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAChC,MAqCF,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { ulid } from '../../deps/jsr.io/@std/ulid/1.0.0/mod.js';
2
2
  import { z } from 'zod';
3
+ import { warnOnInvalidExtensionAttributeNames } from './message.js';
3
4
  /**
4
5
  * The Zod schema matching the Query type.
5
6
  *
@@ -8,7 +9,7 @@ import { z } from 'zod';
8
9
  * We do not infer the Query type from this schema because of
9
10
  * slow type issues see https://jsr.io/docs/about-slow-types for more details.
10
11
  */
11
- export const querySchema = z.object({
12
+ export const querySchema = z.looseObject({
12
13
  specversion: z.literal('1.0'),
13
14
  id: z.string(),
14
15
  correlationid: z.string(),
@@ -30,16 +31,22 @@ export const querySchema = z.object({
30
31
  * to skip properties and use the defaults for the rest.
31
32
  */
32
33
  export const createQuery = (input) => {
34
+ const { id, correlationid, time, source, type, data, datacontenttype, dataschema, ...extensions } = input;
35
+ const correlationIdWithFallback = correlationid ?? ulid();
36
+ if (Object.keys(extensions).length > 0) {
37
+ warnOnInvalidExtensionAttributeNames(extensions, correlationIdWithFallback, createQuery);
38
+ }
33
39
  const query = {
34
40
  specversion: '1.0',
35
- id: input.id ?? ulid(),
36
- correlationid: input.correlationid ?? ulid(),
37
- time: input.time ?? new Date().toISOString(),
38
- source: input.source,
39
- type: input.type,
40
- data: input.data,
41
- datacontenttype: input.datacontenttype ?? 'application/json',
42
- ...(input.dataschema && { dataschema: input.dataschema }),
41
+ id: id ?? ulid(),
42
+ correlationid: correlationIdWithFallback,
43
+ time: time ?? new Date().toISOString(),
44
+ source,
45
+ type,
46
+ data,
47
+ datacontenttype: datacontenttype ?? 'application/json',
48
+ ...(dataschema && { dataschema }),
49
+ ...extensions,
43
50
  };
44
51
  return query;
45
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-cqrs/core",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Simplify Event-Driven Applications - Core building blocks of the Nimbus framework.",
5
5
  "keywords": [
6
6
  "nimbus",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@opentelemetry/api": "^1.9.1",
37
- "zod": "^4.3.6"
37
+ "zod": "^4.4.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.0.0"