@mdsiha369/adonis-mercure 2.1.2

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.
Files changed (60) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +274 -0
  3. package/build/bin/test.d.ts +1 -0
  4. package/build/bin/test.js +8 -0
  5. package/build/configure.d.ts +2 -0
  6. package/build/configure.js +34 -0
  7. package/build/index.d.ts +9 -0
  8. package/build/index.js +14 -0
  9. package/build/providers/mercure_provider.d.ts +7 -0
  10. package/build/providers/mercure_provider.js +18 -0
  11. package/build/services/mercure.d.ts +3 -0
  12. package/build/services/mercure.js +6 -0
  13. package/build/src/define_config.d.ts +2 -0
  14. package/build/src/define_config.js +11 -0
  15. package/build/src/errors/config_error.d.ts +4 -0
  16. package/build/src/errors/config_error.js +7 -0
  17. package/build/src/errors/mercure_error.d.ts +3 -0
  18. package/build/src/errors/mercure_error.js +6 -0
  19. package/build/src/errors/publish_error.d.ts +6 -0
  20. package/build/src/errors/publish_error.js +11 -0
  21. package/build/src/errors/timeout_error.d.ts +5 -0
  22. package/build/src/errors/timeout_error.js +9 -0
  23. package/build/src/fake_mercure.d.ts +18 -0
  24. package/build/src/fake_mercure.js +52 -0
  25. package/build/src/mercure.d.ts +9 -0
  26. package/build/src/mercure.js +42 -0
  27. package/build/src/publisher.d.ts +6 -0
  28. package/build/src/publisher.js +54 -0
  29. package/build/src/token_generator.d.ts +7 -0
  30. package/build/src/token_generator.js +24 -0
  31. package/build/src/types/extended.d.ts +6 -0
  32. package/build/src/types/extended.js +1 -0
  33. package/build/src/types/main.d.ts +24 -0
  34. package/build/src/types/main.js +1 -0
  35. package/build/stubs/config/mercure.stub +15 -0
  36. package/build/stubs/main.d.ts +5 -0
  37. package/build/stubs/main.js +7 -0
  38. package/build/tests/fake_mercure.spec.d.ts +1 -0
  39. package/build/tests/fake_mercure.spec.js +76 -0
  40. package/build/tests/mercure.spec.d.ts +1 -0
  41. package/build/tests/mercure.spec.js +84 -0
  42. package/build/tests/publisher.spec.d.ts +1 -0
  43. package/build/tests/publisher.spec.js +101 -0
  44. package/build/tests/token_generator.spec.d.ts +1 -0
  45. package/build/tests/token_generator.spec.js +29 -0
  46. package/configure.ts +41 -0
  47. package/index.ts +14 -0
  48. package/package.json +115 -0
  49. package/providers/mercure_provider.ts +23 -0
  50. package/src/define_config.ts +18 -0
  51. package/src/errors/config_error.ts +8 -0
  52. package/src/errors/mercure_error.ts +6 -0
  53. package/src/errors/publish_error.ts +14 -0
  54. package/src/errors/timeout_error.ts +8 -0
  55. package/src/fake_mercure.ts +74 -0
  56. package/src/mercure.ts +54 -0
  57. package/src/publisher.ts +62 -0
  58. package/src/token_generator.ts +29 -0
  59. package/src/types/extended.ts +7 -0
  60. package/src/types/main.ts +31 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mdsiha/adonis-mercure/main/assets/logo.svg" width="100" alt="adonis-mercure" />
3
+ </p>
4
+
5
+ <h1 align="center">@mdsiha369/adonis-mercure</h1>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/mdsiha/adonis-mercure/actions/workflows/test.yml"><img src="https://github.com/mdsiha/adonis-mercure/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
9
+ <a href="https://www.npmjs.com/package/@mdsiha369/adonis-mercure"><img src="https://img.shields.io/npm/dm/@mdsiha369/adonis-mercure.svg?style=flat-square" alt="Downloads"></a>
10
+ <a href="https://www.npmjs.com/package/@mdsiha369/adonis-mercure"><img src="https://img.shields.io/npm/v/@mdsiha369/adonis-mercure.svg?style=flat-square" alt="Version"></a>
11
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/@mdsiha369/adonis-mercure.svg?style=flat-square" alt="License"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ AdonisJS v6 package to publish real-time updates via a <a href="https://mercure.rocks">Mercure Hub</a> (SSE)
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ - [Requirements](#requirements)
23
+ - [Installation](#installation)
24
+ - [Configuration](#configuration)
25
+ - [Usage](#usage)
26
+ - [Publish an update](#publish-an-update)
27
+ - [Multiple topics](#multiple-topics)
28
+ - [Private updates](#private-updates)
29
+ - [SSE options](#sse-options-id-type-retry)
30
+ - [Generate a subscriber token](#generate-a-subscriber-token)
31
+ - [Health check](#health-check)
32
+ - [Testing](#testing)
33
+ - [API Reference](#api-reference)
34
+ - [Roadmap](#roadmap)
35
+ - [License](#license)
36
+
37
+ ---
38
+
39
+ ## Requirements
40
+
41
+ - AdonisJS **v6**
42
+ - Node.js **>= 20.6.0**
43
+ - A running [Mercure Hub](https://mercure.rocks/docs/hub/install)
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ npm install @mdsiha369/adonis-mercure
49
+ node ace configure @mdsiha369/adonis-mercure
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ After running `ace configure`, a `config/mercure.ts` file is created and your `.env` is updated automatically.
55
+
56
+ ```ts
57
+ // config/mercure.ts
58
+ import env from '#start/env'
59
+ import { defineConfig } from '@mdsiha369/adonis-mercure'
60
+
61
+ export default defineConfig({
62
+ endpoint: env.get('MERCURE_ENDPOINT'),
63
+ adminToken: env.get('MERCURE_ADMIN_JWT'),
64
+ jwt: {
65
+ alg: 'HS256',
66
+ secret: env.get('MERCURE_JWT_SECRET'),
67
+ },
68
+ // optional — default: 5000ms, set to 0 to disable
69
+ http: {
70
+ timeout: 5000,
71
+ },
72
+ })
73
+ ```
74
+
75
+ Set the following env variables in your `.env`:
76
+
77
+ ```env
78
+ MERCURE_ENDPOINT=http://localhost:3000/.well-known/mercure
79
+ MERCURE_ADMIN_JWT=<your-admin-jwt>
80
+ MERCURE_JWT_SECRET=<your-jwt-secret>
81
+ ```
82
+
83
+ > **Note:** The `adminToken` must be a JWT signed with your hub's secret and a `"publish": ["*"]` claim in the `mercure` field. See the [Mercure auth docs](https://mercure.rocks/docs/hub/auth) for details.
84
+
85
+ ---
86
+
87
+ ## Usage
88
+
89
+ Import the service anywhere in your app:
90
+
91
+ ```ts
92
+ import mercure from '@mdsiha369/adonis-mercure/services/main'
93
+ ```
94
+
95
+ ### Publish an update
96
+
97
+ ```ts
98
+ await mercure.send('/orders/42', { status: 'shipped' })
99
+ ```
100
+
101
+ ### Multiple topics
102
+
103
+ ```ts
104
+ await mercure.send(['/orders/42', '/notifications/user/1'], { status: 'shipped' })
105
+ ```
106
+
107
+ ### Private updates
108
+
109
+ Private updates are only delivered to authenticated subscribers who hold a valid token for that topic.
110
+
111
+ ```ts
112
+ // shorthand (backward compatible)
113
+ await mercure.send('/orders/42', { status: 'shipped' }, true)
114
+
115
+ // options object
116
+ await mercure.send('/orders/42', { status: 'shipped' }, { private: true })
117
+ ```
118
+
119
+ ### SSE options (id, type, retry)
120
+
121
+ ```ts
122
+ await mercure.send(
123
+ '/orders/42',
124
+ { status: 'shipped' },
125
+ {
126
+ id: 'msg-001', // event ID — enables reconnection recovery
127
+ type: 'order.shipped', // event type
128
+ retry: 5000, // client reconnect delay in ms
129
+ private: true,
130
+ }
131
+ )
132
+ ```
133
+
134
+ ### Generate a subscriber token
135
+
136
+ Use this to create JWT tokens for your frontend clients so they can subscribe to topics, including private ones.
137
+
138
+ ```ts
139
+ // typed shorthand
140
+ const token = await mercure.generateSubscribeToken(['/orders/42'])
141
+
142
+ // or low-level
143
+ const token = await mercure.generate({ subscribe: ['/orders/42'] })
144
+ ```
145
+
146
+ Pass the token to your frontend:
147
+
148
+ ```ts
149
+ const url = new URL('http://localhost:3000/.well-known/mercure')
150
+ url.searchParams.append('topic', '/orders/42')
151
+
152
+ const eventSource = new EventSource(url.toString(), {
153
+ headers: { Authorization: `Bearer ${token}` },
154
+ })
155
+ ```
156
+
157
+ ### Health check
158
+
159
+ ```ts
160
+ const isReachable = await mercure.ping() // true | false
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Testing
166
+
167
+ `FakeMercure` lets you test your application code without a real Mercure Hub.
168
+
169
+ **Swap the container binding in your test setup:**
170
+
171
+ ```ts
172
+ import { FakeMercure } from '@mdsiha369/adonis-mercure'
173
+
174
+ // before your test
175
+ app.container.swap('mercure', () => new FakeMercure())
176
+
177
+ // after your test
178
+ app.container.restore('mercure')
179
+ ```
180
+
181
+ **Assert on what was sent:**
182
+
183
+ ```ts
184
+ const fake = (await app.container.make('mercure')) as FakeMercure
185
+
186
+ // assert a topic received a message
187
+ fake.assertSent('/orders/42')
188
+
189
+ // assert a topic received a specific payload
190
+ fake.assertSent('/orders/42', { status: 'shipped' })
191
+
192
+ // assert a topic was never sent to
193
+ fake.assertNotSent('/admin/secret')
194
+
195
+ // assert nothing was sent at all
196
+ fake.assertNothingSent()
197
+
198
+ // inspect all recorded messages
199
+ const messages = fake.getSent()
200
+
201
+ // reset between tests
202
+ fake.clear()
203
+ ```
204
+
205
+ ---
206
+
207
+ ## API Reference
208
+
209
+ ### `send(topics, data?, options?)`
210
+
211
+ Publishes an update to the Mercure Hub.
212
+
213
+ | Parameter | Type | Default | Description |
214
+ | --------- | ------------------------- | ------- | ------------------------------------------------------ |
215
+ | `topics` | `string \| string[]` | — | Topic(s) to publish to |
216
+ | `data` | `Record<string, unknown>` | `{}` | Payload — serialized as JSON |
217
+ | `options` | `boolean \| SendOptions` | `false` | `true` for private (legacy), or a `SendOptions` object |
218
+
219
+ **`SendOptions`**
220
+
221
+ | Property | Type | Description |
222
+ | --------- | --------- | ---------------------------------------------- |
223
+ | `private` | `boolean` | Restrict delivery to authenticated subscribers |
224
+ | `id` | `string` | SSE event ID (enables reconnection recovery) |
225
+ | `type` | `string` | SSE event type |
226
+ | `retry` | `number` | Client reconnect delay in milliseconds |
227
+
228
+ Throws `MercurePublishError` if the hub returns a non-2xx response.
229
+ Throws `MercureTimeoutError` if the hub does not respond within the configured timeout.
230
+
231
+ ---
232
+
233
+ ### `generateSubscribeToken(topics)`
234
+
235
+ Generates a JWT with `{ subscribe: topics }` for a frontend client.
236
+
237
+ ```ts
238
+ const token = await mercure.generateSubscribeToken(['/chat/1', '/notifications/me'])
239
+ ```
240
+
241
+ ---
242
+
243
+ ### `generate(payload)`
244
+
245
+ Low-level JWT generation. Wraps `payload` under the `mercure` claim.
246
+
247
+ ```ts
248
+ const token = await mercure.generate({ subscribe: ['/chat/1'], publish: ['/chat/1'] })
249
+ ```
250
+
251
+ ---
252
+
253
+ ### `ping()`
254
+
255
+ Returns `true` if the hub is reachable, `false` on network error or timeout.
256
+
257
+ ---
258
+
259
+ ## Roadmap
260
+
261
+ Features planned for upcoming releases:
262
+
263
+ - **`generatePublishToken(topics)`** — typed shorthand for publisher JWT tokens (symmetric to `generateSubscribeToken`)
264
+ - **`sendBatch(messages[])`** — publish multiple updates in a single call
265
+ - **Retry with backoff** — automatic retry on transient hub errors with configurable strategy
266
+ - **Typed event classes** — define reusable event objects (`new OrderShippedEvent(id)`) instead of inline payloads
267
+
268
+ Have a use case or suggestion? [Open an issue](https://github.com/mdsiha/adonis-mercure/issues).
269
+
270
+ ---
271
+
272
+ ## License
273
+
274
+ MIT — [Michael DAŞ](https://github.com/mdsiha)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { assert } from '@japa/assert';
2
+ import { configure, processCLIArgs, run } from '@japa/runner';
3
+ processCLIArgs(process.argv.splice(2));
4
+ configure({
5
+ files: ['tests/**/*.spec.ts'],
6
+ plugins: [assert()],
7
+ });
8
+ run();
@@ -0,0 +1,2 @@
1
+ import ConfigureCommand from '@adonisjs/core/commands/configure';
2
+ export declare function configure(_command: ConfigureCommand): Promise<void>;
@@ -0,0 +1,34 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Configure hook
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | The configure hook is called when someone runs "node ace configure <package>"
7
+ | command. You are free to perform any operations inside this function to
8
+ | configure the package.
9
+ |
10
+ | To make things easier, you have access to the underlying "ConfigureCommand"
11
+ | instance and you can use codemods to modify the source files.
12
+ |
13
+ */
14
+ import { stubsRoot } from './stubs/main.js';
15
+ export async function configure(_command) {
16
+ const codemods = await _command.createCodemods();
17
+ await codemods.makeUsingStub(stubsRoot, 'config/mercure.stub', {});
18
+ await codemods.defineEnvVariables({
19
+ MERCURE_ENDPOINT: 'http://localhost:3000/.well-known/mercure',
20
+ MERCURE_ADMIN_JWT: '',
21
+ MERCURE_JWT_SECRET: '',
22
+ });
23
+ await codemods.defineEnvValidations({
24
+ variables: {
25
+ MERCURE_ENDPOINT: `Env.schema.string({ format: 'url' })`,
26
+ MERCURE_ADMIN_JWT: `Env.schema.string()`,
27
+ MERCURE_JWT_SECRET: `Env.schema.string()`,
28
+ },
29
+ leadingComment: 'Mercure configuration',
30
+ });
31
+ await codemods.updateRcFile((rcFile) => {
32
+ rcFile.addProvider('@mdsiha369/adonis-mercure/mercure_provider');
33
+ });
34
+ }
@@ -0,0 +1,9 @@
1
+ export { configure } from './configure.js';
2
+ export { Mercure } from './src/mercure.js';
3
+ export { FakeMercure } from './src/fake_mercure.js';
4
+ export { defineConfig } from './src/define_config.js';
5
+ export { stubsRoot } from './stubs/main.js';
6
+ export { MercureError } from './src/errors/mercure_error.js';
7
+ export { MercurePublishError } from './src/errors/publish_error.js';
8
+ export { MercureConfigError } from './src/errors/config_error.js';
9
+ export { MercureTimeoutError } from './src/errors/timeout_error.js';
package/build/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Package entrypoint
4
+ |--------------------------------------------------------------------------
5
+ */
6
+ export { configure } from './configure.js';
7
+ export { Mercure } from './src/mercure.js';
8
+ export { FakeMercure } from './src/fake_mercure.js';
9
+ export { defineConfig } from './src/define_config.js';
10
+ export { stubsRoot } from './stubs/main.js';
11
+ export { MercureError } from './src/errors/mercure_error.js';
12
+ export { MercurePublishError } from './src/errors/publish_error.js';
13
+ export { MercureConfigError } from './src/errors/config_error.js';
14
+ export { MercureTimeoutError } from './src/errors/timeout_error.js';
@@ -0,0 +1,7 @@
1
+ import '../src/types/extended.js';
2
+ import type { ApplicationService } from '@adonisjs/core/types';
3
+ export default class MercureProvider {
4
+ protected app: ApplicationService;
5
+ constructor(app: ApplicationService);
6
+ boot(): Promise<void>;
7
+ }
@@ -0,0 +1,18 @@
1
+ import '../src/types/extended.js';
2
+ import { Mercure } from '../src/mercure.js';
3
+ import { MercureConfigError } from '../src/errors/config_error.js';
4
+ export default class MercureProvider {
5
+ app;
6
+ constructor(app) {
7
+ this.app = app;
8
+ }
9
+ async boot() {
10
+ this.app.container.singleton('mercure', async () => {
11
+ const config = this.app.config.get('mercure');
12
+ if (!config?.endpoint || !config?.adminToken || !config?.jwt?.secret) {
13
+ throw new MercureConfigError('Missing or incomplete Mercure config. Run "node ace configure @mdsiha369/adonis-mercure"');
14
+ }
15
+ return new Mercure(config);
16
+ });
17
+ }
18
+ }
@@ -0,0 +1,3 @@
1
+ import { Mercure } from '../src/mercure.js';
2
+ declare let mercure: Mercure;
3
+ export { mercure as default };
@@ -0,0 +1,6 @@
1
+ import app from '@adonisjs/core/services/app';
2
+ let mercure;
3
+ await app.booted(async () => {
4
+ mercure = await app.container.make('mercure');
5
+ });
6
+ export { mercure as default };
@@ -0,0 +1,2 @@
1
+ import type { MercureConfig } from './types/main.js';
2
+ export declare function defineConfig<T extends MercureConfig>(config: T): T;
@@ -0,0 +1,11 @@
1
+ import { MercureConfigError } from './errors/config_error.js';
2
+ const ALLOWED_ALGORITHMS = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512'];
3
+ export function defineConfig(config) {
4
+ if (!ALLOWED_ALGORITHMS.includes(config.jwt.alg)) {
5
+ throw new MercureConfigError(`Algorithm "${config.jwt.alg}" is not allowed. Use one of: ${ALLOWED_ALGORITHMS.join(', ')}`);
6
+ }
7
+ if (config.jwt.secret.length < 32) {
8
+ throw new MercureConfigError('jwt.secret must be at least 32 characters long');
9
+ }
10
+ return config;
11
+ }
@@ -0,0 +1,4 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export declare class MercureConfigError extends MercureError {
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,7 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export class MercureConfigError extends MercureError {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'MercureConfigError';
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ export declare class MercureError extends Error {
2
+ constructor(message: string);
3
+ }
@@ -0,0 +1,6 @@
1
+ export class MercureError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'MercureError';
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export declare class MercurePublishError extends MercureError {
3
+ readonly statusCode: number;
4
+ readonly responseBody: string;
5
+ constructor(statusCode: number, responseBody: string);
6
+ }
@@ -0,0 +1,11 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export class MercurePublishError extends MercureError {
3
+ statusCode;
4
+ responseBody;
5
+ constructor(statusCode, responseBody) {
6
+ super(`Mercure hub returned ${statusCode}`);
7
+ this.statusCode = statusCode;
8
+ this.name = 'MercurePublishError';
9
+ this.responseBody = responseBody.slice(0, 200);
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export declare class MercureTimeoutError extends MercureError {
3
+ readonly timeoutMs: number;
4
+ constructor(timeoutMs: number);
5
+ }
@@ -0,0 +1,9 @@
1
+ import { MercureError } from './mercure_error.js';
2
+ export class MercureTimeoutError extends MercureError {
3
+ timeoutMs;
4
+ constructor(timeoutMs) {
5
+ super(`Request to Mercure hub timed out after ${timeoutMs}ms`);
6
+ this.timeoutMs = timeoutMs;
7
+ this.name = 'MercureTimeoutError';
8
+ }
9
+ }
@@ -0,0 +1,18 @@
1
+ import type { MercureContract, SendOptions } from './types/main.js';
2
+ export interface SentMessage {
3
+ topics: string[];
4
+ data: Record<string, unknown>;
5
+ options: SendOptions;
6
+ }
7
+ export declare class FakeMercure implements MercureContract {
8
+ #private;
9
+ generate(payload: Record<string, unknown>): Promise<string>;
10
+ generateSubscribeToken(topics: string[]): Promise<string>;
11
+ send(topics: string | string[], data?: Record<string, unknown>, isPrivateOrOptions?: boolean | SendOptions): Promise<Response>;
12
+ ping(): Promise<boolean>;
13
+ assertSent(topic: string, data?: Record<string, unknown>): void;
14
+ assertNotSent(topic: string): void;
15
+ assertNothingSent(): void;
16
+ getSent(): SentMessage[];
17
+ clear(): void;
18
+ }
@@ -0,0 +1,52 @@
1
+ export class FakeMercure {
2
+ #sent = [];
3
+ #tokens = [];
4
+ async generate(payload) {
5
+ this.#tokens.push(payload);
6
+ return 'fake-token';
7
+ }
8
+ async generateSubscribeToken(topics) {
9
+ return this.generate({ subscribe: topics });
10
+ }
11
+ async send(topics, data = {}, isPrivateOrOptions = false) {
12
+ const topicList = Array.isArray(topics) ? topics : [topics];
13
+ const options = typeof isPrivateOrOptions === 'boolean' ? { private: isPrivateOrOptions } : isPrivateOrOptions;
14
+ this.#sent.push({ topics: topicList, data, options });
15
+ return new Response('1', { status: 200 });
16
+ }
17
+ async ping() {
18
+ return true;
19
+ }
20
+ assertSent(topic, data) {
21
+ const match = this.#sent.find((msg) => {
22
+ if (!msg.topics.includes(topic))
23
+ return false;
24
+ if (!data)
25
+ return true;
26
+ return JSON.stringify(msg.data) === JSON.stringify(data);
27
+ });
28
+ if (!match) {
29
+ const found = this.#sent.map((m) => m.topics.join(', ')).join('; ') || 'nothing';
30
+ throw new Error(`Expected a message on topic "${topic}" but found: [${found}]`);
31
+ }
32
+ }
33
+ assertNotSent(topic) {
34
+ const match = this.#sent.find((msg) => msg.topics.includes(topic));
35
+ if (match) {
36
+ throw new Error(`Expected no message on topic "${topic}" but one was found`);
37
+ }
38
+ }
39
+ assertNothingSent() {
40
+ if (this.#sent.length > 0) {
41
+ const topics = this.#sent.map((m) => m.topics.join(', ')).join('; ');
42
+ throw new Error(`Expected nothing to be sent but found messages on: [${topics}]`);
43
+ }
44
+ }
45
+ getSent() {
46
+ return [...this.#sent];
47
+ }
48
+ clear() {
49
+ this.#sent = [];
50
+ this.#tokens = [];
51
+ }
52
+ }
@@ -0,0 +1,9 @@
1
+ import type { MercureConfig, MercureContract, SendOptions } from './types/main.js';
2
+ export declare class Mercure implements MercureContract {
3
+ #private;
4
+ constructor(config: MercureConfig);
5
+ generate(payload: Record<string, unknown>): Promise<string>;
6
+ generateSubscribeToken(topics: string[]): Promise<string>;
7
+ send(topics: string | string[], data?: Record<string, unknown>, isPrivateOrOptions?: boolean | SendOptions): Promise<Response>;
8
+ ping(): Promise<boolean>;
9
+ }
@@ -0,0 +1,42 @@
1
+ import { TokenGenerator } from './token_generator.js';
2
+ import { Publisher } from './publisher.js';
3
+ const DEFAULT_TIMEOUT = 5000;
4
+ export class Mercure {
5
+ #config;
6
+ #tokenGenerator;
7
+ #publisher;
8
+ constructor(config) {
9
+ this.#config = config;
10
+ this.#tokenGenerator = new TokenGenerator(config);
11
+ this.#publisher = new Publisher(config);
12
+ }
13
+ generate(payload) {
14
+ return this.#tokenGenerator.generate(payload);
15
+ }
16
+ generateSubscribeToken(topics) {
17
+ return this.#tokenGenerator.generateSubscribeToken(topics);
18
+ }
19
+ send(topics, data = {}, isPrivateOrOptions = false) {
20
+ const options = typeof isPrivateOrOptions === 'boolean' ? { private: isPrivateOrOptions } : isPrivateOrOptions;
21
+ return this.#publisher.publish(topics, data, options);
22
+ }
23
+ async ping() {
24
+ const timeout = this.#config.http?.timeout ?? DEFAULT_TIMEOUT;
25
+ const controller = timeout > 0 ? new AbortController() : null;
26
+ const timer = controller ? setTimeout(() => controller.abort(), timeout) : null;
27
+ try {
28
+ await fetch(this.#config.endpoint, {
29
+ method: 'HEAD',
30
+ signal: controller?.signal,
31
+ });
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ finally {
38
+ if (timer)
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,6 @@
1
+ import type { MercureConfig, SendOptions } from './types/main.js';
2
+ export declare class Publisher {
3
+ #private;
4
+ constructor(config: MercureConfig);
5
+ publish(topics: string | string[], data?: Record<string, unknown>, options?: SendOptions): Promise<Response>;
6
+ }