@cloudpss/message-stream 0.4.20 → 0.4.22
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/README.md +1 -24
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -17
- package/src/index.ts +6 -2
- package/dist/plugin.d.ts +0 -49
- package/dist/plugin.js +0 -133
- package/dist/plugin.js.map +0 -1
- package/jest.config.js +0 -3
- package/src/plugin.ts +0 -163
- package/tests/e2e.js +0 -24
package/README.md
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
1
|
# @cloudpss/message-stream
|
|
2
2
|
|
|
3
|
-
Message stream
|
|
4
|
-
|
|
5
|
-
## Example
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
import { HttpClient } from '@cloudpss/http-client';
|
|
9
|
-
import { MessageStreamPlugin } from '@cloudpss/message-stream/plugin';
|
|
10
|
-
import type { Stream } from '@cloudpss/message-stream';
|
|
11
|
-
import { interval } from 'rxjs';
|
|
12
|
-
import { map, take } from 'rxjs/operators';
|
|
13
|
-
|
|
14
|
-
const http = new HttpClient({
|
|
15
|
-
/* config */
|
|
16
|
-
}).use(MessageStreamPlugin());
|
|
17
|
-
const stream = await http.stream.create({ type: 'object', comment: 'test', durability: 0 });
|
|
18
|
-
await http.stream.write(
|
|
19
|
-
stream.token,
|
|
20
|
-
stream.type,
|
|
21
|
-
interval(100)
|
|
22
|
-
.pipe(take(10))
|
|
23
|
-
.pipe(map((i) => ({ i }))),
|
|
24
|
-
);
|
|
25
|
-
await http.stream.freeze(stream.token);
|
|
26
|
-
```
|
|
3
|
+
Message stream type definitions for cloudpss APIs.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import type { Opaque } from 'type-fest';
|
|
1
|
+
import type { Opaque, Simplify } from 'type-fest';
|
|
2
2
|
/** UUID */
|
|
3
3
|
type UUID = `${string}-${string}-${string}-${string}-${string}`;
|
|
4
4
|
/**
|
|
5
5
|
* 标识流的读权限
|
|
6
6
|
*/
|
|
7
7
|
export type StreamId = Opaque<UUID, 'StreamId'>;
|
|
8
|
+
/** 表示一个空白的流 ID,读取此 ID 对应的流会得到不包含消息的且已冻结的空白流 */
|
|
9
|
+
export declare const EMPTY_STREAM_ID: StreamId;
|
|
8
10
|
/**
|
|
9
11
|
* 标识流的写权限
|
|
10
12
|
*/
|
|
11
13
|
export type StreamToken = Opaque<UUID, 'StreamToken'>;
|
|
14
|
+
/** 表示一个空白的流 Token,写入此 Token 的消息会被直接丢弃,不会产生错误 */
|
|
15
|
+
export declare const EMPTY_STREAM_TOKEN: StreamToken;
|
|
12
16
|
export declare const StreamType: readonly ["object", "binary"];
|
|
13
17
|
/**
|
|
14
18
|
* 流中消息的类型
|
|
@@ -50,7 +54,7 @@ export interface Stream {
|
|
|
50
54
|
/** 消息流信息,使用 {@link StreamId} 查询 */
|
|
51
55
|
export type StreamInfo = Omit<Stream, 'token' | 'handler'>;
|
|
52
56
|
/** 创建流需要的数据 */
|
|
53
|
-
export type StreamArgs = Pick<Stream, 'type'> & Partial<Pick<Stream, 'comment' | 'durability'
|
|
57
|
+
export type StreamArgs = Simplify<Pick<Stream, 'type'> & Partial<Pick<Stream, 'comment' | 'durability'>>>;
|
|
54
58
|
/** 消息 ID */
|
|
55
59
|
export type MessageId = Opaque<UUID, 'MessageId'>;
|
|
56
60
|
/** 原始消息 */
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
/** 表示一个空白的流 ID,读取此 ID 对应的流会得到不包含消息的且已冻结的空白流 */
|
|
2
|
+
export const EMPTY_STREAM_ID = '00000000-0000-0000-0000-000000000000';
|
|
3
|
+
/** 表示一个空白的流 Token,写入此 Token 的消息会被直接丢弃,不会产生错误 */
|
|
4
|
+
export const EMPTY_STREAM_TOKEN = '00000000-0000-0000-0000-000000000000';
|
|
1
5
|
export const StreamType = ['object', 'binary'];
|
|
2
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,+CAA+C;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAkD,CAAC;AAKlF,gDAAgD;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,sCAAqD,CAAC;AACxF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAU,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudpss/message-stream",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"author": "CloudPSS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,30 +8,17 @@
|
|
|
8
8
|
"module": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"default": "./dist/index.js"
|
|
14
|
-
},
|
|
15
|
-
"./*": {
|
|
16
|
-
"types": "./dist/*.d.ts",
|
|
17
|
-
"default": "./dist/*.js"
|
|
18
|
-
}
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
19
13
|
},
|
|
20
14
|
"scripts": {
|
|
21
15
|
"start": "yarn clean && tsc --watch",
|
|
22
16
|
"build": "yarn clean && tsc",
|
|
23
17
|
"prepublishOnly": "yarn build",
|
|
24
|
-
"clean": "rimraf dist"
|
|
25
|
-
"test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
|
18
|
+
"clean": "rimraf dist"
|
|
26
19
|
},
|
|
27
20
|
"dependencies": {
|
|
28
|
-
"@cloudpss/ubjson": "~0.4.20",
|
|
29
|
-
"@cloudpss/ws": "~0.4.20",
|
|
30
|
-
"rxjs": "^7.8.1",
|
|
31
21
|
"type-fest": "^3.10.0"
|
|
32
22
|
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"@cloudpss/http-client": "~0.4.19"
|
|
35
|
-
},
|
|
36
23
|
"devDependencies": {}
|
|
37
24
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Opaque } from 'type-fest';
|
|
1
|
+
import type { Opaque, Simplify } from 'type-fest';
|
|
2
2
|
|
|
3
3
|
/** UUID */
|
|
4
4
|
type UUID = `${string}-${string}-${string}-${string}-${string}`;
|
|
@@ -6,10 +6,14 @@ type UUID = `${string}-${string}-${string}-${string}-${string}`;
|
|
|
6
6
|
* 标识流的读权限
|
|
7
7
|
*/
|
|
8
8
|
export type StreamId = Opaque<UUID, 'StreamId'>;
|
|
9
|
+
/** 表示一个空白的流 ID,读取此 ID 对应的流会得到不包含消息的且已冻结的空白流 */
|
|
10
|
+
export const EMPTY_STREAM_ID = '00000000-0000-0000-0000-000000000000' as StreamId;
|
|
9
11
|
/**
|
|
10
12
|
* 标识流的写权限
|
|
11
13
|
*/
|
|
12
14
|
export type StreamToken = Opaque<UUID, 'StreamToken'>;
|
|
15
|
+
/** 表示一个空白的流 Token,写入此 Token 的消息会被直接丢弃,不会产生错误 */
|
|
16
|
+
export const EMPTY_STREAM_TOKEN = '00000000-0000-0000-0000-000000000000' as StreamToken;
|
|
13
17
|
export const StreamType = ['object', 'binary'] as const;
|
|
14
18
|
/**
|
|
15
19
|
* 流中消息的类型
|
|
@@ -55,7 +59,7 @@ export interface Stream {
|
|
|
55
59
|
/** 消息流信息,使用 {@link StreamId} 查询 */
|
|
56
60
|
export type StreamInfo = Omit<Stream, 'token' | 'handler'>;
|
|
57
61
|
/** 创建流需要的数据 */
|
|
58
|
-
export type StreamArgs = Pick<Stream, 'type'> & Partial<Pick<Stream, 'comment' | 'durability'
|
|
62
|
+
export type StreamArgs = Simplify<Pick<Stream, 'type'> & Partial<Pick<Stream, 'comment' | 'durability'>>>;
|
|
59
63
|
|
|
60
64
|
/** 消息 ID */
|
|
61
65
|
export type MessageId = Opaque<UUID, 'MessageId'>;
|
package/dist/plugin.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { HttpClient, HttpClientPlugin } from '@cloudpss/http-client';
|
|
2
|
-
import type { Message, Stream, StreamArgs, StreamId, StreamInfo, StreamToken, StreamType } from './index.js';
|
|
3
|
-
import { Observable, type ObservableInput } from 'rxjs';
|
|
4
|
-
/** 消息流客户端 */
|
|
5
|
-
declare class MessageStreamClient {
|
|
6
|
-
readonly client: HttpClient;
|
|
7
|
-
constructor(client: HttpClient);
|
|
8
|
-
/** 解析响应 */
|
|
9
|
-
private parse;
|
|
10
|
-
/** 创建流 */
|
|
11
|
-
create<T extends StreamType>(args: StreamArgs & {
|
|
12
|
-
type: T;
|
|
13
|
-
}): Promise<Stream & {
|
|
14
|
-
type: T;
|
|
15
|
-
}>;
|
|
16
|
-
/** 批量创建流 */
|
|
17
|
-
create<T extends StreamType>(args: ReadonlyArray<StreamArgs & {
|
|
18
|
-
type: T;
|
|
19
|
-
}>): Promise<Array<Stream & {
|
|
20
|
-
type: T;
|
|
21
|
-
}>>;
|
|
22
|
-
/** 冻结流 */
|
|
23
|
-
freeze(token: StreamToken): Promise<boolean>;
|
|
24
|
-
/** 删除流 */
|
|
25
|
-
delete(token: StreamToken): Promise<void>;
|
|
26
|
-
/** 获取流信息 */
|
|
27
|
-
info(id: StreamId): Promise<StreamInfo>;
|
|
28
|
-
/** 获取流信息 */
|
|
29
|
-
get(token: StreamToken): Promise<Stream>;
|
|
30
|
-
/** 读取消息 */
|
|
31
|
-
read<T extends StreamType>(stream: {
|
|
32
|
-
id: StreamId;
|
|
33
|
-
type: T;
|
|
34
|
-
}, options?: {
|
|
35
|
-
from?: Date;
|
|
36
|
-
}): Observable<Message<T>>;
|
|
37
|
-
/** 写入消息 */
|
|
38
|
-
write<T extends StreamType>(stream: {
|
|
39
|
-
token: StreamToken;
|
|
40
|
-
type: T;
|
|
41
|
-
}, data: ObservableInput<Message<T>['data']>): Promise<void>;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* 添加消息流插件,使得可以通过 `client.stream` 访问消息流相关接口
|
|
45
|
-
*/
|
|
46
|
-
export declare function MessageStreamPlugin(): HttpClientPlugin<{
|
|
47
|
-
readonly stream: MessageStreamClient;
|
|
48
|
-
}>;
|
|
49
|
-
export {};
|
package/dist/plugin.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { WebSocket, connected, disconnected } from '@cloudpss/ws';
|
|
2
|
-
import { Observable, from } from 'rxjs';
|
|
3
|
-
import { decode, encode } from '@cloudpss/ubjson';
|
|
4
|
-
/** 消息流客户端 */
|
|
5
|
-
class MessageStreamClient {
|
|
6
|
-
constructor(client) {
|
|
7
|
-
this.client = client;
|
|
8
|
-
}
|
|
9
|
-
/** 解析响应 */
|
|
10
|
-
parse(data) {
|
|
11
|
-
data.createdAt = new Date(data.createdAt);
|
|
12
|
-
data.lastReadAt = data.lastReadAt ? new Date(data.lastReadAt) : undefined;
|
|
13
|
-
data.lastWrittenAt = data.lastWrittenAt ? new Date(data.lastWrittenAt) : undefined;
|
|
14
|
-
return data;
|
|
15
|
-
}
|
|
16
|
-
/** 创建流 */
|
|
17
|
-
async create(args) {
|
|
18
|
-
if (Array.isArray(args)) {
|
|
19
|
-
const list = args;
|
|
20
|
-
if (list.length === 0)
|
|
21
|
-
return [];
|
|
22
|
-
const result = await this.client.restful('/streams/bulk', args);
|
|
23
|
-
return result.data.map((item) => this.parse(item));
|
|
24
|
-
}
|
|
25
|
-
const result = await this.client.restful('/streams', args);
|
|
26
|
-
return this.parse(result.data);
|
|
27
|
-
}
|
|
28
|
-
/** 冻结流 */
|
|
29
|
-
async freeze(token) {
|
|
30
|
-
const result = await this.client.restful(`/streams/token/${token}/freeze`, undefined, { method: 'PUT' });
|
|
31
|
-
if (result.response.status === 204)
|
|
32
|
-
return false;
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
/** 删除流 */
|
|
36
|
-
async delete(token) {
|
|
37
|
-
await this.client.restful(`/streams/token/${token}`, undefined, { method: 'DELETE' });
|
|
38
|
-
}
|
|
39
|
-
/** 获取流信息 */
|
|
40
|
-
async info(id) {
|
|
41
|
-
const result = await this.client.restful(`/streams/id/${id}/info`);
|
|
42
|
-
return this.parse(result.data);
|
|
43
|
-
}
|
|
44
|
-
/** 获取流信息 */
|
|
45
|
-
async get(token) {
|
|
46
|
-
const result = await this.client.restful(`/streams/token/${token}/info`);
|
|
47
|
-
return this.parse(result.data);
|
|
48
|
-
}
|
|
49
|
-
/** 读取消息 */
|
|
50
|
-
read(stream, options) {
|
|
51
|
-
const { id, type } = stream;
|
|
52
|
-
const from = options?.from && Number(options.from) ? `?from=${options.from.toISOString()}` : '';
|
|
53
|
-
const url = this.client.config.apiUrl.replace(/^http/, 'ws') + `/streams/id/${id}${from}`;
|
|
54
|
-
return new Observable((subscriber) => {
|
|
55
|
-
const ws = new WebSocket(url);
|
|
56
|
-
if (ws.binaryType === 'blob')
|
|
57
|
-
ws.binaryType = 'arraybuffer';
|
|
58
|
-
ws.addEventListener('message', (event) => {
|
|
59
|
-
if (typeof event.data === 'string' || !event.data)
|
|
60
|
-
return;
|
|
61
|
-
const message = decode(event.data);
|
|
62
|
-
message.timestamp = new Date(message.timestamp);
|
|
63
|
-
if (type === 'object') {
|
|
64
|
-
message.data = decode(message.data);
|
|
65
|
-
}
|
|
66
|
-
subscriber.next(message);
|
|
67
|
-
});
|
|
68
|
-
ws.addEventListener('close', (ev) => {
|
|
69
|
-
if (ev.code !== 1000) {
|
|
70
|
-
subscriber.error(new Error(`WebSocket closed with code ${ev.code}: ${ev.reason}`));
|
|
71
|
-
}
|
|
72
|
-
else if (ev.reason) {
|
|
73
|
-
subscriber.error(new Error(ev.reason));
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
subscriber.complete();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
ws.addEventListener('error', (ev) => {
|
|
80
|
-
const error = ev.error ?? new Error(`WebSocket error`);
|
|
81
|
-
subscriber.error(error);
|
|
82
|
-
});
|
|
83
|
-
return () => {
|
|
84
|
-
if (ws.readyState === ws.OPEN)
|
|
85
|
-
ws.close(1000);
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
/** 写入消息 */
|
|
90
|
-
async write(stream, data) {
|
|
91
|
-
const { token, type } = stream;
|
|
92
|
-
const url = this.client.config.apiUrl.replace(/^http/, 'ws') + `/streams/token/${token}`;
|
|
93
|
-
const ws = new WebSocket(url);
|
|
94
|
-
await connected(ws);
|
|
95
|
-
return new Promise((resolve, reject) => {
|
|
96
|
-
const sub = from(data).subscribe({
|
|
97
|
-
next: (data) => {
|
|
98
|
-
const bin = type === 'binary' ? data : encode(data);
|
|
99
|
-
if (!ArrayBuffer.isView(bin)) {
|
|
100
|
-
reject(new Error(`Invalid data type: ${typeof data}`));
|
|
101
|
-
sub.unsubscribe();
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
ws.send(bin);
|
|
105
|
-
},
|
|
106
|
-
error: (error) => {
|
|
107
|
-
// error from source observable
|
|
108
|
-
reject(error);
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
sub.add(() => {
|
|
112
|
-
if (ws.readyState === ws.OPEN) {
|
|
113
|
-
ws.close(1000);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
disconnected(ws)
|
|
117
|
-
.then(resolve, reject)
|
|
118
|
-
.finally(() => sub.unsubscribe());
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/** 消息流插件 */
|
|
123
|
-
const MessageStream = (client) => {
|
|
124
|
-
const stream = new MessageStreamClient(client);
|
|
125
|
-
Object.defineProperty(client, 'stream', { value: stream });
|
|
126
|
-
};
|
|
127
|
-
/**
|
|
128
|
-
* 添加消息流插件,使得可以通过 `client.stream` 访问消息流相关接口
|
|
129
|
-
*/
|
|
130
|
-
export function MessageStreamPlugin() {
|
|
131
|
-
return MessageStream;
|
|
132
|
-
}
|
|
133
|
-
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
DELETED
package/src/plugin.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { HttpClient, HttpClientPlugin } from '@cloudpss/http-client';
|
|
2
|
-
import { WebSocket, connected, disconnected } from '@cloudpss/ws';
|
|
3
|
-
import type { Writable } from 'type-fest';
|
|
4
|
-
import type {
|
|
5
|
-
Message,
|
|
6
|
-
RawMessage,
|
|
7
|
-
Stream,
|
|
8
|
-
StreamArgs,
|
|
9
|
-
StreamId,
|
|
10
|
-
StreamInfo,
|
|
11
|
-
StreamToken,
|
|
12
|
-
StreamType,
|
|
13
|
-
} from './index.js';
|
|
14
|
-
import { Observable, type ObservableInput, from } from 'rxjs';
|
|
15
|
-
import { decode, encode } from '@cloudpss/ubjson';
|
|
16
|
-
|
|
17
|
-
/** 消息流客户端 */
|
|
18
|
-
class MessageStreamClient {
|
|
19
|
-
constructor(readonly client: HttpClient) {}
|
|
20
|
-
|
|
21
|
-
/** 解析响应 */
|
|
22
|
-
private parse<T extends Stream | StreamInfo>(data: T): T {
|
|
23
|
-
(data as Writable<T>).createdAt = new Date(data.createdAt);
|
|
24
|
-
data.lastReadAt = data.lastReadAt ? new Date(data.lastReadAt) : undefined;
|
|
25
|
-
data.lastWrittenAt = data.lastWrittenAt ? new Date(data.lastWrittenAt) : undefined;
|
|
26
|
-
return data;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** 创建流 */
|
|
30
|
-
async create<T extends StreamType>(args: StreamArgs & { type: T }): Promise<Stream & { type: T }>;
|
|
31
|
-
/** 批量创建流 */
|
|
32
|
-
async create<T extends StreamType>(
|
|
33
|
-
args: ReadonlyArray<StreamArgs & { type: T }>,
|
|
34
|
-
): Promise<Array<Stream & { type: T }>>;
|
|
35
|
-
/** 创建流 */
|
|
36
|
-
async create(args: StreamArgs | readonly StreamArgs[]): Promise<Stream | Stream[]> {
|
|
37
|
-
if (Array.isArray(args)) {
|
|
38
|
-
const list = args as readonly StreamArgs[];
|
|
39
|
-
if (list.length === 0) return [];
|
|
40
|
-
const result = await this.client.restful<Stream[]>('/streams/bulk', args);
|
|
41
|
-
return result.data.map((item) => this.parse(item));
|
|
42
|
-
}
|
|
43
|
-
const result = await this.client.restful<Stream>('/streams', args);
|
|
44
|
-
return this.parse(result.data);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 冻结流 */
|
|
48
|
-
async freeze(token: StreamToken): Promise<boolean> {
|
|
49
|
-
const result = await this.client.restful(`/streams/token/${token}/freeze`, undefined, { method: 'PUT' });
|
|
50
|
-
if (result.response.status === 204) return false;
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** 删除流 */
|
|
55
|
-
async delete(token: StreamToken): Promise<void> {
|
|
56
|
-
await this.client.restful(`/streams/token/${token}`, undefined, { method: 'DELETE' });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 获取流信息 */
|
|
60
|
-
async info(id: StreamId): Promise<StreamInfo> {
|
|
61
|
-
const result = await this.client.restful<StreamInfo>(`/streams/id/${id}/info`);
|
|
62
|
-
return this.parse(result.data);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** 获取流信息 */
|
|
66
|
-
async get(token: StreamToken): Promise<Stream> {
|
|
67
|
-
const result = await this.client.restful<Stream>(`/streams/token/${token}/info`);
|
|
68
|
-
return this.parse(result.data);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** 读取消息 */
|
|
72
|
-
read<T extends StreamType>(
|
|
73
|
-
stream: {
|
|
74
|
-
id: StreamId;
|
|
75
|
-
type: T;
|
|
76
|
-
},
|
|
77
|
-
options?: { from?: Date },
|
|
78
|
-
): Observable<Message<T>> {
|
|
79
|
-
const { id, type } = stream;
|
|
80
|
-
const from = options?.from && Number(options.from) ? `?from=${options.from.toISOString()}` : '';
|
|
81
|
-
const url = this.client.config.apiUrl.replace(/^http/, 'ws') + `/streams/id/${id}${from}`;
|
|
82
|
-
return new Observable<Message<T>>((subscriber) => {
|
|
83
|
-
const ws = new WebSocket(url);
|
|
84
|
-
if (ws.binaryType === 'blob') ws.binaryType = 'arraybuffer';
|
|
85
|
-
ws.addEventListener('message', (event) => {
|
|
86
|
-
if (typeof event.data === 'string' || !event.data) return;
|
|
87
|
-
const message = decode(event.data as BinaryData) as Writable<RawMessage>;
|
|
88
|
-
message.timestamp = new Date(message.timestamp);
|
|
89
|
-
if (type === 'object') {
|
|
90
|
-
(message as Writable<Message<'object'>>).data = decode(message.data);
|
|
91
|
-
}
|
|
92
|
-
subscriber.next(message);
|
|
93
|
-
});
|
|
94
|
-
ws.addEventListener('close', (ev) => {
|
|
95
|
-
if (ev.code !== 1000) {
|
|
96
|
-
subscriber.error(new Error(`WebSocket closed with code ${ev.code}: ${ev.reason}`));
|
|
97
|
-
} else if (ev.reason) {
|
|
98
|
-
subscriber.error(new Error(ev.reason));
|
|
99
|
-
} else {
|
|
100
|
-
subscriber.complete();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
ws.addEventListener('error', (ev) => {
|
|
104
|
-
const error = ((ev as ErrorEvent).error as Error) ?? new Error(`WebSocket error`);
|
|
105
|
-
subscriber.error(error);
|
|
106
|
-
});
|
|
107
|
-
return () => {
|
|
108
|
-
if (ws.readyState === ws.OPEN) ws.close(1000);
|
|
109
|
-
};
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** 写入消息 */
|
|
114
|
-
async write<T extends StreamType>(
|
|
115
|
-
stream: {
|
|
116
|
-
token: StreamToken;
|
|
117
|
-
type: T;
|
|
118
|
-
},
|
|
119
|
-
data: ObservableInput<Message<T>['data']>,
|
|
120
|
-
): Promise<void> {
|
|
121
|
-
const { token, type } = stream;
|
|
122
|
-
const url = this.client.config.apiUrl.replace(/^http/, 'ws') + `/streams/token/${token}`;
|
|
123
|
-
const ws = new WebSocket(url);
|
|
124
|
-
await connected(ws);
|
|
125
|
-
return new Promise((resolve, reject) => {
|
|
126
|
-
const sub = from(data).subscribe({
|
|
127
|
-
next: (data) => {
|
|
128
|
-
const bin = type === 'binary' ? (data as Message<'binary'>['data']) : encode(data);
|
|
129
|
-
if (!ArrayBuffer.isView(bin)) {
|
|
130
|
-
reject(new Error(`Invalid data type: ${typeof data}`));
|
|
131
|
-
sub.unsubscribe();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
ws.send(bin);
|
|
135
|
-
},
|
|
136
|
-
error: (error) => {
|
|
137
|
-
// error from source observable
|
|
138
|
-
reject(error);
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
sub.add(() => {
|
|
142
|
-
if (ws.readyState === ws.OPEN) {
|
|
143
|
-
ws.close(1000);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
disconnected(ws)
|
|
147
|
-
.then(resolve, reject)
|
|
148
|
-
.finally(() => sub.unsubscribe());
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/** 消息流插件 */
|
|
153
|
-
const MessageStream: HttpClientPlugin<{ readonly stream: MessageStreamClient }> = (client) => {
|
|
154
|
-
const stream = new MessageStreamClient(client);
|
|
155
|
-
Object.defineProperty(client, 'stream', { value: stream });
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* 添加消息流插件,使得可以通过 `client.stream` 访问消息流相关接口
|
|
160
|
-
*/
|
|
161
|
-
export function MessageStreamPlugin(): HttpClientPlugin<{ readonly stream: MessageStreamClient }> {
|
|
162
|
-
return MessageStream;
|
|
163
|
-
}
|
package/tests/e2e.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { setTimeout } from 'node:timers/promises';
|
|
3
|
-
import { HttpClient } from '@cloudpss/http-client';
|
|
4
|
-
import { MessageStreamPlugin } from '@cloudpss/message-stream/plugin';
|
|
5
|
-
import { interval, map, take } from 'rxjs';
|
|
6
|
-
|
|
7
|
-
const http = new HttpClient({
|
|
8
|
-
apiUrl: 'http://dev.local.ddns.cloudpss.net/api/',
|
|
9
|
-
}).use(MessageStreamPlugin());
|
|
10
|
-
console.log(http);
|
|
11
|
-
const stream = await http.stream.create({ type: 'binary', comment: 'test', durability: 0 });
|
|
12
|
-
await http.stream.write(
|
|
13
|
-
stream,
|
|
14
|
-
interval(100)
|
|
15
|
-
.pipe(take(10))
|
|
16
|
-
.pipe(map((i) => new Uint32Array([i]))),
|
|
17
|
-
);
|
|
18
|
-
await http.stream.freeze(stream.token);
|
|
19
|
-
console.log(stream);
|
|
20
|
-
http.stream.read(stream).subscribe((x) => {
|
|
21
|
-
console.log(x.data);
|
|
22
|
-
});
|
|
23
|
-
await setTimeout(10);
|
|
24
|
-
await http.stream.delete(stream.token);
|