@bunworks/inngest-realtime 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/CHANGELOG.md +172 -0
- package/CLAUDE.md +69 -0
- package/LICENSE.md +190 -0
- package/README.md +1 -0
- package/bun.lock +527 -0
- package/eslint.config.mjs +19 -0
- package/package.json +88 -0
- package/src/api.ts +83 -0
- package/src/channel.ts +122 -0
- package/src/env.d.ts +15 -0
- package/src/env.ts +152 -0
- package/src/hooks.ts +256 -0
- package/src/index.test.ts +840 -0
- package/src/index.ts +4 -0
- package/src/middleware.ts +68 -0
- package/src/subscribe/StreamFanout.ts +82 -0
- package/src/subscribe/TokenSubscription.ts +524 -0
- package/src/subscribe/helpers.ts +153 -0
- package/src/subscribe/index.ts +1 -0
- package/src/topic.ts +51 -0
- package/src/types.ts +499 -0
- package/src/util.ts +104 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +114 -0
- package/tsdown.config.ts +18 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { Inngest } from "inngest";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { channel, typeOnlyChannel } from "./channel";
|
|
5
|
+
import { getSubscriptionToken, subscribe } from "./subscribe";
|
|
6
|
+
import { topic } from "./topic";
|
|
7
|
+
import { type Realtime } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* assert the subject satisfies the specified type T
|
|
11
|
+
* @type T the type to check against.
|
|
12
|
+
*/
|
|
13
|
+
export function assertType<T>(subject: T): asserts subject is T {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns `true` if the given generic `T` is `any`, or `false` if it is not.
|
|
17
|
+
*/
|
|
18
|
+
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
Returns a boolean for whether the two given types are equal.
|
|
22
|
+
|
|
23
|
+
{@link https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650}
|
|
24
|
+
{@link https://stackoverflow.com/questions/68961864/how-does-the-equals-work-in-typescript/68963796#68963796}
|
|
25
|
+
|
|
26
|
+
Use-cases:
|
|
27
|
+
- If you want to make a conditional branch based on the result of a comparison of two types.
|
|
28
|
+
|
|
29
|
+
@example
|
|
30
|
+
```
|
|
31
|
+
import type {IsEqual} from 'type-fest';
|
|
32
|
+
|
|
33
|
+
// This type returns a boolean for whether the given array includes the given item.
|
|
34
|
+
// `IsEqual` is used to compare the given array at position 0 and the given item and then return true if they are equal.
|
|
35
|
+
type Includes<Value extends readonly any[], Item> =
|
|
36
|
+
Value extends readonly [Value[0], ...infer rest]
|
|
37
|
+
? IsEqual<Value[0], Item> extends true
|
|
38
|
+
? true
|
|
39
|
+
: Includes<rest, Item>
|
|
40
|
+
: false;
|
|
41
|
+
```
|
|
42
|
+
*/
|
|
43
|
+
export type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <
|
|
44
|
+
G,
|
|
45
|
+
>() => G extends B ? 1 : 2
|
|
46
|
+
? true
|
|
47
|
+
: false;
|
|
48
|
+
|
|
49
|
+
describe("subscribe", () => {
|
|
50
|
+
const app = new Inngest({ id: "test" });
|
|
51
|
+
|
|
52
|
+
describe("types", () => {
|
|
53
|
+
const createdTopic = topic("created").schema(
|
|
54
|
+
v.object({
|
|
55
|
+
id: v.string(),
|
|
56
|
+
name: v.string(),
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const updatedTopic = topic("updated").type<boolean>();
|
|
61
|
+
|
|
62
|
+
const unusedTopic = topic("unused").type<number>();
|
|
63
|
+
|
|
64
|
+
const staticChannel = channel("static")
|
|
65
|
+
.addTopic(createdTopic)
|
|
66
|
+
.addTopic(updatedTopic)
|
|
67
|
+
.addTopic(unusedTopic);
|
|
68
|
+
|
|
69
|
+
const userChannel = channel((userId: string) => `user/${userId}`)
|
|
70
|
+
.addTopic(createdTopic)
|
|
71
|
+
.addTopic(updatedTopic)
|
|
72
|
+
.addTopic(unusedTopic);
|
|
73
|
+
|
|
74
|
+
describe("channels and topics", () => {
|
|
75
|
+
describe("topic", () => {
|
|
76
|
+
test("can create a blank topic", () => {
|
|
77
|
+
const t = topic("test");
|
|
78
|
+
|
|
79
|
+
expect(t).toBeDefined();
|
|
80
|
+
assertType<Realtime.Topic.Definition>(t);
|
|
81
|
+
|
|
82
|
+
expect(t.name).toBe("test");
|
|
83
|
+
assertType<"test">(t.name);
|
|
84
|
+
|
|
85
|
+
expect(t.getSchema()).toBeUndefined();
|
|
86
|
+
|
|
87
|
+
assertType<IsAny<Realtime.Topic.InferPublish<typeof t>>>(true);
|
|
88
|
+
assertType<IsAny<Realtime.Topic.InferSubscribe<typeof t>>>(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("topic ID must be a string", () => {
|
|
92
|
+
const _fn = () => {
|
|
93
|
+
// @ts-expect-error Topic ID must be a string
|
|
94
|
+
topic(1);
|
|
95
|
+
|
|
96
|
+
// @ts-expect-error Topic ID must be a string
|
|
97
|
+
topic({ foo: "bar" });
|
|
98
|
+
|
|
99
|
+
// @ts-expect-error Topic ID must be a string
|
|
100
|
+
topic(undefined);
|
|
101
|
+
|
|
102
|
+
// @ts-expect-error Topic ID must be a string
|
|
103
|
+
topic();
|
|
104
|
+
|
|
105
|
+
// @ts-expect-error Topic ID must be a string
|
|
106
|
+
topic(null);
|
|
107
|
+
|
|
108
|
+
// @ts-expect-error Topic ID must be a string
|
|
109
|
+
topic(true);
|
|
110
|
+
|
|
111
|
+
// @ts-expect-error Topic ID must be a string
|
|
112
|
+
topic(false);
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("can type a topic", () => {
|
|
117
|
+
const t = topic("test").type<string>();
|
|
118
|
+
|
|
119
|
+
expect(t).toBeDefined();
|
|
120
|
+
assertType<Realtime.Topic.Definition>(t);
|
|
121
|
+
|
|
122
|
+
expect(t.name).toBe("test");
|
|
123
|
+
assertType<"test">(t.name);
|
|
124
|
+
|
|
125
|
+
expect(t.getSchema()).toBeUndefined();
|
|
126
|
+
|
|
127
|
+
assertType<IsEqual<string, Realtime.Topic.InferPublish<typeof t>>>(
|
|
128
|
+
true,
|
|
129
|
+
);
|
|
130
|
+
assertType<IsEqual<string, Realtime.Topic.InferSubscribe<typeof t>>>(
|
|
131
|
+
true,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("can overwrite a topic's type", () => {
|
|
136
|
+
const t = topic("test").type<string>().type<number>();
|
|
137
|
+
|
|
138
|
+
expect(t).toBeDefined();
|
|
139
|
+
assertType<Realtime.Topic.Definition>(t);
|
|
140
|
+
|
|
141
|
+
expect(t.name).toBe("test");
|
|
142
|
+
assertType<"test">(t.name);
|
|
143
|
+
|
|
144
|
+
expect(t.getSchema()).toBeUndefined();
|
|
145
|
+
|
|
146
|
+
assertType<IsEqual<number, Realtime.Topic.InferPublish<typeof t>>>(
|
|
147
|
+
true,
|
|
148
|
+
);
|
|
149
|
+
assertType<IsEqual<number, Realtime.Topic.InferSubscribe<typeof t>>>(
|
|
150
|
+
true,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("can add a schema to a topic", () => {
|
|
155
|
+
const t = topic("test").schema(v.string());
|
|
156
|
+
|
|
157
|
+
expect(t).toBeDefined();
|
|
158
|
+
assertType<Realtime.Topic.Definition>(t);
|
|
159
|
+
|
|
160
|
+
expect(t.name).toBe("test");
|
|
161
|
+
assertType<"test">(t.name);
|
|
162
|
+
|
|
163
|
+
expect(t.getSchema()).toBeDefined();
|
|
164
|
+
|
|
165
|
+
assertType<IsEqual<string, Realtime.Topic.InferPublish<typeof t>>>(
|
|
166
|
+
true,
|
|
167
|
+
);
|
|
168
|
+
assertType<IsEqual<string, Realtime.Topic.InferSubscribe<typeof t>>>(
|
|
169
|
+
true,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("schema must be a valid schema", () => {
|
|
174
|
+
const _fn = () => {
|
|
175
|
+
// @ts-expect-error Invalid schema
|
|
176
|
+
topic("test").schema({ foo: "bar" });
|
|
177
|
+
|
|
178
|
+
// @ts-expect-error Invalid schema
|
|
179
|
+
topic("test").schema(undefined);
|
|
180
|
+
|
|
181
|
+
// @ts-expect-error Invalid schema
|
|
182
|
+
topic("test").schema();
|
|
183
|
+
|
|
184
|
+
// @ts-expect-error Invalid schema
|
|
185
|
+
topic("test").schema(null);
|
|
186
|
+
|
|
187
|
+
// @ts-expect-error Invalid schema
|
|
188
|
+
topic("test").schema(true);
|
|
189
|
+
|
|
190
|
+
// @ts-expect-error Invalid schema
|
|
191
|
+
topic("test").schema(false);
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("channel", () => {
|
|
197
|
+
test("can create a blank channel", () => {
|
|
198
|
+
const c = channel("test");
|
|
199
|
+
|
|
200
|
+
expect(c).toBeDefined();
|
|
201
|
+
expect(c).toBeInstanceOf(Function);
|
|
202
|
+
assertType<Realtime.Channel.Definition>(c);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("running a static channel definition gets a channel", () => {
|
|
206
|
+
const c = staticChannel();
|
|
207
|
+
|
|
208
|
+
expect(c).toBeDefined();
|
|
209
|
+
assertType<Realtime.Channel>(c);
|
|
210
|
+
|
|
211
|
+
expect(c.name).toBe("static");
|
|
212
|
+
|
|
213
|
+
expect(c.created).toBeDefined();
|
|
214
|
+
assertType<Realtime.Topic>(c.created);
|
|
215
|
+
|
|
216
|
+
expect(c.updated).toBeDefined();
|
|
217
|
+
assertType<Realtime.Topic>(c.updated);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("running a dynamic channel definition gets a channel", () => {
|
|
221
|
+
const c = userChannel("123");
|
|
222
|
+
|
|
223
|
+
expect(c).toBeDefined();
|
|
224
|
+
assertType<Realtime.Channel>(c);
|
|
225
|
+
|
|
226
|
+
expect(c.name).toBe("user/123");
|
|
227
|
+
|
|
228
|
+
expect(c.created).toBeDefined();
|
|
229
|
+
assertType<Realtime.Topic>(c.created);
|
|
230
|
+
|
|
231
|
+
expect(c.updated).toBeDefined();
|
|
232
|
+
assertType<Realtime.Topic>(c.updated);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("channel ID must be a string or a builder", () => {
|
|
236
|
+
const _fn = () => {
|
|
237
|
+
// @ts-expect-error Channel ID must be a string
|
|
238
|
+
channel(1);
|
|
239
|
+
|
|
240
|
+
// @ts-expect-error Channel ID must be a string
|
|
241
|
+
channel({ foo: "bar" });
|
|
242
|
+
|
|
243
|
+
// @ts-expect-error Channel ID must be a string
|
|
244
|
+
channel(undefined);
|
|
245
|
+
|
|
246
|
+
// @ts-expect-error Channel ID must be a string
|
|
247
|
+
channel();
|
|
248
|
+
|
|
249
|
+
// @ts-expect-error Channel ID must be a string
|
|
250
|
+
channel(null);
|
|
251
|
+
|
|
252
|
+
// @ts-expect-error Channel ID must be a string
|
|
253
|
+
channel(true);
|
|
254
|
+
|
|
255
|
+
// @ts-expect-error Channel ID must be a string
|
|
256
|
+
channel(false);
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("can create a blank dynamic channel", () => {
|
|
261
|
+
const c = channel((userId: string) => `user/${userId}`);
|
|
262
|
+
|
|
263
|
+
expect(c).toBeDefined();
|
|
264
|
+
expect(c).toBeInstanceOf(Function);
|
|
265
|
+
assertType<Realtime.Channel.Definition>(c);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("can add a topic to a channel", () => {
|
|
269
|
+
const c = channel("test").addTopic(createdTopic);
|
|
270
|
+
|
|
271
|
+
expect(c).toBeDefined();
|
|
272
|
+
assertType<Realtime.Channel.Definition>(c);
|
|
273
|
+
|
|
274
|
+
expect(c().created).toBeDefined();
|
|
275
|
+
assertType<Realtime.Topic>(c().created);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("can add multiple topics to a channel", () => {
|
|
279
|
+
const c = channel("test")
|
|
280
|
+
.addTopic(createdTopic)
|
|
281
|
+
.addTopic(updatedTopic);
|
|
282
|
+
|
|
283
|
+
expect(c).toBeDefined();
|
|
284
|
+
assertType<Realtime.Channel.Definition>(c);
|
|
285
|
+
|
|
286
|
+
expect(c().created).toBeDefined();
|
|
287
|
+
assertType<Realtime.Topic>(c().created);
|
|
288
|
+
|
|
289
|
+
expect(c().updated).toBeDefined();
|
|
290
|
+
assertType<Realtime.Topic>(c().updated);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("can create a static channel using the types of another channel", () => {
|
|
294
|
+
const c = typeOnlyChannel<typeof staticChannel>("static");
|
|
295
|
+
|
|
296
|
+
expect(c).toBeDefined();
|
|
297
|
+
assertType<Realtime.Channel>(c);
|
|
298
|
+
|
|
299
|
+
expect(c.topics.created).toBeDefined();
|
|
300
|
+
assertType<Realtime.Topic.Definition>(c.topics.created);
|
|
301
|
+
|
|
302
|
+
expect(c.topics.updated).toBeDefined();
|
|
303
|
+
assertType<Realtime.Topic.Definition>(c.topics.updated);
|
|
304
|
+
|
|
305
|
+
expect(c.created).toBeDefined();
|
|
306
|
+
assertType<Realtime.Topic>(c.created);
|
|
307
|
+
|
|
308
|
+
expect(c.updated).toBeDefined();
|
|
309
|
+
assertType<Realtime.Topic>(c.updated);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("static channel ID must be correct if using the types of another channel", () => {
|
|
313
|
+
const _fn = () => {
|
|
314
|
+
// @ts-expect-error Incorrect channel
|
|
315
|
+
typeOnlyChannel<typeof staticChannel>("staatic");
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("can create a dynamic channel using the types of another channel", () => {
|
|
320
|
+
const c = typeOnlyChannel<typeof userChannel>("user/123");
|
|
321
|
+
|
|
322
|
+
expect(c).toBeDefined();
|
|
323
|
+
assertType<Realtime.Channel>(c);
|
|
324
|
+
|
|
325
|
+
expect(c.created).toBeDefined();
|
|
326
|
+
assertType<Realtime.Topic>(c.created);
|
|
327
|
+
|
|
328
|
+
expect(c.updated).toBeDefined();
|
|
329
|
+
assertType<Realtime.Topic>(c.updated);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("dynamic channel ID must be correct if using the types of another channel", () => {
|
|
333
|
+
const _fn = () => {
|
|
334
|
+
// @ts-expect-error Incorrect channel
|
|
335
|
+
typeOnlyChannel<typeof userChannel>("foo");
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe("strings only", () => {
|
|
342
|
+
test("can subscribe with just strings", () => {
|
|
343
|
+
const _fn = async () => {
|
|
344
|
+
const stream = await subscribe(
|
|
345
|
+
{
|
|
346
|
+
channel: "test",
|
|
347
|
+
topics: ["foo", "bar"],
|
|
348
|
+
},
|
|
349
|
+
(message) => {
|
|
350
|
+
assertType<"test">(message.channel);
|
|
351
|
+
assertType<"foo" | "bar">(message.topic);
|
|
352
|
+
|
|
353
|
+
if (message.topic === "foo") {
|
|
354
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
355
|
+
} else {
|
|
356
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
for await (const message of stream) {
|
|
362
|
+
assertType<"test">(message.channel);
|
|
363
|
+
assertType<"foo" | "bar">(message.topic);
|
|
364
|
+
|
|
365
|
+
if (message.topic === "foo") {
|
|
366
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
367
|
+
} else {
|
|
368
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const reader = stream.getReader();
|
|
373
|
+
const { value: message, done } = await reader.read();
|
|
374
|
+
if (!done) {
|
|
375
|
+
assertType<"test">(message.channel);
|
|
376
|
+
assertType<"foo" | "bar">(message.topic);
|
|
377
|
+
|
|
378
|
+
if (message.topic === "foo") {
|
|
379
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
380
|
+
} else {
|
|
381
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe("type-only channel import", () => {
|
|
389
|
+
test("errors if channel name is incorrect", () => {
|
|
390
|
+
const _fn = () => {
|
|
391
|
+
void subscribe({
|
|
392
|
+
// @ts-expect-error Incorrect channel
|
|
393
|
+
channel: typeOnlyChannel<typeof userChannel>("test"),
|
|
394
|
+
topics: ["created", "updated"],
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("errors if topic names are incorrect with static channel", () => {
|
|
400
|
+
const _fn = () => {
|
|
401
|
+
void subscribe({
|
|
402
|
+
channel: typeOnlyChannel<typeof staticChannel>("static"),
|
|
403
|
+
// @ts-expect-error Incorrect topic
|
|
404
|
+
topics: ["created", "updated", "test"],
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("errors if topic names are incorrect with dynamic channel", () => {
|
|
410
|
+
const _fn = () => {
|
|
411
|
+
void subscribe({
|
|
412
|
+
channel: typeOnlyChannel<typeof userChannel>("user/123"),
|
|
413
|
+
// @ts-expect-error Incorrect topic
|
|
414
|
+
topics: ["created", "updated", "test"],
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("can subscribe using types only of a static channel", () => {
|
|
420
|
+
const _fn = async () => {
|
|
421
|
+
const stream = await subscribe(
|
|
422
|
+
{
|
|
423
|
+
channel: typeOnlyChannel<typeof staticChannel>("static"),
|
|
424
|
+
topics: ["created", "updated"],
|
|
425
|
+
},
|
|
426
|
+
(message) => {
|
|
427
|
+
assertType<"static">(message.channel);
|
|
428
|
+
assertType<"created" | "updated">(message.topic);
|
|
429
|
+
|
|
430
|
+
if (message.topic === "created") {
|
|
431
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
432
|
+
} else {
|
|
433
|
+
assertType<boolean>(message.data);
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
for await (const message of stream) {
|
|
439
|
+
assertType<"static">(message.channel);
|
|
440
|
+
assertType<"created" | "updated">(message.topic);
|
|
441
|
+
|
|
442
|
+
if (message.topic === "created") {
|
|
443
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
444
|
+
} else {
|
|
445
|
+
assertType<boolean>(message.data);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const reader = stream.getReader();
|
|
450
|
+
const { value: message, done } = await reader.read();
|
|
451
|
+
if (!done) {
|
|
452
|
+
assertType<"static">(message.channel);
|
|
453
|
+
assertType<"created" | "updated">(message.topic);
|
|
454
|
+
|
|
455
|
+
if (message.topic === "created") {
|
|
456
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
457
|
+
} else {
|
|
458
|
+
assertType<boolean>(message.data);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("can subscribe using types only of a dynamic channel", () => {
|
|
465
|
+
const _fn = async () => {
|
|
466
|
+
const stream = await subscribe(
|
|
467
|
+
{
|
|
468
|
+
channel: typeOnlyChannel<typeof userChannel>("user/123"),
|
|
469
|
+
topics: ["created", "updated"],
|
|
470
|
+
},
|
|
471
|
+
(message) => {
|
|
472
|
+
assertType<`user/${string}`>(message.channel);
|
|
473
|
+
assertType<"created" | "updated">(message.topic);
|
|
474
|
+
|
|
475
|
+
if (message.topic === "created") {
|
|
476
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
477
|
+
} else {
|
|
478
|
+
assertType<boolean>(message.data);
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
for await (const message of stream) {
|
|
484
|
+
assertType<`user/${string}`>(message.channel);
|
|
485
|
+
assertType<"created" | "updated">(message.topic);
|
|
486
|
+
|
|
487
|
+
if (message.topic === "created") {
|
|
488
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
489
|
+
} else {
|
|
490
|
+
assertType<boolean>(message.data);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const reader = stream.getReader();
|
|
495
|
+
const { value: message, done } = await reader.read();
|
|
496
|
+
if (!done) {
|
|
497
|
+
assertType<`user/${string}`>(message.channel);
|
|
498
|
+
assertType<"created" | "updated">(message.topic);
|
|
499
|
+
|
|
500
|
+
if (message.topic === "created") {
|
|
501
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
502
|
+
} else {
|
|
503
|
+
assertType<boolean>(message.data);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("runtime channel import", () => {
|
|
511
|
+
test("errors if static definition given", () => {
|
|
512
|
+
const _fn = () => {
|
|
513
|
+
void subscribe({
|
|
514
|
+
// @ts-expect-error Definition given
|
|
515
|
+
channel: staticChannel,
|
|
516
|
+
topics: ["created", "updated"],
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test("errors if dynamic definition given", () => {
|
|
522
|
+
const _fn = () => {
|
|
523
|
+
void subscribe({
|
|
524
|
+
// @ts-expect-error Definition given
|
|
525
|
+
channel: userChannel,
|
|
526
|
+
topics: ["created", "updated"],
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("errors if topic names are incorrect with static channel", () => {
|
|
532
|
+
const _fn = () => {
|
|
533
|
+
void subscribe({
|
|
534
|
+
channel: staticChannel(),
|
|
535
|
+
// @ts-expect-error Incorrect topic
|
|
536
|
+
topics: ["created", "updated", "test"],
|
|
537
|
+
});
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("errors if topic names are incorrect with dynamic channel", () => {
|
|
542
|
+
const _fn = () => {
|
|
543
|
+
void subscribe({
|
|
544
|
+
channel: userChannel("123"),
|
|
545
|
+
// @ts-expect-error Incorrect topic
|
|
546
|
+
topics: ["created", "updated", "test"],
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("can subscribe with runtime import of a static channel", () => {
|
|
552
|
+
const _fn = async () => {
|
|
553
|
+
const stream = await subscribe({
|
|
554
|
+
channel: staticChannel(),
|
|
555
|
+
topics: ["created", "updated"],
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
for await (const message of stream) {
|
|
559
|
+
assertType<"static">(message.channel);
|
|
560
|
+
assertType<"created" | "updated">(message.topic);
|
|
561
|
+
|
|
562
|
+
if (message.topic === "created") {
|
|
563
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
564
|
+
} else {
|
|
565
|
+
assertType<boolean>(message.data);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const reader = stream.getReader();
|
|
570
|
+
const { value: message, done } = await reader.read();
|
|
571
|
+
if (!done) {
|
|
572
|
+
assertType<"static">(message.channel);
|
|
573
|
+
assertType<"created" | "updated">(message.topic);
|
|
574
|
+
|
|
575
|
+
if (message.topic === "created") {
|
|
576
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
577
|
+
} else {
|
|
578
|
+
assertType<boolean>(message.data);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("can subscribe with runtime import of a dynamic channel", () => {
|
|
585
|
+
const _fn = async () => {
|
|
586
|
+
const stream = await subscribe({
|
|
587
|
+
channel: userChannel("123"),
|
|
588
|
+
topics: ["created", "updated"],
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
for await (const message of stream) {
|
|
592
|
+
assertType<`user/${string}`>(message.channel);
|
|
593
|
+
assertType<"created" | "updated">(message.topic);
|
|
594
|
+
|
|
595
|
+
if (message.topic === "created") {
|
|
596
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
597
|
+
} else {
|
|
598
|
+
assertType<boolean>(message.data);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const reader = stream.getReader();
|
|
603
|
+
const { value: message, done } = await reader.read();
|
|
604
|
+
if (!done) {
|
|
605
|
+
assertType<`user/${string}`>(message.channel);
|
|
606
|
+
assertType<"created" | "updated">(message.topic);
|
|
607
|
+
|
|
608
|
+
if (message.topic === "created") {
|
|
609
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
610
|
+
} else {
|
|
611
|
+
assertType<boolean>(message.data);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe("tokens", () => {
|
|
619
|
+
test("can subscribe with a string-only token", () => {
|
|
620
|
+
const _fn = async () => {
|
|
621
|
+
const token = await getSubscriptionToken(app, {
|
|
622
|
+
channel: "test",
|
|
623
|
+
topics: ["foo", "bar"],
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
const stream = await subscribe(token, (message) => {
|
|
627
|
+
assertType<"test">(message.channel);
|
|
628
|
+
assertType<"foo" | "bar">(message.topic);
|
|
629
|
+
|
|
630
|
+
if (message.topic === "foo") {
|
|
631
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
632
|
+
} else {
|
|
633
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
for await (const message of stream) {
|
|
638
|
+
assertType<"test">(message.channel);
|
|
639
|
+
assertType<"foo" | "bar">(message.topic);
|
|
640
|
+
|
|
641
|
+
if (message.topic === "foo") {
|
|
642
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
643
|
+
} else {
|
|
644
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const reader = stream.getReader();
|
|
649
|
+
const { value: message, done } = await reader.read();
|
|
650
|
+
if (!done) {
|
|
651
|
+
assertType<"test">(message.channel);
|
|
652
|
+
assertType<"foo" | "bar">(message.topic);
|
|
653
|
+
|
|
654
|
+
if (message.topic === "foo") {
|
|
655
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
656
|
+
} else {
|
|
657
|
+
assertType<IsAny<typeof message.data>>(true);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test("can subscribe with a type-only import static typed token", () => {
|
|
664
|
+
const _fn = async () => {
|
|
665
|
+
const token = await getSubscriptionToken(app, {
|
|
666
|
+
channel: typeOnlyChannel<typeof staticChannel>("static"),
|
|
667
|
+
topics: ["created", "updated"],
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const stream = await subscribe(token, (message) => {
|
|
671
|
+
assertType<"static">(message.channel);
|
|
672
|
+
assertType<"created" | "updated">(message.topic);
|
|
673
|
+
|
|
674
|
+
if (message.topic === "created") {
|
|
675
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
676
|
+
} else {
|
|
677
|
+
assertType<boolean>(message.data);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
for await (const message of stream) {
|
|
682
|
+
assertType<"static">(message.channel);
|
|
683
|
+
assertType<"created" | "updated">(message.topic);
|
|
684
|
+
|
|
685
|
+
if (message.topic === "created") {
|
|
686
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
687
|
+
} else {
|
|
688
|
+
assertType<boolean>(message.data);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const reader = stream.getReader();
|
|
693
|
+
const { value: message, done } = await reader.read();
|
|
694
|
+
if (!done) {
|
|
695
|
+
assertType<"static">(message.channel);
|
|
696
|
+
assertType<"created" | "updated">(message.topic);
|
|
697
|
+
|
|
698
|
+
if (message.topic === "created") {
|
|
699
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
700
|
+
} else {
|
|
701
|
+
assertType<boolean>(message.data);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("can subscribe with a runtime import static typed token", () => {
|
|
708
|
+
const _fn = async () => {
|
|
709
|
+
const token = await getSubscriptionToken(app, {
|
|
710
|
+
channel: staticChannel(),
|
|
711
|
+
topics: ["created", "updated"],
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const stream = await subscribe(token, (message) => {
|
|
715
|
+
assertType<"static">(message.channel);
|
|
716
|
+
assertType<"created" | "updated">(message.topic);
|
|
717
|
+
|
|
718
|
+
if (message.topic === "created") {
|
|
719
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
720
|
+
} else {
|
|
721
|
+
assertType<boolean>(message.data);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
for await (const message of stream) {
|
|
726
|
+
assertType<"static">(message.channel);
|
|
727
|
+
assertType<"created" | "updated">(message.topic);
|
|
728
|
+
|
|
729
|
+
if (message.topic === "created") {
|
|
730
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
731
|
+
} else {
|
|
732
|
+
assertType<boolean>(message.data);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const reader = stream.getReader();
|
|
737
|
+
const { value: message, done } = await reader.read();
|
|
738
|
+
if (!done) {
|
|
739
|
+
assertType<"static">(message.channel);
|
|
740
|
+
assertType<"created" | "updated">(message.topic);
|
|
741
|
+
|
|
742
|
+
if (message.topic === "created") {
|
|
743
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
744
|
+
} else {
|
|
745
|
+
assertType<boolean>(message.data);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test("can subscribe with a type-only import dynamic typed token", () => {
|
|
752
|
+
const _fn = async () => {
|
|
753
|
+
const token = await getSubscriptionToken(app, {
|
|
754
|
+
channel: typeOnlyChannel<typeof userChannel>("user/123"),
|
|
755
|
+
topics: ["created", "updated"],
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
const stream = await subscribe(token, (message) => {
|
|
759
|
+
assertType<`user/${string}`>(message.channel);
|
|
760
|
+
assertType<"created" | "updated">(message.topic);
|
|
761
|
+
|
|
762
|
+
if (message.topic === "created") {
|
|
763
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
764
|
+
} else {
|
|
765
|
+
assertType<boolean>(message.data);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
for await (const message of stream) {
|
|
770
|
+
assertType<`user/${string}`>(message.channel);
|
|
771
|
+
assertType<"created" | "updated">(message.topic);
|
|
772
|
+
|
|
773
|
+
if (message.topic === "created") {
|
|
774
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
775
|
+
} else {
|
|
776
|
+
assertType<boolean>(message.data);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const reader = stream.getReader();
|
|
781
|
+
const { value: message, done } = await reader.read();
|
|
782
|
+
if (!done) {
|
|
783
|
+
assertType<`user/${string}`>(message.channel);
|
|
784
|
+
assertType<"created" | "updated">(message.topic);
|
|
785
|
+
|
|
786
|
+
if (message.topic === "created") {
|
|
787
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
788
|
+
} else {
|
|
789
|
+
assertType<boolean>(message.data);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("can subscribe with a runtime import dynamic typed token", () => {
|
|
796
|
+
const _fn = async () => {
|
|
797
|
+
const token = await getSubscriptionToken(app, {
|
|
798
|
+
channel: userChannel("123"),
|
|
799
|
+
topics: ["created", "updated"],
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const stream = await subscribe(token, (message) => {
|
|
803
|
+
assertType<`user/${string}`>(message.channel);
|
|
804
|
+
assertType<"created" | "updated">(message.topic);
|
|
805
|
+
|
|
806
|
+
if (message.topic === "created") {
|
|
807
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
808
|
+
} else {
|
|
809
|
+
assertType<boolean>(message.data);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
for await (const message of stream) {
|
|
814
|
+
assertType<`user/${string}`>(message.channel);
|
|
815
|
+
assertType<"created" | "updated">(message.topic);
|
|
816
|
+
|
|
817
|
+
if (message.topic === "created") {
|
|
818
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
819
|
+
} else {
|
|
820
|
+
assertType<boolean>(message.data);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const reader = stream.getReader();
|
|
825
|
+
const { value: message, done } = await reader.read();
|
|
826
|
+
if (!done) {
|
|
827
|
+
assertType<`user/${string}`>(message.channel);
|
|
828
|
+
assertType<"created" | "updated">(message.topic);
|
|
829
|
+
|
|
830
|
+
if (message.topic === "created") {
|
|
831
|
+
assertType<{ id: string; name: string }>(message.data);
|
|
832
|
+
} else {
|
|
833
|
+
assertType<boolean>(message.data);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
});
|