@syncular/core 0.15.44 → 0.15.46
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/remote-operations.d.ts +67 -0
- package/dist/remote-operations.js +153 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/remote-operations.ts +272 -0
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './message.js';
|
|
|
17
17
|
export * from './realtime.js';
|
|
18
18
|
export * from './rejection-details.js';
|
|
19
19
|
export * from './relational-identifier.js';
|
|
20
|
+
export * from './remote-operations.js';
|
|
20
21
|
export * from './render.js';
|
|
21
22
|
export * from './row-codec.js';
|
|
22
23
|
export * from './segment.js';
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export * from './message.js';
|
|
|
17
17
|
export * from './realtime.js';
|
|
18
18
|
export * from './rejection-details.js';
|
|
19
19
|
export * from './relational-identifier.js';
|
|
20
|
+
export * from './remote-operations.js';
|
|
20
21
|
export * from './render.js';
|
|
21
22
|
export * from './row-codec.js';
|
|
22
23
|
export * from './segment.js';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type RemoteOperationRequest = {
|
|
2
|
+
readonly revision: 1;
|
|
3
|
+
readonly kind: 'query';
|
|
4
|
+
readonly clientId: string;
|
|
5
|
+
readonly operationId: string;
|
|
6
|
+
readonly params: unknown;
|
|
7
|
+
} | {
|
|
8
|
+
readonly revision: 1;
|
|
9
|
+
readonly kind: 'command';
|
|
10
|
+
readonly clientId: string;
|
|
11
|
+
readonly operationId: string;
|
|
12
|
+
readonly requestId: string;
|
|
13
|
+
readonly params: unknown;
|
|
14
|
+
};
|
|
15
|
+
export type RemoteOperationResponse = {
|
|
16
|
+
readonly revision: 1;
|
|
17
|
+
readonly kind: 'query';
|
|
18
|
+
readonly operationId: string;
|
|
19
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
20
|
+
readonly maxCommitSeq: number;
|
|
21
|
+
} | {
|
|
22
|
+
readonly revision: 1;
|
|
23
|
+
readonly kind: 'command';
|
|
24
|
+
readonly operationId: string;
|
|
25
|
+
readonly requestId: string;
|
|
26
|
+
readonly status: 'applied' | 'cached' | 'rejected';
|
|
27
|
+
readonly commitSeq?: number;
|
|
28
|
+
readonly results: readonly unknown[];
|
|
29
|
+
} | {
|
|
30
|
+
readonly revision: 1;
|
|
31
|
+
readonly kind: 'error';
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly retryable: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type RemoteOperationRealtimeMessage = {
|
|
37
|
+
readonly revision: 1;
|
|
38
|
+
readonly kind: 'watch';
|
|
39
|
+
readonly watchId: string;
|
|
40
|
+
readonly clientId: string;
|
|
41
|
+
readonly operationId: string;
|
|
42
|
+
readonly params: unknown;
|
|
43
|
+
} | {
|
|
44
|
+
readonly revision: 1;
|
|
45
|
+
readonly kind: 'unwatch';
|
|
46
|
+
readonly watchId: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly revision: 1;
|
|
49
|
+
readonly kind: 'snapshot';
|
|
50
|
+
readonly watchId: string;
|
|
51
|
+
readonly operationId: string;
|
|
52
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
53
|
+
readonly maxCommitSeq: number;
|
|
54
|
+
} | {
|
|
55
|
+
readonly revision: 1;
|
|
56
|
+
readonly kind: 'watch_error';
|
|
57
|
+
readonly watchId: string;
|
|
58
|
+
readonly code: string;
|
|
59
|
+
readonly message: string;
|
|
60
|
+
readonly retryable: boolean;
|
|
61
|
+
};
|
|
62
|
+
export declare function encodeRemoteOperationRequest(request: RemoteOperationRequest): Uint8Array;
|
|
63
|
+
export declare function decodeRemoteOperationRequest(bytes: Uint8Array): RemoteOperationRequest;
|
|
64
|
+
export declare function encodeRemoteOperationResponse(response: RemoteOperationResponse): Uint8Array;
|
|
65
|
+
export declare function decodeRemoteOperationResponse(bytes: Uint8Array): RemoteOperationResponse;
|
|
66
|
+
export declare function encodeRemoteOperationRealtimeMessage(message: RemoteOperationRealtimeMessage): Uint8Array;
|
|
67
|
+
export declare function decodeRemoteOperationRealtimeMessage(bytes: Uint8Array): RemoteOperationRealtimeMessage;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { bytesToBase64 } from './render.js';
|
|
2
|
+
function base64ToBytes(text) {
|
|
3
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
4
|
+
if (text.length % 4 !== 0 ||
|
|
5
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(text)) {
|
|
6
|
+
throw new Error('invalid base64 value');
|
|
7
|
+
}
|
|
8
|
+
const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0;
|
|
9
|
+
const out = new Uint8Array((text.length / 4) * 3 - padding);
|
|
10
|
+
let offset = 0;
|
|
11
|
+
for (let index = 0; index < text.length; index += 4) {
|
|
12
|
+
const values = [...text.slice(index, index + 4)].map((char) => char === '=' ? 0 : alphabet.indexOf(char));
|
|
13
|
+
if (values.some((value) => value < 0))
|
|
14
|
+
throw new Error('invalid base64 value');
|
|
15
|
+
const chunk = ((values[0] ?? 0) << 18) |
|
|
16
|
+
((values[1] ?? 0) << 12) |
|
|
17
|
+
((values[2] ?? 0) << 6) |
|
|
18
|
+
(values[3] ?? 0);
|
|
19
|
+
if (offset < out.length)
|
|
20
|
+
out[offset++] = (chunk >> 16) & 255;
|
|
21
|
+
if (offset < out.length)
|
|
22
|
+
out[offset++] = (chunk >> 8) & 255;
|
|
23
|
+
if (offset < out.length)
|
|
24
|
+
out[offset++] = chunk & 255;
|
|
25
|
+
}
|
|
26
|
+
if (bytesToBase64(out) !== text)
|
|
27
|
+
throw new Error('invalid base64 value');
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function encodeValue(value) {
|
|
31
|
+
if (value === null)
|
|
32
|
+
return { t: 'null' };
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
throw new Error('remote operation value cannot be undefined');
|
|
35
|
+
if (typeof value === 'boolean')
|
|
36
|
+
return { t: 'boolean', v: value };
|
|
37
|
+
if (typeof value === 'number') {
|
|
38
|
+
if (!Number.isFinite(value))
|
|
39
|
+
throw new Error('remote operation number must be finite');
|
|
40
|
+
return { t: 'number', v: value };
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'string')
|
|
43
|
+
return { t: 'string', v: value };
|
|
44
|
+
if (typeof value === 'bigint') {
|
|
45
|
+
if (value < -9223372036854775808n ||
|
|
46
|
+
value > 9223372036854775807n) {
|
|
47
|
+
throw new Error('remote operation integer exceeds signed 64-bit range');
|
|
48
|
+
}
|
|
49
|
+
return { t: 'integer', v: value.toString() };
|
|
50
|
+
}
|
|
51
|
+
if (value instanceof Uint8Array) {
|
|
52
|
+
return { t: 'bytes', v: bytesToBase64(value) };
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return { t: 'array', v: value.map(encodeValue) };
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === 'object') {
|
|
58
|
+
const prototype = Object.getPrototypeOf(value);
|
|
59
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
60
|
+
throw new Error('remote operation value must be a plain object');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
t: 'object',
|
|
64
|
+
v: Object.entries(value).map(([key, entry]) => [key, encodeValue(entry)]),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
throw new Error('remote operation value has an unsupported type');
|
|
68
|
+
}
|
|
69
|
+
function decodeValue(value) {
|
|
70
|
+
if (typeof value !== 'object' ||
|
|
71
|
+
value === null ||
|
|
72
|
+
typeof value.t !== 'string') {
|
|
73
|
+
throw new Error('invalid remote operation value');
|
|
74
|
+
}
|
|
75
|
+
switch (value.t) {
|
|
76
|
+
case 'null':
|
|
77
|
+
return null;
|
|
78
|
+
case 'boolean':
|
|
79
|
+
if (typeof value.v !== 'boolean')
|
|
80
|
+
throw new Error('invalid remote boolean value');
|
|
81
|
+
return value.v;
|
|
82
|
+
case 'number':
|
|
83
|
+
if (typeof value.v !== 'number' || !Number.isFinite(value.v))
|
|
84
|
+
throw new Error('invalid remote number value');
|
|
85
|
+
return value.v;
|
|
86
|
+
case 'string':
|
|
87
|
+
if (typeof value.v !== 'string')
|
|
88
|
+
throw new Error('invalid remote string value');
|
|
89
|
+
return value.v;
|
|
90
|
+
case 'integer': {
|
|
91
|
+
if (typeof value.v !== 'string' || !/^-?(?:0|[1-9][0-9]*)$/.test(value.v))
|
|
92
|
+
throw new Error('invalid remote integer value');
|
|
93
|
+
const integer = BigInt(value.v);
|
|
94
|
+
if (integer < -9223372036854775808n ||
|
|
95
|
+
integer > 9223372036854775807n) {
|
|
96
|
+
throw new Error('remote operation integer exceeds signed 64-bit range');
|
|
97
|
+
}
|
|
98
|
+
return integer;
|
|
99
|
+
}
|
|
100
|
+
case 'bytes':
|
|
101
|
+
if (typeof value.v !== 'string')
|
|
102
|
+
throw new Error('invalid remote bytes value');
|
|
103
|
+
return base64ToBytes(value.v);
|
|
104
|
+
case 'array':
|
|
105
|
+
if (!Array.isArray(value.v))
|
|
106
|
+
throw new Error('invalid remote array value');
|
|
107
|
+
return value.v.map(decodeValue);
|
|
108
|
+
case 'object': {
|
|
109
|
+
if (!Array.isArray(value.v))
|
|
110
|
+
throw new Error('invalid remote object value');
|
|
111
|
+
const entries = [];
|
|
112
|
+
const keys = new Set();
|
|
113
|
+
for (const entry of value.v) {
|
|
114
|
+
if (!Array.isArray(entry) ||
|
|
115
|
+
entry.length !== 2 ||
|
|
116
|
+
typeof entry[0] !== 'string') {
|
|
117
|
+
throw new Error('invalid remote object entry');
|
|
118
|
+
}
|
|
119
|
+
if (keys.has(entry[0]))
|
|
120
|
+
throw new Error('duplicate remote object key');
|
|
121
|
+
keys.add(entry[0]);
|
|
122
|
+
entries.push([entry[0], decodeValue(entry[1])]);
|
|
123
|
+
}
|
|
124
|
+
return Object.fromEntries(entries);
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
throw new Error('unknown remote operation value tag');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function encodeEnvelope(value) {
|
|
131
|
+
return new TextEncoder().encode(JSON.stringify(encodeValue(value)));
|
|
132
|
+
}
|
|
133
|
+
function decodeEnvelope(bytes) {
|
|
134
|
+
return decodeValue(JSON.parse(new TextDecoder().decode(bytes)));
|
|
135
|
+
}
|
|
136
|
+
export function encodeRemoteOperationRequest(request) {
|
|
137
|
+
return encodeEnvelope(request);
|
|
138
|
+
}
|
|
139
|
+
export function decodeRemoteOperationRequest(bytes) {
|
|
140
|
+
return decodeEnvelope(bytes);
|
|
141
|
+
}
|
|
142
|
+
export function encodeRemoteOperationResponse(response) {
|
|
143
|
+
return encodeEnvelope(response);
|
|
144
|
+
}
|
|
145
|
+
export function decodeRemoteOperationResponse(bytes) {
|
|
146
|
+
return decodeEnvelope(bytes);
|
|
147
|
+
}
|
|
148
|
+
export function encodeRemoteOperationRealtimeMessage(message) {
|
|
149
|
+
return encodeEnvelope(message);
|
|
150
|
+
}
|
|
151
|
+
export function decodeRemoteOperationRealtimeMessage(bytes) {
|
|
152
|
+
return decodeEnvelope(bytes);
|
|
153
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './message';
|
|
|
17
17
|
export * from './realtime';
|
|
18
18
|
export * from './rejection-details';
|
|
19
19
|
export * from './relational-identifier';
|
|
20
|
+
export * from './remote-operations';
|
|
20
21
|
export * from './render';
|
|
21
22
|
export * from './row-codec';
|
|
22
23
|
export * from './segment';
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { bytesToBase64 } from './render';
|
|
2
|
+
|
|
3
|
+
export type RemoteOperationRequest =
|
|
4
|
+
| {
|
|
5
|
+
readonly revision: 1;
|
|
6
|
+
readonly kind: 'query';
|
|
7
|
+
readonly clientId: string;
|
|
8
|
+
readonly operationId: string;
|
|
9
|
+
readonly params: unknown;
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
readonly revision: 1;
|
|
13
|
+
readonly kind: 'command';
|
|
14
|
+
readonly clientId: string;
|
|
15
|
+
readonly operationId: string;
|
|
16
|
+
readonly requestId: string;
|
|
17
|
+
readonly params: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type RemoteOperationResponse =
|
|
21
|
+
| {
|
|
22
|
+
readonly revision: 1;
|
|
23
|
+
readonly kind: 'query';
|
|
24
|
+
readonly operationId: string;
|
|
25
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
26
|
+
readonly maxCommitSeq: number;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
readonly revision: 1;
|
|
30
|
+
readonly kind: 'command';
|
|
31
|
+
readonly operationId: string;
|
|
32
|
+
readonly requestId: string;
|
|
33
|
+
readonly status: 'applied' | 'cached' | 'rejected';
|
|
34
|
+
readonly commitSeq?: number;
|
|
35
|
+
readonly results: readonly unknown[];
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
readonly revision: 1;
|
|
39
|
+
readonly kind: 'error';
|
|
40
|
+
readonly code: string;
|
|
41
|
+
readonly message: string;
|
|
42
|
+
readonly retryable: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type RemoteOperationRealtimeMessage =
|
|
46
|
+
| {
|
|
47
|
+
readonly revision: 1;
|
|
48
|
+
readonly kind: 'watch';
|
|
49
|
+
readonly watchId: string;
|
|
50
|
+
readonly clientId: string;
|
|
51
|
+
readonly operationId: string;
|
|
52
|
+
readonly params: unknown;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
readonly revision: 1;
|
|
56
|
+
readonly kind: 'unwatch';
|
|
57
|
+
readonly watchId: string;
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
readonly revision: 1;
|
|
61
|
+
readonly kind: 'snapshot';
|
|
62
|
+
readonly watchId: string;
|
|
63
|
+
readonly operationId: string;
|
|
64
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
65
|
+
readonly maxCommitSeq: number;
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
readonly revision: 1;
|
|
69
|
+
readonly kind: 'watch_error';
|
|
70
|
+
readonly watchId: string;
|
|
71
|
+
readonly code: string;
|
|
72
|
+
readonly message: string;
|
|
73
|
+
readonly retryable: boolean;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type EncodedValue =
|
|
77
|
+
| { readonly t: 'null' }
|
|
78
|
+
| { readonly t: 'boolean'; readonly v: boolean }
|
|
79
|
+
| { readonly t: 'number'; readonly v: number }
|
|
80
|
+
| { readonly t: 'string'; readonly v: string }
|
|
81
|
+
| { readonly t: 'integer'; readonly v: string }
|
|
82
|
+
| { readonly t: 'bytes'; readonly v: string }
|
|
83
|
+
| { readonly t: 'array'; readonly v: readonly EncodedValue[] }
|
|
84
|
+
| {
|
|
85
|
+
readonly t: 'object';
|
|
86
|
+
readonly v: readonly (readonly [string, EncodedValue])[];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function base64ToBytes(text: string): Uint8Array {
|
|
90
|
+
const alphabet =
|
|
91
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
92
|
+
if (
|
|
93
|
+
text.length % 4 !== 0 ||
|
|
94
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
|
|
95
|
+
text,
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
throw new Error('invalid base64 value');
|
|
99
|
+
}
|
|
100
|
+
const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0;
|
|
101
|
+
const out = new Uint8Array((text.length / 4) * 3 - padding);
|
|
102
|
+
let offset = 0;
|
|
103
|
+
for (let index = 0; index < text.length; index += 4) {
|
|
104
|
+
const values = [...text.slice(index, index + 4)].map((char) =>
|
|
105
|
+
char === '=' ? 0 : alphabet.indexOf(char),
|
|
106
|
+
);
|
|
107
|
+
if (values.some((value) => value < 0))
|
|
108
|
+
throw new Error('invalid base64 value');
|
|
109
|
+
const chunk =
|
|
110
|
+
((values[0] ?? 0) << 18) |
|
|
111
|
+
((values[1] ?? 0) << 12) |
|
|
112
|
+
((values[2] ?? 0) << 6) |
|
|
113
|
+
(values[3] ?? 0);
|
|
114
|
+
if (offset < out.length) out[offset++] = (chunk >> 16) & 255;
|
|
115
|
+
if (offset < out.length) out[offset++] = (chunk >> 8) & 255;
|
|
116
|
+
if (offset < out.length) out[offset++] = chunk & 255;
|
|
117
|
+
}
|
|
118
|
+
if (bytesToBase64(out) !== text) throw new Error('invalid base64 value');
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function encodeValue(value: unknown): EncodedValue {
|
|
123
|
+
if (value === null) return { t: 'null' };
|
|
124
|
+
if (value === undefined)
|
|
125
|
+
throw new Error('remote operation value cannot be undefined');
|
|
126
|
+
if (typeof value === 'boolean') return { t: 'boolean', v: value };
|
|
127
|
+
if (typeof value === 'number') {
|
|
128
|
+
if (!Number.isFinite(value))
|
|
129
|
+
throw new Error('remote operation number must be finite');
|
|
130
|
+
return { t: 'number', v: value };
|
|
131
|
+
}
|
|
132
|
+
if (typeof value === 'string') return { t: 'string', v: value };
|
|
133
|
+
if (typeof value === 'bigint') {
|
|
134
|
+
if (
|
|
135
|
+
value < -9_223_372_036_854_775_808n ||
|
|
136
|
+
value > 9_223_372_036_854_775_807n
|
|
137
|
+
) {
|
|
138
|
+
throw new Error('remote operation integer exceeds signed 64-bit range');
|
|
139
|
+
}
|
|
140
|
+
return { t: 'integer', v: value.toString() };
|
|
141
|
+
}
|
|
142
|
+
if (value instanceof Uint8Array) {
|
|
143
|
+
return { t: 'bytes', v: bytesToBase64(value) };
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(value)) {
|
|
146
|
+
return { t: 'array', v: value.map(encodeValue) };
|
|
147
|
+
}
|
|
148
|
+
if (typeof value === 'object') {
|
|
149
|
+
const prototype = Object.getPrototypeOf(value);
|
|
150
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
151
|
+
throw new Error('remote operation value must be a plain object');
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
t: 'object',
|
|
155
|
+
v: Object.entries(value).map(([key, entry]) => [key, encodeValue(entry)]),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
throw new Error('remote operation value has an unsupported type');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function decodeValue(value: EncodedValue): unknown {
|
|
162
|
+
if (
|
|
163
|
+
typeof value !== 'object' ||
|
|
164
|
+
value === null ||
|
|
165
|
+
typeof value.t !== 'string'
|
|
166
|
+
) {
|
|
167
|
+
throw new Error('invalid remote operation value');
|
|
168
|
+
}
|
|
169
|
+
switch (value.t) {
|
|
170
|
+
case 'null':
|
|
171
|
+
return null;
|
|
172
|
+
case 'boolean':
|
|
173
|
+
if (typeof value.v !== 'boolean')
|
|
174
|
+
throw new Error('invalid remote boolean value');
|
|
175
|
+
return value.v;
|
|
176
|
+
case 'number':
|
|
177
|
+
if (typeof value.v !== 'number' || !Number.isFinite(value.v))
|
|
178
|
+
throw new Error('invalid remote number value');
|
|
179
|
+
return value.v;
|
|
180
|
+
case 'string':
|
|
181
|
+
if (typeof value.v !== 'string')
|
|
182
|
+
throw new Error('invalid remote string value');
|
|
183
|
+
return value.v;
|
|
184
|
+
case 'integer': {
|
|
185
|
+
if (typeof value.v !== 'string' || !/^-?(?:0|[1-9][0-9]*)$/.test(value.v))
|
|
186
|
+
throw new Error('invalid remote integer value');
|
|
187
|
+
const integer = BigInt(value.v);
|
|
188
|
+
if (
|
|
189
|
+
integer < -9_223_372_036_854_775_808n ||
|
|
190
|
+
integer > 9_223_372_036_854_775_807n
|
|
191
|
+
) {
|
|
192
|
+
throw new Error('remote operation integer exceeds signed 64-bit range');
|
|
193
|
+
}
|
|
194
|
+
return integer;
|
|
195
|
+
}
|
|
196
|
+
case 'bytes':
|
|
197
|
+
if (typeof value.v !== 'string')
|
|
198
|
+
throw new Error('invalid remote bytes value');
|
|
199
|
+
return base64ToBytes(value.v);
|
|
200
|
+
case 'array':
|
|
201
|
+
if (!Array.isArray(value.v))
|
|
202
|
+
throw new Error('invalid remote array value');
|
|
203
|
+
return value.v.map(decodeValue);
|
|
204
|
+
case 'object': {
|
|
205
|
+
if (!Array.isArray(value.v))
|
|
206
|
+
throw new Error('invalid remote object value');
|
|
207
|
+
const entries: Array<readonly [string, unknown]> = [];
|
|
208
|
+
const keys = new Set<string>();
|
|
209
|
+
for (const entry of value.v) {
|
|
210
|
+
if (
|
|
211
|
+
!Array.isArray(entry) ||
|
|
212
|
+
entry.length !== 2 ||
|
|
213
|
+
typeof entry[0] !== 'string'
|
|
214
|
+
) {
|
|
215
|
+
throw new Error('invalid remote object entry');
|
|
216
|
+
}
|
|
217
|
+
if (keys.has(entry[0])) throw new Error('duplicate remote object key');
|
|
218
|
+
keys.add(entry[0]);
|
|
219
|
+
entries.push([entry[0], decodeValue(entry[1])]);
|
|
220
|
+
}
|
|
221
|
+
return Object.fromEntries(entries);
|
|
222
|
+
}
|
|
223
|
+
default:
|
|
224
|
+
throw new Error('unknown remote operation value tag');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function encodeEnvelope(value: unknown): Uint8Array {
|
|
229
|
+
return new TextEncoder().encode(JSON.stringify(encodeValue(value)));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function decodeEnvelope(bytes: Uint8Array): unknown {
|
|
233
|
+
return decodeValue(
|
|
234
|
+
JSON.parse(new TextDecoder().decode(bytes)) as EncodedValue,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function encodeRemoteOperationRequest(
|
|
239
|
+
request: RemoteOperationRequest,
|
|
240
|
+
): Uint8Array {
|
|
241
|
+
return encodeEnvelope(request);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function decodeRemoteOperationRequest(
|
|
245
|
+
bytes: Uint8Array,
|
|
246
|
+
): RemoteOperationRequest {
|
|
247
|
+
return decodeEnvelope(bytes) as RemoteOperationRequest;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function encodeRemoteOperationResponse(
|
|
251
|
+
response: RemoteOperationResponse,
|
|
252
|
+
): Uint8Array {
|
|
253
|
+
return encodeEnvelope(response);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function decodeRemoteOperationResponse(
|
|
257
|
+
bytes: Uint8Array,
|
|
258
|
+
): RemoteOperationResponse {
|
|
259
|
+
return decodeEnvelope(bytes) as RemoteOperationResponse;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function encodeRemoteOperationRealtimeMessage(
|
|
263
|
+
message: RemoteOperationRealtimeMessage,
|
|
264
|
+
): Uint8Array {
|
|
265
|
+
return encodeEnvelope(message);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function decodeRemoteOperationRealtimeMessage(
|
|
269
|
+
bytes: Uint8Array,
|
|
270
|
+
): RemoteOperationRealtimeMessage {
|
|
271
|
+
return decodeEnvelope(bytes) as RemoteOperationRealtimeMessage;
|
|
272
|
+
}
|