@@ -2,19 +2,62 @@ import type RestChannel from 'common/lib/client/restchannel';
22import type * as Utils from 'common/lib/util/utils' ;
33import type {
44 GetObjectParams ,
5+ ObjectsMapSemantics ,
56 PrimitiveOrObjectReference ,
67 RestCompactObjectData ,
78 RestLiveObject ,
9+ RestObjectData ,
10+ RestObjectMapEntry ,
811 RestObjectOperation ,
912 RestObjectPublishResult ,
1013} from '../../../ably' ;
1114
12- export class RestObject {
13- channel : RestChannel ;
15+ enum WireObjectsMapSemantics {
16+ LWW = 'LWW' ,
17+ }
1418
15- constructor ( channel : RestChannel ) {
16- this . channel = channel ;
17- }
19+ const mapSemantics : Record < WireObjectsMapSemantics , ObjectsMapSemantics > = {
20+ [ WireObjectsMapSemantics . LWW ] : 'lww' ,
21+ } ;
22+
23+ type WireRestLiveObject = WireRestLiveMap | WireRestLiveCounter | WireAnyRestLiveObject ;
24+
25+ interface WireRestLiveMap {
26+ objectId : string ;
27+ map : {
28+ semantics : WireObjectsMapSemantics ;
29+ entries : Record < string , WireRestObjectMapEntry > ;
30+ } ;
31+ }
32+
33+ export interface WireRestObjectMapEntry {
34+ data : WireRestLiveObject | WireRestObjectData ;
35+ }
36+
37+ export interface WireRestObjectData {
38+ objectId ?: string ;
39+ number ?: number ;
40+ boolean ?: boolean ;
41+ string ?: string ;
42+ bytes ?: string | Buffer | ArrayBuffer ;
43+ json ?: string ;
44+ }
45+
46+ export interface WireRestLiveCounter {
47+ objectId : string ;
48+ counter : {
49+ data : {
50+ number : number ;
51+ } ;
52+ } ;
53+ }
54+
55+ export type WireAnyRestLiveObject = {
56+ objectId : string ;
57+ } ;
58+
59+ export class RestObject {
60+ constructor ( private _channel : RestChannel ) { }
1861
1962 /**
2063 * Read object data. Defaults to { compact: true } and entrypoint=root when no id is provided.
@@ -23,15 +66,15 @@ export class RestObject {
2366 async get ( params ?: Omit < GetObjectParams , 'compact' > & { compact ?: true } ) : Promise < RestCompactObjectData | undefined > ;
2467 async get ( params : Omit < GetObjectParams , 'compact' > & { compact : false } ) : Promise < RestLiveObject | undefined > ;
2568 async get ( params ?: GetObjectParams ) : Promise < RestCompactObjectData | RestLiveObject | undefined > {
26- const client = this . channel . client ;
69+ const client = this . _channel . client ;
2770 const format = client . options . useBinaryProtocol ? client . Utils . Format . msgpack : client . Utils . Format . json ;
2871 const envelope = client . http . supportsLinkHeaders ? null : format ;
2972 const headers = client . Defaults . defaultGetHeaders ( client . options ) ;
3073
3174 client . Utils . mixin ( headers , client . options . headers ) ;
3275
3376 try {
34- const response = await client . rest . Resource . get < RestCompactObjectData | RestLiveObject > (
77+ const response = await client . rest . Resource . get < RestCompactObjectData | WireRestLiveObject > (
3578 client ,
3679 this . _basePath ( params ?. objectId ?? 'root' ) ,
3780 headers ,
@@ -40,13 +83,30 @@ export class RestObject {
4083 true ,
4184 ) ;
4285
86+ let decodedBody : RestCompactObjectData | WireRestLiveObject | undefined ;
4387 if ( format ) {
44- return client . Utils . decodeBody ( response . body , client . _MsgPack , format ) ;
88+ decodedBody = client . Utils . decodeBody ( response . body , client . _MsgPack , format ) ;
89+ } else {
90+ decodedBody = response . body ;
91+ }
92+
93+ if ( decodedBody == undefined ) {
94+ return undefined ;
95+ }
96+
97+ // non-object primitive values
98+ if ( typeof decodedBody !== 'object' ) {
99+ return decodedBody ;
45100 }
46101
47- return response . body ;
102+ // live counter or JSON object
103+ if ( ! this . _isWireLiveMap ( decodedBody ) ) {
104+ return decodedBody ;
105+ }
106+
107+ return this . _decodeWireLiveObject ( decodedBody , format ) ;
48108 } catch ( error ) {
49- if ( this . channel . client . Utils . isErrorInfoOrPartialErrorInfo ( error ) && error . code === 40400 ) {
109+ if ( this . _channel . client . Utils . isErrorInfoOrPartialErrorInfo ( error ) && error . code === 40400 ) {
50110 // ignore object resolution errors and return undefined
51111 return undefined ;
52112 }
@@ -59,7 +119,7 @@ export class RestObject {
59119 * Publish one or more operations.
60120 */
61121 async publish ( op : RestObjectOperation | RestObjectOperation [ ] ) : Promise < RestObjectPublishResult > {
62- const client = this . channel . client ;
122+ const client = this . _channel . client ;
63123 const options = client . options ;
64124 const format = options . useBinaryProtocol ? client . Utils . Format . msgpack : client . Utils . Format . json ;
65125 const headers = client . Defaults . defaultPostHeaders ( client . options ) ;
@@ -91,7 +151,7 @@ export class RestObject {
91151
92152 private _basePath ( objectId ?: string ) : string {
93153 return (
94- this . channel . client . rest . channelMixin . basePath ( this . channel ) +
154+ this . _channel . client . rest . channelMixin . basePath ( this . _channel ) +
95155 '/object' +
96156 ( objectId ? '/' + encodeURIComponent ( objectId ) : '' )
97157 ) ;
@@ -101,6 +161,10 @@ export class RestObject {
101161 const operation = op . operation ;
102162 switch ( operation ) {
103163 case 'map.create' :
164+ if ( op . path == null ) {
165+ throw new this . _channel . client . ErrorInfo ( 'Path must be provided for "map.create" operation' , 40003 , 400 ) ;
166+ }
167+
104168 return {
105169 operation : 'MAP_CREATE' ,
106170 id : op . id ,
@@ -115,6 +179,7 @@ export class RestObject {
115179 { } as Record < string , any > ,
116180 ) ,
117181 } ;
182+
118183 case 'map.set' :
119184 return {
120185 operation : 'MAP_SET' ,
@@ -130,6 +195,7 @@ export class RestObject {
130195 } ,
131196 } ,
132197 } ;
198+
133199 case 'map.remove' :
134200 return {
135201 operation : 'MAP_REMOVE' ,
@@ -139,7 +205,12 @@ export class RestObject {
139205 path : op . path ,
140206 data : { key : op . key } ,
141207 } ;
208+
142209 case 'counter.create' :
210+ if ( op . path == null ) {
211+ throw new this . _channel . client . ErrorInfo ( 'Path must be provided for "counter.create" operation' , 40003 , 400 ) ;
212+ }
213+
143214 return {
144215 operation : 'COUNTER_CREATE' ,
145216 id : op . id ,
@@ -148,6 +219,7 @@ export class RestObject {
148219 path : op . path ,
149220 data : { number : op . count } ,
150221 } ;
222+
151223 case 'counter.inc' :
152224 return {
153225 operation : 'COUNTER_INC' ,
@@ -159,12 +231,12 @@ export class RestObject {
159231 } ;
160232
161233 default :
162- throw new this . channel . client . ErrorInfo ( 'Unsupported publish operation action: ' + operation , 40003 , 400 ) ;
234+ throw new this . _channel . client . ErrorInfo ( 'Unsupported publish operation action: ' + operation , 40003 , 400 ) ;
163235 }
164236 }
165237
166238 private _encodePrimitive ( value : PrimitiveOrObjectReference , format : Utils . Format ) : any {
167- const client = this . channel . client ;
239+ const client = this . _channel . client ;
168240 if ( client . Platform . BufferUtils . isBuffer ( value ) ) {
169241 return {
170242 bytes : client . MessageEncoding . encodeDataForWire ( value , null , format ) . data ,
@@ -185,4 +257,98 @@ export class RestObject {
185257
186258 return value ;
187259 }
260+
261+ private _decodeWireLiveObject ( obj : WireRestLiveObject , format : Utils . Format ) : RestLiveObject {
262+ // expanded live map object which needs decoding
263+ if ( this . _isWireLiveMap ( obj ) ) {
264+ return this . _decodeWireRestLiveMap ( obj , format ) ;
265+ }
266+
267+ // live counter or JSON object
268+ return obj ;
269+ }
270+
271+ private _decodeWireRestLiveMap ( obj : WireRestLiveMap , format : Utils . Format ) : RestLiveObject {
272+ return {
273+ objectId : obj . objectId ,
274+ map : {
275+ semantics : ( mapSemantics [ obj . map . semantics ] ?? 'unknown' ) as ObjectsMapSemantics ,
276+ entries :
277+ typeof obj . map . entries === 'object'
278+ ? Object . entries ( obj . map . entries ) . reduce (
279+ ( acc , entry ) => {
280+ const [ key , value ] = entry ;
281+ const entryData = value . data ;
282+ acc [ key ] = {
283+ data : this . _isWireObjectData ( entryData )
284+ ? this . _decodeWireObjectData ( entryData , format )
285+ : this . _decodeWireLiveObject ( entryData , format ) ,
286+ } ;
287+ return acc ;
288+ } ,
289+ { } as Record < string , RestObjectMapEntry > ,
290+ )
291+ : obj . map . entries ,
292+ } ,
293+ } ;
294+ }
295+
296+ private _decodeWireObjectData ( obj : WireRestObjectData , format : Utils . Format ) : RestObjectData {
297+ const client = this . _channel . client ;
298+
299+ if ( obj . objectId != null ) {
300+ return { objectId : obj . objectId } ;
301+ }
302+ if ( obj . number != null ) {
303+ return { number : obj . number } ;
304+ }
305+ if ( obj . boolean != null ) {
306+ return { boolean : obj . boolean } ;
307+ }
308+ if ( obj . string != null ) {
309+ return { string : obj . string } ;
310+ }
311+ if ( obj . bytes != null ) {
312+ const decodedBytes =
313+ format === 'msgpack'
314+ ? // connection is using msgpack protocol, bytes are already a buffer
315+ ( obj . bytes as Buffer | ArrayBuffer )
316+ : // connection is using JSON protocol, Base64-decode bytes value
317+ client . Platform . BufferUtils . base64Decode ( String ( obj . bytes ) ) ;
318+ return { bytes : decodedBytes } ;
319+ }
320+ if ( obj . json != null ) {
321+ return { json : JSON . parse ( obj . json ) } ;
322+ }
323+
324+ client . Logger . logAction (
325+ client . logger ,
326+ client . Logger . LOG_ERROR ,
327+ 'RestObject._decodeWireObjectData: Unknown object data format, returning empty object; object data: ' +
328+ client . Platform . Config . inspect ( obj ) ,
329+ ) ;
330+ return { } ;
331+ }
332+
333+ private _isWireLiveMap ( obj : unknown ) : obj is WireRestLiveMap {
334+ return (
335+ typeof obj === 'object' &&
336+ obj != undefined &&
337+ 'objectId' in obj &&
338+ 'map' in obj &&
339+ typeof obj . map === 'object' &&
340+ obj . map != undefined &&
341+ 'semantics' in obj . map &&
342+ 'entries' in obj . map
343+ ) ;
344+ }
345+
346+ private _isWireObjectData ( obj : unknown ) : obj is WireRestObjectData {
347+ return (
348+ typeof obj === 'object' &&
349+ obj != undefined &&
350+ Object . keys ( obj ) . length === 1 &&
351+ [ 'objectId' , 'number' , 'boolean' , 'string' , 'bytes' , 'json' ] . includes ( Object . keys ( obj ) [ 0 ] )
352+ ) ;
353+ }
188354}
0 commit comments