@hitchy/plugin-odem-socket.io 0.2.2 → 0.2.4
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/api/services/odem/websocket-provider.js +302 -180
- package/package.json +13 -14
- package/readme.md +3 -3
|
@@ -7,8 +7,8 @@ module.exports = function() {
|
|
|
7
7
|
const logError = api.log( "hitchy:odem:socket.io:error" );
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Implements API for controlling and monitoring server-side
|
|
11
|
-
* websocket.
|
|
10
|
+
* Implements API for controlling and monitoring server-side
|
|
11
|
+
* document-oriented database via websocket.
|
|
12
12
|
*/
|
|
13
13
|
class OdemWebsocketProvider {
|
|
14
14
|
/**
|
|
@@ -18,33 +18,47 @@ module.exports = function() {
|
|
|
18
18
|
*/
|
|
19
19
|
static start() {
|
|
20
20
|
api.once( "websocket", io => {
|
|
21
|
+
const { crud, notifications } = api.config.socket.odem;
|
|
22
|
+
|
|
21
23
|
const namespace = io.of( "/hitchy/odem" );
|
|
22
24
|
|
|
25
|
+
const txPerModel = {};
|
|
26
|
+
|
|
27
|
+
for ( const [ modelName, model ] of Object.entries( api.models ) ) {
|
|
28
|
+
txPerModel[modelName] = this.broadcastModelNotifications( notifications, namespace, modelName, model );
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
namespace.on( "connection", socket => {
|
|
24
|
-
|
|
25
|
-
const model = api.models[modelName];
|
|
32
|
+
const rxPerModel = {};
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
for ( const [ modelName, model ] of Object.entries( api.models ) ) {
|
|
35
|
+
rxPerModel[modelName] = this.handleModelRequests( crud, socket, modelName, model );
|
|
28
36
|
}
|
|
37
|
+
|
|
38
|
+
socket.once( "disconnect", () => {
|
|
39
|
+
for ( const [ modelName, handlers ] of Object.entries( rxPerModel ) ) {
|
|
40
|
+
for ( const [ eventName, handler ] of Object.entries( handlers ) ) {
|
|
41
|
+
const prefix = api.utility.case.pascalToKebab( modelName );
|
|
42
|
+
|
|
43
|
+
socket.off( `${prefix}:${eventName}`, handler );
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} );
|
|
29
47
|
} );
|
|
30
|
-
} );
|
|
31
|
-
}
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* provided server-side websocket.
|
|
36
|
-
*
|
|
37
|
-
* @param {Server} io server-side websocket
|
|
38
|
-
* @param {WebSocket} socket socket representing established incoming connection
|
|
39
|
-
* @param {string} modelName name of model to manage
|
|
40
|
-
* @param {class<Hitchy.Plugin.Odem.Model>} model ODM-based model instance to manage
|
|
41
|
-
* @returns {void}
|
|
42
|
-
*/
|
|
43
|
-
static manageModel( io, socket, modelName, model ) {
|
|
44
|
-
const { crud, notifications } = api.config.socket.odem;
|
|
49
|
+
api.once( "close", () => {
|
|
50
|
+
namespace.disconnectSockets( true );
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
for ( const [ modelName, model ] of Object.entries( api.models ) ) {
|
|
53
|
+
const handlers = txPerModel[modelName];
|
|
54
|
+
|
|
55
|
+
model.notifications
|
|
56
|
+
.off( "created", handlers.created )
|
|
57
|
+
.off( "changed", handlers.changed )
|
|
58
|
+
.off( "removed", handlers.removed );
|
|
59
|
+
}
|
|
60
|
+
} );
|
|
61
|
+
} );
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
@@ -54,8 +68,8 @@ module.exports = function() {
|
|
|
54
68
|
* @param {boolean} enabled true if command handling is enabled in configuration
|
|
55
69
|
* @param {WebSocket} socket socket representing established incoming connection
|
|
56
70
|
* @param {string} modelName name of model to manage
|
|
57
|
-
* @param {class<Hitchy.Plugin.Odem.Model>} model
|
|
58
|
-
* @returns {
|
|
71
|
+
* @param {class<Hitchy.Plugin.Odem.Model>} model model instance based on document-oriented database to manage
|
|
72
|
+
* @returns {Object<string,function>} map of registered handler functions
|
|
59
73
|
*/
|
|
60
74
|
static handleModelRequests( enabled, socket, modelName, model ) {
|
|
61
75
|
const prefix = api.utility.case.pascalToKebab( modelName );
|
|
@@ -63,194 +77,302 @@ module.exports = function() {
|
|
|
63
77
|
if ( !enabled ) {
|
|
64
78
|
logDebug( "disabling socket-based action requests for model %s (%s)", modelName, prefix );
|
|
65
79
|
|
|
80
|
+
const handlers = {
|
|
81
|
+
list: ( _, __, ___, response ) => reject( response ),
|
|
82
|
+
find: ( _, __, ___, ____, response ) => reject( response ),
|
|
83
|
+
create: ( _, __, response ) => reject( response ),
|
|
84
|
+
read: ( _, __, response ) => reject( response ),
|
|
85
|
+
update: ( _, __, ___, response ) => reject( response ),
|
|
86
|
+
delete: ( _, __, response ) => reject( response ),
|
|
87
|
+
};
|
|
88
|
+
|
|
66
89
|
socket
|
|
67
|
-
.on( `${prefix}:list`,
|
|
68
|
-
.on( `${prefix}:find`,
|
|
69
|
-
.on( `${prefix}:create`,
|
|
70
|
-
.on( `${prefix}:read`,
|
|
71
|
-
.on( `${prefix}:update`,
|
|
72
|
-
.on( `${prefix}:delete`,
|
|
73
|
-
|
|
74
|
-
return;
|
|
90
|
+
.on( `${prefix}:list`, handlers.list )
|
|
91
|
+
.on( `${prefix}:find`, handlers.find )
|
|
92
|
+
.on( `${prefix}:create`, handlers.create )
|
|
93
|
+
.on( `${prefix}:read`, handlers.read )
|
|
94
|
+
.on( `${prefix}:update`, handlers.update )
|
|
95
|
+
.on( `${prefix}:delete`, handlers.delete );
|
|
96
|
+
|
|
97
|
+
return handlers;
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
logDebug( "enabling socket-based action requests for model %s (%s)", modelName, prefix );
|
|
78
101
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
response( {
|
|
88
|
-
success: true,
|
|
89
|
-
count: meta.count,
|
|
90
|
-
items: items.map( item => ( loadRecords ? item.toObject( { serialized: true } ) : { uuid: item.uuid } ) ),
|
|
91
|
-
} );
|
|
92
|
-
} catch ( error ) {
|
|
93
|
-
logError( "listing %s instances failed: %s", modelName, error.stack );
|
|
94
|
-
|
|
95
|
-
response( {
|
|
96
|
-
error: error.message,
|
|
97
|
-
} );
|
|
98
|
-
}
|
|
99
|
-
} )
|
|
100
|
-
.on( `${prefix}:find`, async( sessionId, query, queryOptions, loadRecords, response ) => {
|
|
101
|
-
logDebug( "request for finding %s instances matching %j w/ queryOptions %j and uuidsOnly %j", modelName, query, queryOptions, Boolean( loadRecords ) );
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const meta = {};
|
|
105
|
-
const items = await model.find( query, queryOptions, { loadRecords: Boolean( loadRecords ), metaCollector: meta } );
|
|
106
|
-
|
|
107
|
-
response( {
|
|
108
|
-
success: true,
|
|
109
|
-
count: meta.count,
|
|
110
|
-
items: items.map( item => ( loadRecords ? item.toObject( { serialized: true } ) : { uuid: item.uuid } ) ),
|
|
111
|
-
} );
|
|
112
|
-
} catch ( error ) {
|
|
113
|
-
logError( "finding %s instances matching %j failed: %s", modelName, error.stack );
|
|
114
|
-
|
|
115
|
-
response( {
|
|
116
|
-
error: error.message,
|
|
117
|
-
} );
|
|
118
|
-
}
|
|
119
|
-
} )
|
|
120
|
-
.on( `${prefix}:create`, async( sessionId, properties, response ) => {
|
|
121
|
-
logDebug( "request for creating %s instance with %j", modelName, properties );
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const schema = model.schema;
|
|
125
|
-
const instance = new model; // eslint-disable-line new-cap
|
|
126
|
-
|
|
127
|
-
for ( const propName of Object.keys( properties ) ) {
|
|
128
|
-
if ( schema.props.hasOwnProperty( propName ) ||
|
|
129
|
-
schema.computed.hasOwnProperty( propName ) ) {
|
|
130
|
-
instance[propName] = properties[propName];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await instance.save();
|
|
102
|
+
const handlers = {
|
|
103
|
+
list: ( ...args ) => this.handleListRequest( modelName, model, ...args ),
|
|
104
|
+
find: ( ...args ) => this.handleFindRequest( modelName, model, ...args ),
|
|
105
|
+
create: ( ...args ) => this.handleCreateRequest( modelName, model, ...args ),
|
|
106
|
+
read: ( ...args ) => this.handleReadRequest( modelName, model, ...args ),
|
|
107
|
+
update: ( ...args ) => this.handleUpdateRequest( modelName, model, ...args ),
|
|
108
|
+
delete: ( ...args ) => this.handleDeleteRequest( modelName, model, ...args ),
|
|
109
|
+
};
|
|
135
110
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} );
|
|
146
|
-
}
|
|
147
|
-
} )
|
|
148
|
-
.on( `${prefix}:read`, async( sessionId, uuid, response ) => {
|
|
149
|
-
logDebug( "request for reading %s instance %s", modelName, uuid );
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
153
|
-
await instance.load();
|
|
154
|
-
|
|
155
|
-
response( {
|
|
156
|
-
success: true,
|
|
157
|
-
properties: instance.toObject( { serialized: true } ),
|
|
158
|
-
} );
|
|
159
|
-
} catch ( error ) {
|
|
160
|
-
logError( "reading %s instance failed: %s", modelName, error.stack );
|
|
161
|
-
|
|
162
|
-
response( {
|
|
163
|
-
error: error.message,
|
|
164
|
-
} );
|
|
165
|
-
}
|
|
166
|
-
} )
|
|
167
|
-
.on( `${prefix}:update`, async( sessionId, uuid, properties, response ) => {
|
|
168
|
-
logDebug( "request for updating %s instance %s with %j", modelName, uuid, properties );
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
172
|
-
await instance.load();
|
|
173
|
-
|
|
174
|
-
for ( const propName of Object.keys( properties ) ) {
|
|
175
|
-
if ( model.schema.props.hasOwnProperty( propName ) || model.schema.computed.hasOwnProperty( propName ) ) {
|
|
176
|
-
instance[propName] = properties[propName];
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
await instance.save();
|
|
181
|
-
|
|
182
|
-
response( {
|
|
183
|
-
success: true,
|
|
184
|
-
properties: instance.toObject( { serialized: true } ),
|
|
185
|
-
} );
|
|
186
|
-
} catch ( error ) {
|
|
187
|
-
logError( "updating %s instance failed: %s", modelName, error.stack );
|
|
188
|
-
|
|
189
|
-
response( {
|
|
190
|
-
error: error.message,
|
|
191
|
-
} );
|
|
192
|
-
}
|
|
193
|
-
} )
|
|
194
|
-
.on( `${prefix}:delete`, async( sessionId, uuid, response ) => {
|
|
195
|
-
logDebug( "request for deleting %s instance %s", modelName, uuid );
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
199
|
-
await instance.remove();
|
|
200
|
-
|
|
201
|
-
response( {
|
|
202
|
-
success: true,
|
|
203
|
-
properties: { uuid },
|
|
204
|
-
} );
|
|
205
|
-
} catch ( error ) {
|
|
206
|
-
logError( "deleting %s instance %s failed: %s", modelName, uuid, error.stack );
|
|
207
|
-
|
|
208
|
-
response( {
|
|
209
|
-
error: error.message,
|
|
210
|
-
} );
|
|
211
|
-
}
|
|
212
|
-
} );
|
|
111
|
+
socket
|
|
112
|
+
.on( `${prefix}:list`, handlers.list )
|
|
113
|
+
.on( `${prefix}:find`, handlers.find )
|
|
114
|
+
.on( `${prefix}:create`, handlers.create )
|
|
115
|
+
.on( `${prefix}:read`, handlers.read )
|
|
116
|
+
.on( `${prefix}:update`, handlers.update )
|
|
117
|
+
.on( `${prefix}:delete`, handlers.delete );
|
|
118
|
+
|
|
119
|
+
return handlers;
|
|
213
120
|
}
|
|
214
121
|
|
|
215
122
|
/**
|
|
216
|
-
* Sets up listeners for model-related notifications in
|
|
217
|
-
* them as broadcast events to
|
|
123
|
+
* Sets up listeners for model-related notifications in
|
|
124
|
+
* document-oriented database and forwards them as broadcast events to
|
|
125
|
+
* connected clients.
|
|
218
126
|
*
|
|
219
127
|
* @param {boolean} enabled true if command handling is enabled in configuration
|
|
220
|
-
* @param {Server}
|
|
128
|
+
* @param {Server} namespace namespace managing a list of server-side sockets with common prefix
|
|
221
129
|
* @param {string} modelName name of model to manage
|
|
222
|
-
* @param {class<Hitchy.Plugin.Odem.Model>} model
|
|
223
|
-
* @returns {
|
|
130
|
+
* @param {class<Hitchy.Plugin.Odem.Model>} model model instance of document-oriented database to manage
|
|
131
|
+
* @returns {Object<string,function>} map of registered handler functions
|
|
224
132
|
*/
|
|
225
|
-
static
|
|
133
|
+
static broadcastModelNotifications( enabled, namespace, modelName, model ) {
|
|
226
134
|
if ( enabled ) {
|
|
227
135
|
const prefix = api.utility.case.pascalToKebab( modelName );
|
|
228
136
|
|
|
229
|
-
|
|
230
|
-
|
|
137
|
+
const handlers = {
|
|
138
|
+
created: async( uuid, newRecord, asyncGeneratorFn ) => {
|
|
231
139
|
const event = {
|
|
232
140
|
change: "created",
|
|
233
141
|
properties: ( await asyncGeneratorFn() ).toObject( { serialized: true } ),
|
|
234
142
|
};
|
|
235
143
|
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
144
|
+
namespace.emit( `${prefix}:changed`, event );
|
|
145
|
+
},
|
|
146
|
+
changed: async( uuid, newRecord, oldRecord, asyncGeneratorFn ) => {
|
|
239
147
|
const event = {
|
|
240
148
|
change: "updated",
|
|
241
149
|
properties: ( await asyncGeneratorFn() ).toObject( { serialized: true } ),
|
|
242
150
|
};
|
|
243
151
|
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
152
|
+
namespace.emit( `${prefix}:changed`, event );
|
|
153
|
+
},
|
|
154
|
+
removed: uuid => {
|
|
247
155
|
const event = {
|
|
248
156
|
change: "deleted",
|
|
249
157
|
properties: { uuid: model.formatUUID( uuid ) },
|
|
250
158
|
};
|
|
251
159
|
|
|
252
|
-
|
|
253
|
-
}
|
|
160
|
+
namespace.emit( `${prefix}:changed`, event );
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
model.notifications
|
|
165
|
+
.on( "created", handlers.created )
|
|
166
|
+
.on( "changed", handlers.changed )
|
|
167
|
+
.on( "removed", handlers.removed );
|
|
168
|
+
|
|
169
|
+
return handlers;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Handles incoming request for listing items of a model.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} modelName name of model
|
|
179
|
+
* @param {string} model named server-side model
|
|
180
|
+
* @param {string} sessionId client-provided session ID
|
|
181
|
+
* @param {Hitchy.Odem.ListOptions} queryOptions client-provided options for listing items
|
|
182
|
+
* @param {boolean} loadRecords if true, whole records shall be fetched per item
|
|
183
|
+
* @param {function} response callback to invoke with response
|
|
184
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
185
|
+
*/
|
|
186
|
+
static async handleListRequest( modelName, model, sessionId, queryOptions, loadRecords, response ) {
|
|
187
|
+
logDebug( "request for listing %s instances w/ queryOptions %j and loadRecords %j", modelName, queryOptions, Boolean( loadRecords ) );
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const meta = {};
|
|
191
|
+
const items = await model.list( queryOptions, { loadRecords: Boolean( loadRecords ), metaCollector: meta } );
|
|
192
|
+
|
|
193
|
+
response( {
|
|
194
|
+
success: true,
|
|
195
|
+
count: meta.count,
|
|
196
|
+
items: items.map( item => ( loadRecords ? item.toObject( { serialized: true } ) : { uuid: item.uuid } ) ),
|
|
197
|
+
} );
|
|
198
|
+
} catch ( error ) {
|
|
199
|
+
logError( "listing %s instances failed: %s", modelName, error.stack );
|
|
200
|
+
|
|
201
|
+
response( {
|
|
202
|
+
error: error.message,
|
|
203
|
+
} );
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handles incoming request for finding items of a model matching some
|
|
209
|
+
* provided query.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} modelName name of model
|
|
212
|
+
* @param {string} model named server-side model
|
|
213
|
+
* @param {string} sessionId client-provided session ID
|
|
214
|
+
* @param {Hitchy.Odem.Query} query client-provided constraints to be met by returned items
|
|
215
|
+
* @param {Hitchy.Odem.ListOptions} queryOptions client-provided options for listing items
|
|
216
|
+
* @param {boolean} loadRecords if true, whole records shall be fetched per item
|
|
217
|
+
* @param {function} response callback to invoke with response
|
|
218
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
219
|
+
*/
|
|
220
|
+
static async handleFindRequest( modelName, model, sessionId, query, queryOptions, loadRecords, response ) {
|
|
221
|
+
logDebug( "request for finding %s instances matching %j w/ queryOptions %j and uuidsOnly %j", modelName, query, queryOptions, Boolean( loadRecords ) );
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const meta = {};
|
|
225
|
+
const items = await model.find( query, queryOptions, { loadRecords: Boolean( loadRecords ), metaCollector: meta } );
|
|
226
|
+
|
|
227
|
+
response( {
|
|
228
|
+
success: true,
|
|
229
|
+
count: meta.count,
|
|
230
|
+
items: items.map( item => ( loadRecords ? item.toObject( { serialized: true } ) : { uuid: item.uuid } ) ),
|
|
231
|
+
} );
|
|
232
|
+
} catch ( error ) {
|
|
233
|
+
logError( "finding %s instances matching %j failed: %s", modelName, error.stack );
|
|
234
|
+
|
|
235
|
+
response( {
|
|
236
|
+
error: error.message,
|
|
237
|
+
} );
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Handles incoming request for creating item of a model.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} modelName name of model
|
|
245
|
+
* @param {string} model named server-side model
|
|
246
|
+
* @param {string} sessionId client-provided session ID
|
|
247
|
+
* @param {Hitchy.Odem.ListOptions} properties properties of item to create
|
|
248
|
+
* @param {function} response callback to invoke with response
|
|
249
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
250
|
+
*/
|
|
251
|
+
static async handleCreateRequest( modelName, model, sessionId, properties, response ) {
|
|
252
|
+
logDebug( "request for creating %s instance with %j", modelName, properties );
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const schema = model.schema;
|
|
256
|
+
const instance = new model; // eslint-disable-line new-cap
|
|
257
|
+
|
|
258
|
+
for ( const propName of Object.keys( properties ) ) {
|
|
259
|
+
if ( schema.props.hasOwnProperty( propName ) ||
|
|
260
|
+
schema.computed.hasOwnProperty( propName ) ) {
|
|
261
|
+
instance[propName] = properties[propName];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await instance.save();
|
|
266
|
+
|
|
267
|
+
response( {
|
|
268
|
+
success: true,
|
|
269
|
+
properties: instance.toObject( { serialized: true } ),
|
|
270
|
+
} );
|
|
271
|
+
} catch ( error ) {
|
|
272
|
+
logError( "creating %s instance failed: %s", modelName, error.stack );
|
|
273
|
+
|
|
274
|
+
response( {
|
|
275
|
+
error: error.message,
|
|
276
|
+
} );
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Handles incoming request for reading properties of a single item.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} modelName name of item's model
|
|
284
|
+
* @param {string} model named server-side model
|
|
285
|
+
* @param {string} sessionId client-provided session ID
|
|
286
|
+
* @param {string} uuid client-provided UUID of item to read
|
|
287
|
+
* @param {function} response callback to invoke with response
|
|
288
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
289
|
+
*/
|
|
290
|
+
static async handleReadRequest( modelName, model, sessionId, uuid, response ) {
|
|
291
|
+
logDebug( "request for reading %s instance %s", modelName, uuid );
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
295
|
+
await instance.load();
|
|
296
|
+
|
|
297
|
+
response( {
|
|
298
|
+
success: true,
|
|
299
|
+
properties: instance.toObject( { serialized: true } ),
|
|
300
|
+
} );
|
|
301
|
+
} catch ( error ) {
|
|
302
|
+
logError( "reading %s instance failed: %s", modelName, error.stack );
|
|
303
|
+
|
|
304
|
+
response( {
|
|
305
|
+
error: error.message,
|
|
306
|
+
} );
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Handles incoming request for adjusting properties of a single item.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} modelName name of item's model
|
|
314
|
+
* @param {string} model named server-side model
|
|
315
|
+
* @param {string} sessionId client-provided session ID
|
|
316
|
+
* @param {string} uuid client-provided UUID of item to modify
|
|
317
|
+
* @param {Object} properties client-provided properties of item replacing its existing ones
|
|
318
|
+
* @param {function} response callback to invoke with response
|
|
319
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
320
|
+
*/
|
|
321
|
+
static async handleUpdateRequest( modelName, model, sessionId, uuid, properties, response ) {
|
|
322
|
+
logDebug( "request for updating %s instance %s with %j", modelName, uuid, properties );
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
326
|
+
await instance.load();
|
|
327
|
+
|
|
328
|
+
for ( const propName of Object.keys( properties ) ) {
|
|
329
|
+
if ( model.schema.props.hasOwnProperty( propName ) || model.schema.computed.hasOwnProperty( propName ) ) {
|
|
330
|
+
instance[propName] = properties[propName];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await instance.save();
|
|
335
|
+
|
|
336
|
+
response( {
|
|
337
|
+
success: true,
|
|
338
|
+
properties: instance.toObject( { serialized: true } ),
|
|
339
|
+
} );
|
|
340
|
+
} catch ( error ) {
|
|
341
|
+
logError( "updating %s instance failed: %s", modelName, error.stack );
|
|
342
|
+
|
|
343
|
+
response( {
|
|
344
|
+
error: error.message,
|
|
345
|
+
} );
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Handles incoming request for removing a model's item.
|
|
351
|
+
*
|
|
352
|
+
* @param {string} modelName name of item's model
|
|
353
|
+
* @param {string} model named server-side model
|
|
354
|
+
* @param {string} sessionId client-provided session ID
|
|
355
|
+
* @param {string} uuid client-provided UUID of item to remove
|
|
356
|
+
* @param {function} response callback to invoke with response
|
|
357
|
+
* @returns {Promise<void>} promise settled on request handled
|
|
358
|
+
*/
|
|
359
|
+
static async handleDeleteRequest( modelName, model, sessionId, uuid, response ) {
|
|
360
|
+
logDebug( "request for deleting %s instance %s", modelName, uuid );
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const instance = new model( uuid ); // eslint-disable-line new-cap
|
|
364
|
+
await instance.remove();
|
|
365
|
+
|
|
366
|
+
response( {
|
|
367
|
+
success: true,
|
|
368
|
+
properties: { uuid },
|
|
369
|
+
} );
|
|
370
|
+
} catch ( error ) {
|
|
371
|
+
logError( "deleting %s instance %s failed: %s", modelName, uuid, error.stack );
|
|
372
|
+
|
|
373
|
+
response( {
|
|
374
|
+
error: error.message,
|
|
375
|
+
} );
|
|
254
376
|
}
|
|
255
377
|
}
|
|
256
378
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitchy/plugin-odem-socket.io",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "exposing Hitchy's
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "exposing Hitchy's document-oriented database via websocket",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"Hitchy",
|
|
17
|
-
"socket.io"
|
|
18
|
-
"ODM"
|
|
17
|
+
"socket.io"
|
|
19
18
|
],
|
|
20
19
|
"author": "cepharum GmbH",
|
|
21
20
|
"license": "MIT",
|
|
@@ -30,18 +29,18 @@
|
|
|
30
29
|
"index.js"
|
|
31
30
|
],
|
|
32
31
|
"peerDependencies": {
|
|
33
|
-
"@hitchy/core": ">=0.
|
|
34
|
-
"@hitchy/plugin-odem": ">=0.7.
|
|
35
|
-
"@hitchy/plugin-socket.io": ">=0.1.
|
|
32
|
+
"@hitchy/core": ">=0.8.1",
|
|
33
|
+
"@hitchy/plugin-odem": ">=0.7.8",
|
|
34
|
+
"@hitchy/plugin-socket.io": ">=0.1.1"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
|
-
"@hitchy/server-dev-tools": "^0.4.
|
|
39
|
-
"c8": "^
|
|
40
|
-
"eslint": "^8.
|
|
41
|
-
"eslint-config-cepharum": "^1.0.
|
|
42
|
-
"eslint-plugin-promise": "^
|
|
43
|
-
"mocha": "^10.
|
|
37
|
+
"@hitchy/server-dev-tools": "^0.4.9",
|
|
38
|
+
"c8": "^10.1.2",
|
|
39
|
+
"eslint": "^8.57.0",
|
|
40
|
+
"eslint-config-cepharum": "^1.0.14",
|
|
41
|
+
"eslint-plugin-promise": "^7.1.0",
|
|
42
|
+
"mocha": "^10.7.3",
|
|
44
43
|
"should": "^13.2.3",
|
|
45
|
-
"socket.io-client": "^4.7.
|
|
44
|
+
"socket.io-client": "^4.7.5"
|
|
46
45
|
}
|
|
47
46
|
}
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hitchy/plugin-odem-socket.io
|
|
2
2
|
|
|
3
|
-
_exposing Hitchy's
|
|
3
|
+
_exposing Hitchy's document-oriented database via websocket_
|
|
4
4
|
|
|
5
5
|
## License
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ npm i @hitchy/plugin-odem @hitchy/plugin-socket.io
|
|
|
29
29
|
|
|
30
30
|
### Basics
|
|
31
31
|
|
|
32
|
-
This plugin is establishing an API via websocket for controlling and monitoring models of server-side
|
|
32
|
+
This plugin is establishing an API via websocket for controlling and monitoring models of server-side document-oriented database. Relying on particular features of [socket.io](https://socket.io/), this API is exposed in [namespace](https://socket.io/docs/v4/namespaces/) `/hitchy/odem`.
|
|
33
33
|
|
|
34
34
|
For every server-side model, request listeners and/or notification broadcasters are set up. Either model's name is converted into its kebab-case variant and used as colon-separated prefix in event names emitted either by the server or by a client.
|
|
35
35
|
|
|
@@ -156,7 +156,7 @@ Last argument is a callback to be invoked with the resulting response from serve
|
|
|
156
156
|
|
|
157
157
|
### Notifications
|
|
158
158
|
|
|
159
|
-
[
|
|
159
|
+
[notifications of the document-oriented database](https://odem.hitchy.org/api/model.html#model-notifications) are forwarded as broadcast events named `<modelName>:changed`. Either event consists of
|
|
160
160
|
|
|
161
161
|
- a type of change and
|
|
162
162
|
- the changed item's properties (limited to its UUID on deletion).
|