@live-change/security-service 0.2.5 → 0.2.19

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/ban.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const app = require("@live-change/framework").app()
2
2
  const definition = require('./definition.js')
3
- const { getClientKeysStrings } = require('./utils.js')
3
+ const { getClientKeysStrings, multiKeyIndexQuery, fastMultiKeyIndexQuery } = require('./utils.js')
4
4
  const lcp = require('@live-change/pattern')
5
5
 
6
6
  const banProperties = {
@@ -49,7 +49,10 @@ const Ban = definition.model({
49
49
  },
50
50
  actionBans: {
51
51
  function: async function(input, output) {
52
- const values = (ban) => {
52
+ function prefixes(ban) {
53
+ //output.debug("BAN", ban)
54
+ if(!ban.keys) return []
55
+ if(!ban.actions) return []
53
56
  const v = ban.keys.length
54
57
  const w = ban.actions.length
55
58
  let res = new Array(v * w)
@@ -59,26 +62,89 @@ const Ban = definition.model({
59
62
  res[i * v + j] = `${ban.actions[j]}:${key.key}:${key.value}`
60
63
  }
61
64
  }
65
+ //output.debug("BAN ACTIONS", res)
62
66
  return res
63
67
  }
64
- await input.table("securityService_Ban").onChange((obj, oldObj) => {
68
+ function indexObject(prefix, obj) {
69
+ //output.debug("BAN", obj)
70
+ return {
71
+ id: prefix+'_'+obj.id,
72
+ to: obj.id,
73
+ type: obj.type,
74
+ expire: obj.expire,
75
+ actions: obj.actions
76
+ }
77
+ }
78
+ await input.table("security_Ban").onChange((obj, oldObj) => {
65
79
  if(obj && oldObj) {
66
- let pointers = obj && new Set(values(obj))
67
- let oldPointers = oldObj && new Set(values(oldObj))
80
+ //output.debug("CHANGE!", obj, oldObj)
81
+ const pointers = obj && new Set(prefixes(obj))
82
+ const oldPointers = oldObj && new Set(prefixes(oldObj))
68
83
  for(let pointer of pointers) {
69
- if(!!oldPointers.has(pointer)) output.change({ id: pointer+'_'+obj.id, to: obj.id }, null)
84
+ if(!!oldPointers.has(pointer)) output.change(indexObject(pointer, obj), null)
70
85
  }
71
86
  for(let pointer of oldPointers) {
72
- if(!!pointers.has(pointer)) output.change(null, { id: pointer+'_'+obj.id, to: obj.id })
87
+ if(!!pointers.has(pointer)) output.change(null, indexObject(pointer, oldObj))
73
88
  }
74
89
  } else if(obj) {
75
- values(obj).forEach(v => output.change({ id: v+'_'+obj.id, to: obj.id }, null))
90
+ //output.debug("CREATE!", obj, oldObj)
91
+ prefixes(obj).forEach(v => output.change(indexObject(v, obj), null))
76
92
  } else if(oldObj) {
77
- values(oldObj).forEach(v => output.change(null, { id: v+'_'+obj.id, to: obj.id }))
93
+ //output.debug("DELETE!", obj, oldObj)
94
+ prefixes(oldObj).forEach(v => output.change(null, indexObject(v, oldObj)))
78
95
  }
79
96
  })
80
97
  }
81
- }
98
+ },
99
+ actionBansByType: {
100
+ function: async function(input, output) {
101
+ function prefixes(ban) {
102
+ //output.debug("BAN", ban)
103
+ if(!ban.keys) return []
104
+ if(!ban.actions) return []
105
+ const v = ban.keys.length
106
+ const w = ban.actions.length
107
+ let res = new Array(v * w)
108
+ for(let i = 0; i < v; i++) {
109
+ for(let j = 0; j < w; j++) {
110
+ const key = ban.keys[i]
111
+ res[i * v + j] = `${ban.actions[j]}:${key.key}:${key.value}:${ban.type}:${ban.expire}`
112
+ }
113
+ }
114
+ output.debug("BAN PREFIXES", res)
115
+ return res
116
+ }
117
+ function indexObject(prefix, obj) {
118
+ //output.debug("BAN", obj)
119
+ return {
120
+ id: prefix+'_'+obj.id,
121
+ to: obj.id,
122
+ type: obj.type,
123
+ expire: obj.expire,
124
+ actions: obj.actions
125
+ }
126
+ }
127
+ await input.table("security_Ban").onChange((obj, oldObj) => {
128
+ if(obj && oldObj) {
129
+ //output.debug("CHANGE!", obj, oldObj)
130
+ let pointers = obj && new Set(prefixes(obj))
131
+ let oldPointers = oldObj && new Set(prefixes(oldObj))
132
+ for(let pointer of pointers) {
133
+ if(!!oldPointers.has(pointer)) output.change(indexObject(pointer, obj), null)
134
+ }
135
+ for(let pointer of oldPointers) {
136
+ if(!!pointers.has(pointer)) output.change(null, indexObject(pointer, oldObj))
137
+ }
138
+ } else if(obj) {
139
+ //output.debug("CREATE!", obj, oldObj)
140
+ prefixes(obj).forEach(v => output.change(indexObject(v, obj), null))
141
+ } else if(oldObj) {
142
+ //output.debug("DELETE!", obj, oldObj)
143
+ prefixes(oldObj).forEach(v => output.change(null, indexObject(v, oldObj)))
144
+ }
145
+ })
146
+ }
147
+ },
82
148
  }
83
149
  })
84
150
 
@@ -101,23 +167,87 @@ definition.view({
101
167
  properties: {},
102
168
  daoPath(params, { client, service }) {
103
169
  const keys = getClientKeysStrings(client)
104
- return multiKeyIndexQuery(keys, 'Ban_bans')
170
+ return multiKeyIndexQuery(keys, 'security_Ban_bans', Ban.tableName)
105
171
  },
106
172
  })
107
173
 
108
174
  definition.view({
109
- name: "myActionBans",
175
+ name: "myActionsBans",
110
176
  properties: {
111
- action: {
177
+ actions: {
112
178
  type: String
113
179
  }
114
180
  },
115
- daoPath({ action }, { client, service }) {
116
- const keys = getClientKeysStrings(client, action + ':')
117
- return multiKeyIndexQuery(keys, 'Ban_actionBans')
181
+ daoPath({ actions }, { client, service }) {
182
+ const keys = []
183
+ for(const action of actions) {
184
+ keys.push(...getClientKeysStrings(client, action + ':'))
185
+ }
186
+ return fastMultiKeyIndexQuery(keys, 'security_Ban_actionBans', Ban.tableName)
118
187
  },
119
188
  })
120
189
 
190
+ definition.view({
191
+ name: "myActionsBansByTypes",
192
+ properties: {
193
+ actions: {
194
+ type: Array,
195
+ of: {
196
+ type: String
197
+ }
198
+ },
199
+ types: {
200
+ type: Array,
201
+ of: {
202
+ type: String
203
+ }
204
+ }
205
+ },
206
+ daoPath({ actions, types }, { client, service }) {
207
+ const keys = []
208
+ for(const type of types) {
209
+ for (const action of actions) {
210
+ keys.push(...getClientKeysStrings(client, action + ':',':' + type))
211
+ }
212
+ }
213
+ console.log("BAN KEYS", keys)
214
+ return ['database', 'query', app.databaseName, `(${
215
+ async (input, output, { keys, indexName }) => {
216
+ function mapper(obj) {
217
+ if(!obj) return null
218
+ const { id, to, ...safeObj } = obj
219
+ return { ...safeObj, id: to }
220
+ }
221
+ function onIndexChange(obj, oldObj) {
222
+ output.change(mapper(obj), mapper(oldObj))
223
+ }
224
+ await Promise.all(keys.map(async (encodedKey) => {
225
+ const range = {
226
+ gte: encodedKey,
227
+ lte: encodedKey + "\xFF",
228
+ reverse: true,
229
+ limit: 1 // only last expire
230
+ }
231
+ await (await input.index(indexName)).range(range).onChange(onIndexChange)
232
+ }))
233
+ }
234
+ })`, { keys, indexName: 'security_Ban_actionBansByType' }]
235
+ },
236
+ })
237
+
238
+ /*function getBanPrefixes(keys, actions) {
239
+ const v = keys.length
240
+ const w = actions.length
241
+ let res = new Array(v * w)
242
+ for(let i = 0; i < v; i++) {
243
+ for(let j = 0; j < w; j++) {
244
+ const key = keys[i]
245
+ res[i * v + j] = `${actions[j]}:${key.key}:${key.value}`
246
+ }
247
+ }
248
+ return res
249
+ }*/
250
+
121
251
  definition.trigger({
122
252
  name: "securityActionBan",
123
253
  properties: {
@@ -136,7 +266,6 @@ definition.trigger({
136
266
  }
137
267
  },
138
268
  async execute({ keys, event, ban: { actions, expire, type } }, { service }, emit) {
139
- const ban = app.generateUid()
140
269
 
141
270
  console.log("SECURITY BAN!", arguments[0])
142
271
 
@@ -152,23 +281,25 @@ definition.trigger({
152
281
  console.log("BAN KEYS", banKeys)
153
282
  console.log("BAN EXPIRE", banExpire)
154
283
 
155
- // service.trigger({
156
- // type: 'createTimer',
157
- // timer: {
158
- // timestamp: banExpire.getTime() + 1000,
159
- // service: 'security',
160
- // trigger: {
161
- // type: 'removeExpiredBan',
162
- // ban
163
- // }
164
- // }
165
- // })
166
-
167
- // emit({
168
- // type: "banCreated",
169
- // ban,
170
- // data: { actions, banKeys, expire, type }
171
- // })
284
+ const ban = app.generateUid()
285
+
286
+ service.trigger({
287
+ type: 'createTimer',
288
+ timer: {
289
+ timestamp: banExpire.getTime() + 1000,
290
+ service: 'security',
291
+ trigger: {
292
+ type: 'removeExpiredBan',
293
+ ban
294
+ }
295
+ }
296
+ })
297
+
298
+ emit({
299
+ type: "banCreated",
300
+ ban,
301
+ data: { actions, keys: banKeys, expire: banExpire, type }
302
+ })
172
303
  }
173
304
  })
174
305
 
@@ -177,7 +308,8 @@ definition.trigger({
177
308
  properties: {
178
309
  ...banProperties
179
310
  },
180
- async execute({ ban }, {client, service}, emit) {
311
+ async execute({ ban }, { client, service }, emit) {
312
+ console.log("REMOVE EXPIRED BAN", ban)
181
313
  emit({
182
314
  type: "banRemoved",
183
315
  ban
package/definition.js CHANGED
@@ -1,8 +1,7 @@
1
1
  const app = require("@live-change/framework").app()
2
2
 
3
3
  const definition = app.createServiceDefinition({
4
- name: "securityService"
4
+ name: "security"
5
5
  })
6
- const config = definition.config
7
6
 
8
7
  module.exports = definition
package/event.js CHANGED
@@ -1,18 +1,18 @@
1
1
  const crypto = require('crypto')
2
2
  const app = require("@live-change/framework").app()
3
3
  const definition = require('./definition.js')
4
- const { getClientKeysObject } = require('./utils.js')
4
+ const { getClientKeysObject, getClientKeysStrings} = require('./utils.js')
5
5
 
6
6
  const lcp = require('@live-change/pattern')
7
7
  const lcpDb = require('@live-change/pattern-db')
8
8
  const { request } = require('http')
9
9
 
10
- const securityPatterns = definition.config.patterns
11
- const relationsStore = lcpDb.relationsStore(app.dao, app.databaseName, 'securityService_relations')
10
+ const securityPatterns = definition.config?.patterns ?? []
11
+ const relationsStore = lcpDb.relationsStore(app.dao, app.databaseName, 'security_relations')
12
12
  lcp.prepareModelForLive(securityPatterns)
13
13
  //console.log("SECURITY PATTERNS", securityPatterns)
14
14
 
15
- const securityCounters = definition.config.counters
15
+ const securityCounters = definition.config?.counters ?? []
16
16
 
17
17
  definition.beforeStart(service => {
18
18
  relationsStore.createTable()
@@ -23,22 +23,10 @@ const eventProperties = {
23
23
  type: String
24
24
  },
25
25
  keys: {
26
- type: Array,
27
- of: {
28
- type: Object,
29
- properties: {
30
- key: {
31
- type: {
32
- String
33
- }
34
- },
35
- value: {
36
- type: {
37
- String
38
- }
39
- }
40
- }
41
- }
26
+ type: Object
27
+ },
28
+ properties: {
29
+ type: Object
42
30
  },
43
31
  timestamp: {
44
32
  type: Date
@@ -51,8 +39,21 @@ const Event = definition.model({
51
39
  ...eventProperties
52
40
  },
53
41
  indexes: {
54
- byTypeAndTimestamp: {
55
- property: ['type', 'timestamp']
42
+ byKeyTypeAndTimestamp: {
43
+ function: async function(input, output) {
44
+ async function spread(event) {
45
+ for(const key in event.keys) {
46
+ output.put({
47
+ id: `${key}:${JSON.stringify(event.keys[key])}:${event.type}:${event.timestamp}_${event.id}`,
48
+ type: event.type,
49
+ timestamp: event.timestamp
50
+ })
51
+ }
52
+ }
53
+ await input.table('security_Event').onChange(
54
+ (obj, oldObj) => spread(obj)
55
+ )
56
+ }
56
57
  }
57
58
  }
58
59
  })
@@ -140,30 +141,42 @@ async function processSecurityCounters(event, timestamp, service) {
140
141
  for(const counter of counters) {
141
142
  const duration = lcp.parseDuration(counter.duration)
142
143
  for(const eventType of counter.match) {
143
- const request = counterEventsRequests.find(req => req.type == eventType)
144
- if(request) {
145
- request.max = Math.max(request.max, counter.max)
146
- request.duration = Math.max(request.duration, duration)
147
- } else {
148
- counterEventsRequests.push({
149
- type: eventType,
150
- max: counter.max,
151
- duration
152
- })
144
+ for(const key of counter.keys) {
145
+ const request = counterEventsRequests.find(req => req.type == eventType && req.key == key)
146
+ if(request) {
147
+ request.max = Math.max(request.max, counter.max)
148
+ request.duration = Math.max(request.duration, duration)
149
+ } else {
150
+ counterEventsRequests.push({
151
+ type: eventType,
152
+ key,
153
+ max: counter.max,
154
+ duration
155
+ })
156
+ }
153
157
  }
154
158
  }
155
159
  }
156
- console.log("COUNTER EVENTS REQUESTS", counterEventsRequests)
157
- const counterEvents = await Promise.all(counterEventsRequests.map(async request => ({
158
- type: request.type,
159
- events: await Event.indexRangeGet('byTypeAndTimestamp',{
160
- gt: `"${request.type}":"${(new Date(now - request.duration - 1000)).toISOString()}"`,
161
- lt: `"${request.type}":\xFF`,
160
+ const indexName = Event.tableName+'_byKeyTypeAndTimestamp'
161
+ //console.log("ALL COUNTER EVENTS REQUESTS", counterEventsRequests)
162
+ const counterEvents = await Promise.all(counterEventsRequests.map(async request => {
163
+ const back = (new Date(now - request.duration - 1000)).toISOString()
164
+ const prefix = `${request.key}:${JSON.stringify(event.keys[request.key])}:${request.type}:`
165
+ console.log("{", prefix)
166
+ const range = {
167
+ gt: prefix + back, // time limited
168
+ lt: prefix + '\xFF',
162
169
  reverse: true,
163
170
  limit: request.max
164
- })
165
- })))
166
- console.log("COUNTER EVENTS", counterEvents)
171
+ }
172
+ console.log("R", range)
173
+ const events = await app.dao.get(['database', 'indexRange', service.databaseName, indexName, range])
174
+ console.log("RR", events)
175
+ return {
176
+ type: request.type,
177
+ events
178
+ }
179
+ }))
167
180
  for(const counter of counters) {
168
181
  const duration = lcp.parseDuration(counter.duration)
169
182
  const fromTime = (new Date(now - duration)).toISOString()
@@ -177,7 +190,7 @@ async function processSecurityCounters(event, timestamp, service) {
177
190
  }
178
191
  }
179
192
  if(count + 1 > counter.max) { // +1 for the new event, not added yet
180
- console.log("COUNTER FIRE", counter)
193
+ //console.log("COUNTER FIRE", counter)
181
194
  actions.push(...counter.actions)
182
195
  }
183
196
  }
@@ -189,24 +202,14 @@ definition.trigger({
189
202
  properties: {
190
203
  event: {
191
204
  type: Object,
192
- properties: {
193
- type: {
194
- type: String
195
- },
196
- keys: {
197
- type: Object
198
- },
199
- properties: {
200
- type: Object
201
- }
202
- }
205
+ properties: eventProperties
203
206
  },
204
207
  client: {
205
208
  type: Object
206
209
  }
207
210
  },
208
211
  async execute({ event, client, timestamp }, { service }, emit) {
209
- console.log("SECURITY EVENT TRIGGERED", arguments[0])
212
+ //console.log("SECURITY EVENT TRIGGERED", arguments[0])
210
213
  event.id = event.id || app.generateUid()
211
214
  event.time = event.time || timestamp
212
215
  const time = (typeof event.time == 'number') ? event.time : new Date(event.time).getTime()
@@ -215,7 +218,7 @@ definition.trigger({
215
218
  event.keys = { ...getClientKeysObject(client), ...event.keys }
216
219
  }
217
220
 
218
- console.log("PROCESS EVENT", event)
221
+ //console.log("PROCESS EVENT", event)
219
222
  let [
220
223
  { newRelations, canceledRelations, actions: patternsActions },
221
224
  { actions: countersActions }
@@ -229,7 +232,7 @@ definition.trigger({
229
232
 
230
233
  for(const action of actions) {
231
234
  const actionTypeUpperCase = action.type[0].toUpperCase() + action.type.slice(1)
232
- console.log("ACTION", JSON.stringify(action, null, ' '))
235
+ //console.log("ACTION", JSON.stringify(action, null, ' '))
233
236
  promises.push(service.trigger({
234
237
  ...action,
235
238
  event,
@@ -252,3 +255,158 @@ definition.trigger({
252
255
  }
253
256
  })
254
257
 
258
+ definition.view({
259
+ name: 'myCountersState',
260
+ properties: {
261
+ events: {
262
+ type: String
263
+ }
264
+ },
265
+ daoPath({ events }, { client, service }) {
266
+ const keys = getClientKeysObject(client)
267
+ const eventRequests = []
268
+ for(const eventType of events) {
269
+ for(const counter of securityCounters) {
270
+ if(!counter.visible) continue
271
+ if(!counter.match.includes(eventType)) continue
272
+ const duration = lcp.parseDuration(counter.duration)
273
+ for(const keyName in keys) {
274
+ const keyValue = keys[keyName]
275
+ if(keyValue === undefined) continue
276
+ eventRequests.push({
277
+ eventType,
278
+ counter: counter.id,
279
+ duration,
280
+ keyName,
281
+ keyValue,
282
+ max: counter.max
283
+ })
284
+ }
285
+ }
286
+ }
287
+ return ['database', 'query', service.databaseName, `(${
288
+ async (input, output, { eventRequests, indexName } ) => {
289
+ const index = await input.index(indexName)
290
+
291
+ class Request {
292
+ constructor(eventRequest) {
293
+ this.eventRequest = eventRequest
294
+
295
+ this.id = eventRequest.counter + ':' + eventRequest.keyName
296
+ this.count = 0
297
+ this.max = this.eventRequest.max
298
+ this.remaining = this.max + 1
299
+
300
+ this.prefix = `${eventRequest.keyName}:${JSON.stringify(eventRequest.keyValue)}:${eventRequest.eventType}:`
301
+ this.range = undefined
302
+ this.indexRange = undefined
303
+ this.fromTime = 0
304
+ this.value = undefined
305
+ this.observer = undefined
306
+ this.oldest = undefined
307
+ this.expire = Infinity
308
+
309
+ this.loading = false
310
+ }
311
+ async refresh() {
312
+ this.loading = true
313
+ const newFromTime = Date.now() - this.eventRequest.duration
314
+ //output.debug("REFRESH", newFromTime, this.fromTime, newFromTime - this.fromTime)
315
+ if(Math.abs(newFromTime - this.fromTime) > 5000) { // refresh only when time is different by 5s
316
+ //output.debug("OBSERVER REFRESH!")
317
+ this.fromTime = newFromTime
318
+ const newRange = {
319
+ gt: this.prefix + (new Date(this.fromTime)).toISOString(), // time limited
320
+ lt: this.prefix + '\xFF',
321
+ reverse: true,
322
+ limit: this.eventRequest.max
323
+ }
324
+ this.range = newRange
325
+ //output.debug("NEW RANGE", this.range)
326
+ if(this.observer) {
327
+ this.indexRange.unobserve(this.observer)
328
+ }
329
+ //output.debug("OBSERVE RANGE!", this.range)
330
+ this.indexRange = index.range(this.range)
331
+
332
+ this.observer = await this.indexRange.onChange(async (ind, oldInd) => {
333
+ //output.debug("RANGE CHANGE!", newRange, ind)
334
+ if(!this.loading) {
335
+ await this.refresh()
336
+ }
337
+ })
338
+ }
339
+ const newValue = await this.indexRange.get()
340
+ this.loading = false
341
+ if(JSON.stringify(this.value) != JSON.stringify(newValue)) {
342
+ this.value = newValue
343
+ if(this.value) {
344
+ this.count = this.value.length
345
+ this.oldest = this.value[this.count - 1]
346
+ this.remaining = this.max - this.count + 1
347
+ this.expire = this.oldest
348
+ ? (new Date(this.oldest.timestamp)).getTime() + this.eventRequest.duration
349
+ : Infinity
350
+ }
351
+ await recompute()
352
+ }
353
+ }
354
+ }
355
+
356
+ const requests = new Array(eventRequests.length)
357
+
358
+ let currentTimeout = null
359
+ let expire = Infinity
360
+ async function recompute() {
361
+ //output.debug("RECOMPUTE?")
362
+ for(const request of requests) {
363
+ if(request.value === undefined) return // no recompute until all readed
364
+ }
365
+ //output.debug("RECOMPUTE!")
366
+ let firstExpire = Infinity
367
+ let firstExpireRequest = null
368
+ for(const request of requests) {
369
+ //console.log("REQUEST EXPIRE", request.prefix, request.expire)
370
+ if(request.expire < firstExpire) {
371
+ firstExpire = request.expire
372
+ firstExpireRequest = request
373
+ }
374
+ const { id, count, remaining, max, oldest, value } = request
375
+ output.change({
376
+ id,
377
+ count, remaining, max,
378
+ oldest: oldest?.timestamp,
379
+ expire: request.expire ? new Date(request.expire) : null,
380
+ //value
381
+ })
382
+ }
383
+ //console.log("FIRST EXPIRE", firstExpire)
384
+ //console.log("LAST EXPIRE", expire)
385
+ if(firstExpire != expire) {
386
+ expire = firstExpire
387
+ if(currentTimeout) { // clears timeout
388
+ currentTimeout()
389
+ }
390
+ expire += 1000 // additional delay
391
+ //console.log("SET TIMEOUT", new Date(expire))
392
+ currentTimeout = output.timeout(expire, () => {
393
+ //output.debug("EXPIRE TIMEOUT!", expire)
394
+ firstExpireRequest.refresh()
395
+ }) // time parameter can be number or date or iso
396
+ }
397
+ }
398
+
399
+ if(eventRequests.length == 0) return
400
+ for(const i in eventRequests) {
401
+ const eventRequest = eventRequests[i]
402
+ const request = new Request(eventRequest)
403
+ requests[i] = request
404
+ //output.debug("REQUEST", eventRequest, '=>', request)
405
+ }
406
+ await Promise.all(requests.map(async request => {
407
+ await request.refresh()
408
+ }))
409
+ }
410
+ })`, { eventRequests, indexName: Event.tableName + '_byKeyTypeAndTimestamp' }]
411
+ }
412
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/security-service",
3
- "version": "0.2.5",
3
+ "version": "0.2.19",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -26,5 +26,5 @@
26
26
  "@live-change/pattern-db": "^0.2.2",
27
27
  "nodemailer": "^6.7.2"
28
28
  },
29
- "gitHead": "7691357c7569a18373668691eb6a81a9e161194d"
29
+ "gitHead": "c2833a9660374ee17dd3def99fd1cdf47f6b1531"
30
30
  }
package/utils.js CHANGED
@@ -1,9 +1,10 @@
1
+ const app = require("@live-change/framework").app()
1
2
  const definition = require('./definition.js')
2
3
 
3
- const clientKeys = definition.config.clientKeys
4
+ const clientKeys = definition.config?.clientKeys ?? []
4
5
 
5
- function multiKeyIndexQuery(keys, indexName) {
6
- return ['database', 'query', database, `(${
6
+ function multiKeyIndexQuery(keys, indexName, tableName) {
7
+ return ['database', 'query', app.databaseName, `(${
7
8
  async (input, output, { keys, indexName, tableName }) => {
8
9
  const objectStates = new Map()
9
10
  async function mapper(res) {
@@ -55,16 +56,38 @@ function multiKeyIndexQuery(keys, indexName) {
55
56
  await (await input.index(indexName)).range(range).onChange(onIndexChange)
56
57
  }))
57
58
  }
58
- })`, { keys, indexName, tableName: Ban.tableName }]
59
+ })`, { keys, indexName, tableName }]
59
60
  }
60
61
 
61
- function getClientKeysStrings(client, prefix = '') {
62
+ function fastMultiKeyIndexQuery(keys, indexName) {
63
+ return ['database', 'query', app.databaseName, `(${
64
+ async (input, output, { keys, indexName }) => {
65
+ function mapper(obj) {
66
+ if(!obj) return null
67
+ const { id, to, ...safeObj } = obj
68
+ return { ...safeObj, id: to }
69
+ }
70
+ function onIndexChange(obj, oldObj) {
71
+ output.change(mapper(obj), mapper(oldObj))
72
+ }
73
+ await Promise.all(keys.map(async (encodedKey) => {
74
+ const range = {
75
+ gte: encodedKey + '_',
76
+ lte: encodedKey + "_\xFF\xFF\xFF\xFF"
77
+ }
78
+ await (await input.index(indexName)).range(range).onChange(onIndexChange)
79
+ }))
80
+ }
81
+ })`, { keys, indexName}]
82
+ }
83
+
84
+ function getClientKeysStrings(client, prefix = '', suffix = '') {
62
85
  if(clientKeys) {
63
- return clientKeys(client).map(k => prefix + k.key + ':' + k.value)
86
+ return clientKeys(client).filter(k => k.value).map(k => prefix + k.key + ':' + k.value + suffix)
64
87
  } else {
65
88
  const keys = []
66
89
  for(let key in client) {
67
- keys.push(prefix + key + ':' + client[key])
90
+ if(client[key]) keys.push(prefix + key + ':' + client[key] + suffix)
68
91
  }
69
92
  return keys
70
93
  }
@@ -82,4 +105,4 @@ function getClientKeysObject(client, prefix = '') {
82
105
  }
83
106
  }
84
107
 
85
- module.exports = { multiKeyIndexQuery, getClientKeysStrings, getClientKeysObject }
108
+ module.exports = { multiKeyIndexQuery, fastMultiKeyIndexQuery, getClientKeysStrings, getClientKeysObject }