@babelqueue/core 0.1.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 +126 -0
- package/dist/index.cjs +200 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muhammet Şafak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# BabelQueue for Node.js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/BabelQueue/babelqueue-node/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@babelqueue/core)
|
|
5
|
+
[](https://www.npmjs.com/package/@babelqueue/core)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
> **Polyglot Queues, Simplified.** Read and write the canonical BabelQueue message
|
|
9
|
+
> envelope from Node.js — so your Node services exchange messages with Laravel,
|
|
10
|
+
> Symfony, Python, Go and .NET over one strict JSON format, on the broker you
|
|
11
|
+
> already run.
|
|
12
|
+
|
|
13
|
+
This is the framework-agnostic **Node/TypeScript core**: the wire-envelope codec,
|
|
14
|
+
contracts and dead-letter helpers — **zero runtime dependencies**, shipped as a
|
|
15
|
+
dual **ESM + CommonJS** package with bundled types. The full standard is documented
|
|
16
|
+
at **[babelqueue.com](https://babelqueue.com)**.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @babelqueue/core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Node `>=18`.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { EnvelopeCodec } from "@babelqueue/core";
|
|
30
|
+
|
|
31
|
+
// Produce — build the canonical envelope and publish the JSON to your broker.
|
|
32
|
+
const env = EnvelopeCodec.make(
|
|
33
|
+
"urn:babel:orders:created",
|
|
34
|
+
{ order_id: 1042 },
|
|
35
|
+
{ queue: "orders" },
|
|
36
|
+
);
|
|
37
|
+
const body = EnvelopeCodec.encode(env); // compact UTF-8 JSON string
|
|
38
|
+
// await redis.rpush("queues:orders", body);
|
|
39
|
+
// / channel.sendToQueue("orders", Buffer.from(body));
|
|
40
|
+
|
|
41
|
+
// Consume — decode a message produced by ANY BabelQueue SDK.
|
|
42
|
+
const incoming = EnvelopeCodec.decode(body);
|
|
43
|
+
if (EnvelopeCodec.accepts(incoming)) {
|
|
44
|
+
// `incoming` is now narrowed to a fully-typed Envelope
|
|
45
|
+
switch (EnvelopeCodec.urn(incoming)) {
|
|
46
|
+
case "urn:babel:orders:created":
|
|
47
|
+
console.log(incoming.data.order_id, incoming.trace_id);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
CommonJS works too:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const { EnvelopeCodec } = require("@babelqueue/core");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The envelope is identical to every other SDK's:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"job": "urn:babel:orders:created",
|
|
64
|
+
"trace_id": "…",
|
|
65
|
+
"data": { "order_id": 1042 },
|
|
66
|
+
"meta": { "id": "…", "queue": "orders", "lang": "node", "schema_version": 1, "created_at": 1749132727000 },
|
|
67
|
+
"attempts": 0
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Typed messages (optional)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { EnvelopeCodec, type PolyglotMessage } from "@babelqueue/core";
|
|
75
|
+
|
|
76
|
+
class OrderCreated implements PolyglotMessage {
|
|
77
|
+
constructor(private readonly orderId: number) {}
|
|
78
|
+
getBabelUrn() {
|
|
79
|
+
return "urn:babel:orders:created";
|
|
80
|
+
}
|
|
81
|
+
toPayload() {
|
|
82
|
+
return { order_id: this.orderId };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const env = EnvelopeCodec.fromMessage(new OrderCreated(1042), "orders");
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Continue an existing trace by adding `getBabelTraceId(): string | null` (see
|
|
90
|
+
`HasTraceId`), or pass `{ traceId }` to `EnvelopeCodec.make`.
|
|
91
|
+
|
|
92
|
+
### Dead-letter
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { annotate, EnvelopeCodec } from "@babelqueue/core";
|
|
96
|
+
|
|
97
|
+
const dlq = annotate(env, "failed", "orders", { attempts: 3, error: "boom" });
|
|
98
|
+
// publish EnvelopeCodec.encode(dlq) to the "orders.dlq" queue
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`annotate` returns a copy — the original envelope is preserved unchanged inside
|
|
102
|
+
the dead-lettered message, so any-language consumers can still read it.
|
|
103
|
+
|
|
104
|
+
## What this core is (and isn't)
|
|
105
|
+
|
|
106
|
+
It enforces the **contract**: the envelope shape, URN identity, trace propagation,
|
|
107
|
+
schema-version gating and the dead-letter block. It is intentionally **not** a
|
|
108
|
+
worker/runtime — broker wiring, acks and retry loops stay in your own code (or a
|
|
109
|
+
future thin adapter), exactly as with the other SDK cores.
|
|
110
|
+
|
|
111
|
+
`UnknownUrnStrategy` (`FAIL`, `DELETE`, `RELEASE`, `DEAD_LETTER`) is provided for
|
|
112
|
+
adapters to act on.
|
|
113
|
+
|
|
114
|
+
## Conformance
|
|
115
|
+
|
|
116
|
+
This core passes the shared **cross-SDK conformance suite** (vendored under
|
|
117
|
+
[`test/conformance/`](test/conformance)) — the same fixtures every BabelQueue SDK
|
|
118
|
+
must satisfy, so a Node producer and, say, a Laravel consumer agree byte-for-byte.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm test
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
[MIT](LICENSE) © Muhammet Şafak
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BabelQueueError: () => BabelQueueError,
|
|
24
|
+
EnvelopeCodec: () => EnvelopeCodec,
|
|
25
|
+
SCHEMA_VERSION: () => SCHEMA_VERSION,
|
|
26
|
+
SOURCE_LANG: () => SOURCE_LANG,
|
|
27
|
+
UnknownUrnError: () => UnknownUrnError,
|
|
28
|
+
UnknownUrnStrategy: () => UnknownUrnStrategy,
|
|
29
|
+
annotate: () => annotate,
|
|
30
|
+
deadLetter: () => deadLetter_exports
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/codec.ts
|
|
35
|
+
var import_node_crypto = require("crypto");
|
|
36
|
+
|
|
37
|
+
// src/errors.ts
|
|
38
|
+
var BabelQueueError = class extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "BabelQueueError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var UnknownUrnError = class extends BabelQueueError {
|
|
45
|
+
constructor(urn) {
|
|
46
|
+
super(`No handler is mapped for the message URN "${urn}".`);
|
|
47
|
+
this.name = "UnknownUrnError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/codec.ts
|
|
52
|
+
var SCHEMA_VERSION = 1;
|
|
53
|
+
var SOURCE_LANG = "node";
|
|
54
|
+
var EnvelopeCodec = {
|
|
55
|
+
SCHEMA_VERSION,
|
|
56
|
+
SOURCE_LANG,
|
|
57
|
+
/**
|
|
58
|
+
* Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id
|
|
59
|
+
* unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.
|
|
60
|
+
* Throws {@link BabelQueueError} when the URN is blank.
|
|
61
|
+
*/
|
|
62
|
+
make(urn, data, options = {}) {
|
|
63
|
+
const resolvedUrn = (urn ?? "").trim();
|
|
64
|
+
if (resolvedUrn === "") {
|
|
65
|
+
throw new BabelQueueError(
|
|
66
|
+
"A polyglot message must expose a stable, non-empty URN so consumers can identify it without any class name."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const traceId = (options.traceId ?? "").trim() || (0, import_node_crypto.randomUUID)();
|
|
70
|
+
return {
|
|
71
|
+
job: resolvedUrn,
|
|
72
|
+
trace_id: traceId,
|
|
73
|
+
data: { ...data },
|
|
74
|
+
meta: {
|
|
75
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
76
|
+
queue: options.queue ?? "default",
|
|
77
|
+
lang: SOURCE_LANG,
|
|
78
|
+
schema_version: SCHEMA_VERSION,
|
|
79
|
+
created_at: Date.now()
|
|
80
|
+
},
|
|
81
|
+
attempts: 0
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Build the envelope from a {@link PolyglotMessage}. If the message also
|
|
86
|
+
* implements {@link HasTraceId} and returns a non-empty value, that trace id is
|
|
87
|
+
* reused.
|
|
88
|
+
*/
|
|
89
|
+
fromMessage(message, queue = "default") {
|
|
90
|
+
const traceId = typeof message.getBabelTraceId === "function" ? message.getBabelTraceId() ?? void 0 : void 0;
|
|
91
|
+
return EnvelopeCodec.make(message.getBabelUrn(), message.toPayload(), {
|
|
92
|
+
queue,
|
|
93
|
+
traceId
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the
|
|
98
|
+
* canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching
|
|
99
|
+
* the other SDK cores.
|
|
100
|
+
*/
|
|
101
|
+
encode(envelope) {
|
|
102
|
+
return JSON.stringify(envelope);
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Parse a raw JSON body. Returns `{}` for malformed or non-object input (call
|
|
106
|
+
* {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound
|
|
107
|
+
* alias into `job`.
|
|
108
|
+
*/
|
|
109
|
+
decode(raw) {
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = JSON.parse(raw);
|
|
113
|
+
} catch {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
const envelope = parsed;
|
|
120
|
+
if (!envelope.job && typeof envelope.urn === "string") {
|
|
121
|
+
envelope.job = envelope.urn;
|
|
122
|
+
}
|
|
123
|
+
return envelope;
|
|
124
|
+
},
|
|
125
|
+
/** The message URN — canonical `job`, with `urn` accepted as an alias. */
|
|
126
|
+
urn(envelope) {
|
|
127
|
+
const value = envelope?.job ?? envelope?.urn ?? "";
|
|
128
|
+
return typeof value === "string" ? value.trim() : "";
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Whether a consumer should accept this envelope. Rejects a missing URN, an
|
|
132
|
+
* unsupported `meta.schema_version`, a non-object `data`, a non-integer
|
|
133
|
+
* `attempts`, or a blank `trace_id` — the consumer-side counterpart to the
|
|
134
|
+
* producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.
|
|
135
|
+
*/
|
|
136
|
+
accepts(envelope) {
|
|
137
|
+
if (EnvelopeCodec.urn(envelope) === "") {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const meta = envelope.meta;
|
|
141
|
+
if (meta === null || typeof meta !== "object" || meta.schema_version !== SCHEMA_VERSION) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const data = envelope.data;
|
|
145
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const attempts = envelope.attempts;
|
|
149
|
+
if (typeof attempts !== "number" || !Number.isInteger(attempts)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const traceId = envelope.trace_id;
|
|
153
|
+
if (typeof traceId !== "string" || traceId.trim() === "") {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/deadLetter.ts
|
|
161
|
+
var deadLetter_exports = {};
|
|
162
|
+
__export(deadLetter_exports, {
|
|
163
|
+
annotate: () => annotate
|
|
164
|
+
});
|
|
165
|
+
function annotate(envelope, reason, originalQueue, options = {}) {
|
|
166
|
+
const deadLetter = {
|
|
167
|
+
reason,
|
|
168
|
+
error: options.error ?? null,
|
|
169
|
+
exception: options.exception ?? null,
|
|
170
|
+
failed_at: Date.now(),
|
|
171
|
+
original_queue: originalQueue,
|
|
172
|
+
attempts: options.attempts ?? envelope.attempts ?? 0,
|
|
173
|
+
lang: SOURCE_LANG
|
|
174
|
+
};
|
|
175
|
+
return { ...envelope, dead_letter: deadLetter };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/routing.ts
|
|
179
|
+
var UnknownUrnStrategy = {
|
|
180
|
+
/** Surface an error; let the worker decide. */
|
|
181
|
+
FAIL: "fail",
|
|
182
|
+
/** Drop the message. */
|
|
183
|
+
DELETE: "delete",
|
|
184
|
+
/** Requeue for another consumer. */
|
|
185
|
+
RELEASE: "release",
|
|
186
|
+
/** Route to the dead-letter queue. */
|
|
187
|
+
DEAD_LETTER: "dead_letter"
|
|
188
|
+
};
|
|
189
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
190
|
+
0 && (module.exports = {
|
|
191
|
+
BabelQueueError,
|
|
192
|
+
EnvelopeCodec,
|
|
193
|
+
SCHEMA_VERSION,
|
|
194
|
+
SOURCE_LANG,
|
|
195
|
+
UnknownUrnError,
|
|
196
|
+
UnknownUrnStrategy,
|
|
197
|
+
annotate,
|
|
198
|
+
deadLetter
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/codec.ts","../src/errors.ts","../src/deadLetter.ts","../src/routing.ts"],"sourcesContent":["/**\n * BabelQueue — Polyglot Queues, Simplified.\n *\n * The framework-agnostic Node/TypeScript core: the canonical wire-envelope codec,\n * contracts and dead-letter helpers. Zero runtime dependencies.\n *\n * ```ts\n * import { EnvelopeCodec } from \"@babelqueue/core\";\n *\n * const env = EnvelopeCodec.make(\"urn:babel:orders:created\", { order_id: 1042 });\n * const body = EnvelopeCodec.encode(env); // publish body to Redis / RabbitMQ / ...\n * ```\n *\n * Full spec: https://babelqueue.com\n */\n\nexport { EnvelopeCodec, SCHEMA_VERSION, SOURCE_LANG } from \"./codec.js\";\nexport type {\n DeadLetter,\n Envelope,\n IncomingEnvelope,\n MakeOptions,\n Meta,\n} from \"./codec.js\";\n\nexport type { HasTraceId, PolyglotMessage } from \"./contracts.js\";\n\nexport { annotate } from \"./deadLetter.js\";\nexport type { AnnotateOptions } from \"./deadLetter.js\";\nexport * as deadLetter from \"./deadLetter.js\";\n\nexport { UnknownUrnStrategy } from \"./routing.js\";\n\nexport { BabelQueueError, UnknownUrnError } from \"./errors.js\";\n","import { randomUUID } from \"node:crypto\";\n\nimport type { HasTraceId, PolyglotMessage } from \"./contracts.js\";\nimport { BabelQueueError } from \"./errors.js\";\n\n/** The wire envelope schema version this core implements (versioned independently of the package version). */\nexport const SCHEMA_VERSION = 1;\n\n/** Stamped into `meta.lang` for envelopes produced by this core. */\nexport const SOURCE_LANG = \"node\";\n\n/** Immutable per-message metadata. */\nexport interface Meta {\n id: string;\n queue: string;\n lang: string;\n schema_version: number;\n /** Unix milliseconds, UTC. */\n created_at: number;\n}\n\n/** The additive block appended to an envelope when a message is dead-lettered. */\nexport interface DeadLetter {\n reason: string;\n error: string | null;\n exception: string | null;\n /** Unix milliseconds, UTC. */\n failed_at: number;\n original_queue: string;\n attempts: number;\n lang: string;\n}\n\n/**\n * The canonical BabelQueue wire message: a strict, language-neutral JSON shape\n * that every SDK produces and consumes identically. The property order here is\n * significant — it matches the other cores so {@link EnvelopeCodec.encode} is\n * byte-for-byte identical across the insertion-order languages (PHP/Python).\n */\nexport interface Envelope {\n /** The message URN (never a class name). */\n job: string;\n /** Correlation id, preserved across every hop. */\n trace_id: string;\n /** The pure-JSON payload. */\n data: Record<string, unknown>;\n meta: Meta;\n /** Top-level transport retry counter. */\n attempts: number;\n /** Present only once the message has been dead-lettered. */\n dead_letter?: DeadLetter;\n}\n\n/**\n * A decoded, not-yet-validated envelope. Fields are loosely typed because they\n * come off the wire; `urn` is accepted as an inbound alias for `job`. Narrow it\n * with {@link EnvelopeCodec.accepts} before trusting the contents.\n */\nexport interface IncomingEnvelope {\n job?: string;\n /** Inbound alias for `job`. */\n urn?: string;\n trace_id?: string;\n data?: unknown;\n meta?: unknown;\n attempts?: unknown;\n dead_letter?: unknown;\n}\n\n/** Options for {@link EnvelopeCodec.make}. */\nexport interface MakeOptions {\n /** Logical queue name recorded in `meta.queue` (default `\"default\"`). */\n queue?: string;\n /** Reuse an existing trace id (trace continuation) instead of minting one. */\n traceId?: string;\n}\n\n/**\n * Builds, encodes and decodes the canonical envelope — the single Node/TypeScript\n * implementation of the wire format.\n */\nexport const EnvelopeCodec = {\n SCHEMA_VERSION,\n SOURCE_LANG,\n\n /**\n * Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id\n * unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.\n * Throws {@link BabelQueueError} when the URN is blank.\n */\n make(\n urn: string,\n data: Record<string, unknown>,\n options: MakeOptions = {},\n ): Envelope {\n const resolvedUrn = (urn ?? \"\").trim();\n if (resolvedUrn === \"\") {\n throw new BabelQueueError(\n \"A polyglot message must expose a stable, non-empty URN so consumers can identify it without any class name.\",\n );\n }\n\n const traceId = (options.traceId ?? \"\").trim() || randomUUID();\n\n return {\n job: resolvedUrn,\n trace_id: traceId,\n data: { ...data },\n meta: {\n id: randomUUID(),\n queue: options.queue ?? \"default\",\n lang: SOURCE_LANG,\n schema_version: SCHEMA_VERSION,\n created_at: Date.now(),\n },\n attempts: 0,\n };\n },\n\n /**\n * Build the envelope from a {@link PolyglotMessage}. If the message also\n * implements {@link HasTraceId} and returns a non-empty value, that trace id is\n * reused.\n */\n fromMessage(\n message: PolyglotMessage & Partial<HasTraceId>,\n queue = \"default\",\n ): Envelope {\n const traceId =\n typeof message.getBabelTraceId === \"function\"\n ? (message.getBabelTraceId() ?? undefined)\n : undefined;\n\n return EnvelopeCodec.make(message.getBabelUrn(), message.toPayload(), {\n queue,\n traceId,\n });\n },\n\n /**\n * Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the\n * canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching\n * the other SDK cores.\n */\n encode(envelope: Envelope): string {\n return JSON.stringify(envelope);\n },\n\n /**\n * Parse a raw JSON body. Returns `{}` for malformed or non-object input (call\n * {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound\n * alias into `job`.\n */\n decode(raw: string): IncomingEnvelope {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return {};\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return {};\n }\n\n const envelope = parsed as IncomingEnvelope;\n if (!envelope.job && typeof envelope.urn === \"string\") {\n envelope.job = envelope.urn;\n }\n return envelope;\n },\n\n /** The message URN — canonical `job`, with `urn` accepted as an alias. */\n urn(envelope: IncomingEnvelope): string {\n const value = envelope?.job ?? envelope?.urn ?? \"\";\n return typeof value === \"string\" ? value.trim() : \"\";\n },\n\n /**\n * Whether a consumer should accept this envelope. Rejects a missing URN, an\n * unsupported `meta.schema_version`, a non-object `data`, a non-integer\n * `attempts`, or a blank `trace_id` — the consumer-side counterpart to the\n * producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.\n */\n accepts(envelope: IncomingEnvelope): envelope is Envelope {\n if (EnvelopeCodec.urn(envelope) === \"\") {\n return false;\n }\n\n const meta = envelope.meta;\n if (\n meta === null ||\n typeof meta !== \"object\" ||\n (meta as Meta).schema_version !== SCHEMA_VERSION\n ) {\n return false;\n }\n\n const data = envelope.data;\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return false;\n }\n\n const attempts = envelope.attempts;\n if (typeof attempts !== \"number\" || !Number.isInteger(attempts)) {\n return false;\n }\n\n const traceId = envelope.trace_id;\n if (typeof traceId !== \"string\" || traceId.trim() === \"\") {\n return false;\n }\n\n return true;\n },\n} as const;\n","/** Base error for all BabelQueue failures. */\nexport class BabelQueueError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BabelQueueError\";\n }\n}\n\n/** Raised when no handler is mapped for a message URN. */\nexport class UnknownUrnError extends BabelQueueError {\n constructor(urn: string) {\n super(`No handler is mapped for the message URN \"${urn}\".`);\n this.name = \"UnknownUrnError\";\n }\n}\n","import type { DeadLetter, Envelope } from \"./codec.js\";\nimport { SOURCE_LANG } from \"./codec.js\";\n\n/** Options for {@link annotate}. */\nexport interface AnnotateOptions {\n /** Defaults to the envelope's current `attempts`. */\n attempts?: number;\n /** A human-readable error message (JSON `null` when omitted). */\n error?: string | null;\n /** The originating error type/class name (JSON `null` when omitted). */\n exception?: string | null;\n}\n\n/**\n * Return a copy of the envelope with a `dead_letter` block attached, recording\n * why and where it failed. The original envelope is preserved unchanged inside\n * the result, so any-language consumers can still read it.\n */\nexport function annotate(\n envelope: Envelope,\n reason: string,\n originalQueue: string,\n options: AnnotateOptions = {},\n): Envelope {\n const deadLetter: DeadLetter = {\n reason,\n error: options.error ?? null,\n exception: options.exception ?? null,\n failed_at: Date.now(),\n original_queue: originalQueue,\n attempts: options.attempts ?? envelope.attempts ?? 0,\n lang: SOURCE_LANG,\n };\n\n return { ...envelope, dead_letter: deadLetter };\n}\n","/**\n * What a consumer does with a message whose URN has no registered handler.\n * Mirrors the constants in every other SDK core.\n */\nexport const UnknownUrnStrategy = {\n /** Surface an error; let the worker decide. */\n FAIL: \"fail\",\n /** Drop the message. */\n DELETE: \"delete\",\n /** Requeue for another consumer. */\n RELEASE: \"release\",\n /** Route to the dead-letter queue. */\n DEAD_LETTER: \"dead_letter\",\n} as const;\n\nexport type UnknownUrnStrategy =\n (typeof UnknownUrnStrategy)[keyof typeof UnknownUrnStrategy];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA2B;;;ACCpB,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,kBAAN,cAA8B,gBAAgB;AAAA,EACnD,YAAY,KAAa;AACvB,UAAM,6CAA6C,GAAG,IAAI;AAC1D,SAAK,OAAO;AAAA,EACd;AACF;;;ADRO,IAAM,iBAAiB;AAGvB,IAAM,cAAc;AAwEpB,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KACE,KACA,MACA,UAAuB,CAAC,GACd;AACV,UAAM,eAAe,OAAO,IAAI,KAAK;AACrC,QAAI,gBAAgB,IAAI;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,WAAW,IAAI,KAAK,SAAK,+BAAW;AAE7D,WAAO;AAAA,MACL,KAAK;AAAA,MACL,UAAU;AAAA,MACV,MAAM,EAAE,GAAG,KAAK;AAAA,MAChB,MAAM;AAAA,QACJ,QAAI,+BAAW;AAAA,QACf,OAAO,QAAQ,SAAS;AAAA,QACxB,MAAM;AAAA,QACN,gBAAgB;AAAA,QAChB,YAAY,KAAK,IAAI;AAAA,MACvB;AAAA,MACA,UAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YACE,SACA,QAAQ,WACE;AACV,UAAM,UACJ,OAAO,QAAQ,oBAAoB,aAC9B,QAAQ,gBAAgB,KAAK,SAC9B;AAEN,WAAO,cAAc,KAAK,QAAQ,YAAY,GAAG,QAAQ,UAAU,GAAG;AAAA,MACpE;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,UAA4B;AACjC,WAAO,KAAK,UAAU,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,KAA+B;AACpC,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AACA,QAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,WAAW;AACjB,QAAI,CAAC,SAAS,OAAO,OAAO,SAAS,QAAQ,UAAU;AACrD,eAAS,MAAM,SAAS;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,UAAoC;AACtC,UAAM,QAAQ,UAAU,OAAO,UAAU,OAAO;AAChD,WAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,UAAkD;AACxD,QAAI,cAAc,IAAI,QAAQ,MAAM,IAAI;AACtC,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,SAAS;AACtB,QACE,SAAS,QACT,OAAO,SAAS,YACf,KAAc,mBAAmB,gBAClC;AACA,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,SAAS;AACtB,QAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AACpE,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,SAAS;AAC1B,QAAI,OAAO,aAAa,YAAY,CAAC,OAAO,UAAU,QAAQ,GAAG;AAC/D,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,SAAS;AACzB,QAAI,OAAO,YAAY,YAAY,QAAQ,KAAK,MAAM,IAAI;AACxD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;;;AEtNA;AAAA;AAAA;AAAA;AAkBO,SAAS,SACd,UACA,QACA,eACA,UAA2B,CAAC,GAClB;AACV,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA,OAAO,QAAQ,SAAS;AAAA,IACxB,WAAW,QAAQ,aAAa;AAAA,IAChC,WAAW,KAAK,IAAI;AAAA,IACpB,gBAAgB;AAAA,IAChB,UAAU,QAAQ,YAAY,SAAS,YAAY;AAAA,IACnD,MAAM;AAAA,EACR;AAEA,SAAO,EAAE,GAAG,UAAU,aAAa,WAAW;AAChD;;;AC/BO,IAAM,qBAAqB;AAAA;AAAA,EAEhC,MAAM;AAAA;AAAA,EAEN,QAAQ;AAAA;AAAA,EAER,SAAS;AAAA;AAAA,EAET,aAAa;AACf;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A message that can be produced as a polyglot envelope. Implement it on your
|
|
3
|
+
* own classes/objects so {@link EnvelopeCodec.fromMessage} can build the canonical
|
|
4
|
+
* envelope without ever leaking a language-specific class name onto the wire.
|
|
5
|
+
*/
|
|
6
|
+
interface PolyglotMessage {
|
|
7
|
+
/** The stable URN that identifies this message across languages. */
|
|
8
|
+
getBabelUrn(): string;
|
|
9
|
+
/** The pure-JSON payload (no class instances). */
|
|
10
|
+
toPayload(): Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Optionally implemented alongside {@link PolyglotMessage} to continue an existing
|
|
14
|
+
* distributed trace instead of minting a fresh one.
|
|
15
|
+
*/
|
|
16
|
+
interface HasTraceId {
|
|
17
|
+
/** The trace id to reuse, or null/undefined to mint a new one. */
|
|
18
|
+
getBabelTraceId(): string | null | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The wire envelope schema version this core implements (versioned independently of the package version). */
|
|
22
|
+
declare const SCHEMA_VERSION = 1;
|
|
23
|
+
/** Stamped into `meta.lang` for envelopes produced by this core. */
|
|
24
|
+
declare const SOURCE_LANG = "node";
|
|
25
|
+
/** Immutable per-message metadata. */
|
|
26
|
+
interface Meta {
|
|
27
|
+
id: string;
|
|
28
|
+
queue: string;
|
|
29
|
+
lang: string;
|
|
30
|
+
schema_version: number;
|
|
31
|
+
/** Unix milliseconds, UTC. */
|
|
32
|
+
created_at: number;
|
|
33
|
+
}
|
|
34
|
+
/** The additive block appended to an envelope when a message is dead-lettered. */
|
|
35
|
+
interface DeadLetter {
|
|
36
|
+
reason: string;
|
|
37
|
+
error: string | null;
|
|
38
|
+
exception: string | null;
|
|
39
|
+
/** Unix milliseconds, UTC. */
|
|
40
|
+
failed_at: number;
|
|
41
|
+
original_queue: string;
|
|
42
|
+
attempts: number;
|
|
43
|
+
lang: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The canonical BabelQueue wire message: a strict, language-neutral JSON shape
|
|
47
|
+
* that every SDK produces and consumes identically. The property order here is
|
|
48
|
+
* significant — it matches the other cores so {@link EnvelopeCodec.encode} is
|
|
49
|
+
* byte-for-byte identical across the insertion-order languages (PHP/Python).
|
|
50
|
+
*/
|
|
51
|
+
interface Envelope {
|
|
52
|
+
/** The message URN (never a class name). */
|
|
53
|
+
job: string;
|
|
54
|
+
/** Correlation id, preserved across every hop. */
|
|
55
|
+
trace_id: string;
|
|
56
|
+
/** The pure-JSON payload. */
|
|
57
|
+
data: Record<string, unknown>;
|
|
58
|
+
meta: Meta;
|
|
59
|
+
/** Top-level transport retry counter. */
|
|
60
|
+
attempts: number;
|
|
61
|
+
/** Present only once the message has been dead-lettered. */
|
|
62
|
+
dead_letter?: DeadLetter;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A decoded, not-yet-validated envelope. Fields are loosely typed because they
|
|
66
|
+
* come off the wire; `urn` is accepted as an inbound alias for `job`. Narrow it
|
|
67
|
+
* with {@link EnvelopeCodec.accepts} before trusting the contents.
|
|
68
|
+
*/
|
|
69
|
+
interface IncomingEnvelope {
|
|
70
|
+
job?: string;
|
|
71
|
+
/** Inbound alias for `job`. */
|
|
72
|
+
urn?: string;
|
|
73
|
+
trace_id?: string;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
meta?: unknown;
|
|
76
|
+
attempts?: unknown;
|
|
77
|
+
dead_letter?: unknown;
|
|
78
|
+
}
|
|
79
|
+
/** Options for {@link EnvelopeCodec.make}. */
|
|
80
|
+
interface MakeOptions {
|
|
81
|
+
/** Logical queue name recorded in `meta.queue` (default `"default"`). */
|
|
82
|
+
queue?: string;
|
|
83
|
+
/** Reuse an existing trace id (trace continuation) instead of minting one. */
|
|
84
|
+
traceId?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Builds, encodes and decodes the canonical envelope — the single Node/TypeScript
|
|
88
|
+
* implementation of the wire format.
|
|
89
|
+
*/
|
|
90
|
+
declare const EnvelopeCodec: {
|
|
91
|
+
readonly SCHEMA_VERSION: 1;
|
|
92
|
+
readonly SOURCE_LANG: "node";
|
|
93
|
+
/**
|
|
94
|
+
* Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id
|
|
95
|
+
* unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.
|
|
96
|
+
* Throws {@link BabelQueueError} when the URN is blank.
|
|
97
|
+
*/
|
|
98
|
+
readonly make: (urn: string, data: Record<string, unknown>, options?: MakeOptions) => Envelope;
|
|
99
|
+
/**
|
|
100
|
+
* Build the envelope from a {@link PolyglotMessage}. If the message also
|
|
101
|
+
* implements {@link HasTraceId} and returns a non-empty value, that trace id is
|
|
102
|
+
* reused.
|
|
103
|
+
*/
|
|
104
|
+
readonly fromMessage: (message: PolyglotMessage & Partial<HasTraceId>, queue?: string) => Envelope;
|
|
105
|
+
/**
|
|
106
|
+
* Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the
|
|
107
|
+
* canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching
|
|
108
|
+
* the other SDK cores.
|
|
109
|
+
*/
|
|
110
|
+
readonly encode: (envelope: Envelope) => string;
|
|
111
|
+
/**
|
|
112
|
+
* Parse a raw JSON body. Returns `{}` for malformed or non-object input (call
|
|
113
|
+
* {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound
|
|
114
|
+
* alias into `job`.
|
|
115
|
+
*/
|
|
116
|
+
readonly decode: (raw: string) => IncomingEnvelope;
|
|
117
|
+
/** The message URN — canonical `job`, with `urn` accepted as an alias. */
|
|
118
|
+
readonly urn: (envelope: IncomingEnvelope) => string;
|
|
119
|
+
/**
|
|
120
|
+
* Whether a consumer should accept this envelope. Rejects a missing URN, an
|
|
121
|
+
* unsupported `meta.schema_version`, a non-object `data`, a non-integer
|
|
122
|
+
* `attempts`, or a blank `trace_id` — the consumer-side counterpart to the
|
|
123
|
+
* producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.
|
|
124
|
+
*/
|
|
125
|
+
readonly accepts: (envelope: IncomingEnvelope) => envelope is Envelope;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Options for {@link annotate}. */
|
|
129
|
+
interface AnnotateOptions {
|
|
130
|
+
/** Defaults to the envelope's current `attempts`. */
|
|
131
|
+
attempts?: number;
|
|
132
|
+
/** A human-readable error message (JSON `null` when omitted). */
|
|
133
|
+
error?: string | null;
|
|
134
|
+
/** The originating error type/class name (JSON `null` when omitted). */
|
|
135
|
+
exception?: string | null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Return a copy of the envelope with a `dead_letter` block attached, recording
|
|
139
|
+
* why and where it failed. The original envelope is preserved unchanged inside
|
|
140
|
+
* the result, so any-language consumers can still read it.
|
|
141
|
+
*/
|
|
142
|
+
declare function annotate(envelope: Envelope, reason: string, originalQueue: string, options?: AnnotateOptions): Envelope;
|
|
143
|
+
|
|
144
|
+
type deadLetter_AnnotateOptions = AnnotateOptions;
|
|
145
|
+
declare const deadLetter_annotate: typeof annotate;
|
|
146
|
+
declare namespace deadLetter {
|
|
147
|
+
export { type deadLetter_AnnotateOptions as AnnotateOptions, deadLetter_annotate as annotate };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a consumer does with a message whose URN has no registered handler.
|
|
152
|
+
* Mirrors the constants in every other SDK core.
|
|
153
|
+
*/
|
|
154
|
+
declare const UnknownUrnStrategy: {
|
|
155
|
+
/** Surface an error; let the worker decide. */
|
|
156
|
+
readonly FAIL: "fail";
|
|
157
|
+
/** Drop the message. */
|
|
158
|
+
readonly DELETE: "delete";
|
|
159
|
+
/** Requeue for another consumer. */
|
|
160
|
+
readonly RELEASE: "release";
|
|
161
|
+
/** Route to the dead-letter queue. */
|
|
162
|
+
readonly DEAD_LETTER: "dead_letter";
|
|
163
|
+
};
|
|
164
|
+
type UnknownUrnStrategy = (typeof UnknownUrnStrategy)[keyof typeof UnknownUrnStrategy];
|
|
165
|
+
|
|
166
|
+
/** Base error for all BabelQueue failures. */
|
|
167
|
+
declare class BabelQueueError extends Error {
|
|
168
|
+
constructor(message: string);
|
|
169
|
+
}
|
|
170
|
+
/** Raised when no handler is mapped for a message URN. */
|
|
171
|
+
declare class UnknownUrnError extends BabelQueueError {
|
|
172
|
+
constructor(urn: string);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { type AnnotateOptions, BabelQueueError, type DeadLetter, type Envelope, EnvelopeCodec, type HasTraceId, type IncomingEnvelope, type MakeOptions, type Meta, type PolyglotMessage, SCHEMA_VERSION, SOURCE_LANG, UnknownUrnError, UnknownUrnStrategy, annotate, deadLetter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A message that can be produced as a polyglot envelope. Implement it on your
|
|
3
|
+
* own classes/objects so {@link EnvelopeCodec.fromMessage} can build the canonical
|
|
4
|
+
* envelope without ever leaking a language-specific class name onto the wire.
|
|
5
|
+
*/
|
|
6
|
+
interface PolyglotMessage {
|
|
7
|
+
/** The stable URN that identifies this message across languages. */
|
|
8
|
+
getBabelUrn(): string;
|
|
9
|
+
/** The pure-JSON payload (no class instances). */
|
|
10
|
+
toPayload(): Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Optionally implemented alongside {@link PolyglotMessage} to continue an existing
|
|
14
|
+
* distributed trace instead of minting a fresh one.
|
|
15
|
+
*/
|
|
16
|
+
interface HasTraceId {
|
|
17
|
+
/** The trace id to reuse, or null/undefined to mint a new one. */
|
|
18
|
+
getBabelTraceId(): string | null | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The wire envelope schema version this core implements (versioned independently of the package version). */
|
|
22
|
+
declare const SCHEMA_VERSION = 1;
|
|
23
|
+
/** Stamped into `meta.lang` for envelopes produced by this core. */
|
|
24
|
+
declare const SOURCE_LANG = "node";
|
|
25
|
+
/** Immutable per-message metadata. */
|
|
26
|
+
interface Meta {
|
|
27
|
+
id: string;
|
|
28
|
+
queue: string;
|
|
29
|
+
lang: string;
|
|
30
|
+
schema_version: number;
|
|
31
|
+
/** Unix milliseconds, UTC. */
|
|
32
|
+
created_at: number;
|
|
33
|
+
}
|
|
34
|
+
/** The additive block appended to an envelope when a message is dead-lettered. */
|
|
35
|
+
interface DeadLetter {
|
|
36
|
+
reason: string;
|
|
37
|
+
error: string | null;
|
|
38
|
+
exception: string | null;
|
|
39
|
+
/** Unix milliseconds, UTC. */
|
|
40
|
+
failed_at: number;
|
|
41
|
+
original_queue: string;
|
|
42
|
+
attempts: number;
|
|
43
|
+
lang: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The canonical BabelQueue wire message: a strict, language-neutral JSON shape
|
|
47
|
+
* that every SDK produces and consumes identically. The property order here is
|
|
48
|
+
* significant — it matches the other cores so {@link EnvelopeCodec.encode} is
|
|
49
|
+
* byte-for-byte identical across the insertion-order languages (PHP/Python).
|
|
50
|
+
*/
|
|
51
|
+
interface Envelope {
|
|
52
|
+
/** The message URN (never a class name). */
|
|
53
|
+
job: string;
|
|
54
|
+
/** Correlation id, preserved across every hop. */
|
|
55
|
+
trace_id: string;
|
|
56
|
+
/** The pure-JSON payload. */
|
|
57
|
+
data: Record<string, unknown>;
|
|
58
|
+
meta: Meta;
|
|
59
|
+
/** Top-level transport retry counter. */
|
|
60
|
+
attempts: number;
|
|
61
|
+
/** Present only once the message has been dead-lettered. */
|
|
62
|
+
dead_letter?: DeadLetter;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A decoded, not-yet-validated envelope. Fields are loosely typed because they
|
|
66
|
+
* come off the wire; `urn` is accepted as an inbound alias for `job`. Narrow it
|
|
67
|
+
* with {@link EnvelopeCodec.accepts} before trusting the contents.
|
|
68
|
+
*/
|
|
69
|
+
interface IncomingEnvelope {
|
|
70
|
+
job?: string;
|
|
71
|
+
/** Inbound alias for `job`. */
|
|
72
|
+
urn?: string;
|
|
73
|
+
trace_id?: string;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
meta?: unknown;
|
|
76
|
+
attempts?: unknown;
|
|
77
|
+
dead_letter?: unknown;
|
|
78
|
+
}
|
|
79
|
+
/** Options for {@link EnvelopeCodec.make}. */
|
|
80
|
+
interface MakeOptions {
|
|
81
|
+
/** Logical queue name recorded in `meta.queue` (default `"default"`). */
|
|
82
|
+
queue?: string;
|
|
83
|
+
/** Reuse an existing trace id (trace continuation) instead of minting one. */
|
|
84
|
+
traceId?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Builds, encodes and decodes the canonical envelope — the single Node/TypeScript
|
|
88
|
+
* implementation of the wire format.
|
|
89
|
+
*/
|
|
90
|
+
declare const EnvelopeCodec: {
|
|
91
|
+
readonly SCHEMA_VERSION: 1;
|
|
92
|
+
readonly SOURCE_LANG: "node";
|
|
93
|
+
/**
|
|
94
|
+
* Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id
|
|
95
|
+
* unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.
|
|
96
|
+
* Throws {@link BabelQueueError} when the URN is blank.
|
|
97
|
+
*/
|
|
98
|
+
readonly make: (urn: string, data: Record<string, unknown>, options?: MakeOptions) => Envelope;
|
|
99
|
+
/**
|
|
100
|
+
* Build the envelope from a {@link PolyglotMessage}. If the message also
|
|
101
|
+
* implements {@link HasTraceId} and returns a non-empty value, that trace id is
|
|
102
|
+
* reused.
|
|
103
|
+
*/
|
|
104
|
+
readonly fromMessage: (message: PolyglotMessage & Partial<HasTraceId>, queue?: string) => Envelope;
|
|
105
|
+
/**
|
|
106
|
+
* Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the
|
|
107
|
+
* canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching
|
|
108
|
+
* the other SDK cores.
|
|
109
|
+
*/
|
|
110
|
+
readonly encode: (envelope: Envelope) => string;
|
|
111
|
+
/**
|
|
112
|
+
* Parse a raw JSON body. Returns `{}` for malformed or non-object input (call
|
|
113
|
+
* {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound
|
|
114
|
+
* alias into `job`.
|
|
115
|
+
*/
|
|
116
|
+
readonly decode: (raw: string) => IncomingEnvelope;
|
|
117
|
+
/** The message URN — canonical `job`, with `urn` accepted as an alias. */
|
|
118
|
+
readonly urn: (envelope: IncomingEnvelope) => string;
|
|
119
|
+
/**
|
|
120
|
+
* Whether a consumer should accept this envelope. Rejects a missing URN, an
|
|
121
|
+
* unsupported `meta.schema_version`, a non-object `data`, a non-integer
|
|
122
|
+
* `attempts`, or a blank `trace_id` — the consumer-side counterpart to the
|
|
123
|
+
* producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.
|
|
124
|
+
*/
|
|
125
|
+
readonly accepts: (envelope: IncomingEnvelope) => envelope is Envelope;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Options for {@link annotate}. */
|
|
129
|
+
interface AnnotateOptions {
|
|
130
|
+
/** Defaults to the envelope's current `attempts`. */
|
|
131
|
+
attempts?: number;
|
|
132
|
+
/** A human-readable error message (JSON `null` when omitted). */
|
|
133
|
+
error?: string | null;
|
|
134
|
+
/** The originating error type/class name (JSON `null` when omitted). */
|
|
135
|
+
exception?: string | null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Return a copy of the envelope with a `dead_letter` block attached, recording
|
|
139
|
+
* why and where it failed. The original envelope is preserved unchanged inside
|
|
140
|
+
* the result, so any-language consumers can still read it.
|
|
141
|
+
*/
|
|
142
|
+
declare function annotate(envelope: Envelope, reason: string, originalQueue: string, options?: AnnotateOptions): Envelope;
|
|
143
|
+
|
|
144
|
+
type deadLetter_AnnotateOptions = AnnotateOptions;
|
|
145
|
+
declare const deadLetter_annotate: typeof annotate;
|
|
146
|
+
declare namespace deadLetter {
|
|
147
|
+
export { type deadLetter_AnnotateOptions as AnnotateOptions, deadLetter_annotate as annotate };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* What a consumer does with a message whose URN has no registered handler.
|
|
152
|
+
* Mirrors the constants in every other SDK core.
|
|
153
|
+
*/
|
|
154
|
+
declare const UnknownUrnStrategy: {
|
|
155
|
+
/** Surface an error; let the worker decide. */
|
|
156
|
+
readonly FAIL: "fail";
|
|
157
|
+
/** Drop the message. */
|
|
158
|
+
readonly DELETE: "delete";
|
|
159
|
+
/** Requeue for another consumer. */
|
|
160
|
+
readonly RELEASE: "release";
|
|
161
|
+
/** Route to the dead-letter queue. */
|
|
162
|
+
readonly DEAD_LETTER: "dead_letter";
|
|
163
|
+
};
|
|
164
|
+
type UnknownUrnStrategy = (typeof UnknownUrnStrategy)[keyof typeof UnknownUrnStrategy];
|
|
165
|
+
|
|
166
|
+
/** Base error for all BabelQueue failures. */
|
|
167
|
+
declare class BabelQueueError extends Error {
|
|
168
|
+
constructor(message: string);
|
|
169
|
+
}
|
|
170
|
+
/** Raised when no handler is mapped for a message URN. */
|
|
171
|
+
declare class UnknownUrnError extends BabelQueueError {
|
|
172
|
+
constructor(urn: string);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { type AnnotateOptions, BabelQueueError, type DeadLetter, type Envelope, EnvelopeCodec, type HasTraceId, type IncomingEnvelope, type MakeOptions, type Meta, type PolyglotMessage, SCHEMA_VERSION, SOURCE_LANG, UnknownUrnError, UnknownUrnStrategy, annotate, deadLetter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/codec.ts
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
|
|
10
|
+
// src/errors.ts
|
|
11
|
+
var BabelQueueError = class extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "BabelQueueError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var UnknownUrnError = class extends BabelQueueError {
|
|
18
|
+
constructor(urn) {
|
|
19
|
+
super(`No handler is mapped for the message URN "${urn}".`);
|
|
20
|
+
this.name = "UnknownUrnError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/codec.ts
|
|
25
|
+
var SCHEMA_VERSION = 1;
|
|
26
|
+
var SOURCE_LANG = "node";
|
|
27
|
+
var EnvelopeCodec = {
|
|
28
|
+
SCHEMA_VERSION,
|
|
29
|
+
SOURCE_LANG,
|
|
30
|
+
/**
|
|
31
|
+
* Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id
|
|
32
|
+
* unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.
|
|
33
|
+
* Throws {@link BabelQueueError} when the URN is blank.
|
|
34
|
+
*/
|
|
35
|
+
make(urn, data, options = {}) {
|
|
36
|
+
const resolvedUrn = (urn ?? "").trim();
|
|
37
|
+
if (resolvedUrn === "") {
|
|
38
|
+
throw new BabelQueueError(
|
|
39
|
+
"A polyglot message must expose a stable, non-empty URN so consumers can identify it without any class name."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const traceId = (options.traceId ?? "").trim() || randomUUID();
|
|
43
|
+
return {
|
|
44
|
+
job: resolvedUrn,
|
|
45
|
+
trace_id: traceId,
|
|
46
|
+
data: { ...data },
|
|
47
|
+
meta: {
|
|
48
|
+
id: randomUUID(),
|
|
49
|
+
queue: options.queue ?? "default",
|
|
50
|
+
lang: SOURCE_LANG,
|
|
51
|
+
schema_version: SCHEMA_VERSION,
|
|
52
|
+
created_at: Date.now()
|
|
53
|
+
},
|
|
54
|
+
attempts: 0
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Build the envelope from a {@link PolyglotMessage}. If the message also
|
|
59
|
+
* implements {@link HasTraceId} and returns a non-empty value, that trace id is
|
|
60
|
+
* reused.
|
|
61
|
+
*/
|
|
62
|
+
fromMessage(message, queue = "default") {
|
|
63
|
+
const traceId = typeof message.getBabelTraceId === "function" ? message.getBabelTraceId() ?? void 0 : void 0;
|
|
64
|
+
return EnvelopeCodec.make(message.getBabelUrn(), message.toPayload(), {
|
|
65
|
+
queue,
|
|
66
|
+
traceId
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the
|
|
71
|
+
* canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching
|
|
72
|
+
* the other SDK cores.
|
|
73
|
+
*/
|
|
74
|
+
encode(envelope) {
|
|
75
|
+
return JSON.stringify(envelope);
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* Parse a raw JSON body. Returns `{}` for malformed or non-object input (call
|
|
79
|
+
* {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound
|
|
80
|
+
* alias into `job`.
|
|
81
|
+
*/
|
|
82
|
+
decode(raw) {
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(raw);
|
|
86
|
+
} catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
const envelope = parsed;
|
|
93
|
+
if (!envelope.job && typeof envelope.urn === "string") {
|
|
94
|
+
envelope.job = envelope.urn;
|
|
95
|
+
}
|
|
96
|
+
return envelope;
|
|
97
|
+
},
|
|
98
|
+
/** The message URN — canonical `job`, with `urn` accepted as an alias. */
|
|
99
|
+
urn(envelope) {
|
|
100
|
+
const value = envelope?.job ?? envelope?.urn ?? "";
|
|
101
|
+
return typeof value === "string" ? value.trim() : "";
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Whether a consumer should accept this envelope. Rejects a missing URN, an
|
|
105
|
+
* unsupported `meta.schema_version`, a non-object `data`, a non-integer
|
|
106
|
+
* `attempts`, or a blank `trace_id` — the consumer-side counterpart to the
|
|
107
|
+
* producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.
|
|
108
|
+
*/
|
|
109
|
+
accepts(envelope) {
|
|
110
|
+
if (EnvelopeCodec.urn(envelope) === "") {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const meta = envelope.meta;
|
|
114
|
+
if (meta === null || typeof meta !== "object" || meta.schema_version !== SCHEMA_VERSION) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const data = envelope.data;
|
|
118
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const attempts = envelope.attempts;
|
|
122
|
+
if (typeof attempts !== "number" || !Number.isInteger(attempts)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const traceId = envelope.trace_id;
|
|
126
|
+
if (typeof traceId !== "string" || traceId.trim() === "") {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/deadLetter.ts
|
|
134
|
+
var deadLetter_exports = {};
|
|
135
|
+
__export(deadLetter_exports, {
|
|
136
|
+
annotate: () => annotate
|
|
137
|
+
});
|
|
138
|
+
function annotate(envelope, reason, originalQueue, options = {}) {
|
|
139
|
+
const deadLetter = {
|
|
140
|
+
reason,
|
|
141
|
+
error: options.error ?? null,
|
|
142
|
+
exception: options.exception ?? null,
|
|
143
|
+
failed_at: Date.now(),
|
|
144
|
+
original_queue: originalQueue,
|
|
145
|
+
attempts: options.attempts ?? envelope.attempts ?? 0,
|
|
146
|
+
lang: SOURCE_LANG
|
|
147
|
+
};
|
|
148
|
+
return { ...envelope, dead_letter: deadLetter };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/routing.ts
|
|
152
|
+
var UnknownUrnStrategy = {
|
|
153
|
+
/** Surface an error; let the worker decide. */
|
|
154
|
+
FAIL: "fail",
|
|
155
|
+
/** Drop the message. */
|
|
156
|
+
DELETE: "delete",
|
|
157
|
+
/** Requeue for another consumer. */
|
|
158
|
+
RELEASE: "release",
|
|
159
|
+
/** Route to the dead-letter queue. */
|
|
160
|
+
DEAD_LETTER: "dead_letter"
|
|
161
|
+
};
|
|
162
|
+
export {
|
|
163
|
+
BabelQueueError,
|
|
164
|
+
EnvelopeCodec,
|
|
165
|
+
SCHEMA_VERSION,
|
|
166
|
+
SOURCE_LANG,
|
|
167
|
+
UnknownUrnError,
|
|
168
|
+
UnknownUrnStrategy,
|
|
169
|
+
annotate,
|
|
170
|
+
deadLetter_exports as deadLetter
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codec.ts","../src/errors.ts","../src/deadLetter.ts","../src/routing.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport type { HasTraceId, PolyglotMessage } from \"./contracts.js\";\nimport { BabelQueueError } from \"./errors.js\";\n\n/** The wire envelope schema version this core implements (versioned independently of the package version). */\nexport const SCHEMA_VERSION = 1;\n\n/** Stamped into `meta.lang` for envelopes produced by this core. */\nexport const SOURCE_LANG = \"node\";\n\n/** Immutable per-message metadata. */\nexport interface Meta {\n id: string;\n queue: string;\n lang: string;\n schema_version: number;\n /** Unix milliseconds, UTC. */\n created_at: number;\n}\n\n/** The additive block appended to an envelope when a message is dead-lettered. */\nexport interface DeadLetter {\n reason: string;\n error: string | null;\n exception: string | null;\n /** Unix milliseconds, UTC. */\n failed_at: number;\n original_queue: string;\n attempts: number;\n lang: string;\n}\n\n/**\n * The canonical BabelQueue wire message: a strict, language-neutral JSON shape\n * that every SDK produces and consumes identically. The property order here is\n * significant — it matches the other cores so {@link EnvelopeCodec.encode} is\n * byte-for-byte identical across the insertion-order languages (PHP/Python).\n */\nexport interface Envelope {\n /** The message URN (never a class name). */\n job: string;\n /** Correlation id, preserved across every hop. */\n trace_id: string;\n /** The pure-JSON payload. */\n data: Record<string, unknown>;\n meta: Meta;\n /** Top-level transport retry counter. */\n attempts: number;\n /** Present only once the message has been dead-lettered. */\n dead_letter?: DeadLetter;\n}\n\n/**\n * A decoded, not-yet-validated envelope. Fields are loosely typed because they\n * come off the wire; `urn` is accepted as an inbound alias for `job`. Narrow it\n * with {@link EnvelopeCodec.accepts} before trusting the contents.\n */\nexport interface IncomingEnvelope {\n job?: string;\n /** Inbound alias for `job`. */\n urn?: string;\n trace_id?: string;\n data?: unknown;\n meta?: unknown;\n attempts?: unknown;\n dead_letter?: unknown;\n}\n\n/** Options for {@link EnvelopeCodec.make}. */\nexport interface MakeOptions {\n /** Logical queue name recorded in `meta.queue` (default `\"default\"`). */\n queue?: string;\n /** Reuse an existing trace id (trace continuation) instead of minting one. */\n traceId?: string;\n}\n\n/**\n * Builds, encodes and decodes the canonical envelope — the single Node/TypeScript\n * implementation of the wire format.\n */\nexport const EnvelopeCodec = {\n SCHEMA_VERSION,\n SOURCE_LANG,\n\n /**\n * Build the canonical envelope for a `(urn, data)` pair. Mints a fresh trace id\n * unless `options.traceId` is given, starts `attempts` at 0, and stamps `meta`.\n * Throws {@link BabelQueueError} when the URN is blank.\n */\n make(\n urn: string,\n data: Record<string, unknown>,\n options: MakeOptions = {},\n ): Envelope {\n const resolvedUrn = (urn ?? \"\").trim();\n if (resolvedUrn === \"\") {\n throw new BabelQueueError(\n \"A polyglot message must expose a stable, non-empty URN so consumers can identify it without any class name.\",\n );\n }\n\n const traceId = (options.traceId ?? \"\").trim() || randomUUID();\n\n return {\n job: resolvedUrn,\n trace_id: traceId,\n data: { ...data },\n meta: {\n id: randomUUID(),\n queue: options.queue ?? \"default\",\n lang: SOURCE_LANG,\n schema_version: SCHEMA_VERSION,\n created_at: Date.now(),\n },\n attempts: 0,\n };\n },\n\n /**\n * Build the envelope from a {@link PolyglotMessage}. If the message also\n * implements {@link HasTraceId} and returns a non-empty value, that trace id is\n * reused.\n */\n fromMessage(\n message: PolyglotMessage & Partial<HasTraceId>,\n queue = \"default\",\n ): Envelope {\n const traceId =\n typeof message.getBabelTraceId === \"function\"\n ? (message.getBabelTraceId() ?? undefined)\n : undefined;\n\n return EnvelopeCodec.make(message.getBabelUrn(), message.toPayload(), {\n queue,\n traceId,\n });\n },\n\n /**\n * Encode the envelope as compact UTF-8 JSON. `JSON.stringify` already emits the\n * canonical form — no spaces, and slashes/unicode/HTML left unescaped — matching\n * the other SDK cores.\n */\n encode(envelope: Envelope): string {\n return JSON.stringify(envelope);\n },\n\n /**\n * Parse a raw JSON body. Returns `{}` for malformed or non-object input (call\n * {@link EnvelopeCodec.accepts} before trusting it). Resolves the `urn` inbound\n * alias into `job`.\n */\n decode(raw: string): IncomingEnvelope {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return {};\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return {};\n }\n\n const envelope = parsed as IncomingEnvelope;\n if (!envelope.job && typeof envelope.urn === \"string\") {\n envelope.job = envelope.urn;\n }\n return envelope;\n },\n\n /** The message URN — canonical `job`, with `urn` accepted as an alias. */\n urn(envelope: IncomingEnvelope): string {\n const value = envelope?.job ?? envelope?.urn ?? \"\";\n return typeof value === \"string\" ? value.trim() : \"\";\n },\n\n /**\n * Whether a consumer should accept this envelope. Rejects a missing URN, an\n * unsupported `meta.schema_version`, a non-object `data`, a non-integer\n * `attempts`, or a blank `trace_id` — the consumer-side counterpart to the\n * producer JSON Schema. Acts as a type guard that narrows to {@link Envelope}.\n */\n accepts(envelope: IncomingEnvelope): envelope is Envelope {\n if (EnvelopeCodec.urn(envelope) === \"\") {\n return false;\n }\n\n const meta = envelope.meta;\n if (\n meta === null ||\n typeof meta !== \"object\" ||\n (meta as Meta).schema_version !== SCHEMA_VERSION\n ) {\n return false;\n }\n\n const data = envelope.data;\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return false;\n }\n\n const attempts = envelope.attempts;\n if (typeof attempts !== \"number\" || !Number.isInteger(attempts)) {\n return false;\n }\n\n const traceId = envelope.trace_id;\n if (typeof traceId !== \"string\" || traceId.trim() === \"\") {\n return false;\n }\n\n return true;\n },\n} as const;\n","/** Base error for all BabelQueue failures. */\nexport class BabelQueueError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BabelQueueError\";\n }\n}\n\n/** Raised when no handler is mapped for a message URN. */\nexport class UnknownUrnError extends BabelQueueError {\n constructor(urn: string) {\n super(`No handler is mapped for the message URN \"${urn}\".`);\n this.name = \"UnknownUrnError\";\n }\n}\n","import type { DeadLetter, Envelope } from \"./codec.js\";\nimport { SOURCE_LANG } from \"./codec.js\";\n\n/** Options for {@link annotate}. */\nexport interface AnnotateOptions {\n /** Defaults to the envelope's current `attempts`. */\n attempts?: number;\n /** A human-readable error message (JSON `null` when omitted). */\n error?: string | null;\n /** The originating error type/class name (JSON `null` when omitted). */\n exception?: string | null;\n}\n\n/**\n * Return a copy of the envelope with a `dead_letter` block attached, recording\n * why and where it failed. The original envelope is preserved unchanged inside\n * the result, so any-language consumers can still read it.\n */\nexport function annotate(\n envelope: Envelope,\n reason: string,\n originalQueue: string,\n options: AnnotateOptions = {},\n): Envelope {\n const deadLetter: DeadLetter = {\n reason,\n error: options.error ?? null,\n exception: options.exception ?? null,\n failed_at: Date.now(),\n original_queue: originalQueue,\n attempts: options.attempts ?? envelope.attempts ?? 0,\n lang: SOURCE_LANG,\n };\n\n return { ...envelope, dead_letter: deadLetter };\n}\n","/**\n * What a consumer does with a message whose URN has no registered handler.\n * Mirrors the constants in every other SDK core.\n */\nexport const UnknownUrnStrategy = {\n /** Surface an error; let the worker decide. */\n FAIL: \"fail\",\n /** Drop the message. */\n DELETE: \"delete\",\n /** Requeue for another consumer. */\n RELEASE: \"release\",\n /** Route to the dead-letter queue. */\n DEAD_LETTER: \"dead_letter\",\n} as const;\n\nexport type UnknownUrnStrategy =\n (typeof UnknownUrnStrategy)[keyof typeof UnknownUrnStrategy];\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@babelqueue/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Polyglot Queues, Simplified — the Node/TypeScript core: the canonical BabelQueue wire-envelope codec, contracts and dead-letter helpers.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"queue",
|
|
7
|
+
"polyglot",
|
|
8
|
+
"microservices",
|
|
9
|
+
"json",
|
|
10
|
+
"envelope",
|
|
11
|
+
"messaging",
|
|
12
|
+
"rabbitmq",
|
|
13
|
+
"redis"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://babelqueue.com",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/BabelQueue/babelqueue-node/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/BabelQueue/babelqueue-node.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Muhammet Şafak <info@muhammetsafak.com.tr>",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/index.d.cts",
|
|
34
|
+
"default": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.cjs",
|
|
39
|
+
"module": "./dist/index.js",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "node --import tsx --test test/codec.test.ts test/dead-letter.test.ts test/conformance.test.ts",
|
|
52
|
+
"prepublishOnly": "npm run build"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22",
|
|
56
|
+
"tsup": "^8",
|
|
57
|
+
"tsx": "^4",
|
|
58
|
+
"typescript": "^5.5"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|