youeduc-sdk-messaging 0.0.0 → 0.5.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/admin.d.ts +61 -0
- package/dist/admin.js +143 -0
- package/dist/admin.js.map +1 -0
- package/dist/config.d.ts +74 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/consumer-client.d.ts +155 -0
- package/dist/consumer-client.js +299 -0
- package/dist/consumer-client.js.map +1 -0
- package/dist/consumer.d.ts +156 -0
- package/dist/consumer.js +1228 -0
- package/dist/consumer.js.map +1 -0
- package/dist/contracts/index.d.ts +16 -0
- package/dist/contracts/index.js +42 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/schemas.d.ts +570 -0
- package/dist/contracts/schemas.js +902 -0
- package/dist/contracts/schemas.js.map +1 -0
- package/dist/contracts/usuarios.d.ts +163 -0
- package/dist/contracts/usuarios.js +5 -0
- package/dist/contracts/usuarios.js.map +1 -0
- package/dist/dedup.d.ts +29 -0
- package/dist/dedup.js +65 -0
- package/dist/dedup.js.map +1 -0
- package/dist/dlq.d.ts +161 -0
- package/dist/dlq.js +341 -0
- package/dist/dlq.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.js +95 -0
- package/dist/errors.js.map +1 -0
- package/dist/headers.d.ts +92 -0
- package/dist/headers.js +144 -0
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/redis.d.ts +118 -0
- package/dist/integrations/redis.js +272 -0
- package/dist/integrations/redis.js.map +1 -0
- package/dist/integrations/runtime.d.ts +232 -0
- package/dist/integrations/runtime.js +554 -0
- package/dist/integrations/runtime.js.map +1 -0
- package/dist/logger.d.ts +37 -0
- package/dist/logger.js +49 -0
- package/dist/logger.js.map +1 -0
- package/dist/messaging.d.ts +145 -0
- package/dist/messaging.js +164 -0
- package/dist/messaging.js.map +1 -0
- package/dist/naming.d.ts +64 -0
- package/dist/naming.js +167 -0
- package/dist/naming.js.map +1 -0
- package/dist/payload.d.ts +8 -0
- package/dist/payload.js +32 -0
- package/dist/payload.js.map +1 -0
- package/dist/publisher.d.ts +50 -0
- package/dist/publisher.js +199 -0
- package/dist/publisher.js.map +1 -0
- package/dist/retry.d.ts +24 -0
- package/dist/retry.js +42 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts +83 -0
- package/dist/schema.js +676 -0
- package/dist/schema.js.map +1 -0
- package/dist/telemetry.d.ts +52 -0
- package/dist/telemetry.js +221 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/transport.d.ts +130 -0
- package/dist/transport.js +283 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +38 -0
- package/dist/util.js +112 -0
- package/dist/util.js.map +1 -0
- package/dist/w3c.d.ts +16 -0
- package/dist/w3c.js +119 -0
- package/dist/w3c.js.map +1 -0
- package/package.json +74 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Publication of schema-validated events. */
|
|
2
|
+
import * as headersMod from './headers.js';
|
|
3
|
+
import { type Logger } from './logger.js';
|
|
4
|
+
import { type MessageSender, type PayloadValidator, type PublishResult, type Telemetry } from './types.js';
|
|
5
|
+
export interface PublishOptions {
|
|
6
|
+
/** Defines the partition (order per key). Empty/absent → no key (default partitioner). */
|
|
7
|
+
key?: string | null | undefined;
|
|
8
|
+
/** Given or a new UUID4. */
|
|
9
|
+
correlationId?: string | null | undefined;
|
|
10
|
+
/** Preferred dedup key for consumers (header `idempotency_key`). */
|
|
11
|
+
idempotencyKey?: string | null | undefined;
|
|
12
|
+
/** Header `x-tenant-id`. */
|
|
13
|
+
tenantId?: string | null | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface PublisherOptions {
|
|
16
|
+
/** Name (kebab-case) of the publishing service; goes in the `producer` header. */
|
|
17
|
+
producer: string;
|
|
18
|
+
/** Default {@link NoopTelemetry}. */
|
|
19
|
+
telemetry?: Telemetry | undefined;
|
|
20
|
+
/** Default: the SDK default logger. */
|
|
21
|
+
logger?: Logger | undefined;
|
|
22
|
+
/** Clock (epoch ms) for `published_at`. Default `Date.now`. */
|
|
23
|
+
clock?: headersMod.EpochClock | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Resource prefix (spec §2.7): `hmg` → topic `hmg.{domain}.events.{version}`. The `producer`
|
|
26
|
+
* header does not change. Default: none.
|
|
27
|
+
*/
|
|
28
|
+
resourcePrefix?: string | null | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validates the payload against the event schema and publishes it to the domain topic.
|
|
32
|
+
*
|
|
33
|
+
* Same flow as Python: names → schema validation → JSON serialization → headers → send inside
|
|
34
|
+
* the PRODUCER span (the injected `traceparent` reaches the broker) → metric + log.
|
|
35
|
+
*/
|
|
36
|
+
export declare class Publisher {
|
|
37
|
+
#private;
|
|
38
|
+
/** Resource prefix applied to the topic (`null` = none). */
|
|
39
|
+
readonly resourcePrefix: string | null;
|
|
40
|
+
constructor(sender: MessageSender, validator: PayloadValidator, options: PublisherOptions);
|
|
41
|
+
/**
|
|
42
|
+
* Publishes and waits for the broker ack.
|
|
43
|
+
*
|
|
44
|
+
* Throws `TypeError` (invalid names), `SchemaNotFoundError` / `SchemaValidationError`
|
|
45
|
+
* (nothing is sent) or the send error (`PublishError`, `NotStartedError`...).
|
|
46
|
+
*/
|
|
47
|
+
publish(domain: string, aggregate: string, event: string, version: string, payload: Readonly<Record<string, unknown>>, options?: PublishOptions): Promise<PublishResult>;
|
|
48
|
+
}
|
|
49
|
+
/** Key bytes of a published record (spec §3): UTF-8, and `""`/absent → `null`. */
|
|
50
|
+
export declare function publishKey(key: string | null | undefined): Buffer | null;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** Publication of schema-validated events. */
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { SchemaNotFoundError, SchemaValidationError } from './errors.js';
|
|
4
|
+
import * as headersMod from './headers.js';
|
|
5
|
+
import { defaultLogger } from './logger.js';
|
|
6
|
+
import * as naming from './naming.js';
|
|
7
|
+
import { NoopTelemetry } from './telemetry.js';
|
|
8
|
+
import { createMessageInfo, } from './types.js';
|
|
9
|
+
import { errorFields, isPlainObject } from './util.js';
|
|
10
|
+
/**
|
|
11
|
+
* Validates the payload against the event schema and publishes it to the domain topic.
|
|
12
|
+
*
|
|
13
|
+
* Same flow as Python: names → schema validation → JSON serialization → headers → send inside
|
|
14
|
+
* the PRODUCER span (the injected `traceparent` reaches the broker) → metric + log.
|
|
15
|
+
*/
|
|
16
|
+
export class Publisher {
|
|
17
|
+
#sender;
|
|
18
|
+
#validator;
|
|
19
|
+
#producer;
|
|
20
|
+
/** Resource prefix applied to the topic (`null` = none). */
|
|
21
|
+
resourcePrefix;
|
|
22
|
+
#telemetry;
|
|
23
|
+
#logger;
|
|
24
|
+
#clock;
|
|
25
|
+
constructor(sender, validator, options) {
|
|
26
|
+
naming.serviceName(options.producer);
|
|
27
|
+
this.resourcePrefix = naming.resourcePrefix(options.resourcePrefix);
|
|
28
|
+
this.#sender = sender;
|
|
29
|
+
this.#validator = validator;
|
|
30
|
+
this.#producer = options.producer;
|
|
31
|
+
this.#telemetry = options.telemetry ?? new NoopTelemetry();
|
|
32
|
+
this.#logger = options.logger;
|
|
33
|
+
this.#clock = options.clock;
|
|
34
|
+
}
|
|
35
|
+
get #log() {
|
|
36
|
+
return this.#logger ?? defaultLogger();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Publishes and waits for the broker ack.
|
|
40
|
+
*
|
|
41
|
+
* Throws `TypeError` (invalid names), `SchemaNotFoundError` / `SchemaValidationError`
|
|
42
|
+
* (nothing is sent) or the send error (`PublishError`, `NotStartedError`...).
|
|
43
|
+
*/
|
|
44
|
+
async publish(domain, aggregate, event, version, payload, options = {}) {
|
|
45
|
+
const topic = naming.topic(domain, version, { resourcePrefix: this.resourcePrefix });
|
|
46
|
+
const eventType = naming.eventType(aggregate, event);
|
|
47
|
+
let info = createMessageInfo({
|
|
48
|
+
topic,
|
|
49
|
+
eventType,
|
|
50
|
+
messageId: '',
|
|
51
|
+
schemaVersion: version,
|
|
52
|
+
key: options.key,
|
|
53
|
+
correlationId: options.correlationId,
|
|
54
|
+
tenantId: options.tenantId,
|
|
55
|
+
});
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
this.#validator.validate({ domain, eventType, version, payload });
|
|
59
|
+
body = serialize(payload);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof SchemaValidationError || error instanceof SchemaNotFoundError) {
|
|
63
|
+
// A missing schema also counts as a schema error (parity with the consumer).
|
|
64
|
+
this.#telemetry.recordSchemaError(info);
|
|
65
|
+
this.#logSchemaFailure(info, error);
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
const messageHeaders = headersMod.buildPublishHeaders({
|
|
70
|
+
eventType,
|
|
71
|
+
schemaVersion: version,
|
|
72
|
+
producer: this.#producer,
|
|
73
|
+
correlationId: options.correlationId,
|
|
74
|
+
idempotencyKey: options.idempotencyKey,
|
|
75
|
+
tenantId: options.tenantId,
|
|
76
|
+
clock: this.#clock,
|
|
77
|
+
});
|
|
78
|
+
info = createMessageInfo({
|
|
79
|
+
...info,
|
|
80
|
+
messageId: messageHeaders[headersMod.MESSAGE_ID] ?? '',
|
|
81
|
+
correlationId: messageHeaders[headersMod.CORRELATION_ID] ?? null,
|
|
82
|
+
});
|
|
83
|
+
const key = publishKey(options.key);
|
|
84
|
+
const started = performance.now();
|
|
85
|
+
let record;
|
|
86
|
+
try {
|
|
87
|
+
record = await this.#telemetry.publishSpan(info, messageHeaders, () =>
|
|
88
|
+
// Encoded inside the span: includes the injected traceparent.
|
|
89
|
+
this.#sender.send(topic, body, { key, headers: headersMod.encode(messageHeaders) }));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const durationMs = performance.now() - started;
|
|
93
|
+
this.#telemetry.recordPublish(info, { status: 'error', durationMs });
|
|
94
|
+
this.#log.error('message_publish_failed', {
|
|
95
|
+
messaging: messagingFields(info, durationMs),
|
|
96
|
+
error: errorFields(error),
|
|
97
|
+
});
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const durationMs = performance.now() - started;
|
|
101
|
+
this.#telemetry.recordPublish(info, { status: 'success', durationMs });
|
|
102
|
+
const fields = messagingFields(info, durationMs);
|
|
103
|
+
fields.partition = record.partition;
|
|
104
|
+
fields.offset = record.offset;
|
|
105
|
+
this.#log.info('message_published', { messaging: fields });
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
messageId: info.messageId,
|
|
108
|
+
topic: record.topic,
|
|
109
|
+
partition: record.partition,
|
|
110
|
+
offset: record.offset,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
#logSchemaFailure(info, error) {
|
|
114
|
+
const messaging = {
|
|
115
|
+
event_type: info.eventType,
|
|
116
|
+
schema_version: info.schemaVersion,
|
|
117
|
+
topic: info.topic,
|
|
118
|
+
};
|
|
119
|
+
if (info.correlationId !== null)
|
|
120
|
+
messaging.correlation_id = info.correlationId;
|
|
121
|
+
if (info.tenantId)
|
|
122
|
+
messaging.tenant_id = info.tenantId;
|
|
123
|
+
const errorInfo = errorFields(error);
|
|
124
|
+
if (error instanceof SchemaValidationError)
|
|
125
|
+
errorInfo.field = error.field;
|
|
126
|
+
this.#log.error('message_schema_validation_failed', { messaging, error: errorInfo });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Key bytes of a published record (spec §3): UTF-8, and `""`/absent → `null`. */
|
|
130
|
+
export function publishKey(key) {
|
|
131
|
+
return key ? Buffer.from(key, 'utf8') : null;
|
|
132
|
+
}
|
|
133
|
+
const NOT_SERIALIZABLE = 'payload is not JSON serializable';
|
|
134
|
+
/**
|
|
135
|
+
* Strict JSON check (Python `json.dumps(..., allow_nan=False)`): only plain objects, arrays,
|
|
136
|
+
* strings, finite numbers, booleans and `null`. `undefined` object properties are omitted
|
|
137
|
+
* (treated as absent, like the schema validation does); anything else (NaN/Infinity, bigint,
|
|
138
|
+
* Date, Map/Set, class instances, functions, symbols, cycles) is rejected.
|
|
139
|
+
*/
|
|
140
|
+
function assertJson(value, ancestors) {
|
|
141
|
+
switch (typeof value) {
|
|
142
|
+
case 'string':
|
|
143
|
+
case 'boolean':
|
|
144
|
+
return;
|
|
145
|
+
case 'number':
|
|
146
|
+
if (!Number.isFinite(value))
|
|
147
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
148
|
+
return;
|
|
149
|
+
case 'object': {
|
|
150
|
+
if (value === null)
|
|
151
|
+
return;
|
|
152
|
+
if (ancestors.has(value))
|
|
153
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
154
|
+
ancestors.add(value);
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
157
|
+
if (!(i in value) || value[i] === undefined)
|
|
158
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
159
|
+
assertJson(value[i], ancestors);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else if (isPlainObject(value)) {
|
|
163
|
+
if (Object.getOwnPropertySymbols(value).length > 0)
|
|
164
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
165
|
+
for (const item of Object.values(value)) {
|
|
166
|
+
if (item !== undefined)
|
|
167
|
+
assertJson(item, ancestors);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
172
|
+
}
|
|
173
|
+
ancestors.delete(value);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
default:
|
|
177
|
+
throw new SchemaValidationError('', NOT_SERIALIZABLE);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** Compact UTF-8 JSON; never includes payload values in the error. */
|
|
181
|
+
function serialize(payload) {
|
|
182
|
+
assertJson(payload, new Set());
|
|
183
|
+
return Buffer.from(JSON.stringify(payload), 'utf8');
|
|
184
|
+
}
|
|
185
|
+
function messagingFields(info, durationMs) {
|
|
186
|
+
const fields = {
|
|
187
|
+
message_id: info.messageId,
|
|
188
|
+
event_type: info.eventType,
|
|
189
|
+
schema_version: info.schemaVersion,
|
|
190
|
+
topic: info.topic,
|
|
191
|
+
has_key: Boolean(info.key),
|
|
192
|
+
correlation_id: info.correlationId,
|
|
193
|
+
publish_duration_ms: Math.round(durationMs * 1000) / 1000,
|
|
194
|
+
};
|
|
195
|
+
if (info.tenantId)
|
|
196
|
+
fields.tenant_id = info.tenantId;
|
|
197
|
+
return fields;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=publisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../src/publisher.ts"],"names":[],"mappings":"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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default schedule: 30 s, 5 min, 1 h, 12 h (~13h35min before the DLQ; same as Python's
|
|
3
|
+
* `(30, 300, 3600, 43200)`, spec §6).
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_RETRY_DELAYS_MS: readonly number[];
|
|
6
|
+
export interface RetryPolicyOptions {
|
|
7
|
+
/** Delay of each retry, in milliseconds; whole seconds, positive and distinct labels. */
|
|
8
|
+
delaysMs?: readonly number[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Backoff through retry topics: attempt `n` (0-based) waits `delaysMs[n]`.
|
|
12
|
+
*
|
|
13
|
+
* When attempts are exhausted (`x-retry-count >= delaysMs.length`) the message goes to the DLQ.
|
|
14
|
+
* `delaysMs: []` disables retry (every transient failure goes to the DLQ). Immutable.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RetryPolicy {
|
|
17
|
+
readonly delaysMs: readonly number[];
|
|
18
|
+
constructor(options?: RetryPolicyOptions);
|
|
19
|
+
get maxRetries(): number;
|
|
20
|
+
/** Delay (ms) of the next attempt, or `null` when exhausted. */
|
|
21
|
+
delayFor(retryCount: number): number | null;
|
|
22
|
+
/** Retry topics of the group, in schedule order. */
|
|
23
|
+
retryTopics(mainTopic: string, group: string): string[];
|
|
24
|
+
}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as naming from './naming.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default schedule: 30 s, 5 min, 1 h, 12 h (~13h35min before the DLQ; same as Python's
|
|
4
|
+
* `(30, 300, 3600, 43200)`, spec §6).
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_RETRY_DELAYS_MS = Object.freeze([
|
|
7
|
+
30_000, 300_000, 3_600_000, 43_200_000,
|
|
8
|
+
]);
|
|
9
|
+
/**
|
|
10
|
+
* Backoff through retry topics: attempt `n` (0-based) waits `delaysMs[n]`.
|
|
11
|
+
*
|
|
12
|
+
* When attempts are exhausted (`x-retry-count >= delaysMs.length`) the message goes to the DLQ.
|
|
13
|
+
* `delaysMs: []` disables retry (every transient failure goes to the DLQ). Immutable.
|
|
14
|
+
*/
|
|
15
|
+
export class RetryPolicy {
|
|
16
|
+
delaysMs;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
const delays = Object.freeze([...(options.delaysMs ?? DEFAULT_RETRY_DELAYS_MS)]);
|
|
19
|
+
const labels = delays.map((delay) => naming.delayLabel(delay));
|
|
20
|
+
if (new Set(labels).size !== labels.length) {
|
|
21
|
+
throw new TypeError(`retry delays must be distinct; got [${delays.join(', ')}]`);
|
|
22
|
+
}
|
|
23
|
+
this.delaysMs = delays;
|
|
24
|
+
Object.freeze(this);
|
|
25
|
+
}
|
|
26
|
+
get maxRetries() {
|
|
27
|
+
return this.delaysMs.length;
|
|
28
|
+
}
|
|
29
|
+
/** Delay (ms) of the next attempt, or `null` when exhausted. */
|
|
30
|
+
delayFor(retryCount) {
|
|
31
|
+
if (retryCount < 0)
|
|
32
|
+
throw new RangeError(`retry_count must be >= 0; got ${retryCount}`);
|
|
33
|
+
if (retryCount >= this.delaysMs.length)
|
|
34
|
+
return null;
|
|
35
|
+
return this.delaysMs[retryCount] ?? null;
|
|
36
|
+
}
|
|
37
|
+
/** Retry topics of the group, in schedule order. */
|
|
38
|
+
retryTopics(mainTopic, group) {
|
|
39
|
+
return this.delaysMs.map((delay) => naming.retryTopic(mainTopic, group, delay));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACtE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU;CACvC,CAAC,CAAC;AAOH;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IACb,QAAQ,CAAoB;IAErC,YAAY,OAAO,GAAuB,EAAE;QAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,uBAAuB,CAAC,CAAC,CAAC,CAAC;QACjF,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;YAC3C,MAAM,IAAI,SAAS,CAAC,uCAAuC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,gEAAgE;IAChE,QAAQ,CAAC,UAAkB;QACzB,IAAI,UAAU,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,iCAAiC,UAAU,EAAE,CAAC,CAAC;QACxF,IAAI,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED,oDAAoD;IACpD,WAAW,CAAC,SAAiB,EAAE,KAAa;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAClF,CAAC;CACF"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading and validation of JSON Schemas (Draft 2020-12).
|
|
3
|
+
*
|
|
4
|
+
* No error message produced here contains payload *values* (PII): only the JSON pointer of the
|
|
5
|
+
* field, the name of the violated rule and, for `required` / `additionalProperties`, the names
|
|
6
|
+
* of the properties involved. Wording, the selected error and the checked formats follow the
|
|
7
|
+
* Python SDK (`jsonschema` + `best_match`).
|
|
8
|
+
*/
|
|
9
|
+
import type { JsonObject, PayloadValidationRequest, PayloadValidator, SchemaLoader, SchemaValidator, SchemaValidatorFactory } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Schemas in memory, indexed by `"domain/eventSlug/version"` (spec §17). Stores and returns
|
|
12
|
+
* deep copies: caller mutations never leak.
|
|
13
|
+
*/
|
|
14
|
+
export declare class InMemorySchemaLoader implements SchemaLoader {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(schemas: Readonly<Record<string, JsonObject>>);
|
|
17
|
+
load(domain: string, event: string, version: string): JsonObject;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Asks `loaders` in order: the first one that does not throw `SchemaNotFoundError` wins.
|
|
21
|
+
*
|
|
22
|
+
* Typical use: the service's own schemas first, the SDK's bundled contracts after them:
|
|
23
|
+
* `new ChainSchemaLoader(new FileSystemSchemaLoader('schemas'), contracts.schemaLoader())`.
|
|
24
|
+
* Any other error of a loader (I/O, `SchemaValidationError` for a malformed file) propagates at
|
|
25
|
+
* once, without asking the next ones. None found → `SchemaNotFoundError`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class ChainSchemaLoader implements SchemaLoader {
|
|
28
|
+
#private;
|
|
29
|
+
constructor(...loaders: SchemaLoader[]);
|
|
30
|
+
get loaders(): readonly SchemaLoader[];
|
|
31
|
+
load(domain: string, event: string, version: string): JsonObject;
|
|
32
|
+
}
|
|
33
|
+
/** File-system primitives used by {@link FileSystemSchemaLoader} (injectable for tests). */
|
|
34
|
+
export interface SchemaFileSystem {
|
|
35
|
+
readFile(filePath: string): Buffer;
|
|
36
|
+
realpath(filePath: string): string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reads `root/{domain}/{event}/{version}.json`.
|
|
40
|
+
*
|
|
41
|
+
* Protected against path traversal: empty components, `.`/`..`, separators or NUL are refused,
|
|
42
|
+
* and the final path (after resolving symlinks) must stay inside `root`. Otherwise →
|
|
43
|
+
* `SchemaNotFoundError`. Only a missing file (`ENOENT`, `ENOTDIR`, `EISDIR`) becomes
|
|
44
|
+
* `SchemaNotFoundError`; other I/O errors propagate unchanged. Non-UTF-8, malformed JSON or a
|
|
45
|
+
* non-object → `SchemaValidationError`.
|
|
46
|
+
*
|
|
47
|
+
* Synchronous I/O: meant to be called once per schema (`SchemaRegistry` caches validators).
|
|
48
|
+
*/
|
|
49
|
+
export declare class FileSystemSchemaLoader implements SchemaLoader {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(root: string, options?: {
|
|
52
|
+
fileSystem?: SchemaFileSystem;
|
|
53
|
+
});
|
|
54
|
+
get root(): string;
|
|
55
|
+
load(domain: string, event: string, version: string): JsonObject;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validates payloads with Ajv (Draft 2020-12), including `format`.
|
|
59
|
+
*
|
|
60
|
+
* The schema is checked in the constructor (an invalid schema fails early). `$ref` is never
|
|
61
|
+
* fetched over the network: only local references (`#/$defs/...`, anchors, embedded subschemas
|
|
62
|
+
* with `$id`) resolve, and every `$ref`/`$dynamicRef` is resolved statically at construction;
|
|
63
|
+
* one that does not resolve (remote included) becomes `SchemaValidationError`.
|
|
64
|
+
*
|
|
65
|
+
* The reported error is the one Python's `jsonschema.best_match` would pick (shallowest
|
|
66
|
+
* instance path, lexicographically last sibling, anyOf/oneOf descended into their context).
|
|
67
|
+
*/
|
|
68
|
+
export declare class JsonSchemaValidator implements SchemaValidator {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(schema: JsonObject);
|
|
71
|
+
validate(payload: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Implements {@link PayloadValidator}: resolves the schema of an event and validates.
|
|
75
|
+
*
|
|
76
|
+
* Validators are cached per `[domain, slug, version]`. Load failures (`SchemaNotFoundError`)
|
|
77
|
+
* and invalid schemas are not cached, so a schema published later is found without restarting.
|
|
78
|
+
*/
|
|
79
|
+
export declare class SchemaRegistry implements PayloadValidator {
|
|
80
|
+
#private;
|
|
81
|
+
constructor(loader: SchemaLoader, validatorFactory?: SchemaValidatorFactory);
|
|
82
|
+
validate(request: PayloadValidationRequest): void;
|
|
83
|
+
}
|