@zmdb/transport-nats 1.0.0-beta.1
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 +674 -0
- package/README.md +61 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/matcher.d.ts +9 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +84 -0
- package/dist/matcher.js.map +1 -0
- package/package.json +49 -0
- package/src/index.ts +251 -0
- package/src/matcher.ts +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @zmdb/transport-nats
|
|
2
|
+
|
|
3
|
+
`@zmdb/transport-nats` connects the public `@zmdb/app/messaging` transport strategy contract to core NATS.
|
|
4
|
+
|
|
5
|
+
It supports native `*` and final-`>` subjects, queue groups, one-way events, request/reply, cancellation, deadlines, and bounded shutdown. It does not imply JetStream durability or own a
|
|
6
|
+
process-global connection.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
yarn add @zmdb/transport-nats@1.0.0-beta.1 @nats-io/transport-node@^3.4.0
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> **Prerelease** (`1.0.0-beta.1`). Requires **Node.js 26+** and is **ESM-only**. Ships built ESM `.js` + `.d.ts` under `./dist`.
|
|
15
|
+
|
|
16
|
+
The sole peer is `@nats-io/transport-node@^3.4.0`. Neither it nor this package is installed by `yarn add zmdb@1.0.0-beta.1`.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { transportExtension } from '@zmdb/app/messaging';
|
|
22
|
+
import { createNatsStrategy } from '@zmdb/transport-nats';
|
|
23
|
+
|
|
24
|
+
const connection = process.env.NATS_URL;
|
|
25
|
+
if (connection === undefined) throw new Error('NATS_URL is required');
|
|
26
|
+
|
|
27
|
+
const nats = createNatsStrategy({
|
|
28
|
+
connection: { servers: connection },
|
|
29
|
+
subscriptions: [{ subject: 'orders.*', queue: 'orders-workers' }],
|
|
30
|
+
onError: error => console.error(error),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const extension = transportExtension({
|
|
34
|
+
transports: [nats],
|
|
35
|
+
dispatcher: {
|
|
36
|
+
onUnhandled: message => console.warn('unhandled', message.pattern),
|
|
37
|
+
onInvalidPayload: (message, error) => console.error('invalid', message.pattern, error),
|
|
38
|
+
onHandlerError: (message, error) => console.error('handler', message.pattern, error),
|
|
39
|
+
onUndeliverable: (message, settlement) => console.error('dropped', message.pattern, settlement),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
void extension;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Core NATS is at-most-once. `redelivery` and `deadLetter` are therefore `false`, so an application attaching this strategy must provide `dispatcher.onUndeliverable`.
|
|
47
|
+
|
|
48
|
+
`transportExtension` starts the strategy, stops intake, waits for accepted dispatches within the application grace budget, drains the NATS connection, and closes it. The strategy creates no
|
|
49
|
+
process-global connection.
|
|
50
|
+
|
|
51
|
+
## Entry points
|
|
52
|
+
|
|
53
|
+
- `@zmdb/transport-nats` — `createNatsStrategy`, `NatsStrategyOptions`, and `NatsSubscription`.
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
Full docs: **https://ambasta.github.io/zmdb/**
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
GNU General Public License v3.0 or later (GPL-3.0-or-later) — see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type NodeConnectionOptions } from '@nats-io/transport-node';
|
|
2
|
+
import { type TransportErrorSink, type TransportStrategy } from '@zmdb/app/messaging';
|
|
3
|
+
export interface NatsSubscription {
|
|
4
|
+
/** NATS subject; `*` and a final `>` use native NATS token semantics. */
|
|
5
|
+
readonly subject: string;
|
|
6
|
+
/** Queue group for horizontally balanced delivery. */
|
|
7
|
+
readonly queue?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface NatsStrategyOptions {
|
|
10
|
+
readonly connection?: NodeConnectionOptions;
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly onError: TransportErrorSink;
|
|
13
|
+
readonly subscriptions: readonly NatsSubscription[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Core NATS strategy.
|
|
17
|
+
*
|
|
18
|
+
* Core NATS is at-most-once: successful settlements need no acknowledgement,
|
|
19
|
+
* while `retry` and `dead` cannot be honoured after delivery. Queue groups and
|
|
20
|
+
* subject wildcards are native subscription features; wildcard membership is
|
|
21
|
+
* also checked through a startup-built trie rather than a pattern scan.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createNatsStrategy(options: NatsStrategyOptions): TransportStrategy;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAWL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrD;AAoBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,iBAAiB,CAsLlF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { connect, createInbox, } from '@nats-io/transport-node';
|
|
2
|
+
import { abortError, decodeDelivery, decodeReply, encodeDelivery, encodeReply, InFlight, MessageTimeoutError, reportTransportError, withinGrace, } from '@zmdb/app/messaging';
|
|
3
|
+
import { createNatsSubjectMatcher } from './matcher.js';
|
|
4
|
+
function bytes(text) {
|
|
5
|
+
return new TextEncoder().encode(text);
|
|
6
|
+
}
|
|
7
|
+
function validateSubscriptions(subscriptions) {
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
return subscriptions.map(subscription => {
|
|
10
|
+
if (subscription.subject.length === 0) {
|
|
11
|
+
throw new RangeError('@zmdb/transport-nats: a NATS subscription subject cannot be empty');
|
|
12
|
+
}
|
|
13
|
+
if (subscription.queue !== undefined && subscription.queue.length === 0) {
|
|
14
|
+
throw new RangeError('@zmdb/transport-nats: a NATS queue group cannot be empty');
|
|
15
|
+
}
|
|
16
|
+
const key = `${subscription.subject}\u0000${subscription.queue ?? ''}`;
|
|
17
|
+
if (seen.has(key)) {
|
|
18
|
+
throw new Error(`@zmdb/transport-nats: duplicate NATS subscription "${subscription.subject}"`);
|
|
19
|
+
}
|
|
20
|
+
seen.add(key);
|
|
21
|
+
return { ...subscription };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Core NATS strategy.
|
|
26
|
+
*
|
|
27
|
+
* Core NATS is at-most-once: successful settlements need no acknowledgement,
|
|
28
|
+
* while `retry` and `dead` cannot be honoured after delivery. Queue groups and
|
|
29
|
+
* subject wildcards are native subscription features; wildcard membership is
|
|
30
|
+
* also checked through a startup-built trie rather than a pattern scan.
|
|
31
|
+
*/
|
|
32
|
+
export function createNatsStrategy(options) {
|
|
33
|
+
const subscriptions = validateSubscriptions(options.subscriptions);
|
|
34
|
+
const matcher = createNatsSubjectMatcher(subscriptions.map(subscription => subscription.subject));
|
|
35
|
+
const inFlight = new InFlight(options.onError);
|
|
36
|
+
const name = options.name ?? 'nats';
|
|
37
|
+
let connection;
|
|
38
|
+
let activeSubscriptions = [];
|
|
39
|
+
let started = false;
|
|
40
|
+
let closed = false;
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
capabilities: { redelivery: false, deadLetter: false, requestResponse: true },
|
|
44
|
+
async listen(dispatch) {
|
|
45
|
+
if (started) {
|
|
46
|
+
throw new Error('@zmdb/transport-nats: NATS strategy is already listening');
|
|
47
|
+
}
|
|
48
|
+
if (closed) {
|
|
49
|
+
throw new Error('@zmdb/transport-nats: NATS strategy is closed');
|
|
50
|
+
}
|
|
51
|
+
started = true;
|
|
52
|
+
const nextConnection = await connect(options.connection);
|
|
53
|
+
const nextSubscriptions = [];
|
|
54
|
+
try {
|
|
55
|
+
for (const subscription of subscriptions) {
|
|
56
|
+
const opened = nextConnection.subscribe(subscription.subject, {
|
|
57
|
+
...(subscription.queue === undefined ? {} : { queue: subscription.queue }),
|
|
58
|
+
callback(error, message) {
|
|
59
|
+
if (error !== null) {
|
|
60
|
+
reportTransportError(options.onError, error);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
void inFlight.run(async () => {
|
|
64
|
+
if (!matcher.matches(message.subject)) {
|
|
65
|
+
throw new Error(`@zmdb/transport-nats: NATS delivered unsubscribed subject "${message.subject}"`);
|
|
66
|
+
}
|
|
67
|
+
const delivery = decodeDelivery(message.subject, message.string(), 1, message.reply === undefined ? {} : { replyTo: message.reply });
|
|
68
|
+
const outcome = await dispatch(delivery);
|
|
69
|
+
if (outcome.reply !== undefined && !message.respond(bytes(encodeReply(outcome.reply)))) {
|
|
70
|
+
throw new Error('@zmdb/transport-nats: NATS request has no reply subject');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
nextSubscriptions.push(opened);
|
|
76
|
+
}
|
|
77
|
+
await nextConnection.flush();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
await nextConnection.close();
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
connection = nextConnection;
|
|
84
|
+
activeSubscriptions = nextSubscriptions;
|
|
85
|
+
void nextConnection.closed().then(error => {
|
|
86
|
+
if (error !== undefined) {
|
|
87
|
+
reportTransportError(options.onError, error);
|
|
88
|
+
}
|
|
89
|
+
}, error => reportTransportError(options.onError, error));
|
|
90
|
+
},
|
|
91
|
+
async send(request) {
|
|
92
|
+
const activeConnection = connection;
|
|
93
|
+
if (activeConnection === undefined) {
|
|
94
|
+
throw new Error('@zmdb/transport-nats: NATS strategy is not listening');
|
|
95
|
+
}
|
|
96
|
+
if (request.signal.aborted) {
|
|
97
|
+
throw abortError(request.signal);
|
|
98
|
+
}
|
|
99
|
+
const inbox = createInbox();
|
|
100
|
+
let responseSubscription;
|
|
101
|
+
let timer;
|
|
102
|
+
let abort = () => undefined;
|
|
103
|
+
let resolveResponse = (_reply) => undefined;
|
|
104
|
+
let rejectResponse = (_error) => undefined;
|
|
105
|
+
let finished = false;
|
|
106
|
+
const response = new Promise((resolve, reject) => {
|
|
107
|
+
resolveResponse = resolve;
|
|
108
|
+
rejectResponse = reject;
|
|
109
|
+
});
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
responseSubscription?.unsubscribe();
|
|
112
|
+
request.signal.removeEventListener('abort', abort);
|
|
113
|
+
if (timer !== undefined) {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const fail = (error) => {
|
|
118
|
+
if (finished) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
finished = true;
|
|
122
|
+
cleanup();
|
|
123
|
+
rejectResponse(error);
|
|
124
|
+
};
|
|
125
|
+
const succeed = (reply) => {
|
|
126
|
+
if (finished) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
finished = true;
|
|
130
|
+
cleanup();
|
|
131
|
+
resolveResponse(reply);
|
|
132
|
+
};
|
|
133
|
+
abort = () => fail(abortError(request.signal));
|
|
134
|
+
try {
|
|
135
|
+
responseSubscription = activeConnection.subscribe(inbox, {
|
|
136
|
+
max: 1,
|
|
137
|
+
callback(error, message) {
|
|
138
|
+
if (error !== null) {
|
|
139
|
+
fail(error);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
succeed(decodeReply(message.string()));
|
|
144
|
+
}
|
|
145
|
+
catch (decodeError) {
|
|
146
|
+
fail(decodeError);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
request.signal.addEventListener('abort', abort, { once: true });
|
|
151
|
+
timer = setTimeout(() => {
|
|
152
|
+
fail(new MessageTimeoutError(request.pattern, request.timeoutMs, request.correlationId));
|
|
153
|
+
}, request.timeoutMs);
|
|
154
|
+
await activeConnection.flush();
|
|
155
|
+
if (!finished) {
|
|
156
|
+
activeConnection.publish(request.pattern, bytes(encodeDelivery(request.payload, request, { correlationId: request.correlationId })), { reply: inbox });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
fail(error);
|
|
161
|
+
}
|
|
162
|
+
return response;
|
|
163
|
+
},
|
|
164
|
+
async emit(pattern, payload, carrier) {
|
|
165
|
+
const activeConnection = connection;
|
|
166
|
+
if (activeConnection === undefined) {
|
|
167
|
+
throw new Error('@zmdb/transport-nats: NATS strategy is not listening');
|
|
168
|
+
}
|
|
169
|
+
activeConnection.publish(pattern, bytes(encodeDelivery(payload, carrier)));
|
|
170
|
+
},
|
|
171
|
+
async close(graceMs) {
|
|
172
|
+
if (closed) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
closed = true;
|
|
176
|
+
const activeConnection = connection;
|
|
177
|
+
const subscriptionsToClose = activeSubscriptions;
|
|
178
|
+
connection = undefined;
|
|
179
|
+
activeSubscriptions = [];
|
|
180
|
+
if (activeConnection === undefined) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const graceful = (async () => {
|
|
184
|
+
await Promise.all(subscriptionsToClose.map(subscription => subscription.drain()));
|
|
185
|
+
inFlight.stop();
|
|
186
|
+
await inFlight.settled();
|
|
187
|
+
await activeConnection.flush();
|
|
188
|
+
await activeConnection.close();
|
|
189
|
+
})();
|
|
190
|
+
if (!(await withinGrace(graceful, graceMs))) {
|
|
191
|
+
await activeConnection.close();
|
|
192
|
+
throw new Error(`@zmdb/transport-nats: NATS strategy did not drain within ${String(graceMs)}ms`);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface NatsSubjectMatcher {
|
|
2
|
+
matches(subject: string): boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Compile NATS `*`/`>` subscriptions into a trie. Matching walks subject
|
|
6
|
+
* tokens and active trie nodes; it never iterates the configured patterns.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createNatsSubjectMatcher(patterns: readonly string[]): NatsSubjectMatcher;
|
|
9
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AA6CA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CAoCxF"}
|
package/dist/matcher.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
class SubjectNode {
|
|
2
|
+
literals = new Map();
|
|
3
|
+
one;
|
|
4
|
+
tail = false;
|
|
5
|
+
terminal = false;
|
|
6
|
+
}
|
|
7
|
+
function tokens(value, description) {
|
|
8
|
+
const parts = value.split('.');
|
|
9
|
+
if (parts.length === 0 || parts.some(part => part.length === 0)) {
|
|
10
|
+
throw new RangeError(`@zmdb/transport-nats: ${description} must contain non-empty dot-separated tokens`);
|
|
11
|
+
}
|
|
12
|
+
return parts;
|
|
13
|
+
}
|
|
14
|
+
function add(root, pattern) {
|
|
15
|
+
const parts = tokens(pattern, 'a NATS subscription');
|
|
16
|
+
let node = root;
|
|
17
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
18
|
+
const part = parts[index];
|
|
19
|
+
if (part === '>') {
|
|
20
|
+
if (index !== parts.length - 1) {
|
|
21
|
+
throw new RangeError('@zmdb/transport-nats: a NATS > wildcard must be the final token');
|
|
22
|
+
}
|
|
23
|
+
node.tail = true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (part === '*') {
|
|
27
|
+
node.one ??= new SubjectNode();
|
|
28
|
+
node = node.one;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (part?.includes('*') || part?.includes('>')) {
|
|
32
|
+
throw new RangeError('@zmdb/transport-nats: NATS wildcards must occupy a whole token');
|
|
33
|
+
}
|
|
34
|
+
let child = node.literals.get(part ?? '');
|
|
35
|
+
if (child === undefined) {
|
|
36
|
+
child = new SubjectNode();
|
|
37
|
+
node.literals.set(part ?? '', child);
|
|
38
|
+
}
|
|
39
|
+
node = child;
|
|
40
|
+
}
|
|
41
|
+
node.terminal = true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compile NATS `*`/`>` subscriptions into a trie. Matching walks subject
|
|
45
|
+
* tokens and active trie nodes; it never iterates the configured patterns.
|
|
46
|
+
*/
|
|
47
|
+
export function createNatsSubjectMatcher(patterns) {
|
|
48
|
+
const root = new SubjectNode();
|
|
49
|
+
for (const pattern of patterns) {
|
|
50
|
+
add(root, pattern);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
matches(subject) {
|
|
54
|
+
const parts = tokens(subject, 'a NATS subject');
|
|
55
|
+
let active = new Set([root]);
|
|
56
|
+
for (const part of parts) {
|
|
57
|
+
const next = new Set();
|
|
58
|
+
for (const node of active) {
|
|
59
|
+
if (node.tail) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const literal = node.literals.get(part);
|
|
63
|
+
if (literal !== undefined) {
|
|
64
|
+
next.add(literal);
|
|
65
|
+
}
|
|
66
|
+
if (node.one !== undefined) {
|
|
67
|
+
next.add(node.one);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
active = next;
|
|
71
|
+
if (active.size === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const node of active) {
|
|
76
|
+
if (node.terminal) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zmdb/transport-nats",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Core NATS transport strategy for the public messaging contract owned by the zmdb application kernel.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"messaging",
|
|
7
|
+
"nats",
|
|
8
|
+
"request-reply",
|
|
9
|
+
"transport",
|
|
10
|
+
"typescript",
|
|
11
|
+
"zmdb"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/ambasta/zmdb#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ambasta/zmdb/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "GPL-3.0-or-later",
|
|
18
|
+
"author": "zmdb contributors",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/ambasta/zmdb.git",
|
|
22
|
+
"directory": "packages/transport-nats"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"tag": "beta"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node ../../scripts/build-package.mjs",
|
|
38
|
+
"test": "vitest run"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@nats-io/transport-node": "^3.4.0",
|
|
42
|
+
"@zmdb/app": "1.0.0-beta.1"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=26"
|
|
46
|
+
},
|
|
47
|
+
"main": "./dist/index.js",
|
|
48
|
+
"types": "./dist/index.d.ts"
|
|
49
|
+
}
|