@live-change/framework 0.8.145 → 0.8.147

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/lib/App.js CHANGED
@@ -13,10 +13,7 @@ import SessionDao from "./runtime/SessionDao.js"
13
13
  import LiveDao from "./runtime/LiveDao.js"
14
14
  import ApiServer from "./runtime/ApiServer.js"
15
15
 
16
- import reverseRelationProcessor from "./processors/reverseRelation.js"
17
16
  import indexListProcessor from "./processors/indexList.js"
18
- //import crudGenerator from "./processors/crudGenerator.js"
19
- import draftGenerator from "./processors/draftGenerator.js"
20
17
  import daoPathView from "./processors/daoPathView.js"
21
18
  import fetchView from "./processors/fetchView.js"
22
19
  import accessControl from "./processors/accessControl.js"
@@ -51,9 +48,6 @@ class App {
51
48
  this.requestTimeout = config?.db?.requestTimeout || 10*1000
52
49
 
53
50
  this.defaultProcessors = [
54
- // crudGenerator,
55
- draftGenerator,
56
- reverseRelationProcessor,
57
51
  indexListProcessor,
58
52
  daoPathView,
59
53
  fetchView,
@@ -13,6 +13,9 @@ class PropertyDefinition {
13
13
  if(definition.of) {
14
14
  this.of = new PropertyDefinition(definition.of)
15
15
  }
16
+ if(definition.items) {
17
+ this.items = new PropertyDefinition(definition.items)
18
+ }
16
19
  }
17
20
 
18
21
  createAndAddProperty(name, definition) {
@@ -36,6 +39,9 @@ class PropertyDefinition {
36
39
  if(this.of) {
37
40
  json.of = this.of.toJSON()
38
41
  }
42
+ if(this.items) {
43
+ json.items = this.items.toJSON()
44
+ }
39
45
 
40
46
  return json
41
47
  }
@@ -43,8 +49,10 @@ class PropertyDefinition {
43
49
  computeChanges( oldProperty, params, name) {
44
50
  let changes = []
45
51
  let typeChanged = false
46
- if(typeName(this.type) != typeName(oldProperty.type)) typeChanged = true
47
- if((this.of && typeName(this.of.type)) != (oldProperty.of && typeName(oldProperty.of.type)))
52
+ if(typeName(this.type) !== typeName(oldProperty.type)) typeChanged = true
53
+ if((this.of && typeName(this.of.type)) !== (oldProperty.of && typeName(oldProperty.of.type)))
54
+ typeChanged = true
55
+ if((this.items && typeName(this.items.type)) !== (oldProperty.items && typeName(oldProperty.items.type)))
48
56
  typeChanged = true
49
57
  if(typeChanged) {
50
58
  changes.push({
@@ -54,7 +62,7 @@ class PropertyDefinition {
54
62
  ...this
55
63
  })
56
64
  }
57
- if(JSON.stringify(this.search) != JSON.stringify(oldProperty.search)) {
65
+ if(JSON.stringify(this.search) !== JSON.stringify(oldProperty.search)) {
58
66
  changes.push({
59
67
  operation: "changePropertySearch",
60
68
  ...params,
@@ -93,6 +93,7 @@ class ServiceDefinition {
93
93
  }
94
94
 
95
95
  model(definition) {
96
+ if(this.models[definition.name]) throw new Error('model ' + definition.name + ' already exists')
96
97
  const model = new ModelDefinition(definition, this.name)
97
98
  this.models[model.name] = model
98
99
  return createModelProxy(this, model)
@@ -105,6 +106,7 @@ class ServiceDefinition {
105
106
  }
106
107
 
107
108
  index(definition) {
109
+ if(this.indexes[definition.name]) throw new Error('index ' + definition.name + ' already exists')
108
110
  const index = new IndexDefinition(definition)
109
111
  this.indexes[index.name] = index
110
112
  return createIndexProxy(this, index)
@@ -117,18 +119,21 @@ class ServiceDefinition {
117
119
  }
118
120
 
119
121
  action(definition) {
122
+ if(this.actions[definition.name]) throw new Error('action ' + definition.name + ' already exists')
120
123
  const action = new ActionDefinition(definition)
121
124
  this.actions[action.name] = action
122
125
  return action
123
126
  }
124
127
 
125
128
  event(definition) {
129
+ if(this.events[definition.name]) throw new Error('event ' + definition.name + ' already exists')
126
130
  const event = new EventDefinition(definition)
127
131
  this.events[event.name] = event
128
132
  return event
129
133
  }
130
134
 
131
135
  view(definition) {
136
+ if(this.views[definition.name]) throw new Error('view ' + definition.name + ' already exists')
132
137
  const view = new ViewDefinition(definition)
133
138
  this.views[view.name] = view
134
139
  return view
@@ -168,6 +173,7 @@ class ServiceDefinition {
168
173
  }
169
174
 
170
175
  validator(name, validator) {
176
+ if(this.validators[name]) throw new Error('validator ' + name + ' already exists')
171
177
  this.validators[name] = validator
172
178
  console.log("VALIDATOR DEFINED", name, validator)
173
179
  }
@@ -28,8 +28,8 @@ async function prepareParameter(parameter, prop, service) {
28
28
  }
29
29
  if(prop.type === Array) {
30
30
  if(!parameter) return parameter
31
- if(!prop.of) return parameter
32
- return await Promise.all(parameter.map(item => prepareParameter(item, prop.of, service)))
31
+ if(!(prop.of || prop.items)) return parameter
32
+ return await Promise.all(parameter.map(item => prepareParameter(item, (prop.of || prop.items), service)))
33
33
  }
34
34
  if(prop.type === Date && parameter) {
35
35
  return new Date(parameter)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/framework",
3
- "version": "0.8.145",
3
+ "version": "0.8.147",
4
4
  "description": "Live Change Framework - ultimate solution for real time mobile/web apps",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "homepage": "https://github.com/live-change/live-change-stack",
24
24
  "devDependencies": {
25
- "@live-change/dao": "^0.8.145",
26
- "@live-change/uid": "^0.8.145"
25
+ "@live-change/dao": "^0.8.147",
26
+ "@live-change/uid": "^0.8.147"
27
27
  },
28
- "gitHead": "fadc33dc99f47714c3d40f2ce1b5cdf3f050a70a"
28
+ "gitHead": "2bfde1b464cba759052a10453b004be386922177"
29
29
  }
@@ -1,206 +0,0 @@
1
- import * as utils from "../utils.js"
2
-
3
- function ignoreValidation(prop) {
4
- delete prop.validation
5
- if(prop.properties) {
6
- for(let propName in prop.properties) {
7
- ignoreValidation(prop.properties[propName])
8
- }
9
- }
10
- if(prop.of) {
11
- delete ignoreValidation(prop.of)
12
- }
13
- }
14
-
15
- export default function(service, app) {
16
- if(!service) throw new Error("no service")
17
- if(!app) throw new Error("no service")
18
- for(let modelName in service.models) {
19
- const model = service.models[modelName]
20
- if(!model.crud) continue
21
- const crud = model.crud
22
- const generateId = crud.id || (() => app.generateUid())
23
- const options = crud.options || {}
24
- const writeOptions = crud.writeOptions || {}
25
- const readOptions = crud.readOptions || {}
26
- const idName = model.name.slice(0, 1).toLowerCase() + model.name.slice(1)
27
- let properties = {}
28
- for(let propName in model.properties) {
29
- let property = model.properties[propName]
30
- let typeName = utils.typeName(property.type)
31
- properties[propName] = {
32
- ...property,
33
- idOnly: !!service.models[typeName] // we need only id of entity, nothing more
34
- }
35
- if(crud.ignoreValidation) {
36
- ignoreValidation(properties[propName])
37
- }
38
- }
39
- let idProp = {}
40
- idProp[idName] = {
41
- type: model,
42
- idOnly: true
43
- }
44
- function genName(name) {
45
- return (crud.prefix || "") + model.name + name
46
- }
47
- function modelRuntime() {
48
- return service._runtime.models[modelName]
49
- }
50
-
51
- const defaults = utils.getDefaults(model.properties)
52
-
53
- if(!service.events[genName("Created")]) { // Events:
54
- service.event({
55
- name: genName("Created"),
56
- async execute(props) {
57
- console.log("CREATE !!!!")
58
- const data = utils.mergeDeep({}, defaults, props.data)
59
- console.log("DEFAULTS", JSON.stringify(defaults, null, " "))
60
- console.log(`CREATE ${modelName} WITH DATA ${JSON.stringify(data, null, " ")}`)
61
- await modelRuntime().update(props[idName], { ...data, id: props[idName] })
62
- }
63
- })
64
- }
65
- if(!service.actions[genName("Create")]) {
66
- service.action({
67
- name: genName("Create"),
68
- properties,
69
- returns: {
70
- type: model,
71
- idOnly: true
72
- },
73
- ...options,
74
- ...writeOptions,
75
- execute: async function(props, {service}, emit) {
76
- const id = generateId(props)
77
- let event = {
78
- data: props || {},
79
- type: genName("Created")
80
- }
81
- event[idName] = id
82
- emit(event)
83
- return id
84
- }
85
- })
86
- }
87
-
88
- if(!service.events[genName("Updated")]) { // Events:
89
- service.event({
90
- name: genName("Updated"),
91
- async execute(props) {
92
- if(crud.updateMethod == 'create') {
93
- await modelRuntime().create({ ...props.data, id: props[idName] })
94
- } else {
95
- await modelRuntime().update(props[idName], { ...props.data, id: props[idName] })
96
- }
97
- }
98
- })
99
- }
100
- if(!service.actions[genName("Update")]) {
101
- service.action({
102
- name: genName("Update"),
103
- properties: {
104
- ...idProp,
105
- ...properties
106
- },
107
- returns: {
108
- type: model,
109
- idOnly: true
110
- },
111
- ...options,
112
- ...writeOptions,
113
- execute: async function (props, {service}, emit) {
114
- let event = {
115
- data: {...props},
116
- type: genName("Updated")
117
- }
118
- event[idName] = props[idName]
119
- delete event.data[idName]
120
- emit(event)
121
- return props[idName]
122
- }
123
- })
124
- }
125
-
126
- if(!service.events[genName("Deleted")]) { // Events:
127
- service.event({
128
- name: genName("Deleted"),
129
- async execute(props) {
130
- await modelRuntime().delete(props[idName])
131
- }
132
- })
133
- }
134
-
135
- if(!service.actions[genName("Delete")]) {
136
- service.action({
137
- name: genName("Delete"),
138
- properties: idProp,
139
- returns: {
140
- type: null
141
- },
142
- ...options,
143
- ...writeOptions,
144
- execute: async function (props, {service}, emit) {
145
- if(crud.deleteTrigger || crud.triggers) {
146
- await service.trigger({ type: genName("Deleted") }, { [idName] : props[idName ] })
147
- }
148
- emit({
149
- ...props,
150
- type: genName("Deleted")
151
- })
152
- }
153
- })
154
- }
155
-
156
- service.view({
157
- name: genName("One"),
158
- properties: idProp,
159
- ...options,
160
- ...readOptions,
161
- returns: {
162
- type: model
163
- },
164
- daoPath(props) {
165
- const id = props[idName]
166
- if(!id) throw new Error(idName + " is required")
167
- return modelRuntime().path(id)
168
- }
169
- })
170
-
171
- service.view({
172
- name: genName("Range"),
173
- properties: {
174
- gt: {
175
- type: String,
176
- },
177
- lt: {
178
- type: String,
179
- },
180
- gte: {
181
- type: String,
182
- },
183
- lte: {
184
- type: String,
185
- },
186
- limit: {
187
- type: Number
188
- },
189
- reverse: {
190
- type: Boolean
191
- }
192
- },
193
- returns: {
194
- type: Array,
195
- of: {
196
- type: model
197
- }
198
- },
199
- ...options,
200
- ...readOptions,
201
- daoPath(props) {
202
- return modelRuntime().rangePath(props)
203
- }
204
- })
205
- }
206
- }
@@ -1,291 +0,0 @@
1
- import * as utils from "../utils.js"
2
-
3
- function propertyWithoutValidation(property) {
4
- let prop = { ...property }
5
- delete prop.validation
6
- if(prop.draftValidation) prop.validation = prop.draftValidation
7
- if(prop.of) prop.of = withoutValidation(prop.of)
8
- if(prop.properties) prop.properties = propertiesWithoutValidation(prop)
9
- return prop
10
- }
11
-
12
- function propertiesWithoutValidation(properties, validateFields) {
13
- let propertiesWV = {}
14
- for(let k in properties) {
15
- propertiesWV[k] =
16
- (validateFields && validateFields.indexOf(k) != -1)
17
- ? properties[k]
18
- : propertyWithoutValidation(properties[k])
19
- }
20
- return propertiesWV
21
- }
22
-
23
- export default function(service, app) {
24
- if(!service) throw new Error("no service")
25
- if(!app) throw new Error("no service")
26
- for(let actionName in service.actions) {
27
- const action = service.actions[actionName]
28
- if (!action.draft) continue
29
-
30
- const actionExecute = action.execute
31
- const draft = action.draft
32
- const steps = draft.steps
33
-
34
- const modelName = `${actionName}_draft`
35
- let indexes = {}
36
- let properties = {
37
- ...action.properties
38
- }
39
-
40
- if(draft.identification) {
41
- properties = {
42
- ...(draft.identification),
43
- ...properties
44
- }
45
- indexes.identifier = {
46
- property: Object.keys(draft.identification)
47
- }
48
- }
49
-
50
- if(draft.steps) {
51
- properties = {
52
- draftStep: {
53
- type: String
54
- },
55
- ...properties
56
- }
57
- }
58
-
59
- const DraftModel = service.model({
60
- name: modelName,
61
- properties,
62
- indexes
63
- })
64
- function modelRuntime() {
65
- return service._runtime.models[modelName]
66
- }
67
-
68
- const propertiesWV = propertiesWithoutValidation(properties, draft.validateFields)
69
-
70
- service.action({
71
- name: `${actionName}_saveDraft`,
72
- properties: {
73
- ...propertiesWV,
74
- draft: {
75
- type: String
76
- }
77
- },
78
- access: draft.saveAccess || draft.access || action.access,
79
- async execute(params, {service, client}, emit) {
80
- let draft = params.draft
81
- if(!draft) draft = app.generateUid()
82
- let data = {}
83
- for(let k in properties) data[k] = params[k]
84
- emit({
85
- type: `${actionName}_draftSaved`,
86
- draft, data
87
- })
88
- return draft
89
- }
90
- })
91
- service.event({
92
- name: `${actionName}_draftSaved`,
93
- async execute(props) {
94
- await modelRuntime().create({...props.data, id: props.draft})
95
- }
96
- })
97
-
98
- service.action({
99
- name: `${actionName}_deleteDraft`,
100
- properties: {
101
- draft: {
102
- type: String,
103
- validation: ['nonEmpty']
104
- }
105
- },
106
- access: draft.deleteAccess || draft.access || action.access,
107
- async execute(params, {service, client}, emit) {
108
- emit({
109
- type: `${actionName}_draftDeleted`,
110
- draft: params.draft
111
- })
112
- }
113
- })
114
- service.event({
115
- name: `${actionName}_draftDeleted`,
116
- async execute({draft}) {
117
- await modelRuntime().delete(draft)
118
- }
119
- })
120
-
121
- service.action({
122
- name: `${actionName}_finishDraft`,
123
- properties: {
124
- ...propertiesWV,
125
- draft: {
126
- type: String
127
- }
128
- },
129
- access: draft.finishAccess || draft.access || action.access,
130
- async execute(params, context, emit) {
131
- let draft = params.draft
132
- if(!draft) draft = app.generateUid()
133
- let actionProps = {}
134
- for(let k in action.properties) actionProps[k] = params[k]
135
- const result = await actionExecute.call(action, actionProps, context, emit)
136
- emit({
137
- type: `${actionName}_draftFinished`,
138
- draft
139
- })
140
- return result
141
- }
142
- })
143
- service.event({
144
- name: `${actionName}_draftFinished`,
145
- async execute({draft}) {
146
- await modelRuntime().delete(draft)
147
- }
148
- })
149
-
150
- service.view({
151
- name: `${actionName}_draft`,
152
- properties: {
153
- draft: {
154
- type: String,
155
- validation: ['nonEmpty']
156
- }
157
- },
158
- returns: {
159
- type: DraftModel
160
- },
161
- access: draft.readAccess || draft.access || action.access,
162
- daoPath({ draft }) {
163
- if(!draft) return null
164
- return modelRuntime().path(draft)
165
- }
166
- })
167
-
168
- if(draft.identification) {
169
- service.view({
170
- name: `${actionName}_drafts`,
171
- properties: {
172
- ...draft.identification
173
- },
174
- access: draft.listAccess || draft.access || action.access,
175
- daoPath(params) {
176
- const ident = Object.keys(draft.identification).map(p => params[p])
177
- return modelRuntime().indexRangePath('identifier', ident)
178
- }
179
- })
180
- }
181
-
182
- if(steps) {
183
- for(let i = 0; i < steps.length; i++) {
184
- const step = steps[i]
185
- const nextStep = steps[i + 1]
186
-
187
- //console.log("ACTION PROPERTIES", action.properties)
188
- const stepProperties = {}
189
- for(let fieldName of step.fields) {
190
- utils.setProperty({ properties: stepProperties }, fieldName, utils.getProperty(action, fieldName))
191
- }
192
- const stepPropertiesVW = propertyWithoutValidation(stepProperties)
193
-
194
- //console.log(`STEP ${step.name} PROPERTIES`, stepProperties)
195
- //console.log(`STEP ${step.name} PROPERTIES VW`, stepPropertiesVW)
196
-
197
- service.action({
198
- name: `${actionName}_saveStepDraft_${step.name || i}`,
199
- properties: stepPropertiesVW,
200
- async execute(params, {service, client}, emit) {
201
- let draft = params.draft
202
- if(!draft) draft = app.generateUid()
203
- let data = {}
204
- for(let k in properties) data[k] = params[k]
205
- emit({
206
- type: `${actionName}_stepDraftSaved`,
207
- draft, data,
208
- draftStep: step.name || i
209
- })
210
- return draft
211
- }
212
- })
213
- service.event({
214
- name: `${actionName}_stepDraftSaved`,
215
- async execute(props) {
216
- await app.dao.request(['database', 'query', app.databaseName, `(${
217
- async (input, output, { table, id, data, draftStep }) => {
218
- const value = await input.table(table).object(id).get()
219
- if(!value) {
220
- return await output.table(table).put({ ...data, id, draftStep })
221
- } else {
222
- return await output.table(table).update(id, [
223
- { op:'merge', property: null, value: { ...data, draftStep} }
224
- ])
225
- }
226
- }
227
- })`, { table: modelRuntime().tableName, id: props.draft, data: props.data, draftStep: props.draftStep }])
228
- }
229
- })
230
-
231
- service.action({
232
- name: `${actionName}_finishStep_${step.name || i}`,
233
- properties: stepProperties,
234
- async execute(params, context, emit) {
235
- let data = {}
236
- console.log("PARAMS", params)
237
- for (let k in stepProperties) data[k] = params[k]
238
- if(nextStep) {
239
- let draft = params.draft
240
- if(!draft) draft = app.generateUid()
241
- delete data.draftStep
242
- emit({
243
- type: `${actionName}_stepSaved`,
244
- draft, data,
245
- draftStep: step.name || i,
246
- draftNextStep: nextStep.name || (i + 1)
247
- })
248
- return draft
249
- } else {
250
- console.log("PM", params)
251
- let actionProps = params.draft ? await modelRuntime().get(params.draft) : {}
252
- console.log("AP", actionProps)
253
- console.log("DT", data)
254
- delete actionProps.draft
255
- delete actionProps.draftStep
256
- utils.mergeDeep(actionProps, data)
257
- const result = await actionExecute.call(action, actionProps, context, emit)
258
- if(params.draft) {
259
- emit({
260
- type: `${actionName}_draftFinished`,
261
- draft: params.draft
262
- })
263
- }
264
- return result
265
- }
266
- }
267
- })
268
- service.event({
269
- name: `${actionName}_stepSaved`,
270
- async execute(props) {
271
- await app.dao.request(['database', 'query', app.databaseName,`(${
272
- async (input, output, { table, id, data, draftStep }) => {
273
- const value = await input.table(table).object(id).get()
274
- if(!value) {
275
- return await output.table(table).put({ ...data, id, draftStep })
276
- } else {
277
- return await output.table(table).update(id, [
278
- { op:'merge', property: null, value: { ...data, draftStep} }
279
- ])
280
- }
281
- }
282
- })`, { table: modelRuntime().tableName, id: props.draft, data: props.data, draftStep: props.draftStep }])
283
- //await modelRuntime().create({...props.data, id: props.draft, draftStep: props.draftStep}, { conflict: 'update' })
284
- }
285
- })
286
-
287
- }
288
- }
289
-
290
- }
291
- }
@@ -1,40 +0,0 @@
1
- import * as utils from "../utils.js"
2
-
3
- export default function(module, app) {
4
- for(let modelName in module.models) {
5
- const model = module.models[modelName]
6
- for(let propertyName in model.properties) {
7
- const property = model.properties[propertyName]
8
- if(property.reverseRelation) { // X to one
9
- const reverse = property.reverseRelation
10
- const targetModel = module.models[utils.typeName(property.type)]
11
- if(reverse.many) {
12
- targetModel.createAndAddProperty(reverse.name, {
13
- type: "Array",
14
- ... reverse.options
15
- })
16
- } else {
17
- targetModel.createAndAddProperty(reverse.name, {
18
- type: model,
19
- ... reverse.options
20
- })
21
- }
22
- }
23
- if(property.of && property.of.reverseRelation) { // X to many
24
- const targetModel = module.models[utils.typeName(property.of.type)]
25
- const reverse = property.of.reverseRelation
26
- if(reverse.many) {
27
- targetModel.createAndAddProperty(reverse.name, {
28
- type: "Array",
29
- ... reverse.options
30
- })
31
- } else {
32
- targetModel.createAndAddProperty(reverse.name, {
33
- type: model,
34
- ... reverse.options
35
- })
36
- }
37
- }
38
- }
39
- }
40
- }
@@ -1,314 +0,0 @@
1
- const SEARCH_INDEX_NOTSTARTED = 0
2
- const SEARCH_INDEX_CREATING = 1
3
- const SEARCH_INDEX_UPDATING = 2
4
- const SEARCH_INDEX_READY = 3
5
-
6
- const bucketSize = 256
7
- const saveStateThrottle = 2000
8
- const saveStateDelay = saveStateThrottle + 200
9
-
10
- function prepareArray(data, of) {
11
- if(!data) return
12
- if(of.properties) for(let i = 0; i < data.length; i++) prepareObject(data[i], of)
13
- if(of.of) for(let i = 0; i < data.length; i++) prepareArray(data[i], of.of)
14
- }
15
-
16
- function prepareObject(data, props) {
17
- if(!data) return
18
- for(const propName in props) {
19
- if(!data.hasOwnProperty(propName)) continue
20
- const prop = props[propName]
21
- if(prop.properties) {
22
- prepareObject(data[propName], prop.properties)
23
- }
24
- if(prop.of) {
25
- prepareArray(data[propName], prop.of)
26
- }
27
- if(prop.search) {
28
- if(Array.isArray(prop.search)) {
29
- for(const search of prop.search) {
30
- if(search.name) data[search.name] = data[propName]
31
- }
32
- } else {
33
- if(prop.search.name) data[prop.search.name] = data[propName]
34
- }
35
- }
36
- }
37
- }
38
-
39
- class SearchIndexer {
40
- constructor(dao, databaseName, sourceType, sourceName, elasticsearch, indexName, model ) {
41
- this.dao = dao
42
- this.databaseName = databaseName
43
- this.sourceType = sourceType
44
- this.sourceName = sourceName
45
- this.elasticsearch = elasticsearch
46
- this.indexName = indexName
47
- this.model = model
48
- this.state = SEARCH_INDEX_NOTSTARTED
49
- this.lastUpdateId = ''
50
- this.lastSavedId = ''
51
-
52
- this.observable = null
53
-
54
- this.queue = []
55
- this.queueWriteResolve = []
56
- this.queueLastUpdateId = ''
57
- this.queueWritePromise = null
58
- this.queueWriteResolve = null
59
- this.currentWritePromise = null
60
-
61
- this.readingMore = true
62
-
63
- this.lastStateSave = 0
64
- this.saveStateTimer = null
65
- }
66
-
67
- prepareObject(object) {
68
- prepareObject(object, this.model.properties)
69
- }
70
-
71
- async start() {
72
- const searchIndexState = await this.dao.get(
73
- ['database','tableObject', this.databaseName, 'searchIndexes', this.indexName])
74
- const firstSourceOperation = (await this.dao.get(
75
- ['database', this.sourceType.toLowerCase()+'OpLogRange', this.databaseName, this.sourceName, {
76
- limit: 1
77
- }]))[0]
78
-
79
- console.log("Index State", searchIndexState)
80
- console.log("first Source Operation", firstSourceOperation && firstSourceOperation.id)
81
-
82
- let lastUpdateTimestamp = 0
83
- if(!searchIndexState || (firstSourceOperation && firstSourceOperation.id > searchIndexState.lastOpLogId)) {
84
- const indexCreateTimestamp = Date.now()
85
- this.state = SEARCH_INDEX_CREATING
86
- console.log("CREATING SEARCH INDEX")
87
- await this.copyAll()
88
- lastUpdateTimestamp = indexCreateTimestamp - 1000 // one second overlay
89
- this.lastUpdateId = (''+(lastUpdateTimestamp - 1000)).padStart(16,'0')
90
- } else {
91
- this.state = SEARCH_INDEX_UPDATING
92
- console.log("UPDATING SEARCH INDEX")
93
- lastUpdateTimestamp = (+searchIndexState.lastOpLogId.split(':')[0]) - 1000 // one second overlap
94
- this.lastUpdateId = searchIndexState.lastOpLogId
95
- await this.updateAll()
96
- }
97
-
98
- await this.dao.request(['database', 'put', this.databaseName, 'searchIndexes', {
99
- id: this.indexName,
100
- lastOpLogId: this.lastUpdateId
101
- }])
102
- this.lastStateSave = Date.now()
103
-
104
- console.log("SEARCH INDEX READY")
105
- this.state = SEARCH_INDEX_READY
106
-
107
- this.observeMore()
108
- }
109
-
110
- async saveState() {
111
- if(this.lastSavedId == this.lastUpdateId) return
112
- if(Date.now() - this.lastStateSave < saveStateThrottle) {
113
- if(this.saveStateTimer === null) {
114
- setTimeout(() => this.saveState(), saveStateDelay)
115
- }
116
- return
117
- }
118
- console.log("SAVE INDEXER STATE", this.lastUpdateId)
119
- this.lastSavedId = this.lastUpdateId
120
- this.lastStateSave = Date.now()
121
- await this.dao.request(['database', 'put', this.databaseName, 'searchIndexes', {
122
- id: this.indexName,
123
- lastOpLogId: this.lastUpdateId
124
- }])
125
- }
126
-
127
- doWrite() {
128
- if(this.queueWritePromise) {
129
- return this.queueWritePromise
130
- }
131
- const operations = this.queue
132
- if(operations.length == 0) {
133
- this.lastUpdateId = this.queueLastUpdateId
134
- return
135
- }
136
- this.queueWritePromise = new Promise((resolve, reject) => {
137
- this.queueWriteResolve = { resolve, reject }
138
- })
139
- const queueResolve = this.queueWriteResolve
140
- this.queueWriteResolve = null
141
- this.queueWritePromise = null
142
- this.queue = []
143
- //console.log("WRITE BULK", operations)
144
- this.elasticsearch.bulk({
145
- index: this.indexName,
146
- body: operations
147
- }).catch(error => {
148
- error = (error && error.meta && error.meta.body && error.meta.body.error) || error
149
- console.error("ES ERROR:", error)
150
- queueResolve.reject(error)
151
- throw error
152
- }).then(result => {
153
- //console.log("BULK RESULT", result)
154
- if(result.body.errors) {
155
- for(const item of result.body.items) {
156
- if(item.index.error) {
157
- const opId = operations.findIndex(op =>
158
- (op.index && op.index._id == item.index._id)
159
- || (op.delete && op.delete._id == item.delete._id)
160
- )
161
- const op = operations[opId]
162
- const data = op.index ? operations[opId + 1] : null
163
- console.error("INDEX ERROR", item.index.error, "OP", op, "D", data)
164
- }
165
- }
166
- //throw result.body.items.find(item => item.error)
167
- throw new Error("INDEXING ERROR "+ result.body.errors)
168
- }
169
- }).then(written => {
170
- this.lastUpdateId = this.queueLastUpdateId
171
- queueResolve.resolve()
172
- this.currentWritePromise = null
173
- this.saveState()
174
- if(this.queue.length) {
175
- this.doWrite()
176
- }
177
- })
178
- return this.queueWritePromise
179
- }
180
-
181
- async applyOps(ops) {
182
- //console.trace(`apply ${ops.length} ops`)
183
- if(ops.length == 0) return;
184
- let size = 0
185
- for(const op of ops) {
186
- if(op.operation.type == 'put') size += 2
187
- if(op.operation.type == 'delete') size += 1
188
- }
189
- const operations = new Array(size)
190
- let pos = 0
191
- for(const op of ops) {
192
- if(op.operation.type == 'put') {
193
- operations[pos++] = { index: { _id: op.operation.object.id } }
194
- this.prepareObject(op.operation.object)
195
- operations[pos++] = op.operation.object
196
- }
197
- if(op.operation.type == 'delete') {
198
- operations[pos++] = { delete: { _id: op.operation.object.id } }
199
- }
200
- }
201
- const lastUpdateId = ops[ops.length-1].id
202
- //console.log("ES OPS", operations)
203
- this.queue = this.queue.length ? this.queue.concat(operations) : operations
204
- this.queueLastUpdateId = lastUpdateId
205
- //console.log("WRITE OPS!")
206
- await this.doWrite()
207
- //console.log("OPS WRITTEN!")
208
- }
209
-
210
- observeMore() {
211
- this.readingMore = true
212
- if(this.observable) this.observable.unobserve(this)
213
- this.observable = this.dao.observable(
214
- ['database', this.sourceType.toLowerCase()+'OpLogRange', this.databaseName, this.sourceName, {
215
- gt: this.lastUpdateId,
216
- limit: bucketSize
217
- }])
218
- this.observable.observe(this)
219
- }
220
- tryObserveMore() {
221
- if(!this.readingMore && this.observable.list.length == bucketSize) {
222
- this.observeMore()
223
- }
224
- }
225
- async set(ops) {
226
- this.readingMore = false
227
- console.log("SET", this.lastUpdateId, ops.length)
228
- await this.applyOps(ops)
229
- this.tryObserveMore()
230
- }
231
- async putByField(_fd, id, op, _reverse, oldObject) {
232
- this.readingMore = false
233
- await this.applyOps([ op ])
234
- this.tryObserveMore()
235
- }
236
-
237
- async updateAll() {
238
- let ops
239
- do {
240
- console.log("UPDATE FROM", this.lastUpdateId)
241
- ops = await this.dao.get(
242
- ['database', this.sourceType.toLowerCase()+'OpLogRange', this.databaseName, this.sourceName, {
243
- gt: this.lastUpdateId,
244
- limit: bucketSize
245
- }])
246
- console.log("OPS", ops.length)
247
- await this.applyOps(ops)
248
- console.log("OPS APPLIED", ops.length)
249
- } while(ops.length >= bucketSize)
250
- }
251
-
252
- async copyAll() {
253
- const search = this.elasticsearch
254
- let position = ""
255
- let more = true
256
-
257
- console.log("DELETE OLD DATA")
258
- await search.delete_by_query({
259
- index: this.indexName,
260
- body: {
261
- query: {
262
- match_all: {}
263
- }
264
- }
265
- })
266
-
267
- console.log(`INDEXING ${this.sourceType} ${this.sourceName}`)
268
- do {
269
- const rows = await this.dao.get(
270
- ['database', this.sourceType.toLowerCase()+'Range', this.databaseName, this.sourceName, {
271
- gt: position,
272
- limit: bucketSize
273
- }])
274
- position = rows.length ? rows[rows.length-1].id : "\xFF"
275
- more = (rows.length == bucketSize)
276
-
277
- if(more) console.log(`READ ${rows.length} ROWS`)
278
- else console.log(`READ LAST ${rows.length} ROWS`)
279
-
280
- if(rows.length > 0) {
281
- console.log(`WRITING ${rows.length} ROWS`)
282
- let operations = new Array(rows.length * 2)
283
- for(let i = 0; i < rows.length; i++) {
284
- operations[i * 2] = { index: { _id: rows[i].id } }
285
- this.prepareObject(rows[i])
286
- operations[i * 2 + 1] = rows[i]
287
- }
288
- await search.bulk({
289
- index: this.indexName,
290
- body: operations
291
- }).then(result => {
292
- if(result.body.errors) {
293
- for(const item of result.body.items) {
294
- if(item.index && item.index.error) {
295
- console.error("ES ERROR:", item.index.error)
296
- console.error("WHEN INDEXING", rows.find(row => row.id == item.index._id))
297
- }
298
- }
299
- throw new Error("ES ERRORS")
300
- }
301
- }).catch(error => {
302
- error = (error && error.meta && error.meta.body && error.meta.body.error) || error
303
- console.error("ES ERROR:", error)
304
- throw error
305
- })
306
- }
307
- console.log("ES INDEXED!")
308
-
309
- } while (more)
310
- }
311
-
312
- }
313
-
314
- export default SearchIndexer