@jsreport/jsreport-core 3.0.1 → 3.1.2-test.2

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.
Files changed (79) hide show
  1. package/LICENSE +166 -166
  2. package/README.md +298 -284
  3. package/index.js +29 -27
  4. package/lib/main/blobStorage/blobStorage.js +52 -47
  5. package/lib/main/blobStorage/inMemoryProvider.js +27 -27
  6. package/lib/main/blobStorage/mainActions.js +24 -24
  7. package/lib/main/createDefaultLoggerFormat.js +17 -17
  8. package/lib/main/defaults.js +14 -14
  9. package/lib/main/extensions/discover.js +20 -20
  10. package/lib/main/extensions/extensionsManager.js +264 -265
  11. package/lib/main/extensions/fileUtils.js +56 -55
  12. package/lib/main/extensions/findVersion.js +49 -53
  13. package/lib/main/extensions/locationCache.js +103 -97
  14. package/lib/main/extensions/sorter.js +10 -10
  15. package/lib/main/extensions/validateMinimalVersion.js +50 -50
  16. package/lib/main/folders/cascadeFolderRemove.js +25 -25
  17. package/lib/main/folders/getEntitiesInFolder.js +53 -53
  18. package/lib/main/folders/index.js +42 -42
  19. package/lib/main/folders/moveBetweenFolders.js +354 -354
  20. package/lib/main/folders/validateDuplicatedName.js +107 -107
  21. package/lib/main/folders/validateReservedName.js +53 -53
  22. package/lib/main/logger.js +244 -244
  23. package/lib/main/migration/resourcesToAssets.js +230 -210
  24. package/lib/main/migration/xlsxTemplatesToAssets.js +128 -118
  25. package/lib/main/monitoring.js +91 -91
  26. package/lib/main/optionsLoad.js +237 -237
  27. package/lib/main/optionsSchema.js +237 -237
  28. package/lib/main/reporter.js +579 -578
  29. package/lib/main/schemaValidator.js +252 -252
  30. package/lib/main/settings.js +154 -154
  31. package/lib/main/store/checkDuplicatedId.js +27 -27
  32. package/lib/main/store/collection.js +329 -329
  33. package/lib/main/store/documentStore.js +469 -469
  34. package/lib/main/store/mainActions.js +28 -28
  35. package/lib/main/store/memoryStoreProvider.js +99 -99
  36. package/lib/main/store/queue.js +48 -48
  37. package/lib/main/store/referenceUtils.js +251 -251
  38. package/lib/main/store/setupValidateId.js +43 -43
  39. package/lib/main/store/setupValidateShortid.js +71 -71
  40. package/lib/main/store/transaction.js +69 -69
  41. package/lib/main/store/typeUtils.js +180 -180
  42. package/lib/main/templates.js +34 -34
  43. package/lib/main/validateEntityName.js +62 -62
  44. package/lib/shared/createError.js +36 -36
  45. package/lib/shared/encryption.js +114 -114
  46. package/lib/shared/folders/index.js +11 -11
  47. package/lib/shared/folders/normalizeEntityPath.js +15 -15
  48. package/lib/shared/folders/resolveEntityFromPath.js +88 -88
  49. package/lib/shared/folders/resolveEntityPath.js +46 -46
  50. package/lib/shared/folders/resolveFolderFromPath.js +38 -38
  51. package/lib/shared/generateRequestId.js +4 -4
  52. package/lib/shared/listenerCollection.js +169 -0
  53. package/lib/shared/normalizeMetaFromLogs.js +30 -30
  54. package/lib/shared/reporter.js +123 -123
  55. package/lib/shared/request.js +64 -64
  56. package/lib/shared/tempFilesHandler.js +81 -81
  57. package/lib/shared/templates.js +82 -82
  58. package/lib/static/helpers.js +33 -33
  59. package/lib/worker/blobStorage.js +34 -34
  60. package/lib/worker/defaultProxyExtend.js +46 -46
  61. package/lib/worker/documentStore.js +49 -49
  62. package/lib/worker/extensionsManager.js +17 -17
  63. package/lib/worker/logger.js +48 -48
  64. package/lib/worker/render/diff.js +138 -138
  65. package/lib/worker/render/executeEngine.js +207 -200
  66. package/lib/worker/render/htmlRecipe.js +10 -10
  67. package/lib/worker/render/moduleHelper.js +43 -43
  68. package/lib/worker/render/noneEngine.js +12 -12
  69. package/lib/worker/render/profiler.js +158 -158
  70. package/lib/worker/render/render.js +205 -209
  71. package/lib/worker/render/resolveReferences.js +60 -60
  72. package/lib/worker/reporter.js +191 -187
  73. package/lib/worker/sandbox/runInSandbox.js +13 -4
  74. package/lib/worker/sandbox/safeSandbox.js +828 -822
  75. package/lib/worker/templates.js +78 -78
  76. package/lib/worker/workerHandler.js +54 -54
  77. package/package.json +92 -92
  78. package/test/blobStorage/common.js +21 -21
  79. package/test/store/common.js +1449 -1449
@@ -1,469 +1,469 @@
1
- const events = require('events')
2
- const { nanoid } = require('nanoid')
3
- const { v4: uuidv4 } = require('uuid')
4
- const ListenerCollection = require('listener-collection')
5
- const { resolvePropDefinition, typeDefToJSONSchema } = require('./typeUtils')
6
- const { findReferencePropertiesInType, findLinkedEntitiesForReferenceValue, existsReferenceValue, updateReferenceValue } = require('./referenceUtils')
7
- const collection = require('./collection')
8
- const checkDuplicatedId = require('./checkDuplicatedId')
9
-
10
- // factory function
11
- const DocumentStore = (options, validator, encryption) => {
12
- const entitySchemasToGenerate = []
13
- const generateSchemaEntityTypeConfig = {}
14
- const defaultGenerateSchemaForEntityType = true
15
-
16
- // internal sets are not listed in normal documentStore.model.entitySets, or available in
17
- // documentStore.collection(), instead they are available in documentStore.internalCollection()
18
- // and entity set definitions of these internals are only available in store provider implementations.
19
- //
20
- // this allows having collections that are available for specific uses cases (playground, jsreportonline)
21
- // which needs to save/load data using jsreport store abstraction but they don't need to be visible
22
- // from extensions. (to avoid adding permissions, attributes or other logic that modifies these internal entities from extensions)
23
- const internalEntitySets = {}
24
- const transactions = new Map()
25
- const fileExtensionResolvers = []
26
-
27
- transactions.getActiveTransaction = function (req) {
28
- if (req && req.context && req.context.storeTransaction) {
29
- if (!transactions.has(req.context.storeTransaction)) {
30
- throw new Error('transaction does not exists or is no longer active, make sure you are not closing the transaction previously using store.commitTransaction or store.rollbackTransaction')
31
- }
32
-
33
- return transactions.get(req.context.storeTransaction)
34
- }
35
- }
36
-
37
- let initialized = false
38
-
39
- const store = {
40
- options,
41
- model: {
42
- namespace: 'jsreport',
43
- complexTypes: {},
44
- entitySets: {}
45
- },
46
- internalAfterInitListeners: new ListenerCollection(),
47
- emitter: new events.EventEmitter(),
48
-
49
- registerProvider (provider) {
50
- this.provider = provider
51
- },
52
-
53
- async init () {
54
- initialized = true
55
-
56
- if (!this.provider && this.options.store.provider === 'memory') {
57
- this.provider = require('./memoryStoreProvider')()
58
- }
59
-
60
- if (!this.provider) {
61
- throw new Error(`The document store provider ${this.options.store.provider} was not registered.`)
62
- }
63
-
64
- this.emit('before-init', this)
65
-
66
- this.collections = {}
67
- this.internalCollections = {}
68
-
69
- const entitySetsLinkedReferenceProperties = {}
70
-
71
- Object.entries(this.model.entitySets).forEach((e) => {
72
- const eName = e[0]
73
- const es = e[1]
74
- es.normalizedEntityTypeName = es.entityType.replace(this.model.namespace + '.', '')
75
- es.entityTypeDef = this.model.entityTypes[es.normalizedEntityTypeName]
76
- const entityType = es.entityTypeDef
77
-
78
- if (!entityType._id) {
79
- entityType._id = { type: 'Edm.String' }
80
-
81
- if (!entityTypeHasKey(entityType)) {
82
- // add key: true only if there is no other field defined already
83
- // key: true is used for ODATA XML generation, it includes new information to the final XML
84
- entityType._id.key = true
85
- }
86
- }
87
-
88
- if (!entityType.creationDate) {
89
- entityType.creationDate = { type: 'Edm.DateTimeOffset' }
90
- }
91
-
92
- if (!entityType.modificationDate) {
93
- entityType.modificationDate = { type: 'Edm.DateTimeOffset' }
94
- }
95
-
96
- if (!entityType.shortid) {
97
- entityType.shortid = { type: 'Edm.String' }
98
- }
99
-
100
- const referenceProperties = findReferencePropertiesInType(this.model, entityType)
101
-
102
- referenceProperties.forEach((property) => {
103
- const entitySetName = property.referenceTo
104
- entitySetsLinkedReferenceProperties[entitySetName] = entitySetsLinkedReferenceProperties[entitySetName] || []
105
-
106
- entitySetsLinkedReferenceProperties[entitySetName].push({
107
- name: property.name,
108
- entitySet: eName
109
- })
110
- })
111
-
112
- es.referenceProperties = referenceProperties
113
- })
114
-
115
- Object.keys(this.model.entitySets).forEach((eName) => {
116
- const es = this.model.entitySets[eName]
117
-
118
- if (entitySetsLinkedReferenceProperties[eName]) {
119
- es.linkedReferenceProperties = entitySetsLinkedReferenceProperties[eName]
120
- } else {
121
- es.linkedReferenceProperties = []
122
- }
123
-
124
- const col = collection(eName, this.provider, this.model, validator, encryption, transactions)
125
-
126
- const addDefaultFields = (doc) => {
127
- doc.creationDate = new Date()
128
- doc.modificationDate = new Date()
129
- doc.shortid = doc.shortid || nanoid(7)
130
- }
131
-
132
- col.beforeInsertListeners.add('core-default-fields', (doc, req) => {
133
- addDefaultFields(doc)
134
- })
135
-
136
- col.beforeUpdateListeners.add('core-default-fields', (q, u, o, req) => {
137
- if (u.$set && o && o.upsert === true) {
138
- addDefaultFields(u.$set)
139
- }
140
-
141
- if (req && req.context.skipModificationDateUpdate === true) {
142
- return
143
- }
144
-
145
- if (u.$set) {
146
- u.$set.modificationDate = new Date()
147
- }
148
- })
149
-
150
- this.collections[eName] = col
151
- })
152
-
153
- Object.keys(internalEntitySets).forEach((e) => (this.internalCollections[e] = collection(e, this.provider, this.model, undefined, undefined, transactions)))
154
-
155
- if (this.provider.load) {
156
- // we combine internal and public entity sets in order for the store provider
157
- // be able to recognize both set of entities and be able to work with them
158
- const modelToLoad = Object.assign({}, this.model)
159
-
160
- modelToLoad.entitySets = Object.assign({}, modelToLoad.entitySets, internalEntitySets)
161
-
162
- await this.provider.load(modelToLoad)
163
- }
164
-
165
- entitySchemasToGenerate.forEach((entityType) => {
166
- const schema = typeDefToJSONSchema(this.model, this.model.entityTypes[entityType])
167
-
168
- if (schema == null) {
169
- return
170
- }
171
-
172
- if (initialized && validator.getSchema(entityType) != null) {
173
- validator.addSchema(entityType, schema, true)
174
- } else {
175
- validator.addSchema(entityType, schema)
176
- }
177
- })
178
-
179
- this.emit('after-init', this)
180
- return this.internalAfterInitListeners.fire()
181
- },
182
-
183
- /**
184
- * Register type for odata.
185
- * Example:
186
- * documentStore.registerEntityType('UserType', {
187
- * _id: {type: 'Edm.String', key: true}
188
- * })
189
- *
190
- * @param {String} type
191
- * @param {Object} def
192
- */
193
- registerEntityType (type, def, generateJSONSchema = defaultGenerateSchemaForEntityType) {
194
- generateSchemaEntityTypeConfig[type] = generateJSONSchema === true
195
- this.model.entityTypes[type] = def
196
- },
197
-
198
- addFileExtensionResolver (fn) {
199
- fileExtensionResolvers.push(fn)
200
- },
201
-
202
- resolveFileExtension (doc, entitySetName, propertyName) {
203
- const model = this.model
204
- const entitySets = { ...model.entitySets, ...internalEntitySets }
205
- const es = entitySets[entitySetName]
206
-
207
- if (es == null) {
208
- throw new Error(`Entity set "${entitySetName}" does not exists`)
209
- }
210
-
211
- const entityTypeName = es.entityType
212
- const entityType = es.entityTypeDef
213
- const propTypeParts = propertyName.split('.')
214
- const propTypePartsLastIndex = propTypeParts.length - 1
215
- let propType = entityType
216
-
217
- propTypeParts.forEach((propName, idx) => {
218
- if (propType == null || propType[propName] == null) {
219
- throw new Error(`Property "${propertyName}" does not exists in entity type "${entityTypeName}"`)
220
- }
221
-
222
- propType = propType[propName]
223
-
224
- const resolveResult = this.resolvePropertyDefinition(propType)
225
-
226
- if (!resolveResult) {
227
- throw new Error(`Property "${propertyName}" does not have a valid type`)
228
- }
229
-
230
- if (resolveResult.def.type.startsWith('Collection(') && resolveResult.subType == null) {
231
- if (propTypePartsLastIndex !== idx) {
232
- propType = null
233
- }
234
- } else if (resolveResult.subType) {
235
- propType = resolveResult.subType
236
- }
237
- })
238
-
239
- if (!propType || propType.document == null) {
240
- return
241
- }
242
-
243
- for (const resolver of fileExtensionResolvers) {
244
- const extension = resolver(doc, entitySetName, entityType, propType)
245
-
246
- if (extension) {
247
- return extension
248
- }
249
- }
250
-
251
- return propType.document.extension
252
- },
253
-
254
- /**
255
- * Register complex type for odata.
256
- * Example:
257
- * documentStore.registerComplexType('DataItemRefType', {
258
- * name: {type: 'Edm.String' }
259
- * })
260
- *
261
- * @param {String} name
262
- * @param {Object} def
263
- */
264
- registerComplexType (name, def) {
265
- this.model.complexTypes[name] = def
266
- },
267
-
268
- /**
269
- * Register complete entity set for odata. The first parameter is then use as a collection name
270
- * Example:
271
- * documentStore.registerEntitySet('users', {
272
- * entityType: 'jsreport.UserType'
273
- * })
274
- *
275
- * @param {String} name
276
- * @param {Object} def
277
- */
278
- registerEntitySet (name, def) {
279
- const isInternal = def.internal === true
280
-
281
- if (def.exportable == null || def.exportable === true) {
282
- def.exportable = true
283
- } else {
284
- def.exportable = false
285
- }
286
-
287
- if (
288
- isInternal &&
289
- this.model.entitySets[name] != null
290
- ) {
291
- throw new Error(
292
- `Entity set "${name}" can not be registered as internal entity because it was register as public entity previously`
293
- )
294
- } else if (
295
- !isInternal &&
296
- internalEntitySets[name] != null
297
- ) {
298
- throw new Error(
299
- `Entity set "${name}" can not be registered as public entity because it was register as internal entity previously`
300
- )
301
- }
302
-
303
- if (!isInternal) {
304
- this.model.entitySets[name] = def
305
- } else {
306
- def.normalizedEntityTypeName = def.entityType.replace(this.model.namespace + '.', '')
307
- def.entityTypeDef = this.model.entityTypes[def.normalizedEntityTypeName]
308
- internalEntitySets[name] = def
309
- }
310
- },
311
-
312
- /**
313
- * Resolves the passed definition to contain subtype definition
314
- * (useful when dealing with complex types or collection types)
315
- * @param {Object} def
316
- * @return {Object}
317
- */
318
- resolvePropertyDefinition (def) {
319
- return resolvePropDefinition(this.model, def)
320
- },
321
-
322
- /**
323
- * Get the document Collection by the name provided in registerEntitySet
324
- * @param {String} name
325
- * @returns {Collection}
326
- */
327
- collection (name) {
328
- return this.collections[name]
329
- },
330
-
331
- /**
332
- * Get the document internal Collection by the name provided in registerEntitySet
333
- * @param {String} name
334
- * @returns {Collection}
335
- */
336
- internalCollection (name) {
337
- return this.internalCollections[name]
338
- },
339
-
340
- findLinkedEntitiesForReference (entitiesByCollection, collectionReferenceTargetName, referenceValue) {
341
- return findLinkedEntitiesForReferenceValue(this, entitiesByCollection, collectionReferenceTargetName, referenceValue)
342
- },
343
-
344
- existsReference (collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceValue) {
345
- return existsReferenceValue(this, collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceValue)
346
- },
347
-
348
- updateReference (collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceOpts, newReferenceValue) {
349
- return updateReferenceValue(this, collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceOpts, newReferenceValue)
350
- },
351
-
352
- checkDuplicatedId (collectionName, idValue, req) {
353
- return checkDuplicatedId(this, collectionName, idValue, req)
354
- },
355
-
356
- async close () {
357
- transactions.clear()
358
-
359
- if (this.provider && this.provider.close) {
360
- await this.provider.close()
361
- }
362
- },
363
-
364
- /**
365
- * Drop the whole document store
366
- * @returns {Promise}
367
- */
368
- async drop (req) {
369
- return this.provider.drop({
370
- transaction: transactions.getActiveTransaction(req)
371
- })
372
- },
373
-
374
- async beginTransaction (req) {
375
- if (req.context.storeTransaction && transactions.has(req.context.storeTransaction)) {
376
- throw new Error('Can not call store.beginTransaction when an active transaction already exists, make sure you are not calling store.beginTransaction more than once')
377
- }
378
-
379
- const tran = await this.provider.beginTransaction()
380
-
381
- const tranId = uuidv4()
382
-
383
- transactions.set(tranId, tran)
384
-
385
- req.context.storeTransaction = tranId
386
- },
387
-
388
- async commitTransaction (req) {
389
- const tranId = req.context.storeTransaction
390
- const tran = transactions.get(tranId)
391
-
392
- if (!tran) {
393
- throw new Error('Can not call store.commitTransaction without an active transaction, make sure you are calling store.beginTransaction previously or that you are not calling store.commitTransaction, store.rollbackTransaction more than once')
394
- }
395
-
396
- await this.provider.commitTransaction(tran)
397
-
398
- transactions.delete(tranId)
399
- delete req.context.storeTransaction
400
- },
401
-
402
- async rollbackTransaction (req) {
403
- const tranId = req.context.storeTransaction
404
- const tran = transactions.get(tranId)
405
-
406
- if (!tran) {
407
- throw new Error('Can not call store.rollbackTransaction without an active transaction, make sure you are calling store.beginTransaction previously or that you are not calling store.rollbackTransaction, store.commitTransaction more than once')
408
- }
409
-
410
- await this.provider.rollbackTransaction(tran)
411
-
412
- transactions.delete(tranId)
413
- delete req.context.storeTransaction
414
- }
415
- }
416
-
417
- store.model.entityTypes = proxyTypeCollection({
418
- toGenerate: entitySchemasToGenerate,
419
- config: generateSchemaEntityTypeConfig,
420
- generateSchemaDefault: defaultGenerateSchemaForEntityType
421
- })
422
-
423
- return store
424
- }
425
-
426
- function entityTypeHasKey (entityType) {
427
- let hasKey = false
428
-
429
- Object.entries(entityType).forEach(([field, fieldDef]) => {
430
- if (hasKey === true) {
431
- return
432
- }
433
-
434
- if (fieldDef.key === true) {
435
- hasKey = true
436
- }
437
- })
438
-
439
- return hasKey
440
- }
441
-
442
- function proxyTypeCollection ({ toGenerate, config, generateSchemaDefault }) {
443
- return new Proxy({}, {
444
- set: (target, property, value, receiver) => {
445
- let shouldGenerate = config[property]
446
-
447
- if (shouldGenerate == null) {
448
- shouldGenerate = generateSchemaDefault
449
- }
450
-
451
- if (shouldGenerate === true) {
452
- toGenerate.push(property)
453
- } else {
454
- const index = toGenerate.indexOf(property)
455
-
456
- if (index !== -1) {
457
- toGenerate.splice(index, 1)
458
- }
459
- }
460
-
461
- // ensure clean config for next call
462
- delete config[property]
463
-
464
- return Reflect.set(target, property, value, receiver)
465
- }
466
- })
467
- }
468
-
469
- module.exports = (...args) => Object.assign(DocumentStore(...args), events.EventEmitter.prototype)
1
+ const events = require('events')
2
+ const { nanoid } = require('nanoid')
3
+ const { v4: uuidv4 } = require('uuid')
4
+ const createListenerCollection = require('../../shared/listenerCollection')
5
+ const { resolvePropDefinition, typeDefToJSONSchema } = require('./typeUtils')
6
+ const { findReferencePropertiesInType, findLinkedEntitiesForReferenceValue, existsReferenceValue, updateReferenceValue } = require('./referenceUtils')
7
+ const collection = require('./collection')
8
+ const checkDuplicatedId = require('./checkDuplicatedId')
9
+
10
+ // factory function
11
+ const DocumentStore = (options, validator, encryption) => {
12
+ const entitySchemasToGenerate = []
13
+ const generateSchemaEntityTypeConfig = {}
14
+ const defaultGenerateSchemaForEntityType = true
15
+
16
+ // internal sets are not listed in normal documentStore.model.entitySets, or available in
17
+ // documentStore.collection(), instead they are available in documentStore.internalCollection()
18
+ // and entity set definitions of these internals are only available in store provider implementations.
19
+ //
20
+ // this allows having collections that are available for specific uses cases (playground, jsreportonline)
21
+ // which needs to save/load data using jsreport store abstraction but they don't need to be visible
22
+ // from extensions. (to avoid adding permissions, attributes or other logic that modifies these internal entities from extensions)
23
+ const internalEntitySets = {}
24
+ const transactions = new Map()
25
+ const fileExtensionResolvers = []
26
+
27
+ transactions.getActiveTransaction = function (req) {
28
+ if (req && req.context && req.context.storeTransaction) {
29
+ if (!transactions.has(req.context.storeTransaction)) {
30
+ throw new Error('transaction does not exists or is no longer active, make sure you are not closing the transaction previously using store.commitTransaction or store.rollbackTransaction')
31
+ }
32
+
33
+ return transactions.get(req.context.storeTransaction)
34
+ }
35
+ }
36
+
37
+ let initialized = false
38
+
39
+ const store = {
40
+ options,
41
+ model: {
42
+ namespace: 'jsreport',
43
+ complexTypes: {},
44
+ entitySets: {}
45
+ },
46
+ internalAfterInitListeners: createListenerCollection('DocumentStore@internalAfterInit'),
47
+ emitter: new events.EventEmitter(),
48
+
49
+ registerProvider (provider) {
50
+ this.provider = provider
51
+ },
52
+
53
+ async init () {
54
+ initialized = true
55
+
56
+ if (!this.provider && this.options.store.provider === 'memory') {
57
+ this.provider = require('./memoryStoreProvider')()
58
+ }
59
+
60
+ if (!this.provider) {
61
+ throw new Error(`The document store provider ${this.options.store.provider} was not registered.`)
62
+ }
63
+
64
+ this.emit('before-init', this)
65
+
66
+ this.collections = {}
67
+ this.internalCollections = {}
68
+
69
+ const entitySetsLinkedReferenceProperties = {}
70
+
71
+ Object.entries(this.model.entitySets).forEach((e) => {
72
+ const eName = e[0]
73
+ const es = e[1]
74
+ es.normalizedEntityTypeName = es.entityType.replace(this.model.namespace + '.', '')
75
+ es.entityTypeDef = this.model.entityTypes[es.normalizedEntityTypeName]
76
+ const entityType = es.entityTypeDef
77
+
78
+ if (!entityType._id) {
79
+ entityType._id = { type: 'Edm.String' }
80
+
81
+ if (!entityTypeHasKey(entityType)) {
82
+ // add key: true only if there is no other field defined already
83
+ // key: true is used for ODATA XML generation, it includes new information to the final XML
84
+ entityType._id.key = true
85
+ }
86
+ }
87
+
88
+ if (!entityType.creationDate) {
89
+ entityType.creationDate = { type: 'Edm.DateTimeOffset' }
90
+ }
91
+
92
+ if (!entityType.modificationDate) {
93
+ entityType.modificationDate = { type: 'Edm.DateTimeOffset' }
94
+ }
95
+
96
+ if (!entityType.shortid) {
97
+ entityType.shortid = { type: 'Edm.String' }
98
+ }
99
+
100
+ const referenceProperties = findReferencePropertiesInType(this.model, entityType)
101
+
102
+ referenceProperties.forEach((property) => {
103
+ const entitySetName = property.referenceTo
104
+ entitySetsLinkedReferenceProperties[entitySetName] = entitySetsLinkedReferenceProperties[entitySetName] || []
105
+
106
+ entitySetsLinkedReferenceProperties[entitySetName].push({
107
+ name: property.name,
108
+ entitySet: eName
109
+ })
110
+ })
111
+
112
+ es.referenceProperties = referenceProperties
113
+ })
114
+
115
+ Object.keys(this.model.entitySets).forEach((eName) => {
116
+ const es = this.model.entitySets[eName]
117
+
118
+ if (entitySetsLinkedReferenceProperties[eName]) {
119
+ es.linkedReferenceProperties = entitySetsLinkedReferenceProperties[eName]
120
+ } else {
121
+ es.linkedReferenceProperties = []
122
+ }
123
+
124
+ const col = collection(eName, this.provider, this.model, validator, encryption, transactions)
125
+
126
+ const addDefaultFields = (doc) => {
127
+ doc.creationDate = new Date()
128
+ doc.modificationDate = new Date()
129
+ doc.shortid = doc.shortid || nanoid(7)
130
+ }
131
+
132
+ col.beforeInsertListeners.add('core-default-fields', (doc, req) => {
133
+ addDefaultFields(doc)
134
+ })
135
+
136
+ col.beforeUpdateListeners.add('core-default-fields', (q, u, o, req) => {
137
+ if (u.$set && o && o.upsert === true) {
138
+ addDefaultFields(u.$set)
139
+ }
140
+
141
+ if (req && req.context.skipModificationDateUpdate === true) {
142
+ return
143
+ }
144
+
145
+ if (u.$set) {
146
+ u.$set.modificationDate = new Date()
147
+ }
148
+ })
149
+
150
+ this.collections[eName] = col
151
+ })
152
+
153
+ Object.keys(internalEntitySets).forEach((e) => (this.internalCollections[e] = collection(e, this.provider, this.model, undefined, undefined, transactions)))
154
+
155
+ if (this.provider.load) {
156
+ // we combine internal and public entity sets in order for the store provider
157
+ // be able to recognize both set of entities and be able to work with them
158
+ const modelToLoad = Object.assign({}, this.model)
159
+
160
+ modelToLoad.entitySets = Object.assign({}, modelToLoad.entitySets, internalEntitySets)
161
+
162
+ await this.provider.load(modelToLoad)
163
+ }
164
+
165
+ entitySchemasToGenerate.forEach((entityType) => {
166
+ const schema = typeDefToJSONSchema(this.model, this.model.entityTypes[entityType])
167
+
168
+ if (schema == null) {
169
+ return
170
+ }
171
+
172
+ if (initialized && validator.getSchema(entityType) != null) {
173
+ validator.addSchema(entityType, schema, true)
174
+ } else {
175
+ validator.addSchema(entityType, schema)
176
+ }
177
+ })
178
+
179
+ this.emit('after-init', this)
180
+ return this.internalAfterInitListeners.fire()
181
+ },
182
+
183
+ /**
184
+ * Register type for odata.
185
+ * Example:
186
+ * documentStore.registerEntityType('UserType', {
187
+ * _id: {type: 'Edm.String', key: true}
188
+ * })
189
+ *
190
+ * @param {String} type
191
+ * @param {Object} def
192
+ */
193
+ registerEntityType (type, def, generateJSONSchema = defaultGenerateSchemaForEntityType) {
194
+ generateSchemaEntityTypeConfig[type] = generateJSONSchema === true
195
+ this.model.entityTypes[type] = def
196
+ },
197
+
198
+ addFileExtensionResolver (fn) {
199
+ fileExtensionResolvers.push(fn)
200
+ },
201
+
202
+ resolveFileExtension (doc, entitySetName, propertyName) {
203
+ const model = this.model
204
+ const entitySets = { ...model.entitySets, ...internalEntitySets }
205
+ const es = entitySets[entitySetName]
206
+
207
+ if (es == null) {
208
+ throw new Error(`Entity set "${entitySetName}" does not exists`)
209
+ }
210
+
211
+ const entityTypeName = es.entityType
212
+ const entityType = es.entityTypeDef
213
+ const propTypeParts = propertyName.split('.')
214
+ const propTypePartsLastIndex = propTypeParts.length - 1
215
+ let propType = entityType
216
+
217
+ propTypeParts.forEach((propName, idx) => {
218
+ if (propType == null || propType[propName] == null) {
219
+ throw new Error(`Property "${propertyName}" does not exists in entity type "${entityTypeName}"`)
220
+ }
221
+
222
+ propType = propType[propName]
223
+
224
+ const resolveResult = this.resolvePropertyDefinition(propType)
225
+
226
+ if (!resolveResult) {
227
+ throw new Error(`Property "${propertyName}" does not have a valid type`)
228
+ }
229
+
230
+ if (resolveResult.def.type.startsWith('Collection(') && resolveResult.subType == null) {
231
+ if (propTypePartsLastIndex !== idx) {
232
+ propType = null
233
+ }
234
+ } else if (resolveResult.subType) {
235
+ propType = resolveResult.subType
236
+ }
237
+ })
238
+
239
+ if (!propType || propType.document == null) {
240
+ return
241
+ }
242
+
243
+ for (const resolver of fileExtensionResolvers) {
244
+ const extension = resolver(doc, entitySetName, entityType, propType)
245
+
246
+ if (extension) {
247
+ return extension
248
+ }
249
+ }
250
+
251
+ return propType.document.extension
252
+ },
253
+
254
+ /**
255
+ * Register complex type for odata.
256
+ * Example:
257
+ * documentStore.registerComplexType('DataItemRefType', {
258
+ * name: {type: 'Edm.String' }
259
+ * })
260
+ *
261
+ * @param {String} name
262
+ * @param {Object} def
263
+ */
264
+ registerComplexType (name, def) {
265
+ this.model.complexTypes[name] = def
266
+ },
267
+
268
+ /**
269
+ * Register complete entity set for odata. The first parameter is then use as a collection name
270
+ * Example:
271
+ * documentStore.registerEntitySet('users', {
272
+ * entityType: 'jsreport.UserType'
273
+ * })
274
+ *
275
+ * @param {String} name
276
+ * @param {Object} def
277
+ */
278
+ registerEntitySet (name, def) {
279
+ const isInternal = def.internal === true
280
+
281
+ if (def.exportable == null || def.exportable === true) {
282
+ def.exportable = true
283
+ } else {
284
+ def.exportable = false
285
+ }
286
+
287
+ if (
288
+ isInternal &&
289
+ this.model.entitySets[name] != null
290
+ ) {
291
+ throw new Error(
292
+ `Entity set "${name}" can not be registered as internal entity because it was register as public entity previously`
293
+ )
294
+ } else if (
295
+ !isInternal &&
296
+ internalEntitySets[name] != null
297
+ ) {
298
+ throw new Error(
299
+ `Entity set "${name}" can not be registered as public entity because it was register as internal entity previously`
300
+ )
301
+ }
302
+
303
+ if (!isInternal) {
304
+ this.model.entitySets[name] = def
305
+ } else {
306
+ def.normalizedEntityTypeName = def.entityType.replace(this.model.namespace + '.', '')
307
+ def.entityTypeDef = this.model.entityTypes[def.normalizedEntityTypeName]
308
+ internalEntitySets[name] = def
309
+ }
310
+ },
311
+
312
+ /**
313
+ * Resolves the passed definition to contain subtype definition
314
+ * (useful when dealing with complex types or collection types)
315
+ * @param {Object} def
316
+ * @return {Object}
317
+ */
318
+ resolvePropertyDefinition (def) {
319
+ return resolvePropDefinition(this.model, def)
320
+ },
321
+
322
+ /**
323
+ * Get the document Collection by the name provided in registerEntitySet
324
+ * @param {String} name
325
+ * @returns {Collection}
326
+ */
327
+ collection (name) {
328
+ return this.collections[name]
329
+ },
330
+
331
+ /**
332
+ * Get the document internal Collection by the name provided in registerEntitySet
333
+ * @param {String} name
334
+ * @returns {Collection}
335
+ */
336
+ internalCollection (name) {
337
+ return this.internalCollections[name]
338
+ },
339
+
340
+ findLinkedEntitiesForReference (entitiesByCollection, collectionReferenceTargetName, referenceValue) {
341
+ return findLinkedEntitiesForReferenceValue(this, entitiesByCollection, collectionReferenceTargetName, referenceValue)
342
+ },
343
+
344
+ existsReference (collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceValue) {
345
+ return existsReferenceValue(this, collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceValue)
346
+ },
347
+
348
+ updateReference (collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceOpts, newReferenceValue) {
349
+ return updateReferenceValue(this, collectionReferenceOriginName, entity, collectionReferenceTargetName, referenceOpts, newReferenceValue)
350
+ },
351
+
352
+ checkDuplicatedId (collectionName, idValue, req) {
353
+ return checkDuplicatedId(this, collectionName, idValue, req)
354
+ },
355
+
356
+ async close () {
357
+ transactions.clear()
358
+
359
+ if (this.provider && this.provider.close) {
360
+ await this.provider.close()
361
+ }
362
+ },
363
+
364
+ /**
365
+ * Drop the whole document store
366
+ * @returns {Promise}
367
+ */
368
+ async drop (req) {
369
+ return this.provider.drop({
370
+ transaction: transactions.getActiveTransaction(req)
371
+ })
372
+ },
373
+
374
+ async beginTransaction (req) {
375
+ if (req.context.storeTransaction && transactions.has(req.context.storeTransaction)) {
376
+ throw new Error('Can not call store.beginTransaction when an active transaction already exists, make sure you are not calling store.beginTransaction more than once')
377
+ }
378
+
379
+ const tran = await this.provider.beginTransaction()
380
+
381
+ const tranId = uuidv4()
382
+
383
+ transactions.set(tranId, tran)
384
+
385
+ req.context.storeTransaction = tranId
386
+ },
387
+
388
+ async commitTransaction (req) {
389
+ const tranId = req.context.storeTransaction
390
+ const tran = transactions.get(tranId)
391
+
392
+ if (!tran) {
393
+ throw new Error('Can not call store.commitTransaction without an active transaction, make sure you are calling store.beginTransaction previously or that you are not calling store.commitTransaction, store.rollbackTransaction more than once')
394
+ }
395
+
396
+ await this.provider.commitTransaction(tran)
397
+
398
+ transactions.delete(tranId)
399
+ delete req.context.storeTransaction
400
+ },
401
+
402
+ async rollbackTransaction (req) {
403
+ const tranId = req.context.storeTransaction
404
+ const tran = transactions.get(tranId)
405
+
406
+ if (!tran) {
407
+ throw new Error('Can not call store.rollbackTransaction without an active transaction, make sure you are calling store.beginTransaction previously or that you are not calling store.rollbackTransaction, store.commitTransaction more than once')
408
+ }
409
+
410
+ await this.provider.rollbackTransaction(tran)
411
+
412
+ transactions.delete(tranId)
413
+ delete req.context.storeTransaction
414
+ }
415
+ }
416
+
417
+ store.model.entityTypes = proxyTypeCollection({
418
+ toGenerate: entitySchemasToGenerate,
419
+ config: generateSchemaEntityTypeConfig,
420
+ generateSchemaDefault: defaultGenerateSchemaForEntityType
421
+ })
422
+
423
+ return store
424
+ }
425
+
426
+ function entityTypeHasKey (entityType) {
427
+ let hasKey = false
428
+
429
+ Object.entries(entityType).forEach(([field, fieldDef]) => {
430
+ if (hasKey === true) {
431
+ return
432
+ }
433
+
434
+ if (fieldDef.key === true) {
435
+ hasKey = true
436
+ }
437
+ })
438
+
439
+ return hasKey
440
+ }
441
+
442
+ function proxyTypeCollection ({ toGenerate, config, generateSchemaDefault }) {
443
+ return new Proxy({}, {
444
+ set: (target, property, value, receiver) => {
445
+ let shouldGenerate = config[property]
446
+
447
+ if (shouldGenerate == null) {
448
+ shouldGenerate = generateSchemaDefault
449
+ }
450
+
451
+ if (shouldGenerate === true) {
452
+ toGenerate.push(property)
453
+ } else {
454
+ const index = toGenerate.indexOf(property)
455
+
456
+ if (index !== -1) {
457
+ toGenerate.splice(index, 1)
458
+ }
459
+ }
460
+
461
+ // ensure clean config for next call
462
+ delete config[property]
463
+
464
+ return Reflect.set(target, property, value, receiver)
465
+ }
466
+ })
467
+ }
468
+
469
+ module.exports = (...args) => Object.assign(DocumentStore(...args), events.EventEmitter.prototype)