@yydb/yydb-client 0.0.1 → 0.0.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.
- package/README.md +69 -16
- package/dist/client.d.ts +11 -4
- package/dist/client.js +22 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/node.d.ts +5 -5
- package/dist/node.js +10 -12
- package/dist/tcp.d.ts +1 -1
- package/dist/tcp.js +62 -26
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js +11 -11
- package/dist/wire.d.ts +36 -1
- package/dist/wire.js +181 -17
- package/dist/ws.d.ts +1 -1
- package/dist/ws.js +52 -32
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,26 +1,79 @@
|
|
|
1
1
|
# `@yydb/yydb-client`
|
|
2
2
|
|
|
3
|
-
TypeScript client for the
|
|
4
|
-
`homepage`.
|
|
3
|
+
**Lightweight TypeScript client for the YY wire protocol (WebSocket or TCP).**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Use this when you already run `yydb serve` (from [`@yydb/yydb`](../yydb) CLI or another host) and only need the client
|
|
6
|
+
surface — browser, WebUI, Electron renderer, or a thin Node process.
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/@yydb/yydb-client)
|
|
9
|
+
[](https://www.npmjs.com/package/@yydb/yydb-client)
|
|
10
|
+
[](https://github.com/yy-database/yydb/actions/workflows/check-rust.yml)
|
|
11
|
+
[](https://github.com/yy-database/yydb/actions/workflows/check-typescript.yml)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
| --- | --- |
|
|
14
|
-
| `@yydb/yydb-client` | WebSocket (`/wire`) — browsers / WebUI |
|
|
15
|
-
| `@yydb/yydb-client/node` | Node TCP (or `ws://` WebSocket) |
|
|
13
|
+
## Install
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install @yydb/yydb-client
|
|
17
|
+
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
- `backends/yydb-client` — YYDB **remote** TCP client for `yydb serve`
|
|
19
|
+
## Browser or WebSocket
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Start a local host first:
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
```text
|
|
24
|
+
yydb serve app.yydb --bind 127.0.0.1:7700
|
|
25
|
+
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Then connect from browser-compatible code:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import {Client} from "@yydb/yydb-client";
|
|
31
|
+
|
|
32
|
+
const db = await Client.connect("ws://127.0.0.1:7700/wire");
|
|
33
|
+
await db.ensureSchema(1, "table Setting { @@id: uuid, key: utf8, value: utf8 }");
|
|
34
|
+
await db.put("theme", "dark");
|
|
35
|
+
console.log(await db.get("theme"));
|
|
36
|
+
db.close();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Node TCP
|
|
40
|
+
|
|
41
|
+
The `/node` entry point supports a TCP endpoint and `ws://` URLs:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import {connect} from "@yydb/yydb-client/node";
|
|
45
|
+
|
|
46
|
+
const db = await connect("127.0.0.1:7700");
|
|
47
|
+
console.log(await db.info());
|
|
48
|
+
db.close();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If a Node application only needs to open a local `.yydb` file, use
|
|
52
|
+
[`@yydb/yydb`](https://www.npmjs.com/package/@yydb/yydb). It manages the local engine for you; this package is for an
|
|
53
|
+
existing host or a custom transport integration.
|
|
54
|
+
|
|
55
|
+
## Exports
|
|
56
|
+
|
|
57
|
+
| Import | Transport | Typical use |
|
|
58
|
+
|--------------------------|-------------------|--------------------|
|
|
59
|
+
| `@yydb/yydb-client` | WebSocket `/wire` | Browsers and WebUI |
|
|
60
|
+
| `@yydb/yydb-client/node` | TCP or WebSocket | Node and Electron |
|
|
61
|
+
|
|
62
|
+
The high-level client includes `info`, schema ensure/get, key/value get/put, server version, and close operations. Frame
|
|
63
|
+
helpers such as `encodeFrame` and
|
|
64
|
+
`MsgType` are exported for tools and tests.
|
|
65
|
+
|
|
66
|
+
## Compatibility and security
|
|
67
|
+
|
|
68
|
+
The client accepts hosts that announce either `YYDB` or `YYDS` on the shared wire. The reference `yydb serve` endpoint
|
|
69
|
+
has no authentication, so connect only to a host you control, normally `127.0.0.1` or `localhost`.
|
|
70
|
+
|
|
71
|
+
Protocol details for implementers are in the
|
|
72
|
+
[`yydb-serve-protocol` skill](../yydb-skills/skills/yydb-serve-protocol/SKILL.md) (`@yydb/yydb-skills`).
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- [Node file API](https://www.npmjs.com/package/@yydb/yydb)
|
|
77
|
+
- [Local WebUI](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/yydb-webui)
|
|
78
|
+
- [YYDB user guides](https://github.com/yy-database/yydb.rs/tree/dev/projects/packages/homepage/documentation/zh-hans)
|
|
79
|
+
- [License](https://github.com/yy-database/yydb.rs/blob/dev/License.md)
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { SchemaVersion } from
|
|
2
|
-
import type { Transport } from
|
|
3
|
-
export type { Frame, MsgTypeCode, SchemaVersion, } from
|
|
4
|
-
export {
|
|
1
|
+
import { MicroRegisterPayload, SchemaVersion, WireUdfScalarValue } from './wire.js';
|
|
2
|
+
import type { MicroHostHandler, Transport } from './transport.js';
|
|
3
|
+
export type { Frame, MicroHostInvokePayload, MicroRegisterPayload, MsgTypeCode, SchemaVersion, WireUdfScalarKind, WireUdfScalarValue, } from './wire.js';
|
|
4
|
+
export type { MicroHostHandler } from './transport.js';
|
|
5
|
+
export { MsgType, encodeFrame, decodeFrame } from './wire.js';
|
|
5
6
|
export interface ConnectOptions {
|
|
6
7
|
/** `ws://127.0.0.1:7700/wire` or `127.0.0.1:7700` (upgraded to `/wire`). */
|
|
7
8
|
endpoint: string;
|
|
@@ -28,5 +29,11 @@ export declare class Client {
|
|
|
28
29
|
ensureSchema(version: number, document: string): Promise<void>;
|
|
29
30
|
get(key: string): Promise<Uint8Array | null>;
|
|
30
31
|
put(key: string, value: Uint8Array | string): Promise<void>;
|
|
32
|
+
/** Register a session-local host micro contract on the remote engine. */
|
|
33
|
+
registerMicro(payload: MicroRegisterPayload): Promise<void>;
|
|
34
|
+
/** Install the host handler used to answer `MicroHostInvoke` server pushes. */
|
|
35
|
+
setMicroHostHandler(handler: MicroHostHandler | undefined): void;
|
|
36
|
+
/** Invoke a registered scalar UDF on the remote engine. */
|
|
37
|
+
callScalar(name: string, version: number, args: readonly WireUdfScalarValue[]): Promise<WireUdfScalarValue>;
|
|
31
38
|
private roundtrip;
|
|
32
39
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { decodeKvGetOk, decodeSchemaGetOk, encodeKvGet, encodeKvPut, encodeSchemaEnsure, MsgType, } from
|
|
2
|
-
import { openWebSocketTransport } from
|
|
3
|
-
export { MsgType, encodeFrame, decodeFrame } from
|
|
1
|
+
import { decodeKvGetOk, decodeScalarCallOk, decodeSchemaGetOk, encodeKvGet, encodeKvPut, encodeMicroRegister, encodeScalarCall, encodeSchemaEnsure, MsgType, } from './wire.js';
|
|
2
|
+
import { openWebSocketTransport } from './ws.js';
|
|
3
|
+
export { MsgType, encodeFrame, decodeFrame } from './wire.js';
|
|
4
4
|
/**
|
|
5
5
|
* TypeScript client for YY wire (browser = WebSocket `/wire`).
|
|
6
6
|
* Accepts product magic `YYDB` or `YYDS` as equivalent self-claims (same wire);
|
|
@@ -20,9 +20,9 @@ export class Client {
|
|
|
20
20
|
}
|
|
21
21
|
/** Open WebSocket to `yydb serve` `/wire` and Hello-handshake. */
|
|
22
22
|
static async connect(options) {
|
|
23
|
-
const endpoint = typeof options ===
|
|
23
|
+
const endpoint = typeof options === 'string' ? options : options.endpoint;
|
|
24
24
|
if (!endpoint.trim()) {
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error('empty serve endpoint');
|
|
26
26
|
}
|
|
27
27
|
const transport = await openWebSocketTransport(endpoint);
|
|
28
28
|
const client = new Client(endpoint, transport);
|
|
@@ -55,9 +55,22 @@ export class Client {
|
|
|
55
55
|
return decodeKvGetOk(response.body);
|
|
56
56
|
}
|
|
57
57
|
async put(key, value) {
|
|
58
|
-
const bytes = typeof value ===
|
|
58
|
+
const bytes = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
|
59
59
|
await this.roundtrip(MsgType.KvPut, encodeKvPut(key, bytes));
|
|
60
60
|
}
|
|
61
|
+
/** Register a session-local host micro contract on the remote engine. */
|
|
62
|
+
async registerMicro(payload) {
|
|
63
|
+
await this.roundtrip(MsgType.MicroRegister, encodeMicroRegister(payload));
|
|
64
|
+
}
|
|
65
|
+
/** Install the host handler used to answer `MicroHostInvoke` server pushes. */
|
|
66
|
+
setMicroHostHandler(handler) {
|
|
67
|
+
this.transport.setMicroHostHandler?.(handler);
|
|
68
|
+
}
|
|
69
|
+
/** Invoke a registered scalar UDF on the remote engine. */
|
|
70
|
+
async callScalar(name, version, args) {
|
|
71
|
+
const response = await this.roundtrip(MsgType.ScalarCall, encodeScalarCall(name, version, args));
|
|
72
|
+
return decodeScalarCallOk(response.body);
|
|
73
|
+
}
|
|
61
74
|
async roundtrip(msgType, body) {
|
|
62
75
|
const requestId = this.nextId++;
|
|
63
76
|
const response = await this.transport.send({
|
|
@@ -67,7 +80,7 @@ export class Client {
|
|
|
67
80
|
body,
|
|
68
81
|
});
|
|
69
82
|
if (response.requestId !== requestId) {
|
|
70
|
-
throw new Error(
|
|
83
|
+
throw new Error('wire response request_id mismatch');
|
|
71
84
|
}
|
|
72
85
|
if (response.msgType === MsgType.Error) {
|
|
73
86
|
throw new Error(new TextDecoder().decode(response.body));
|
|
@@ -79,6 +92,8 @@ export class Client {
|
|
|
79
92
|
[MsgType.SchemaEnsure]: MsgType.SchemaEnsureOk,
|
|
80
93
|
[MsgType.KvGet]: MsgType.KvGetOk,
|
|
81
94
|
[MsgType.KvPut]: MsgType.KvPutOk,
|
|
95
|
+
[MsgType.MicroRegister]: MsgType.MicroRegisterOk,
|
|
96
|
+
[MsgType.ScalarCall]: MsgType.ScalarCallOk,
|
|
82
97
|
};
|
|
83
98
|
const want = expectedOk[msgType];
|
|
84
99
|
if (want !== undefined && response.msgType !== want) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Browser-safe entry (WebSocket). For Node TCP use `@yydb/yydb-client/node`. */
|
|
2
|
-
export { Client } from
|
|
3
|
-
export type { ConnectOptions } from
|
|
4
|
-
export { MAGIC, MAGIC_YYDB, MAGIC_YYDS, WIRE_VERSION, WIRE_VERSION_NEXT, HEADER_LEN, MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk, } from
|
|
5
|
-
export type { Frame, MsgTypeCode, ProductMagic, SchemaVersion } from
|
|
2
|
+
export { Client } from './client.js';
|
|
3
|
+
export type { ConnectOptions } from './client.js';
|
|
4
|
+
export { MAGIC, MAGIC_YYDB, MAGIC_YYDS, WIRE_VERSION, WIRE_VERSION_NEXT, HEADER_LEN, MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk, } from './wire.js';
|
|
5
|
+
export type { Frame, MsgTypeCode, ProductMagic, SchemaVersion } from './wire.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Browser-safe entry (WebSocket). For Node TCP use `@yydb/yydb-client/node`. */
|
|
2
|
-
export { Client } from
|
|
3
|
-
export { MAGIC, MAGIC_YYDB, MAGIC_YYDS, WIRE_VERSION, WIRE_VERSION_NEXT, HEADER_LEN, MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk, } from
|
|
2
|
+
export { Client } from './client.js';
|
|
3
|
+
export { MAGIC, MAGIC_YYDB, MAGIC_YYDS, WIRE_VERSION, WIRE_VERSION_NEXT, HEADER_LEN, MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk, } from './wire.js';
|
package/dist/node.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Client, type ConnectOptions } from
|
|
2
|
-
export { Client } from
|
|
3
|
-
export type { ConnectOptions } from
|
|
4
|
-
export { MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk
|
|
5
|
-
export type { Frame, MsgTypeCode, SchemaVersion } from
|
|
1
|
+
import { Client, type ConnectOptions } from './client.js';
|
|
2
|
+
export { Client } from './client.js';
|
|
3
|
+
export type { ConnectOptions } from './client.js';
|
|
4
|
+
export { MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk } from './wire.js';
|
|
5
|
+
export type { Frame, MsgTypeCode, SchemaVersion } from './wire.js';
|
|
6
6
|
/** Node entry: TCP by default; `ws://` uses WebSocket. */
|
|
7
7
|
export declare function connect(options: ConnectOptions | string): Promise<Client>;
|
package/dist/node.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { Client } from
|
|
2
|
-
import { openTcpTransport } from
|
|
3
|
-
import { openWebSocketTransport } from
|
|
4
|
-
import { normalizeWsUrl } from
|
|
5
|
-
export { Client } from
|
|
6
|
-
export { MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk
|
|
1
|
+
import { Client } from './client.js';
|
|
2
|
+
import { openTcpTransport } from './tcp.js';
|
|
3
|
+
import { openWebSocketTransport } from './ws.js';
|
|
4
|
+
import { normalizeWsUrl } from './transport.js';
|
|
5
|
+
export { Client } from './client.js';
|
|
6
|
+
export { MsgType, encodeFrame, decodeFrame, encodeSchemaEnsure, encodeKvGet, encodeKvPut, decodeSchemaGetOk, decodeKvGetOk } from './wire.js';
|
|
7
7
|
function wantsWebSocket(endpoint) {
|
|
8
8
|
const trimmed = endpoint.trim();
|
|
9
|
-
return trimmed.startsWith(
|
|
9
|
+
return trimmed.startsWith('ws://') || trimmed.startsWith('wss://');
|
|
10
10
|
}
|
|
11
11
|
/** Node entry: TCP by default; `ws://` uses WebSocket. */
|
|
12
12
|
export async function connect(options) {
|
|
13
|
-
const endpoint = typeof options ===
|
|
13
|
+
const endpoint = typeof options === 'string' ? options : options.endpoint;
|
|
14
14
|
if (!endpoint.trim()) {
|
|
15
|
-
throw new Error(
|
|
15
|
+
throw new Error('empty serve endpoint');
|
|
16
16
|
}
|
|
17
|
-
const transport = wantsWebSocket(endpoint)
|
|
18
|
-
? await openWebSocketTransport(endpoint)
|
|
19
|
-
: await openTcpTransport(endpoint);
|
|
17
|
+
const transport = wantsWebSocket(endpoint) ? await openWebSocketTransport(endpoint) : await openTcpTransport(endpoint);
|
|
20
18
|
const client = Client.fromTransport(wantsWebSocket(endpoint) ? normalizeWsUrl(endpoint) : endpoint, transport);
|
|
21
19
|
await client.hello();
|
|
22
20
|
return client;
|
package/dist/tcp.d.ts
CHANGED
package/dist/tcp.js
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import { decodeFrame, encodeFrame } from
|
|
2
|
-
import { normalizeTcpHostPort } from
|
|
1
|
+
import { decodeFrame, decodeMicroHostInvoke, encodeFrame, encodeMicroHostInvokeOk, MsgType } from './wire.js';
|
|
2
|
+
import { normalizeTcpHostPort } from './transport.js';
|
|
3
|
+
async function readExact(readExactBytes, n) {
|
|
4
|
+
return readExactBytes(n);
|
|
5
|
+
}
|
|
6
|
+
async function readFrame(readExactBytes) {
|
|
7
|
+
const header = await readExact(readExactBytes, 20);
|
|
8
|
+
const bodyLen = new DataView(header.buffer, header.byteOffset, header.byteLength).getUint32(16, true);
|
|
9
|
+
const body = bodyLen > 0 ? await readExact(readExactBytes, bodyLen) : new Uint8Array();
|
|
10
|
+
const full = new Uint8Array(20 + body.byteLength);
|
|
11
|
+
full.set(header, 0);
|
|
12
|
+
full.set(body, 20);
|
|
13
|
+
return decodeFrame(full);
|
|
14
|
+
}
|
|
3
15
|
/** Node.js TCP transport for `yydb serve` binary frames. */
|
|
4
16
|
export async function openTcpTransport(endpoint) {
|
|
5
|
-
const net = await import(
|
|
17
|
+
const net = await import('node:net');
|
|
6
18
|
const hostport = normalizeTcpHostPort(endpoint);
|
|
7
|
-
const [host, portText] = hostport.includes(
|
|
19
|
+
const [host, portText] = hostport.includes(']:')
|
|
8
20
|
? (() => {
|
|
9
21
|
const rest = hostport.slice(1);
|
|
10
|
-
const idx = rest.indexOf(
|
|
22
|
+
const idx = rest.indexOf(']:');
|
|
11
23
|
return [rest.slice(0, idx), rest.slice(idx + 2)];
|
|
12
24
|
})()
|
|
13
|
-
: hostport.split(
|
|
25
|
+
: hostport.split(':');
|
|
14
26
|
const port = Number(portText);
|
|
15
27
|
if (!host || !Number.isFinite(port)) {
|
|
16
28
|
throw new Error(`invalid TCP endpoint: ${endpoint}`);
|
|
17
29
|
}
|
|
18
30
|
const socket = net.createConnection({ host, port });
|
|
19
31
|
await new Promise((resolve, reject) => {
|
|
20
|
-
socket.once(
|
|
21
|
-
socket.once(
|
|
32
|
+
socket.once('connect', () => resolve());
|
|
33
|
+
socket.once('error', reject);
|
|
22
34
|
});
|
|
23
35
|
let buffer = new Uint8Array(0);
|
|
24
36
|
const waiters = [];
|
|
25
|
-
|
|
37
|
+
let microHostHandler;
|
|
38
|
+
socket.on('data', (chunk) => {
|
|
26
39
|
const next = new Uint8Array(buffer.byteLength + chunk.byteLength);
|
|
27
40
|
next.set(buffer, 0);
|
|
28
41
|
next.set(chunk, buffer.byteLength);
|
|
@@ -31,7 +44,7 @@ export async function openTcpTransport(endpoint) {
|
|
|
31
44
|
wake();
|
|
32
45
|
}
|
|
33
46
|
});
|
|
34
|
-
async function
|
|
47
|
+
async function readExactBytes(n) {
|
|
35
48
|
while (buffer.byteLength < n) {
|
|
36
49
|
await new Promise((resolve) => waiters.push(resolve));
|
|
37
50
|
}
|
|
@@ -39,24 +52,47 @@ export async function openTcpTransport(endpoint) {
|
|
|
39
52
|
buffer = buffer.slice(n);
|
|
40
53
|
return out;
|
|
41
54
|
}
|
|
55
|
+
async function writeFrame(frame) {
|
|
56
|
+
const encoded = encodeFrame(frame);
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
socket.write(Buffer.from(encoded), (error) => {
|
|
59
|
+
if (error)
|
|
60
|
+
reject(error);
|
|
61
|
+
else
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function handleServerPush(frame) {
|
|
67
|
+
if (frame.msgType !== MsgType.MicroHostInvoke) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (!microHostHandler) {
|
|
71
|
+
throw new Error('received MicroHostInvoke without a micro host handler');
|
|
72
|
+
}
|
|
73
|
+
const payload = decodeMicroHostInvoke(frame.body);
|
|
74
|
+
const result = await microHostHandler(payload);
|
|
75
|
+
await writeFrame({
|
|
76
|
+
msgType: MsgType.MicroHostInvokeOk,
|
|
77
|
+
flags: 0,
|
|
78
|
+
requestId: frame.requestId,
|
|
79
|
+
body: encodeMicroHostInvokeOk(result),
|
|
80
|
+
});
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
42
83
|
return {
|
|
84
|
+
setMicroHostHandler(handler) {
|
|
85
|
+
microHostHandler = handler;
|
|
86
|
+
},
|
|
43
87
|
async send(frame) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
const header = await readExact(20);
|
|
54
|
-
const bodyLen = new DataView(header.buffer, header.byteOffset, header.byteLength).getUint32(16, true);
|
|
55
|
-
const body = bodyLen > 0 ? await readExact(bodyLen) : new Uint8Array();
|
|
56
|
-
const full = new Uint8Array(20 + body.byteLength);
|
|
57
|
-
full.set(header, 0);
|
|
58
|
-
full.set(body, 20);
|
|
59
|
-
return decodeFrame(full);
|
|
88
|
+
await writeFrame(frame);
|
|
89
|
+
while (true) {
|
|
90
|
+
const response = await readFrame(readExactBytes);
|
|
91
|
+
if (await handleServerPush(response)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
60
96
|
},
|
|
61
97
|
close() {
|
|
62
98
|
socket.end();
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { Frame } from
|
|
1
|
+
import type { Frame, MicroHostInvokePayload, WireUdfScalarValue } from './wire.js';
|
|
2
|
+
export type MicroHostHandler = (payload: MicroHostInvokePayload) => WireUdfScalarValue | Promise<WireUdfScalarValue>;
|
|
2
3
|
export type Transport = {
|
|
3
4
|
send(frame: Frame): Promise<Frame>;
|
|
4
5
|
close(): void;
|
|
6
|
+
setMicroHostHandler?(handler: MicroHostHandler | undefined): void;
|
|
5
7
|
};
|
|
6
8
|
export declare function normalizeWsUrl(endpoint: string): string;
|
|
7
9
|
export declare function normalizeTcpHostPort(endpoint: string): string;
|
package/dist/transport.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
export function normalizeWsUrl(endpoint) {
|
|
2
2
|
const trimmed = endpoint.trim();
|
|
3
|
-
if (trimmed.startsWith(
|
|
4
|
-
if (trimmed.includes(
|
|
3
|
+
if (trimmed.startsWith('ws://') || trimmed.startsWith('wss://')) {
|
|
4
|
+
if (trimmed.includes('/wire')) {
|
|
5
5
|
return trimmed;
|
|
6
6
|
}
|
|
7
|
-
return trimmed.replace(/\/$/,
|
|
7
|
+
return trimmed.replace(/\/$/, '') + '/wire';
|
|
8
8
|
}
|
|
9
9
|
const hostport = trimmed
|
|
10
|
-
.replace(/^tcp:\/\//,
|
|
11
|
-
.replace(/^http:\/\//,
|
|
12
|
-
.split(
|
|
10
|
+
.replace(/^tcp:\/\//, '')
|
|
11
|
+
.replace(/^http:\/\//, '')
|
|
12
|
+
.split('/')[0];
|
|
13
13
|
return `ws://${hostport}/wire`;
|
|
14
14
|
}
|
|
15
15
|
export function normalizeTcpHostPort(endpoint) {
|
|
16
16
|
const trimmed = endpoint
|
|
17
17
|
.trim()
|
|
18
|
-
.replace(/^tcp:\/\//,
|
|
19
|
-
.replace(/^http:\/\//,
|
|
20
|
-
.replace(/^ws:\/\//,
|
|
21
|
-
.replace(/^wss:\/\//,
|
|
22
|
-
return trimmed.split(
|
|
18
|
+
.replace(/^tcp:\/\//, '')
|
|
19
|
+
.replace(/^http:\/\//, '')
|
|
20
|
+
.replace(/^ws:\/\//, '')
|
|
21
|
+
.replace(/^wss:\/\//, '');
|
|
22
|
+
return trimmed.split('/')[0];
|
|
23
23
|
}
|
package/dist/wire.d.ts
CHANGED
|
@@ -27,10 +27,26 @@ export declare const MsgType: {
|
|
|
27
27
|
readonly KvGetOk: 10;
|
|
28
28
|
readonly KvPut: 11;
|
|
29
29
|
readonly KvPutOk: 12;
|
|
30
|
+
readonly MicroRegister: 13;
|
|
31
|
+
readonly MicroRegisterOk: 14;
|
|
32
|
+
readonly MicroHostInvoke: 15;
|
|
33
|
+
readonly MicroHostInvokeOk: 16;
|
|
34
|
+
readonly ScalarCall: 17;
|
|
35
|
+
readonly ScalarCallOk: 18;
|
|
30
36
|
readonly Error: 255;
|
|
31
37
|
};
|
|
38
|
+
/** Phase-1 scalar kinds on the YY wire micro register path. */
|
|
39
|
+
export type WireUdfScalarKind = 'null' | 'bool' | 'i64' | 'text';
|
|
40
|
+
/** Phase-1 scalar value on the YY wire host invoke path. */
|
|
41
|
+
export type WireUdfScalarValue = null | boolean | number | string;
|
|
42
|
+
export interface MicroHostInvokePayload {
|
|
43
|
+
hostId: number;
|
|
44
|
+
handleVersion: number;
|
|
45
|
+
functionId: string;
|
|
46
|
+
args: readonly WireUdfScalarValue[];
|
|
47
|
+
}
|
|
32
48
|
export type MsgTypeCode = (typeof MsgType)[keyof typeof MsgType];
|
|
33
|
-
export type ProductMagic =
|
|
49
|
+
export type ProductMagic = 'YYDB' | 'YYDS';
|
|
34
50
|
export interface Frame {
|
|
35
51
|
msgType: number;
|
|
36
52
|
flags: number;
|
|
@@ -45,6 +61,25 @@ export declare function pushU32(out: number[], value: number): void;
|
|
|
45
61
|
export declare function pushBytes(out: number[], bytes: Uint8Array): void;
|
|
46
62
|
export declare function encodeSchemaEnsure(version: number, document: string): Uint8Array;
|
|
47
63
|
export declare function encodeKvGet(key: string): Uint8Array;
|
|
64
|
+
export interface MicroRegisterPayload {
|
|
65
|
+
hostId: number;
|
|
66
|
+
handleVersion: number;
|
|
67
|
+
udfVersion: number;
|
|
68
|
+
name: string;
|
|
69
|
+
functionId: string;
|
|
70
|
+
args: readonly WireUdfScalarKind[];
|
|
71
|
+
returns: WireUdfScalarKind;
|
|
72
|
+
fingerprint: Uint8Array;
|
|
73
|
+
}
|
|
74
|
+
/** Decode a `MicroHostInvoke` body. */
|
|
75
|
+
export declare function decodeMicroHostInvoke(body: Uint8Array): MicroHostInvokePayload;
|
|
76
|
+
/** Encode a `MicroHostInvokeOk` body. */
|
|
77
|
+
export declare function encodeMicroHostInvokeOk(value: WireUdfScalarValue): Uint8Array;
|
|
78
|
+
/** Encode a `ScalarCall` body. */
|
|
79
|
+
export declare function encodeScalarCall(name: string, version: number, args: readonly WireUdfScalarValue[]): Uint8Array;
|
|
80
|
+
/** Decode a `ScalarCallOk` body. */
|
|
81
|
+
export declare function decodeScalarCallOk(body: Uint8Array): WireUdfScalarValue;
|
|
82
|
+
export declare function encodeMicroRegister(payload: MicroRegisterPayload): Uint8Array;
|
|
48
83
|
export declare function encodeKvPut(key: string, value: Uint8Array): Uint8Array;
|
|
49
84
|
export interface SchemaVersion {
|
|
50
85
|
version: number;
|
package/dist/wire.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* then four ASCII version digits (`0000` current, `0001` next).
|
|
5
5
|
* Frontends accept either magic and do not require product consistency.
|
|
6
6
|
*/
|
|
7
|
-
export const MAGIC_YYDB = new TextEncoder().encode(
|
|
8
|
-
export const MAGIC_YYDS = new TextEncoder().encode(
|
|
7
|
+
export const MAGIC_YYDB = new TextEncoder().encode('YYDB');
|
|
8
|
+
export const MAGIC_YYDS = new TextEncoder().encode('YYDS');
|
|
9
9
|
/** @deprecated Use MAGIC_YYDB — encode default product. */
|
|
10
10
|
export const MAGIC = MAGIC_YYDB;
|
|
11
11
|
/** Current wire version digits. */
|
|
12
|
-
export const WIRE_VERSION = new TextEncoder().encode(
|
|
12
|
+
export const WIRE_VERSION = new TextEncoder().encode('0000');
|
|
13
13
|
/** Next planned wire version (not spoken yet). */
|
|
14
|
-
export const WIRE_VERSION_NEXT = new TextEncoder().encode(
|
|
14
|
+
export const WIRE_VERSION_NEXT = new TextEncoder().encode('0001');
|
|
15
15
|
export const HEADER_LEN = 20;
|
|
16
16
|
export const MAX_BODY_LEN = 16 * 1024 * 1024;
|
|
17
17
|
export const MsgType = {
|
|
@@ -27,6 +27,12 @@ export const MsgType = {
|
|
|
27
27
|
KvGetOk: 10,
|
|
28
28
|
KvPut: 11,
|
|
29
29
|
KvPutOk: 12,
|
|
30
|
+
MicroRegister: 13,
|
|
31
|
+
MicroRegisterOk: 14,
|
|
32
|
+
MicroHostInvoke: 15,
|
|
33
|
+
MicroHostInvokeOk: 16,
|
|
34
|
+
ScalarCall: 17,
|
|
35
|
+
ScalarCallOk: 18,
|
|
30
36
|
Error: 255,
|
|
31
37
|
};
|
|
32
38
|
function writeU16(view, offset, value) {
|
|
@@ -42,24 +48,24 @@ function readU32(view, offset) {
|
|
|
42
48
|
return view.getUint32(offset, true);
|
|
43
49
|
}
|
|
44
50
|
function productBytes(product) {
|
|
45
|
-
return product ===
|
|
51
|
+
return product === 'YYDS' ? MAGIC_YYDS : MAGIC_YYDB;
|
|
46
52
|
}
|
|
47
53
|
function parseProduct(bytes) {
|
|
48
54
|
const text = new TextDecoder().decode(bytes.subarray(0, 4));
|
|
49
|
-
if (text ===
|
|
55
|
+
if (text === 'YYDB' || text === 'YYDS') {
|
|
50
56
|
return text;
|
|
51
57
|
}
|
|
52
|
-
throw new Error(
|
|
58
|
+
throw new Error('wire frame bad product magic (want YYDB|YYDS)');
|
|
53
59
|
}
|
|
54
60
|
function checkVersion(bytes) {
|
|
55
61
|
const version = new TextDecoder().decode(bytes.subarray(4, 8));
|
|
56
|
-
if (version !==
|
|
62
|
+
if (version !== '0000') {
|
|
57
63
|
throw new Error(`wire version not supported (got ${version}, want 0000)`);
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
|
-
export function encodeFrame(frame, product =
|
|
66
|
+
export function encodeFrame(frame, product = 'YYDB') {
|
|
61
67
|
if (frame.body.byteLength > MAX_BODY_LEN) {
|
|
62
|
-
throw new Error(
|
|
68
|
+
throw new Error('wire body too large');
|
|
63
69
|
}
|
|
64
70
|
const out = new Uint8Array(HEADER_LEN + frame.body.byteLength);
|
|
65
71
|
out.set(productBytes(product), 0);
|
|
@@ -74,7 +80,7 @@ export function encodeFrame(frame, product = "YYDB") {
|
|
|
74
80
|
}
|
|
75
81
|
export function decodeFrame(bytes) {
|
|
76
82
|
if (bytes.byteLength < HEADER_LEN) {
|
|
77
|
-
throw new Error(
|
|
83
|
+
throw new Error('wire frame truncated header');
|
|
78
84
|
}
|
|
79
85
|
const product = parseProduct(bytes);
|
|
80
86
|
checkVersion(bytes);
|
|
@@ -84,11 +90,11 @@ export function decodeFrame(bytes) {
|
|
|
84
90
|
const requestId = readU32(view, 12);
|
|
85
91
|
const bodyLen = readU32(view, 16);
|
|
86
92
|
if (bodyLen > MAX_BODY_LEN) {
|
|
87
|
-
throw new Error(
|
|
93
|
+
throw new Error('wire body too large');
|
|
88
94
|
}
|
|
89
95
|
const need = HEADER_LEN + bodyLen;
|
|
90
96
|
if (bytes.byteLength < need) {
|
|
91
|
-
throw new Error(
|
|
97
|
+
throw new Error('wire frame truncated body');
|
|
92
98
|
}
|
|
93
99
|
return {
|
|
94
100
|
msgType,
|
|
@@ -118,6 +124,164 @@ export function encodeKvGet(key) {
|
|
|
118
124
|
pushBytes(out, new TextEncoder().encode(key));
|
|
119
125
|
return Uint8Array.from(out);
|
|
120
126
|
}
|
|
127
|
+
function encodeUdfTypeTag(kind) {
|
|
128
|
+
switch (kind) {
|
|
129
|
+
case 'null':
|
|
130
|
+
return 0;
|
|
131
|
+
case 'bool':
|
|
132
|
+
return 1;
|
|
133
|
+
case 'i64':
|
|
134
|
+
return 2;
|
|
135
|
+
case 'text':
|
|
136
|
+
return 3;
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`unsupported wire udf type: ${kind}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function pushU64(out, value) {
|
|
142
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
143
|
+
view.setBigUint64(0, BigInt(value), true);
|
|
144
|
+
out.push(...new Uint8Array(view.buffer));
|
|
145
|
+
}
|
|
146
|
+
/** Encode a `MicroRegister` body. */
|
|
147
|
+
function encodeUdfValue(out, value) {
|
|
148
|
+
if (value === null) {
|
|
149
|
+
out.push(0);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (typeof value === 'boolean') {
|
|
153
|
+
out.push(1, value ? 1 : 0);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === 'number') {
|
|
157
|
+
out.push(2);
|
|
158
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
159
|
+
view.setBigInt64(0, BigInt(value), true);
|
|
160
|
+
out.push(...new Uint8Array(view.buffer));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === 'string') {
|
|
164
|
+
out.push(3);
|
|
165
|
+
pushBytes(out, new TextEncoder().encode(value));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
throw new Error('unsupported wire udf value');
|
|
169
|
+
}
|
|
170
|
+
function decodeUdfValue(body, offset) {
|
|
171
|
+
if (offset.n >= body.byteLength) {
|
|
172
|
+
throw new Error('wire scalar value missing tag');
|
|
173
|
+
}
|
|
174
|
+
const tag = body[offset.n];
|
|
175
|
+
offset.n += 1;
|
|
176
|
+
switch (tag) {
|
|
177
|
+
case 0:
|
|
178
|
+
return null;
|
|
179
|
+
case 1: {
|
|
180
|
+
if (offset.n >= body.byteLength) {
|
|
181
|
+
throw new Error('wire bool value truncated');
|
|
182
|
+
}
|
|
183
|
+
const value = body[offset.n] !== 0;
|
|
184
|
+
offset.n += 1;
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
case 2: {
|
|
188
|
+
if (offset.n + 8 > body.byteLength) {
|
|
189
|
+
throw new Error('wire i64 value truncated');
|
|
190
|
+
}
|
|
191
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
192
|
+
const value = Number(view.getBigInt64(offset.n, true));
|
|
193
|
+
offset.n += 8;
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
case 3: {
|
|
197
|
+
const len = readU32At(body, offset);
|
|
198
|
+
const bytes = readBytesAt(body, offset, len);
|
|
199
|
+
return new TextDecoder().decode(bytes);
|
|
200
|
+
}
|
|
201
|
+
default:
|
|
202
|
+
throw new Error('wire scalar value has invalid type tag');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function encodeUdfValues(values) {
|
|
206
|
+
if (values.length > 255) {
|
|
207
|
+
throw new Error('wire scalar arg count exceeds u8');
|
|
208
|
+
}
|
|
209
|
+
const out = [values.length];
|
|
210
|
+
for (const value of values) {
|
|
211
|
+
encodeUdfValue(out, value);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function decodeUdfValues(body, offset) {
|
|
216
|
+
if (offset.n >= body.byteLength) {
|
|
217
|
+
throw new Error('wire scalar args missing count');
|
|
218
|
+
}
|
|
219
|
+
const count = body[offset.n];
|
|
220
|
+
offset.n += 1;
|
|
221
|
+
const values = [];
|
|
222
|
+
for (let index = 0; index < count; index += 1) {
|
|
223
|
+
values.push(decodeUdfValue(body, offset));
|
|
224
|
+
}
|
|
225
|
+
return values;
|
|
226
|
+
}
|
|
227
|
+
/** Decode a `MicroHostInvoke` body. */
|
|
228
|
+
export function decodeMicroHostInvoke(body) {
|
|
229
|
+
const offset = { n: 0 };
|
|
230
|
+
const hostId = Number(readU64At(body, offset));
|
|
231
|
+
const handleVersion = readU32At(body, offset);
|
|
232
|
+
const functionIdLen = readU32At(body, offset);
|
|
233
|
+
const functionId = new TextDecoder().decode(readBytesAt(body, offset, functionIdLen));
|
|
234
|
+
const args = decodeUdfValues(body, offset);
|
|
235
|
+
return { hostId, handleVersion, functionId, args };
|
|
236
|
+
}
|
|
237
|
+
/** Encode a `MicroHostInvokeOk` body. */
|
|
238
|
+
export function encodeMicroHostInvokeOk(value) {
|
|
239
|
+
const out = [];
|
|
240
|
+
encodeUdfValue(out, value);
|
|
241
|
+
return Uint8Array.from(out);
|
|
242
|
+
}
|
|
243
|
+
/** Encode a `ScalarCall` body. */
|
|
244
|
+
export function encodeScalarCall(name, version, args) {
|
|
245
|
+
const out = [];
|
|
246
|
+
pushU32(out, version >>> 0);
|
|
247
|
+
pushBytes(out, new TextEncoder().encode(name));
|
|
248
|
+
out.push(...encodeUdfValues(args));
|
|
249
|
+
return Uint8Array.from(out);
|
|
250
|
+
}
|
|
251
|
+
/** Decode a `ScalarCallOk` body. */
|
|
252
|
+
export function decodeScalarCallOk(body) {
|
|
253
|
+
return decodeUdfValue(body, { n: 0 });
|
|
254
|
+
}
|
|
255
|
+
function readU64At(body, offset) {
|
|
256
|
+
if (offset.n + 8 > body.byteLength) {
|
|
257
|
+
throw new Error('wire body truncated u64');
|
|
258
|
+
}
|
|
259
|
+
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
260
|
+
const value = view.getBigUint64(offset.n, true);
|
|
261
|
+
offset.n += 8;
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
export function encodeMicroRegister(payload) {
|
|
265
|
+
if (payload.fingerprint.byteLength !== 32) {
|
|
266
|
+
throw new Error('micro register fingerprint must be 32 bytes');
|
|
267
|
+
}
|
|
268
|
+
const out = [];
|
|
269
|
+
pushU64(out, payload.hostId);
|
|
270
|
+
pushU32(out, payload.handleVersion);
|
|
271
|
+
pushU32(out, payload.udfVersion);
|
|
272
|
+
pushBytes(out, new TextEncoder().encode(payload.name));
|
|
273
|
+
pushBytes(out, new TextEncoder().encode(payload.functionId));
|
|
274
|
+
if (payload.args.length > 255) {
|
|
275
|
+
throw new Error('micro register supports at most 255 args');
|
|
276
|
+
}
|
|
277
|
+
out.push(payload.args.length);
|
|
278
|
+
for (const arg of payload.args) {
|
|
279
|
+
out.push(encodeUdfTypeTag(arg));
|
|
280
|
+
}
|
|
281
|
+
out.push(encodeUdfTypeTag(payload.returns));
|
|
282
|
+
out.push(...payload.fingerprint);
|
|
283
|
+
return Uint8Array.from(out);
|
|
284
|
+
}
|
|
121
285
|
export function encodeKvPut(key, value) {
|
|
122
286
|
const out = [];
|
|
123
287
|
pushBytes(out, new TextEncoder().encode(key));
|
|
@@ -126,7 +290,7 @@ export function encodeKvPut(key, value) {
|
|
|
126
290
|
}
|
|
127
291
|
function readU32At(body, offset) {
|
|
128
292
|
if (offset.n + 4 > body.byteLength) {
|
|
129
|
-
throw new Error(
|
|
293
|
+
throw new Error('wire body truncated u32');
|
|
130
294
|
}
|
|
131
295
|
const view = new DataView(body.buffer, body.byteOffset, body.byteLength);
|
|
132
296
|
const value = readU32(view, offset.n);
|
|
@@ -135,7 +299,7 @@ function readU32At(body, offset) {
|
|
|
135
299
|
}
|
|
136
300
|
function readBytesAt(body, offset, len) {
|
|
137
301
|
if (offset.n + len > body.byteLength) {
|
|
138
|
-
throw new Error(
|
|
302
|
+
throw new Error('wire body truncated bytes');
|
|
139
303
|
}
|
|
140
304
|
const slice = body.slice(offset.n, offset.n + len);
|
|
141
305
|
offset.n += len;
|
|
@@ -143,7 +307,7 @@ function readBytesAt(body, offset, len) {
|
|
|
143
307
|
}
|
|
144
308
|
export function decodeSchemaGetOk(body) {
|
|
145
309
|
if (body.byteLength === 0) {
|
|
146
|
-
throw new Error(
|
|
310
|
+
throw new Error('schema get ok empty');
|
|
147
311
|
}
|
|
148
312
|
if (body[0] === 0) {
|
|
149
313
|
return null;
|
|
@@ -156,7 +320,7 @@ export function decodeSchemaGetOk(body) {
|
|
|
156
320
|
}
|
|
157
321
|
export function decodeKvGetOk(body) {
|
|
158
322
|
if (body.byteLength === 0) {
|
|
159
|
-
throw new Error(
|
|
323
|
+
throw new Error('kv get ok empty');
|
|
160
324
|
}
|
|
161
325
|
if (body[0] === 0) {
|
|
162
326
|
return null;
|
package/dist/ws.d.ts
CHANGED
package/dist/ws.js
CHANGED
|
@@ -1,45 +1,65 @@
|
|
|
1
|
-
import { decodeFrame, encodeFrame } from
|
|
2
|
-
import { normalizeWsUrl } from
|
|
1
|
+
import { decodeFrame, decodeMicroHostInvoke, encodeFrame, encodeMicroHostInvokeOk, MsgType } from './wire.js';
|
|
2
|
+
import { normalizeWsUrl } from './transport.js';
|
|
3
3
|
/** Browser / any host with WebSocket — path `/wire`, binary frames. */
|
|
4
4
|
export async function openWebSocketTransport(endpoint) {
|
|
5
5
|
const url = normalizeWsUrl(endpoint);
|
|
6
6
|
const ws = new WebSocket(url);
|
|
7
|
-
ws.binaryType =
|
|
7
|
+
ws.binaryType = 'arraybuffer';
|
|
8
8
|
await new Promise((resolve, reject) => {
|
|
9
|
-
ws.addEventListener(
|
|
10
|
-
ws.addEventListener(
|
|
9
|
+
ws.addEventListener('open', () => resolve(), { once: true });
|
|
10
|
+
ws.addEventListener('error', () => reject(new Error(`websocket failed: ${url}`)), {
|
|
11
|
+
once: true,
|
|
12
|
+
});
|
|
11
13
|
});
|
|
12
|
-
let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const slot = pending;
|
|
21
|
-
pending = null;
|
|
22
|
-
slot.resolve(frame);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (pending) {
|
|
27
|
-
const slot = pending;
|
|
28
|
-
pending = null;
|
|
29
|
-
slot.reject(error);
|
|
30
|
-
}
|
|
14
|
+
let microHostHandler;
|
|
15
|
+
const buffer = [];
|
|
16
|
+
const waiters = [];
|
|
17
|
+
ws.addEventListener('message', (event) => {
|
|
18
|
+
const data = event.data instanceof ArrayBuffer ? new Uint8Array(event.data) : new Uint8Array(event.data);
|
|
19
|
+
buffer.push(decodeFrame(data));
|
|
20
|
+
for (const wake of waiters.splice(0)) {
|
|
21
|
+
wake();
|
|
31
22
|
}
|
|
32
23
|
});
|
|
24
|
+
async function readFrame() {
|
|
25
|
+
while (buffer.length === 0) {
|
|
26
|
+
await new Promise((resolve) => waiters.push(resolve));
|
|
27
|
+
}
|
|
28
|
+
return buffer.shift();
|
|
29
|
+
}
|
|
30
|
+
async function writeFrame(frame) {
|
|
31
|
+
ws.send(encodeFrame(frame));
|
|
32
|
+
}
|
|
33
|
+
async function handleServerPush(frame) {
|
|
34
|
+
if (frame.msgType !== MsgType.MicroHostInvoke) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (!microHostHandler) {
|
|
38
|
+
throw new Error('received MicroHostInvoke without a micro host handler');
|
|
39
|
+
}
|
|
40
|
+
const payload = decodeMicroHostInvoke(frame.body);
|
|
41
|
+
const result = await microHostHandler(payload);
|
|
42
|
+
await writeFrame({
|
|
43
|
+
msgType: MsgType.MicroHostInvokeOk,
|
|
44
|
+
flags: 0,
|
|
45
|
+
requestId: frame.requestId,
|
|
46
|
+
body: encodeMicroHostInvokeOk(result),
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
33
50
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
setMicroHostHandler(handler) {
|
|
52
|
+
microHostHandler = handler;
|
|
53
|
+
},
|
|
54
|
+
async send(frame) {
|
|
55
|
+
await writeFrame(frame);
|
|
56
|
+
while (true) {
|
|
57
|
+
const response = await readFrame();
|
|
58
|
+
if (await handleServerPush(response)) {
|
|
59
|
+
continue;
|
|
39
60
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
43
63
|
},
|
|
44
64
|
close() {
|
|
45
65
|
ws.close();
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yydb/yydb-client",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "TypeScript client for
|
|
5
|
-
"license": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "TypeScript client for the YYDB WebSocket and TCP wire protocol",
|
|
5
|
+
"license": "MPL-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/yy-database/yydb.
|
|
30
|
-
"directory": "
|
|
29
|
+
"url": "git+https://github.com/yy-database/yydb.git",
|
|
30
|
+
"directory": "projects/packages/yydb-client"
|
|
31
31
|
},
|
|
32
|
-
"homepage": "https://github.com/yy-database/yydb
|
|
32
|
+
"homepage": "https://github.com/yy-database/yydb/tree/dev/projects/packages/yydb-client#readme",
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/yy-database/yydb
|
|
34
|
+
"url": "https://github.com/yy-database/yydb/issues"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"yydb",
|